I'd like to know if it's possible to broadcast on different websocket "connections" running from the same node-websocket-server app instance. Imagine a chatroom server with multiple rooms, only broadcasting messages to the participants specific to each room, on a single node.js server process. I've successfully implemented a one-chatroom-per-process solution, but I want to take it to the next level.
4 Answers
You would probably like to try Push-it: http://github.com/aaronblohowiak/Push-It which is built on top of Socket.IO. Design adheres to the Bayeux Protocol.
However, if you need something that uses redis pubsub you can check http://github.com/shripadk/Socket.IO-PubSub
Specifically answering your question: You can maintain an array of all the clients connected to the websocket server. And probably just broadcast to a subset of those clients? The broadcast method does essentially that under the hood. node-websocket-server/Socket.IO maintains an array of all the clients connected and just loops through all of them "send"ing a message to each of the clients. Gist of the code:
// considering you storing all your clients in an array, should be doing this on connection:
clients.push(client)
// loop through that array to send to each client
Client.prototype.broadcast = function(msg, except) {
for(var i in clients) {
if(clients[i].sessionId !== except) {
clients[i].send({message: msg});
}
}
}
So if you want to relay messages only to specific channels, just maintain a list of all the channels subscribed by the client. Here is a simple example (to just get you started) :
clients.push(client);
Client.prototype.subscribe = function(channel) {
this.channel = channel;
}
Client.prototype.unsubscribe = function(channel) {
this.channel = null;
}
Client.prototype.publish = function(channel, msg) {
for(var i in clients) {
if(clients[i].channel === channel) {
clients[i].send({message: msg});
}
}
}
To make it even easier use EventEmitters. So in node-websocket-server/Socket.IO see where the messages are being received and parse the message to check the type (subscribe/unsubscribe/publish) and emit the event with the message depending on the type. Example:
Client.prototype._onMessage = function(message) {
switch(message.type) {
case 'subscribe':
this.emit('subscribe', message.channel);
case 'unsubscribe':
this.emit('unsubscribe', message.channel);
case 'publish':
this.emit('publish', message.channel, message.data);
default:
}
}
Listen to the events emitted in your app's on('connection') :
client.on('subscribe', function(channel) {
// do some checks here if u like
client.subscribe(channel);
});
client.on('unsubscribe', function(channel) {
client.unsubscribe(channel);
});
client.on('publish', function(channel, message) {
client.publish(channel, message);
});
Hope this helps.

- 10,463
- 4
- 52
- 65
-
2That's awesome, thanks very much! If only I'd known when I started, I would have used Socket.IO from the beginning. – S M Dec 15 '10 at 10:38
-
2Immensely helpful. Thank you! – Matt Dec 22 '10 at 04:51
-
2is it good idea to just broadcast the message to all channels and clients, then we will do filter on the browser? – angry kiwi May 07 '11 at 18:28
-
@runrunforest: That depends on your setup really. If you feel your app will function properly even if client's modified the channel they are subscribed to then it is ok i guess. I would normally let the filtering happen on the server side (example: private chat rooms. I wouldn't want a client who isn't authorized, to subscribe to the room, and be able to read the rooms messages) – Shripad Krishna May 08 '11 at 03:03
-
@ShripadK I've an issue about web-sockets and multiple channels [here](http://stackoverflow.com/q/15903816/1659451) maybe you can help me. For example I need to open 10k channels for 10k users. What is the cost of having so so many channels for a notification system? – Ömer Faruk Almalı Apr 13 '13 at 15:14
I'm not sure if rooms were a feature when the other answers were created, but in the documentation, they have a feature exactly what you are looking for. So go to that link and search for rooms
.
Here is an example from the site:
var io = require('socket.io').listen(80);
io.sockets.on('connection', function (socket) {
socket.join('justin bieber fans');
socket.broadcast.to('justin bieber fans').emit('new fan');
io.sockets.in('rammstein fans').emit('new non-fan');
});
Based on the other answers, it was more focused on scaling, I would love some insight if the built in version scales well as the proposed answers.

- 56,733
- 95
- 279
- 406
-
Thanks! Back when I posed this question, rooms were not a feature, but they've been in Socket IO for a while now. – S M Feb 20 '12 at 20:07
-
12Yep, rooms were not then. And I guess the reason for low upvotes on this question is that you created a `justin beiber fans` room :P – gopi1410 Jun 25 '13 at 12:42
Shripad K's answer is very well structured. Good job.
I think that solution will have some scaling issues though.
If you had 10,000 concurrent users in 500 chat rooms, then every time any user sent a message, you'd have to loop through all 10,000 clients. I suspect that it would be faster to store the list of clients in a given room in a structure in redis and just grab this list and send to those clients.
1) Not sure if that's actually faster. 2) Not sure what could be stored in redis that would then allow us to reference clients. Maybe there could be a hash of all clients in the server, by a unique id and in redis, we could just store a set of the user id's per chat room?
Does this seem any more scalable?
I've written a node chat server based on fzysqr's and need to make it scalable for multiple chats before we roll it out widely.

- 170,088
- 45
- 397
- 571

- 1,459
- 17
- 24
-
@Sean: Even if you stored a list in redis you would still eventually have to loop to send the message to each client. However, since you mentioned Redis you should have a look at the pub/sub mechanism if you have scaling in mind. :) See the second link in my answer (Socket.IO-PubSub: Redis + Socket.IO) – Shripad Krishna Apr 02 '11 at 08:18
-
Also scaling is a vast subject. One best way to scale is to launch multiple nodes of your socket server and load balance them using sticky sessions. For such a scenario you need to separate your socket server from the main app server. – Shripad Krishna Apr 02 '11 at 08:21
-
@Shrupad K: It's would be great if you can provide a simple code about launching multiple nodes and load balance using sticky sessions. – angry kiwi May 07 '11 at 18:37
-
1@rurunforst: I already have :) Launching multiple nodes is as simple as launching a copy of your code over different ports/IPs. You need to make sure that each process can function independently and needn't depend on the other process. Each can communicate via redis or 0mq. To load balance, use HAProxy. See my answer here: http://stackoverflow.com/questions/4360221/haproxy-websocket-disconnection/4737648#4737648 – Shripad Krishna May 08 '11 at 02:59
-
I know the ports can be added or modified, but isn't that a server has only one specific IP? – angry kiwi May 08 '11 at 04:21
-
-
1Yes. You need to create multiple instances (example on Amazon EC2, each having its own elastic IP) and have another instance running HAProxy which load balances these instances. – Shripad Krishna May 08 '11 at 04:56
-
2Well if you don't load balance then there is no way to proxy requests to those multiple nodes. So you just have nodes idling with no requests reaching them. – Shripad Krishna May 08 '11 at 04:57
-
@Shripad K: should I install HAProxy in the same server with nodejs or on a different linux server? – angry kiwi May 08 '11 at 15:49
-
Its better to have HAProxy on a different instance (it can also be, along with monit, be used to check the health of your node instance(s)). HAProxy is quite easy to setup. Just go through the "Architecture" document on the HAProxy website which provides for different scenarios. Hope this helps :) – Shripad Krishna May 08 '11 at 17:42
-
1
-
The term "server" refers to the Node process and the term "instance" refers to your linux box. – Shripad Krishna May 09 '11 at 08:31
With rooms my simple test chat looks like
chat.js:
var app = require('http').createServer(handler)
, io = require('socket.io').listen(app)
, fs = require('fs')
app.listen(80);
function handler (req, res) {
fs.readFile(__dirname + '/chat.html',
function (err, data) {
if (err) {
res.writeHead(500);
return res.end('Error loading chat.html');
}
res.writeHead(200);
res.end(data);
});
}
io.sockets.on('connection', function (socket) {
socket.on('join', function (room) {
if (Array.isArray(room)) {
var i;
for (i = 0; i < room.length; ++i) {
console.log('join room ' + room[i]);
socket.join(room[i]);
}
} else if (typeof room === 'string') {
console.log('join room ' + room);
socket.join(room);
}
});
socket.on('leave', function (room) {
if (typeof room === 'string') {
console.log('leave room ' + room);
socket.leave(room);
}
});
socket.on('post', function (data) {
io.sockets.in(data.room).emit('publish', data);
});
});
and chat.html:
<html>
<head>
<title>Node js test</title>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.1/jquery.min.js"></script>
<script src="http://127.0.0.1:80/socket.io/socket.io.js"></script>
</head>
<body>
<h2>Node js test</h2>
<div style="height:400px;">
<div id="controls" style="height:400px; display: inline-block; width:20%; float:left; background-color:lightyellow;">
<input id="room1_check" type="checkbox" value="room_1" checked /><label for="room1_check">Room 1</label><br/><br/>
<input id="room2_check" type="checkbox" value="room_2" /><label for="room2_check">Room 2</label><br/><br/>
<input id="room3_check" type="checkbox" value="room_3" checked /><label for="room3_check">Room 3</label><br/><br/>
<input id="room4_check" type="checkbox" value="room_4" /><label for="room4_check">Room 4</label><br/><br/>
<input id="room5_check" type="checkbox" value="room_5" /><label for="room5_check">Room 5</label><br/><br/>
</div>
<div id="stream" style="height:400px; display: inline-block; width:40%; background-color:white; overflow:auto;"></div>
<div id="post" style="height:400px; display: inline-block; width:40%; float:right; background-color:yellow;">
<label for="postRoom">Room: </label>
<select id="postToRoom">
<option value="room_1">Room 1</option>
<option value="room_2">Room 2</option>
<option value="room_3">Room 3</option>
<option value="room_4">Room 4</option>
<option value="room_5">Room 5</option>
</select>
<br/><br/>
<label for="postBy">By: </label>
<select id="postBy">
<option value="User 1">User 1</option>
<option value="User 2">User 2</option>
<option value="User 3">User 3</option>
<option value="User 4">User 4</option>
<option value="User 5">User 5</option>
</select>
<br/><br/>
<label for="postMessage">Message:</label><br/>
<textarea id="postMessage" style="width:80%; height:100px;" ></textarea>
<br/><br/>
<input id="postBtn" type="button" value="post message" />
</div>
</div>
<script>
var socket = io.connect('http://127.0.0.1:80');
var checkedRooms = [];
$('#controls :checked').each(function() {
checkedRooms.push($(this).val());
});
socket.emit('join', checkedRooms);
socket.on('publish', function (post) {
//console.log(data);
$("#stream").html($("#stream").html() + "room: " + post.room + "<br/>");
$("#stream").html($("#stream").html() + "by: " + post.by + "<br/>");
$("#stream").html($("#stream").html() + "on: " + post.on + "<br/>");
$("#stream").html($("#stream").html() + "message: " + unescape(post.message) + "<br/>");
$("#stream").html($("#stream").html() + "=============================================<br/>");
});
$('#controls :checkbox').change(function () {
socket.emit(this.checked ? 'join' : 'leave', $(this).val());
});
$("#postBtn").click(function() {
socket.emit('post', {room: $("#postToRoom").val(), message: escape($("#postMessage").val()), by: $("#postBy").val(), on: (new Date() + "") });
});
</script>
</body>
</html>

- 919
- 9
- 15