divillysausages.com

An intro to Socket.io

Recently I've been playing around with Socket.io, which, according to the site, enables real-time bidirectional event-based communication. Damn, that's some sexy copy.

Basically you can use it as a real-time server, and one of the things that makes it so interesting is that it works on pretty much every platform, browser, or device. I thought I'd write an intro on the subject as I found their documentation to be lacking in some cases and confusing in others; I frequently found myself with questions that I only answered through trial-and-error and reading the source code.

And you should never have to read the source code.

TypeScript!

I'm using TypeScript, so one of the first things I did was try and hook myself up with some definition files so I could get some code completion. I found the excellent DefinitelyTyped project and related tsd, or TypeScript Definition manager.

Unfortunately, after scratching my head for a day or two, I finally realised that the d.ts files were for an old version. Sadface.

So I learned how to create TypeScript definition files, and went through the code and created new definitions for the latest version (1.3.5 as of time of writing). If the pull request hasn't been accepted yet, then you can find them at the bottom of this post.

They're even commented to boot.

AND I went out of my way to describe the options object, which nobody apparently does, but blow trawling through documentation and READMEs for a lark. If it's not code-completed, it doesn't exist.

The setup

For the setup, I worked from the Chat example on the Socket.io website, with the exception that I didn't use Express (on a side node, Express is great and all, for beginners, one framework at a time, please). You should be able to follow that example in order to get setup. To install Socket.io (locally), just use:

npm install socket.io

Like in my tutorial for Using Phaser with Visual Studio Code I'm not using Express, I'm using node-static (as all it does is serve files from whatever directory you throw at it, so it's a perfect light-weight solution). If you want to use that, then install via:

npm install node-static

Then set up your app.js file like this:

import nodeStatic 	= require( 'node-static' );
import http			= require( 'http' );
import socketIO		= require( 'socket.io' );

// create our file server config
var file = new nodeStatic.Server( 'bin', { // bin is the folder containing our html, etc
	cache:0,	// don't cache
	gzip:true	// gzip our assets
});

// create our server
var httpServer = http.createServer( function( request, response ) {
	request.addListener( 'end', function() {
		file.serve( request, response );
	});
	request.resume();
}).listen( 4000 );

That's it. From this point out, it's all Socket.io. Note that when we call http.createServer(), we save the returned Server as our httpServer var; we'll need that later when binding Socket.io.

The basics

First of all: connecting. On the server, setup Socket.io like this:

var sio = socketIO();
sio.serveClient( true ); // the server will serve the client js file
sio.attach( httpServer );

// listen for a connection
sio.on( 'connection', function( socket ){
	
	console.log( 'User ' + socket.id + ' connected' );
});

To explain a bit:

On the client, you embed the client side code via:

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

That particular path is if you're using sio.serveClient( true ). Otherwise, if you're serving it yourself, replace it with the right path. To connect to our server, it's simply:

var clientSocket = io();

Start your server (node app.js), go to http://localhost:4000 in your browser, and you should see something like

User 8fyIQvC4HUYEpZeDAAAA connected

in your console. Eassssssy.

Namespaces and rooms

A quick note on namespaces and rooms and what the difference between the two is. By default, when you call io() on the client to connect, what you're actually calling is io( 'http://localhost:4000/' ), as by default, it will take the host and port of the current page, and connect to the default namespace, which is /. If you wanted to connect to a namespace called 'foo', then you'd have to call io( 'http://localhost:4000/foo' ).

So what's a namespace?

It's a way of separating your code. It's one socket to a namespace, so if you called:

var news = io( 'http://localhost:4000/news' );
var chat = io( 'http://localhost:4000/chat' );

while it might use the same physical TCP connection, they're two separate objects - news can't send messages to /chat and vice-versa.

The main namespace, /, is created by default, and all clients join it, even if they connect to another namespace.

On the server, when a namespace emits an event, all sockets connected to the namespace receive it.

After that, we have rooms. Rooms are essentially grouping of sockets, and let you send messages to a subgroup of a namespace. You only need to know the name of a room to send a message to it. Unlike with namespaces, sockets can join as many rooms as they want.

Think of it like this: the Socket.io server is your game website (e.g. all-poker.com), namespaces are each game (e.g. /texas, /omaha), and rooms are private rooms within each game (e.g. table1, table2, etc).

The differences in list form:

Or in picture form:

Explaining namespaces and rooms

Sending a message to the client

To send a message to the client, we call emit() on the socket, passing the event name, and whatever data we want to send:

socket.emit( 'greetings', 'Hello from the server!', socket.id );

Then on the client, we add a listener for the 'greetings' event:

clientSocket.on( 'greetings', function( message, id ) {
	console.log( 'Got a message from the server: "' + message + "', my ID is: " + id );
});

Note that we're sending the socket.id to the client with the emit() call. We could have also called clientSocket.id, but we can use this technique to send the IDs of other sockets, which will become useful later. The id is a auto-generated ID set when the socket connects. It'll be different everytime, so there's no point storing it.

Also note that the only parameter that's actually required is the event name, in this case 'greetings'. Anything after that is sent to the client as parameters for the callback - with one exception: if the last argument is a function, then it'll be used as an ack - called when receipt of the message is acknowledged. This is a bit more advanced, so for the minute, we don't care.

Sending a message to the server

Sending a message to the server is exactly the same. On the client:

clientSocket.emit( 'message', 'Hello from the client' );

While on the server, we need to add any listeners to the socket that we get during the 'connection' event:

// listen for a connection
sio.on( 'connection', function( socket ){

	console.log( 'User ' + socket.id + ' connected' );
	
	// listen for the 'message' event
	socket.on( 'message', function( msg ){
		
		console.log( 'User ' + this.id + ' sent message "' + msg + '"' );
	});
});

Note that in the socket callback, you can use this to reference the socket (or socket as it'll be bound).

This is about the height of what you need to do on the client. Everything after this (sending messages to different people etc) happens on the server.

Sending a message to everyone in the namespace

To send a message to everyone, instead of calling socket.emit(), which only sends a message to that socket, we get the namespace itself to send the event.

In this example, we're using the sio Server object, created when we created Socket.io on the server.

socket.on( 'tellEveryone', function( msg ){

	sio.emit( 'heyEveryone', msg, socket.id ); // pass the socket.id so other client know who it's from
});

The sio.emit() method here is actually a helper function that gets the default namespace (/) and calls emit() on that. The following 3 calls all do the same thing:

sio.emit( 'heyEveryone', msg, socket.id );
sio.sockets.emit( 'heyEveryone', msg, socket.id ); // sio.sockets is a reference to the main "/" namespace
sio.of( '/' ).emit( 'heyEveryone', msg, socket.id ); // of( '/' ) is how we reference a namespace, in this case the main "/" one

Notice that last of( '/' ) call? That's how we reference other namespaces. Remember how I said sockets in one namespace couldn't send messages to sockets in other namespaces? This is how you get around that:

socket.on( 'tellNamespace', function( msg, namespaceName ){

	sio.of( namespaceName ).emit( 'heyEveryone', msg, socket.id ); 	// NOTE: namespace will be created if it doesn't exist
});

Sending a message to everyone in a room

To send a message to a room, you need to know it's name. You don't actually need to have joined the room to send a message to it, but you do if you want to receive messages from that room.

socket.on( 'tellRoom', function( msg, roomName ) {

	socket.to( roomName ).emit( 'heyThere', msg, socket.id ); // NOTE: room will be created if it doesn't exist
});

You can also use in( roomName ) instead of to() if you wish.

You can also send to multiple rooms by chaining the to() calls:

socket.on( 'tellRooms', function( msg, room1Name, room2Name ){
	
	socket.to( room1Name ).to( room2Name ).emit( 'heyThere', msg, socket.id );
});

Note that on the client, if they've joined multiple rooms, when they get a message, they won't actually know which room (if any) sent it, so it's generally a good idea to pass the room name as one of the parameters.

Sending a message to everyone except you

The socket has a broadcast flag, that when used will send the event to everyone in the namespace/room except for yourself. For a namespace:

socket.on( 'broadcastMsg', function( msg ) {

	socket.broadcast.emit( 'broadcastFrom', msg, socket.id );
});

or for a room:

socket.on( 'broadcastMsg', function( msg, roomName ) {

	socket.to( roomName ).broadcast.emit( 'broadcastFrom', msg, socket.id );
});

Sending a message to another user

To send a message to another user, we can go about it two ways: we can get the socket of the other user from the connected dictionary on our namespace and call emit() on it:

socket.on( 'sendToUser', function( msg, userID ){
	
	// try and get the socket from our connected list
	var otherSocket = sio.sockets.connected[userID]; // NOTE: sockets is the default "/" namespace
	if( otherSocket )
		otherSocket.emit( 'messageFromUser', msg, socket.id );
});

But remember when I said it's a good idea for the client to know the IDs of other clients? This is because when a socket connects, one of the things that it does it join a room with the same name as its ID. So to send a message to a client, we can use the same logic as sending a message to a room:

socket.on( 'sendToUser', function( msg, userID ){

	socket.to( userID ).emit( 'messageFromUser', msg, socket.id );
});

Technically this means that if we join() this room, we'd be privy to all the private messages sent to this user, which opens up all sorts of interesting possibilities.

Getting the IDs of connected users

If you want to send a list of all the socket IDs in a particular namespace, you can go through the connected dictionary on the namespace:

var sids = keys( sio.sockets.connected ); // sockets = default "/" namespace

To send all the IDs for a particular room, you need to go through the adapter property on the namespace:

var socketsInRoom 	= sio.sockets.adapter.rooms[roomName]; // sockets = default "/" namespace
var sids 			= ( socketsInRoom ) ? keys( socketsInRoom ) : []

Nicknames

To enable nicknames, you simply need to keep a dictionary of socket ids => nicknames on the server. Then you can either do two things:

Fin

So there you have it! You should be set up for firing messages left and right. If there's another scenario you want covered, leave a comment, and I'll add it in.

In the meantime, check out Socket.io and download the v1.3.5 TypeScript definition files below.

Files

Comments

Michael

The best introduction to socket.io that i've ever seen.

Adrien

I agree with Michael, this "introduction" is a nice summary of Socket.io, very helpful thanks

Kev

That's what I was looking for !! Great job !

Sir

Excellent, very clear and helpful.

Varoon

Nicely explained in a very simple form. I am looking for a tutorial that maps the theory of websockets and TCP to the socket.io API. You are probably the best guy to do it. That with this tutorial would be all that one would need to be a good socket.io coder!

Damian Connolly

Hi Varoon, do you mind being more precise as to what you're looking for? The whole point of Socket.io is to handle TCP so you don't have to. Aside from the parameters that are passed to the underlying Engine.io instance, you generally don't need to do any heavy lifting.

Marco

Great job, clear and useful.

Julio Pari

D: ni en la documentacion explican mejor :D

imma hanitya

the best documentation, salute !!

balwant padwal

Nice Tutorial for new User..

Em

This is the most beginner-friendly socket.io tutorial i have seen.

Gayathri G

It really helps me.Thanks dude

ZAIN

In the examples you passed client id to send message to specific user but how are you storing the ids on client side

Damian Connolly

Hi Zain,
A simple Object on the client is enough to store your ID/nickname. Something like:

var nicknames = {};

// when you get a nickname/id:
nicknames[socket.id] = nickname

As the socket.id changes every time, you don't need to stock it, so you can just recover them all when you connect.

Manoj

I'm new to socket.io. I'm struggling to implement multiple chat. so I want implemented code. Please send code

Damian Connolly

Hi Manoj,
In the Getting Started section of Socket.io, you can find a step-by-step guide on creating a chat application. What have you tried?

Fabien

Merci pour ce tuto ! Simple et complet

hamidkardorost

great tutorial . thank you
one thing that's not covered well in web is "Authentication Socket"
it would be nice if you could add some tuts about auth as well

Damian Connolly

Hi Hamidkardorost,
What do you mean by "authentication"? Personally, I simply have an event like any other where the user sends their ID/Token. Prior to that they can be marked as a guest.

There are other approaches as well, such as https://auth0.com/blog/auth-with-socket-io/ for example, which uses JSON Web Tokens

R Martin

Socket.io should use this as its documentation

hi,my name is Hardik

can you please give a more description in Nicknames part with some sample code example ?

Damian Connolly

Hi Hardik,
There's really no special logic to take into account - as in there's no "nickname" event built-in to Socket.io, so how you want to handle it is up to you. Assuming a simple drop-in/drop-out setup where there's no login or anything, one way would be:

  • On the server hold an object where the key is the socket ID, and the value is the nickname for the socket. As the socket ID changes every time you reconnect, there's no need to persist this in a DB or anything
  • When a user connects, they send an event (e.g. "setNickname") with their chosen nickname. We update the object and notify everyone else in the room
  • When a user disconnects, we clear their key from the object as there's no point in keeping it
  • For a new user to know everyone's nickname, you can do one of two things:
    • 1) Send the socket ID/nickname object to them on connect, and the user updates it locally when someone else joins the room
    • 2) When a user sends a message, the server looks up their nickname in the object and sends it along with the message

That's pretty much it

In terms of keeping your nickname between sessions, you can simply save it in LocalStorage.

bluemix

really, as mentioned above, one of the best tutorial. i found it randomly on Google Images. thanks a lot.

Alok

WOW. Wonderful introduction. Have you done other writeups? Specially on understanding CORS etc.

Damian Connolly

Hi Alok,
You can find all my other content listed on the Archive page.

I don't have anything on CORS, but you can find a nice writeup at https://www.html5rocks.com/en/tutorials/cors/.

Jai

When I connect to a namespace, connection event is being fired in the default namespace. Why so?

Damian Connolly

Hi Jai,
As far as I can tell, this is by design. As I noted above:

The main namespace, /, is created by default, and all clients join it, even if they connect to another namespace.

I'm not 100% on whether this is because you connect to the default namespace before being shuffled off to the sub namespace, or if it's to allow you a way of knowing about and contacting all connected sockets.

Either way, it can be quite useful, and pretty easy to ignore if you don't want to use it.

Jeff

How do you use socket.io to push random data updates to client subsequent to the initial client connection event? All examples of emit seem to assume they are triggered on client connection, not a server-side event.

Damian Connolly

Hi Jeff,
How you send messages after the initial connection really depends on your program. The vast majority of the time, you'll be sending clients messages in relation to an event, normally a client event - e.g. ClientA sends a chat message.

The reason why it looks like we're doing everything through the client connection is because that's normally where we'd add all the callbacks for that client. e.g. we can have something like:

sio.on( 'connection', function( socket ){

	console.log( 'User ' + socket.id + ' connected' );
	
	// add our event listeners
	socket.on( 'chat', onClientChat );
	socket.on( 'move', onClientMove );
	socket.on( 'enterRoom', onClientEnterRoom );
	socket.on( 'changeName', onClientChangeName );
	
});

These are all the events that your client can call, which can then trigger data updates for other clients. For example, for the changeName event, you might send a message to all other connected clients so they know the new name. While the listeners are added in the connection event, they're not necessarily called there and then.

Otherwise, you might have an event on the server that's triggered every so often (e.g. using setInterval) that will automatically sent a message (or data) to all connected users (or only those in particular rooms). Again, how you trigger server side events is really dependant on how you want to do things.

Jeff

Thank you very much for your answer. I have in fact solved my problem and now am able to emit messages at will to connected clients independent of the "on connection" loop. Among the various use cases for socket.io it seems that most of the emphasis is on game play or chat room like applications most of which are based on client-driven interactions while use cases which are server-driven, like updating consoles, dashboards with real-time data from sensors or node express application data are not emphasized. Again thank you for all your efforts. Socket.io owes you a debt of gratitude. J'aime Bordeaux, vous avez la chance d'habitez la

Damian Connolly

Thank you, you're welcome!

Most examples are simple chat-based applications simply because it's the easiest example to show off what Socket.io capable of. Once you get the hang of it though, it's easy enough to have nearly anything trigger an update.

Submit a comment

* indicates required