Plugging Into Socket.IO: Advanced

Socket.IO AdvancedLast week, we looked at the basics of using Socket.IO. As we went through that, we discovered that Socket.IO affords us a very simple API based entirely off of sending and receiving messages and being notified of those messages through events. Well, there's more to Socket.IO than that. It has several advanced features that can come in very handy in certain situations.

Built-In Events

This isn't exactly an advanced feature. I just didn't have the time to include it in the previous post. Anyway, you saw that both the client and server side had a means of doing something once the connection was made:

// Server Side
io.listen(80);
io.sockets.on('connection', function(socket){
    . . .
});

// Client Side
socket = io.connect('http://localhost/');
socket.on('connect', function() {
    . . .
});

On the back end, it is required that you react to this, otherwise you won't be able to do anything with the connection. On the front end, though, this isn't required, but it is usually helpful. In addition, both sides can also react to a disconnection. The code below applies to both front and back end.

socket.on("disconnect", function() {
    // Let the users know something bad happened or log the disconnect
});

Don't bother trying to reconnect in there, at least not right away. Socket.IO will try to reconnect on its own a certain number of times and it spaces out each reconnection attempt a little wider each time. But, it does give up after a while, so you might want to throw some setTimeouts in there to continue trying to reconnect later if you want. Oh, and by the way, you can disconnect on purpose, which doesn't cause the client to try connecting again, but should still fire the "disconnect" event:

// Client Side
socket.disconnect();

There is also a "message" event, which is used to make Socket.IO conform more closely to the WebSocket semantics. This way, all messages are received and dealt with within this single callback and you don't need to make up names for the messages. When using this, you also use send rather than emit to send messages.

// Server Side
io.listen(80);
io.sockets.on('connection', function (socket) {
    socket.on('message', function (message) {
        console.log(message);
        socket.send('message was received');
    });
});

// Client Side
socket = io.connect('http://localhost/');
socket.on('connect', function() {
    . . .
});
socket.on('message', function (message) {
    console.log(message);
    socket.send('message was received');
});

Acknowledgement

Sometimes, you want to have the system set up to have a response from the server any time the server receives your messages. You can do this by sending functions to the server, which the server will then call "directly" and pass some data in to it.

// Server Side
socket.on("getMessage", function(func) {
    func("The Message");
});

// Client Side
socket.emit("getMessage", function(message) {
    console.log(message);
});

When this is run, "The Message" will be posted in the client's console. This might seem a bit like magic, but really it's just a clever use of a proxy. The function itself isn't actually being called. Rather a different function is being called in its place, which sends the data back to the client and has the actual function called there with the data that was sent. It's a very nice system to ensure you receive acknowledgement of when the server receives your request. You could, of course, just rewrite the code like this and achieve the exact same effect, though.

// Server Side
socket.on("getMessage", function(func) {
    socket.emit("theMessage", "The Message");
});

// Client Side
socket.emit("getMessage");
socket.on("theMessage", function(message) {
    console.log(message);
});

It's a little more code and it isn't entirely clear from the client's perspective that "theMessage" will be sent back immediately when "getMessage" is received, but it still works just as well.

Storing Client Data

Essentially Socket.IO has a special type of session storage that you can use to store information about each connected socket client. It's also really simple to use, just like pretty everything else about this library.

// Server Side
socket.on('set nickname', function (name) {
    socket.set('nickname', name, function () {
        socket.emit('ready'); 
    });
});

socket.on('msg', function () {
    socket.get('nickname', function (err, name) {
        console.log('Chat message by ', name);
    });
});

// Client Side
socket.emit('set nickname', user.nickname);

socket.on('ready', function () {
    console.log('Connected !');
    socket.emit('msg', message);
});

As you can see, it works well for storing a user's nickname so that everyone in a chat can know who is sending the messages. Simply use socket.set and socket.get. Make sure you notice that they are asynchronous, so they require a callback if you want to do anything immediately after the value is saved or retrieved.

Broadcasting

So you want to be on television? Sorry, wrong kind of broadcasting. By broadcasting, I mean sending a message to everyone who is connected to the server. We already talked about this in the previous post, where I said that on the server side you could use io.sockets.emit rather than socket.emit to send a message to every client that is connected.

socket.on('chat message', function(msg) {
    io.sockets.emit('message', msg);
});

There is potentially a problem with this setup, though: it sends the message to the original client too. The client who sent "chat message" probably doesn't need to receive it back. To get around this, there is a mechanism that allows you to send a message to everyone except the original client. Just use socket.broadcast.emit. The above code can now be written like this:

socket.on('chat message', function(msg) {
    socket.broadcast.emit('message', msg);
});

Segmenting Users: Rooms and Namespaces

Sometimes you don't want all of the users lumped up together in the same pool. You might want to send messages to some clients and not others. For this we have two different ways of segmenting users: Rooms and Namespaces.

Rooms

Users can be assigned to different rooms and then can be contacted when broadcasts are made to that room. First off, let's learn how clients can be assigned to and removed from rooms. All of this is done on the server side. The client has no real power in controlling which rooms it is assigned to, except to send messages normally that the server responds to by changing whether you are in a room or not.

// Server Side
socket.on('addToRoom', function(roomName) {
    socket.join(roomName);
});

socket.on('removeFromRoom', function(roomName) {
    socket.leave(roomName);
});

Simply use socket.join and socket.leave to join and leave rooms, respectively. I'm pretty sure (though I haven't tried it. You should try it if you're interested) that a socket can join multiple rooms at once. Now that you're assigned to a room, whenever someone broadcasts to the entire room, you will be notified. Here's how you broadcast to rooms:

// Broadcast to everyone in a room, except you
socket.broadcast.to("room name").emit("your message");

// Broadcast to everyone in a room, including you
io.sockets.in("room name").emit("your message");

And that's pretty much all there is to rooms!

Namespaces

Namespaces aren't technically intended for segmenting your users. Rather, they are used to allow you to have multiple connections to multiple Socket.IO servers, but only require the use of a single Socket.IO server. In other words, a single server acts like multiple servers that you can connect to separately. While the intention is different, it does work to segregate users.

Let's set up the server side to allow multiple connections:

var io = require('socket.io').listen(80);

var chat = io
    .of('/chat')
    .on('connection', function (socket) {
        // Send message to client like usual
        socket.emit('a message', { that: 'only', socket: 'will get' });
        // Broadcast message to everyone in this namespace
        chat.emit('a message', { everyone: 'in', '/chat': 'will get' });
    });

var news = io
    .of('/news');
    .on('connection', function (socket) {
        socket.emit('item', { news: 'item' });
    });

As you can see, all you do is replace sockets with of('/namespace') when you start the on('connection', function(){}) call. This creates a segment of connections that you can keep separate from other connections. As you can see, this setup allows you to broadcast to everyone in this namespace too.

Now we need the clients to connect to them separately. Simply just create separate connections to each of the namespaces and you're all set.

var chat = io.connect('http://localhost/chat'),
    news = io.connect('http://localhost/news');

chat.on('connect', function () {
    chat.emit('connectedToChat');
});

news.on('news', function () {
    news.emit(' connectedToNews');
});

Just add the namespace to the end of the normal URL and you'll connect to the Socket.IO server with that namespace. You can treat chat or news exactly the same way you used to treat socket in the single-connection scenarios.

Configuration

Socket.IO has many configurable options, so they implemented a way to configure it. Here's a quick look at how to do it.

io.configure('production', function(){
    io.enable('browser client etag');
    io.set('log level', 1);

    io.set('transports', ['websocket', 'flashsocket', 'xhr-polling']);
});

First, call io.configure sending in the environment you want the configuration to be set up for and a function. Inside the function, you can use io.enable (which just sets the option's value to true), io.disable (which sets the value to false), and io.set (which sets the value to whatever the second argument is) to change all of the options available to Socket.IO.

If you want more information on changing the environments and which options are available, I recommend you take a look at the wiki page about configuring Socket.IO.

Conclusion

Socket.IO has proven itself to be a great replacement for straight WebSockets. It allows amazing customizability and a wonderfully simple API for enabling real-time 2-way communication between a server and client. It's definitely worth taking a look at. God bless and happy coding!

About the Author

Author: Joe Zim

Joe Zim

Joe Zimmerman has been doing web development ever since he found an HTML book on his dad's shelf when he was 12. Since then, JavaScript has grown in popularity and he has become passionate about it. He also loves to teach others though his blog and other popular blogs. When he's not writing code, he's spending time with his wife and children and leading them in God's Word.


  • Collin Forrester

    When you store client data the way that you demonstrated, have you found that it effects server/socket.io performance at all? We were developing a game once and we were concerned about storing too much on the server side and had to extract some of it to be client side only. Ever had an issue with that?

    • http://www.joezimjs.com Joe Zimmerman

      I haven’t used this feature enough to have any performance problems. This sounds like something to bring up as an issue on the Github page.

  • KP

    Hey,
    Greate Tutorial.I always wanted to know what happens when we use “var socket = io.connect(“localhost”) from two js files

  • Maslow

    Hi ! Really Nice.

    I have a question about handling disconnect event . Im working on a game using socket IO and playable on smartphone. I would like to smartly handle disconnection due to lack of internet connection (on the train for example ). How can I display an overlay + loader each time the player loses connection ?

    Thx :)

    • http://www.joezimjs.com Joe Zimmerman

      That’s simple enough. Just create a module that listens for the disconnect. When the disconnect event is fired, just create a modal/dialog box however you wish. Make sure that it doesn’t have any way to close it except programmatically. Then when the connect event fires, programmatically close the box. The box can contain some text and an animated image for the loading. Don’t worry about trying to reconnect by yourself. Socket.IO will automatically try to reconnect over and over again for quite a while.

      • Maslow

        Nice, so if I lose connection on the train the “disconnect” event is fired. Then, when internet is back (let’s say 15scd later) the “connect” event is fired ?!

        So If a player wants to disconnect (for real) I have to do add a button which executes socket.disconnect() ? This won’t show the modal ?

        Thx again

        • http://www.joezimjs.com Joe Zimmerman

          Mostly true. When you force the client to disconnect, it will still fire the ‘disconnect’ event, but you can set a flag that you check so that the modal won’t show up if the user wanted to disconnect. The client won’t try to reconnect if it was told to disconnect, luckily.

          • Maslow

            Ok Thx :)