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:
var sio = socketIO()
- here we're just creating our Socket.io server using thesocketIO
import above (NOTE: if you're not using TypeScript, then your import is in the formvar socketIO = require('socket.io');
)sio.serveClient( true )
- here we're telling the server to serve any requests for the socket.io-client.js file. You can serve it yourself if you wish, but this is pretty handysio.attach( httpServer )
- here we're attaching Socket.io to thehttpServer
instance created above viahttp.createServer()
sio.on( 'connection', function( socket ){...
- this is called every time a client connects. In the callback function is where you'd add all your event listeners to the socket etc.
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:
- Namespaces are joined when you create the socket, rooms are joined as you want, but you need to build your own events to call
join()
andleave()
- One socket per namespace, but one socket can be in multiple rooms
- Sockets can leave rooms as they please, but to leave the namespace, they need to disconnect
- Communication can happen between rooms, but not between namespaces (there are work-arounds if you keep references on the server)
Or in picture form:
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:
- Instead of sending the ID to the client, you send the nickname instead. Although this means that you need a way to convert back to the socket ID if you want to send one player to send a message to another, and nicknames generally aren't unique
- Send the client the socket ID/nickname dictionary, and it's up to them to keep it up-to-date and display the right name. A bit more awkward, but more flexible as well.
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.
Comments
The best introduction to socket.io that i've ever seen.
I agree with Michael, this "introduction" is a nice summary of Socket.io, very helpful thanks
That's what I was looking for !! Great job !
Excellent, very clear and helpful.
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!
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.
Great job, clear and useful.
D: ni en la documentacion explican mejor :D
the best documentation, salute !!
Nice Tutorial for new User..
This is the most beginner-friendly socket.io tutorial i have seen.
It really helps me.Thanks dude
In the examples you passed client id to send message to specific user but how are you storing the ids on client side
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.I'm new to socket.io. I'm struggling to implement multiple chat. so I want implemented code. Please send code
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?
Merci pour ce tuto ! Simple et complet
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
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
Socket.io should use this as its documentation
can you please give a more description in Nicknames part with some sample code example ?
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:
That's pretty much it
In terms of keeping your nickname between sessions, you can simply save it in LocalStorage.
really, as mentioned above, one of the best tutorial. i found it randomly on Google Images. thanks a lot.
WOW. Wonderful introduction. Have you done other writeups? Specially on understanding CORS etc.
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/.
When I connect to a namespace,
connection
event is being fired in the default namespace. Why so?Hi Jai,
As far as I can tell, this is by design. As I noted above:
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.
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.
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 theconnection
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.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
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.
The best intro for the socket.io. thanks a lot.
Really nice Tutorial that explained all my doubts. Even better than the socket.io docs for a beginner.
Thanks
the best socket tutorial i have seen. thanks a lot
Hi,
I wanted to build a private chat just like the concept of facebook. When the user clicks on certain name(another user) , it creates a room for the them and conversation should exist only between them. I have logged in users both side of browser. How to handle this and make dynamic room for 2 users? How the client recognize another client in my case so that they could send message to one another.
Could you help me on this? Just idea
Thanks alot
Hi Hari,
The example for sending a message to another user should be sufficient for this. If it's just one-to-one chat, you don't need to create a room for them; just send messages between them directly. I'd go so far as to say that all messages are private unless they're scoped to a room.
For example, on your server, you might have a listener like so:
// either toUserID will be null or roomID will be socket.on( 'sendMsg', function( msg, toUserID, roomID ){ if( toUserID ) socket.to( toUserID ).emit( 'receiveMsg', msg, socket.id ); // private msg else if( roomID ) socket.to( roomID ).emit( 'receiveMsgFromRoom', msg, socket.id, roomID ); // msg to room });
Now all you have to do is call
sendMsg
and pass either atoUserID
for a private msg, or aroomID
if you want to make it a room chat (where other people might be). Listen for thereceiveMsg
andreceiveMsgFromRoom
events and you're golden.Does that make sense?
Hello Damian,
Thanks for the response. I got the idea. But I still could not emit the event to another userId.
For example :
io.to(toUser).emit('private message', { message: msg, user: socket.username, date: moment().valueOf() //date: moment(new Date()).format('YYYY-MM-DD, hh:mm a') });
Here, toUser is the user whom I want to send message, but I could not grab his socket id so that i could send message. Or, I might do another way. When I click the user in client side, I want to create a room for both of us. But, I want to restrict other users to join. Meaning, if the length of the room is 2, I dont want to let other join that room. Is there any way to restrict that way?
Can you help me in either of the way?
Thank you so much again..
Hi Hari,
In order to send the right user a message - or indeed do anything with them, including creating a room - you'll need some form of unique identifier. Happily, the socket ID is just that, so it's simply a question of getting that to the client so they can use it. Here's how I would do it:
clientSocket.emit( 'sendMsgTo', {msg:'Hello UserB', to:userB.socketID} );
sendMsgTo
event and uses theto
parameter to send the private message:socket.on( 'sendMsgTo', function( msgObj ){ socket.to( msgObj.to ).emit( 'privateMsg', {msg:msgObj.msg, from:socket.id} ); });
privateMsg
event, containing our messageNow, I've named the send and receive message event differently for clarification, but you can use the same name if you wish.
The only reason I'd have a chat room for private chat between two users is if you wanted to keep a chat history (e.g. one of the users refreshes their page and they're not coming back to a blank page), or it's possible that another user might join them later.
Also keep in mind that the
socket.id
changes when users refresh/reconnect, so you'll have to have an event to get other users to update their values when this happens.I am facing some issues,
I have posted my question here
http://stackoverflow.com/questions/42906574/azure-web-node-js
I'm making a game using socket.io
The problem I'm facing is that when the first client joins the game, he/she can't do anything until the second client comes. Now I've simply applied an if condition stating that when my lists of clients == 2 only then can you make a move. But when i do this the first player can't make any moves at all.
Could you please help out?
Hi,
This is more of a logic or architecture question than something specific to socket.io. All online games have to deal with the same issue that you have. What you do in your particular case depends on the experience that you want to give.
The way we tackle it on Microgolf is that when you click to play a game, on the server you're added into a queue. On the client, we play an animation to show that we're searching for an opponent. At this point, the only thing that they can do is leave the queue (bascially quitting the screen).
It's only when we have 2 players on the server that we create the game, add the players to it, and notify them that a game was found. At this point, on the client, we transition into the game screen.
Waiting queues, or lobbies, are pretty common in dealing with this sort of issue, and relatively easy to set up. It's dealing with all the timing and delays that's the killer (like someone leaving the queue after the game has been created on the server, but before they've received it on the client :D)
In your particular case, it's normal that the player can't make any moves while waiting on an opponent to arrive. All you need to do is show them that they're waiting with a small animation or something similar.
Hi,
I'm trying to implement a private chat between two clients. But difference is they need to login to send a message, that i have taken care of.
But i am confused to how to send msg to a client from another client if this client is offline. In this case i don't have socket.id of this user.
Hi Vivek,
If you want to do offline message, then you're going to need a DB. You can technically store them in memory, but you'd lose any messages if you're in a multi-server environment, or restart your server. A simple approach:
messages
object in the DBfriends
object, or send it with every message. Note, this is not thesocket.id
for UserB, as that changes every time they connectmessages
object. If it exists, great, if not, then we create it. This object is very basic. If you're using MongoDB, then it's be something like{ _id:"UniqueIDForUserB", messages:[] }
messages
object. As they can get multiple messages while offline, this is obviously an Arraymessages
object, and if there's anything in the array, we dispatch it on and clear the array (unless you want to keep a history)Let me know if any of that doesn't make sense. You can use any DB that you like for this. Personally, I'm more familiar with MongoDB, and as a JavaScript dev, it's super easy to get up and running on it. There aren't very many shared hosting with Mongo included however, so it'll probably mean a dedicated server.
Thanks for a great explanation on IO. Kindly advise what could be the best approach, if i want to send messages to a group of my followers only. something similar to tweeter, where you get to see tweet of people you follow only. Thanks
Hi Norbert,
Socket.io is only really for people that are connected right now, so it would depend on your application:
You touched every part that is needed by Newbie to Socket.io like me. Thanks a lot. I am gonna create an App from this.
Best tutorial i ever seen about socket
https://gist.github.com/rupesh2017/1551c56e39759b412624f0202073b931
https://gist.github.com/rupesh2017/9590be8621b2899caed70a534a00b1f1
Sending a message to another user - connected dictionary on our namespace?
var otherSocket = sio.sockets.connected[userID];
please explain this?
i tried running in console
var socket = io.connect(location.protocol + '//' + document.domain + ':' + location.port); socket.id // it worked
i tried
var otherSocket = socket.sockets.connected[userID]; socket.sockets.connected?? error
does userID here is socket.id
Hi Rupesh,
If I understand you correctly, you're having a problem with sending a message to another user?
It looks like the issue is that you're trying to run this particular code on the client, rather than the server. Any time you see
socket
you can assume it's server code, whileclientSocket
points to client code.Only the server has a list of connected sockets, held in an
Object
with the socket's ID as the key. To break the flow into steps:connected
ObjectFor User1 to send a message to User2, they need to know User2's ID (or any other uniquely identifying feature, such as a username that you're stocking).
How they get this depends largely on your preference, but a simple method is to send a "user joined" event when a socket connects to a room, and to send the ID with that.
Am trying to build a chat app, where user can message themselves privately, but am confuse on either to use the approach of joining a room or storing the socket. because i will like to keep chat history and also am using mysql database. please advice me. if am to use join room approach , how can i dynamically create and subscribe the two user to the room and still keep chat history for them. thanks.
Hi David,
I'm not 100% sure I know what you mean when you say "storing the socket". Because the ID of the socket is different every time the socket connects, in order to keep a history, you'll need to develop that functionality yourself.
I'd probably do something like the following:
{ "id": "User2320203", "nickname": "Damian", "email": "foo@email.com", "password": "sadgj98g2t{agsaA_AGKauasdgu2%&", "friends":[ "User2030020", "User1028238" ], "rooms":[ "Room203082382", "Room191834803" ] }
Auth
event. How you do your authentication is up to you (JSON web tokens, password, whatever), but it's just so that we can associate the unique socket ID with the user, and mark them as onlineChatRoom29837275762
), a list of the users subscribed, and any previous chat historySubmit a comment