Build a Multi-user App using Socket.io (Part 1): Lightweight Chat App
Introduction
I've been hankering to build something for a long time that seemed out of my depth but I feel I could build it given enough motivation. To start my journey, I'd like to build a matchmaking system to connect users together using WebSockets. In some respect, you could make a Tinder clone using the mechanisms outlined today. For my specific circumstances, I'm planning to make a system which will be integrated into a game later.
If you're familiar with any multiplayer first person shooters (like Halo!), you have already used a matchmaking system. In fact, multi-player games that are network-connected will typically have a lobby, a ranking, a map list, and a means to join or watch, and leave parties and rooms.
Editor's note: The author has included additional insights and detailed information about the project. Beginners are encouraged to read through the notes.
What we will build
For today's exercise, we will be building a simple game engine into a Socket.io chat demo. This engine will create game objects for connected users and emit them to other users.
Scope and limitations
The end-goal is to design a matchmaking system where users can "connect" to the game lobby. This lobby that I propose will show a scrollable list of games to join, chat (for fun, cause why not), a button to create a game, and another button to join a game. We will accomplish this game server using Node.js (and friends). This is by no means a minimum viable product on its own, but it can provide great power when used properly. If you are interested in following along, I have packaged my code into a small GitHub repo so you can play along at home yourself! (Bear in mind that the prerequisite is having Node.js installed on your device).
Definition
To really understand what's going on let's define what we want:
We want to have an efficient way to reliably share data between users and the server.
This functionality can occur through different mechanisms, though the prevailing method that I am aware of is through WebSockets. WebSockets are used to send data packets to and from your server and are an interoperable, cross-compatible, browser-oriented solution for sending and receiving data, both between users and between the server applications. It can be implemented near effortlessly using Node.js with the help of Socket.io. Should you desire to add WebSockets into your Apache Cordova, iOS, or Android projects, they have you covered there, too.
Building our project
Let's get the task-keeping out of the way:
mkdir mmserver && cd mmserver
npm init
npm install express - - save
npm install socket.io - - save
Side note #1: From here, I found that the best approach was to start from an existing template as a base and work my way up. Originally I was going to use the Socket.io basic chat example as a guideline, but found its examples of implementation to be too minimal for my level of desire and liking to decipher. Off to Github! Within moments, I found this Github repo of a Socket.io chat demo more suitable — I notably liked that it gave the "X… is typing" example which is essentially real-time communication between the server and the client(s).
My objective is to pool the users together into the same session page that has the matching game ID. I already have a basic idea of what we need here:
- A
gameCollection
"Master Object" that holds all game session data - A means to generate a game object and append it into the master collection
- And a means to allow users to interact with each other AND the game object
Once all of those are done, we piece together our matchmaking system, which we will do by: creating an 'open/closed' state for the game object (when a user wants to join that game we will either allow them to or not).
Without making this primer tutorial overly complicated, I'd like to have some simple validation so that users can't make multiple gaming objects and flood the game server with duplicate games.
By deploying the code found above, I have a quick and dirty username registration system and a chat system that can be modified as needed (we'll perhaps refactor and remove code towards the end).
Once you have the basic server installed, let's start building a few new structures, notably, a starter game object. If you have no idea what's going on that's fine… it's time to do an overview anyway.
Let's take a few steps back
This application uses Node.js which, in layman's term means JavaScript, plenty of JavaScript dependent libraries and build processes, etc, etc, etc...
Whatever setup you are using to edit your code — use this tip and save some time:
npm install nodemon -g -- save
Now create a separate file named Guardfile and fill it in with the following as per instructed by Kevin Vicrey:
guard 'livereload' do
# We watch the client.
watch(%r{dev/client/.+\.(css|js|ejs|html)})
# We watch the server. Path to customize for your case.
watch(%r{dev/server/start.log})
end
Finally, add the following lines into index.js towards the top in the initial var declarations:
var fs = require('fs');
Also, add this in the server.listen (near line 10):
fs.writeFile(__dirname + '/start.log', 'started');
Include that line in so that your entire server.listen portion should look like:
server.listen(port, function () {
console.log('Server listening at port %d', port);
fs.writeFile(__dirname + '/start.log', 'started');
});
Now, simply run nodemon index.js. This will make the server reload each time you edit files, which is handy instead of having to reboot and restart each pass.
Required files
There are three main files that will make our project all work together.
1. index.js
We have a file named index.js — this is the server-side code. This file is where you hold your server details, "routes", various socket events which will trigger and emit or broadcast data to you and/or other users.
In relation to what we're building, our game objects and variables will be created and called from this file. By housing the game logic in the server, we can broadcast events to users and, more importantly, validate those received messages to ensure fair play on the game or at least enforce game rules (such that the server determines the outcome then broadcasts it to the player rather than the other way around). Additionally, we can't have users claiming they scored 10 billion points every 10 seconds. You can also mitigate would-be fraudsters by setting in a mix of client logic to do the heavy lifting with validation logic to detect and disqualify behavior that resembles cheating. Such details are beyond the scope of this article but will be covered in a future installment.
2. main.js
As we're on the topic of client logic, let's go into that next file: main.js.
I disagree with the confusing name choice but essentially this is the client-facing code that is exposed at the bottom of the body markup tag in the index.html file
Side note #2: I considered taking on a game now on the spot, but will instead build that game with you over the series, so that we can apply foundational learning and really have something to show for the effort you spend reading and implementing what we discuss here.
3. HTML
Finally, the third file is the markup file, we lovingly refer to as HTML. The HTML file houses the raw markup structures that we append data onto.
Adding elements
Let's start with a few small parts.
Open the index.html file and add in a gamebuttons
DIV element and a couple of buttons! Make sure to tack on a couple of classnames
while you're at it so that we have some hook points for those buttons to be handled by our client-side JavaScript. Remove the CSS and you can see the actual markup which is a simple <ul>
<li>
group. The mechanism essentially appends new inbound chat messages as new <li>
tags.
<div class='GameButtons'>
<button class='createGame' placeholder="Create Game"/>Create Game</button>
</div>
Imagine for a moment that these three files I mention above are playing a game of telephone — the server creates the message which is handed down to the client code which then gets output to you using that HTML markup file. So now, we want to talk to the server, we use that markup files controls to send a message with the client interface, that client interface (through the simplicity of WebSockets and Socket.io) keeps an open connection to our server so that messages about data can be passed back and forth.
Let's start to do a bit of work on the client -ide code, open Main.js. Right around the top, near line 18, go ahead and add in a jQuery selector objects for those buttons we created:
var $createGame = $('.createGame');
var $joinGame = $('.joinGame');
I quickly changed lines 33–35 to something more along the lines of a game lobby.
Next, take a quick look at line 52, it's the bottom of the setUsername
function — see how it has a 'socket.emit('add user', username);'
? This is the most simple example of socket calls, from here, you would embed further functions in the place of that username
there and reduce down accordingly.
Now, scroll to //click events
on line 224, towards the bottom of that section add in this little snippet:
$createGame.click(function () {
sendGame();
})
We're taking our jQuery selector, applying a click listener to it, then, if clicked, calling the sendGame
function.
Side note #3: That $
in the front of my variable is NOT required for using jQuery click handlers; it's merely a coding convention to help other code reviewers correlate that said var is jQuery Dependent.
Where is the sendGame
function? Let's go ahead and make that. Scroll to the bottom of the file and just above the closing });
and paste in:
socket.on('gameCreated', function (data) {
console.log("Game Created! ID is: " + data.gameId)
log(data.username + ' created Game: ' + data.gameId);
//alert("Game Created! ID is: "+ JSON.stringify(data));
});
function sendGame(){
socket.emit('makeGame');
};
There is a lot going on here so let's look at this in reverse. From the bottom up:
We made our function sendGame()
and all it does is fire a small emit event to the server. Let's go ahead and explain that in English — Socket.io (from my observations) has three different relay states. That means there are only three different ways that messages can be broadcasted:
- Just you and the server (
socket.emit
) - A message from point to everyone, except you (
socket.broadcast.emit
) - Everyone gets the message, you, server, and all broadcast users (
io.emit
)
To receive or listen in on messages, both the client and the server code depend on socket.on
to listen to the various emitted and broadcasted events. sendGame
essentially "emits" the single socket request to the server with the event makeGame
that we've created here on the spot. Similarly, the larger chunk of the code above is the receiver code to be executed whenever the different event gameCreated
is emitted from the server to this user's specific client.
I have three lines of code here, with one being commented out. The commented out code would simply give the user an old-school message alert box to click through, I put in JSON.Stringify
there so I could easily reference any possibly emitted object methods to catch any problems when testing. The log()
function included in the demo (near line 75 in our client-side main.js) is perfect for us to use to display messages to the screen, rather than just hidden in the console log viewer (right-click inspect element > console)"
Side note #4: By the way, as you can see, the method by which you chain sockets is through wrapping a function outside of the code you intend to execute upon a successful event being emitted.
Quick recap
To review what we've done so far:
- Modified the index.html to add buttons
- Modified the client-side JavaScript to listen to those buttons and send server-side code events when they do.
Time to build a game server!
Building the game server
Open index.js and add the following lines near the top of the file just below Express's routing near line 15:
// Entire GameCollection Object holds all games and info
var gameCollection = new function() {
this.totalgameCount = 0,
this.gameList = {}
};
Because we want to have a single unified instance that houses all games on our server and we don't want duplicate copies of that instance running, I've set up the gameCollection
as a Singleton Object (so that we cannot duplicate copies of this gameCollection
object that houses all games generated on the server). Towards the bottom of the file, just before the closing io.on }
, mine was around line 90 but yours will likely differ!). Add in the following:
//when the client requests to make a Game
socket.on('makeGame', function () {
var gameId = (Math.random()+1).toString(36).slice(2, 18);
console.log("Game Created by "+ socket.username + " w/ " + gameId);
gameCollection.gameList.gameId = gameId
gameCollection.gameList.gameId.playerOne = socket.username;
gameCollection.gameList.gameId.open = true;
gameCollection.totalGameCount ++;
io.emit('gameCreated', {
username: socket.username,
gameId: gameId
});
});
Let's explain what that code does from the top down:
- We're listening for any
makeGame
event's emitted. - Then, we generate a
gameID
usingMath.Random +1
(so as to avoid a rare circumstance to where we pull a zero and the ID returns as blank). - We use
toString(36)
to select randomly between 0 to lowercase Z, then we use slice to denote everything after the first digit after the decimal place (the 18 on the end is optional, I just did it to have a consistent 16 digitunique id
for game objects. I don't see me running into a C10K problem anytime soon but presumably, this will have me (or you) covered). - Next, we're echoing on the server output that a game was initiated by "X" user along with the generated
GameID
.
If you fire up nodemon index.js on the terminal to load your server, then browse to localhost:3000
in Chrome, Safari, or Firefox with the changes presented so far, you should have a very simple, all-but-functioning game-server with multiple buttons — but only a single one that sort of works. Hopefully, you'll notice a problem soon enough:
Wrapping up
Currently, it's possible to create unlimited games, on command, en masse. Definitely not a good user experience — imagine some smart cookie building a modified while 1=1 loop to fire games and kill your server resource load. Also, we do have a single instantiated gameCollection
object, but we have no means to interact with it at this moment other than adding to it — it would be more of value to us if we could first, limit the number of available games to create per connected user through the use of some server-side validation. And second, if we could somehow see the global gameObject
data in the lobby.
Once these steps are handled, we can move on to the meat of the project — creating game objects to add into the master collection object, then using a random number generator, selecting and gaining an available game, allowing player two to be linked to the game object with player one.
With those critical pieces defined, we can set up external behaviors such as: "What do we do if the user disconnects from the game server?" , "How can we turn it into an actual matchmaking system?", "How do we keep data persistence (i.e. saved usernames/passwords/game history/rank/etc.)?"
In the next segment, I will discuss how to handle server validation so that users cannot create games indefinitely, destroying gameObjects
on command or when a user disconnects, and broadcast, and how to allow users to join into the same game objects.
Build a Multi-user App using Socket.io (Part 2): Creating a Matchmaking Game Server
Read Part 2 here —Author's Bio
Frankenmint is a technophile with a penchant for Bitcoin, versed in web and software development.
Was wondering why you created
gameCollection
as a class/singleton rather than as a literal object? Wouldn’t this have worked just the same:How can i run this code on my server ?
Hello. Great tutorial. Looking forward for part 2… And maybe part 3? Maybe “Beautifying” with CSS? Web version on Heroku or similar? and running hybrid on mobile devices?
I would like to try my odds at all of those things perhaps! By the way, addressing how to run a hybrid on mobile devices is tricky. I would say keep the codebase unified, but if you must have a hybrid where different applications need to connect and interact with the same server, find a supporting sdk. For IOS:
https://github.com/socketio…
and for Android:
https://github.com/socketio…
Keep in mind these solutions are simply using socket.io I found other robust solutions such as pusher.com and pubnub.com that are designed and positioned as viable means to implement real-time game applications and content experiences.
As for hosting your app on Heroku, I found this great Quickstart to get you rolling: https://devcenter.heroku.co…
I’d love to first attack persistent storage though, we’ll see soon :)
By the Way, here is pt. 2:
https://www.codementor.io/c…
Thanks for dropping by and keep on building!
Frankenmint