Universal Game Server with Socket.IO

In this example I will show how web clients can signal to each other using Socket.IO in both the browser, and on the server.

The messages can be JavaScript objects, and can be triggered by any event you choose. Messages from other clients will be received in callback functions, and the same server code can be used for all kinds of online games, from Chess to Tanks.

Demo

Web page

In this example, the user will come up with a unique token, enter it in the Token field, and click the Send button. The token will be put as the token query parameter.

Next a message is entered into the Message field, and the Send button is clicked.

The message with the token goes to the server, and is sent to all clients that are using the same token.

To run the sample code, you need to have Node.js, and Express installed.

The code

I am using Nodeclipse plugin with Eclipse Kepler SR2, but any IDE or even a text editor will do.

Here is the folder structure in the Project Explorer of Eclipse:
Nodeclipse

package.json

{
  "name": "GameServer",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "start": "node app.js"
  },
  "dependencies": {
    "express": "3.2.6",
  }
}

The package.json file is the standard Node.js package file. We can see that we have a dependency on Express, and that the start script is app.js.

app.js

var express = require("express");  
var app = express();  
var server = require("http").Server(app);  
var path = require("path");

app.use(express.static(path.join(__dirname, "public")));

// Begin Socket.IO stuff 
var io_route = require("./socket/app")(server);  
// End Socket.IO Stuff

server.listen(3000, function(){  
  console.log("listening on *:3000");
});

The app.js is an example of almost the smallest possible Express application. First we require express and instantiate an instance:

var express = require("express");  
var app = express();  

Then we require http and instantiate a Server:

var server = require("http").Server(app);  

We use path to be able to serve static content from the public folder:

var path = require("path");

app.use(express.static(path.join(__dirname, "public")));  

Now we will do the stuff that is specific to Socket.IO:

// Begin Socket.IO stuff 
var io_route = require("./socket/app")(server);  
// End Socket.IO Stuff

We simply require our own module and invoke it, passing in the server.
This is how you would inject this functionality in your existing Express application.

socket/app.js

module.exports = function(server) {  
    var io = require("socket.io")(server);
    io.on("connection", function(socket){
      socket.on("message", function(data) {
         if(data.token) {
             if(!socket.rooms.some(function(room, index, array) {
                 return room.id === data.token;
             })) {
                 socket.join(data.token);            
             };
         }
         io.sockets.to(data.token).emit("message", data);
      });
    });
};

The module.exports is the thing that is obtained when we call require on a module.
First we instantiate a Socket.IO Server object, and assign it to the io variable:

module.exports = function(server) {  
    var io = require("socket.io")(server);

Then we hook up a handler for the connection event. This handler will be called when a new client connects:

    io.on("connection", function(socket){

On the socket we now hook up a handler for an incoming message from the client:

      socket.on("message", function(data) {

In this next part we check if the message contained a token. If it did, we check if the socket has joined a room with the same id as the token. If not, we join this room:

         if(data.token) {
             if(!socket.rooms.some(function(room, index, array) {
                 return room.id === data.token;
             })) {
                 socket.join(data.token);            
             };
         }

Finally we are sending the same message back on all sockets connected to the same room:

         io.sockets.to(data.token).emit("message", data);

public/socket/index.html

<html>  
    <head>
        <script src="/socket.io/socket.io.js"></script>
        <script src="js/main.js"></script>
    </head>
    <body>
        <form id="token-form">
            <label for="token">Token</label>
            <input type="text" placeholder="Type your token here" name="token" id="token" />
            <input id="submit-token" type="submit" value="Send" />
        </form>
        <form>
            <label for="message">Message</label>
            <input type="text" placeholder="Type your message here" name="message" id="message" />
            <input id="submit-message" type="submit" value="Send" />
        </form>
        <h3>Here is the message from other client:</h3>
        <div id="received"></div>
    </body>
</html>  

The index.html file is not very exciting. It is just some boiler-plate HTML to hold the two forms, and a div element to show messages received.

One thing to note though is that the Socket.IO JavaScript we need on the client side is conveniently provided to us from the server without having to write any code for it:

        <script src="/socket.io/socket.io.js"></script>

public/socket/js/main.js

if(!NSGameServer) {  
    var NSGameServer = {};
    NSGameServer.query = function(queryString) {
        var that = {};
        that.parse = function(queryString) {
            if(!queryString) return;
            that.queryString = queryString.substring(1);
            var pairs = that.queryString.split("&");
            that.arguments = {};
            pairs.forEach(function(element, index, array) {
                var pair = element.split("=");
                that.arguments[pair[0]] = decodeURIComponent(pair[1]);
            });
        };
        if(queryString) {
            that.parse(queryString);
        }
        return that;
    };
}
(function(){
  window.addEventListener("load", function(){
    var socket = io();
    var query = NSGameServer.query(document.location.search);
    var tokenInput = document.querySelector("#token");
    tokenInput.value = query.arguments.token;
    var messageInput = document.querySelector("#message");
    var submitMessageButton = document.querySelector("#submit-message");
    submitMessageButton.onclick = function(event) {
        socket.emit("message", {token: query.arguments.token, action: messageInput.value});
        return false;
    };
    var received = document.querySelector("#received");
    socket.on("message", function(data){
        received.innerHTML = JSON.stringify(data);
    });
  });
}());

In the client-side JavaScript, we add an event listener to the document load event:

(function(){
  window.addEventListener("load", function(){

Inside this load event handler, we first instantiate a socket:

    var socket = io();

This also connects to the server, and hits the connection event handler on the server.

We then parse the query of this current request and put the token query parameter into the token field of the form:

    var query = NSGameServer.query(document.location.search);
    var tokenInput = document.querySelector("#token");
    tokenInput.value = query.arguments.token;

Next we add a click event handler to the Send button for the message:

    var messageInput = document.querySelector("#message");
    var submitMessageButton = document.querySelector("#submit-message");
    submitMessageButton.onclick = function(event) {
        socket.emit("message", {token: query.arguments.token, action: messageInput.value});
        return false;
    };

Inside the event handler, we call emit on the socket, which sends a message to the server. On the server the message event handler is invoked.
Notice how the second argument to the emit method is just a JavaScript object, so here you could send any object that contains game specific information about the action.
We return false from this event handler to avoid the form being submitted.

The last thing we need to do is to listen for messages from the server:

    var received = document.querySelector("#received");
    socket.on("message", function(data){
        received.innerHTML = JSON.stringify(data);
    });

In this example, we simply stringify the object received from the server and put it inside the #received div element, but you can execute any action you want based on the received data for your game.

That's it! I hope you enjoyed this little sample, and if anyone is interrested in hosting the server code for the benefit of others reading this post, please feel free to do so, and comment on the post.