room broadcast vs sockets emit - are they the same? - node.js

I'm using my own rooms implementation in Socket.io (got my own Room/Player classes), since I need to perform several filters the room. For now, I save all sockets inside "players" property in a Room, and implemented my own "emit" to the room, the loops players and emits to their sockets.
Is it considerably slower to the traditional broadcast.to('room')? or it basically does what I did to my own room implementation? I'm aiming on having thousands of rooms with 2-4 players in each...
Thanks :)

As you can see by looking at the code for the socket.io adapter .broadcast() on GitHub, all socket.io is doing is looping over a list of sockets and sending a packet to each one (see code below).
So, if your code is doing something similar, then performance would likely be something similar.
Where you might notice a feature difference is if you are using a custom adapter such as the redis adapter that is used with clustering, then the logic of broadcasting to users connected to different servers would be handled for you by the built-in adapter, but may be something you'd have to implement yourself if doing your own broadcast.
Here's the socket.io-adapter version of .broadcast():
Adapter.prototype.broadcast = function(packet, opts){
var rooms = opts.rooms || [];
var except = opts.except || [];
var flags = opts.flags || {};
var packetOpts = {
preEncoded: true,
volatile: flags.volatile,
compress: flags.compress
};
var ids = {};
var self = this;
var socket;
packet.nsp = this.nsp.name;
this.encoder.encode(packet, function(encodedPackets) {
if (rooms.length) {
for (var i = 0; i < rooms.length; i++) {
var room = self.rooms[rooms[i]];
if (!room) continue;
var sockets = room.sockets;
for (var id in sockets) {
if (sockets.hasOwnProperty(id)) {
if (ids[id] || ~except.indexOf(id)) continue;
socket = self.nsp.connected[id];
if (socket) {
socket.packet(encodedPackets, packetOpts);
ids[id] = true;
}
}
}
}
} else {
for (var id in self.sids) {
if (self.sids.hasOwnProperty(id)) {
if (~except.indexOf(id)) continue;
socket = self.nsp.connected[id];
if (socket) socket.packet(encodedPackets, packetOpts);
}
}
}
});
};

Related

Socket.io sending message to specific socket.id not working?

I have this app in socket.io using node.js server and I want to update the points for every one connected in the app. I keep a list of connected users by socket.id and secret in the clients array.
And I tried like this but the points won't update. Can anyone tell me what's wrong?
function updatePoints(){
points = [];
pool.getConnection(function(err, connection) {
connection.query("SELECT * FROM `users`", function(error, rows) {
for (var i = 0; i < rows.length; i++) {
points.push({"secret" : rows[i].secret, "points" : rows[i].pontos});
}
for (var l = 0; l < points.length; l++) {
for (var p = 0; p < clients.length; p++) {
if(points[l].secret == clients[p].secret){
var socketID = clients[p].socket;
var pontos = points[l].points;
io.sockets.connected[socketID].emit('update points', pontos);
}
}
}
});
});
}
Socket.io version: 1.3.7
How about make a client object theb assigning a socket and a point property then push it in a the clients array?
socketServer.on('connection', function(socket){
//make your query here
...
//make a client object
var client = {point: row.point, socket: socket};
clients[row.secret] = client;
});
then you can retrieve the client by:
//query...
....
clients[row.secret].socket.emit('something', function);
edit:
if you want to broadcast to connected clients then you can:
socketServer.emit('something', function);
if you want to broad to everyone except the one who emitted the broadcast. then you can:
clients[row.secret].broadcast.emit('something', function;
Replace the
io.sockets.connected[socketID].emit('update points', pontos);
with this
socket.broadcast.to(socketID).emit('update points', pontos);

Resolve Clients and rooms - Migration from socket.io 0.9 to +1.x

I am trying to adapt a piece of code that was coded with socket.io 0.9 that returns a list of clients in a specific room and list of rooms(typical chat-room example)
Users in room
var usersInRoom = io.sockets.clients(room);
List of rooms
socket.on('rooms', function() {
var tmp = io.sockets.manager.rooms;
socket.emit('rooms', tmp);
});
tmp looks like this
{
0: Array[1],
1: /Lobby: Array[1]
}
So I can show the list in the client with this javascript run on the browser.
socket.on('rooms', function(rooms) {
$('#room-list').empty();
debugger;
for(var room in rooms) {
room = room.substring(1, room.length);
if (room != '') {
$('#room-list').append(divEscapedContentElement(room));
}
}
$('#room-list div').click(function() {
chatApp.processCommand('/join ' + $(this).text());
$('#send-message').focus();
});
});
But for version >1.x I just found the clients/rooms changed.
Following some links I found here, I could manage to get a list of rooms by doing this:
socket.on('rooms', function(){
var tmp = socket.rooms;
socket.emit('rooms', tmp);
});
The problem here is that socket.rooms returns
{
0: "RandomString",
1: "Lobby",
length: 2
}
And I just need to pass the 'Lobby' room. I don't know from where the random string come from.
EDIT
Through some debugging I discovered, the randomstring is the socket.id ... Is it normal this behavior? Returning the room and the socket.id together?
Updated
I finally got some results
Users in room
var usersInRoom = getUsersByRoom('/', room);
function getUsersByRoom(nsp, room) {
var users = []
for (var id in io.of(nsp).adapter.rooms[room]) {
users.push(io.of(nsp).adapter.nsp.connected[id]);
};
return users;
};
List of rooms
function getRooms(io){
var allRooms = io.sockets.adapter.rooms;
var allClients = io.engine.clients;
var result = [];
for(var room in allRooms){
// check the value is not a 'client-socket-id' but a room's name
if(!allClients.hasOwnProperty(room)){
result.push(room);
}
}
return result;
}
Better and more straightforward ways to achieve the results?
These are the links I checked:
How to get room's clients list in socket.io 1.0
socket.io get rooms which socket is currently in
It seems there is no better approach, so I will use my own answer. In case any of you has a better solution, I would update the accepted one.
Users in room
var usersInRoom = getUsersByRoom('/', room);
function getUsersByRoom(nsp, room) {
var users = []
for (var id in io.of(nsp).adapter.rooms[room]) {
users.push(io.of(nsp).adapter.nsp.connected[id]);
};
return users;
};
List of rooms
function getRooms(io){
var allRooms = io.sockets.adapter.rooms;
var allClients = io.engine.clients;
var result = [];
for(var room in allRooms){
// check the value is not a 'client-socket-id' but a room's name
if(!allClients.hasOwnProperty(room)){
result.push(room);
}
}
return result;
}

How to increase performance of redis sub?

I have code like that
var subscribeNewMessages = require("redis").createClient(config.redis.port, config.redis.host);
subscribeNewMessages.subscribe('new-messages');
io.of('/new-messages').on('connection', function (client) {
subscribeNewMessages.on("message", function(channel, message) {
var obj = JSON.parse(message);
if (client.userId == obj.toUserId || client.guestId == obj.toUserId) {
client.send(message);
}
obj = null;
});
})
And how can I optimize it? Because this code parses string json for each new messages.
Also when I try to publish to redis chanel I need to JSON.stringify
redis1.publish(channelPrefix, JSON.stringify(clientData));
There isn't going to be a way to avoid JSON.parse()/JSON.stringify() as long as you're storing js objects. You could use a different serialization format like msgpack, but you're still calling functions to serialize/unserialize your data (also JSON.parse()/JSON.stringify() are already pretty hard to beat performance-wise in node).
I think the only real performance adjustment you could make with the code you've provided is to only parse the JSON once for all clients instead of for each client. Example:
var subscribeNewMessages = require('redis').createClient(config.redis.port, config.redis.host);
subscribeNewMessages.subscribe('new-messages');
var nsNewMsgs = io.of('/new-messages');
subscribeNewMessages.on('message', function(channel, message) {
var obj = JSON.parse(message),
clients = nsNewMsgs.connected,
ids = Object.keys(clients);
for (var i = 0, len = ids.length, client; i < len; ++i) {
client = clients[ids[i]];
if (client.userId == obj.toUserId || client.guestId == obj.toUserId)
client.send(message);
}
});
Depending on your application, you might even be able to avoid the for-loop entirely if you can store the socket.id values in your published messages, then you can simply look up clients[obj.userSockId] and clients[obj.guestSockId] because the connected is keyed on socket.id.

Socket.IO & Node.js - Broadcasting to all rooms a given user has joined

Is there any built-in way to broadcast a message to all rooms a given user has joined except for the default one? I'm using the following code snippet to achieve this, but I'd like to achieve the same in a cleaner way if possible:
for (var room in io.sockets.manager.roomClient[socket.id])) {
if (key) {
socket.broadcast.to(key).emit('something');
}
}
I'm not sure if there's a native way, but maybe something like (pseudo code):
var rooms = {}; (inside client)
on join: rooms[0] = 'roomname'; rooms[1] = 'roomname2'; etc...
for(var i = 0; i < rooms.length; i++) {
if(rooms[i] != 'default_excludedroomname') {
io.sockets.in(rooms[i]).emit('message', etc...);
}
}

What should I be using? Socket.io rooms or Redis pub-sub?

Pretty simple question. I am building a realtime game using nodejs as my backend and I am wondering if there is any information available on which one is more reliable and which one is more efficient?
I am heavily using both Redis and Socket.io throughout my code. So I want to know whether I should be utilizing Socket.io's Rooms or I would be better off using redis' pub-sub ?
Update:
Just realized there is a very important reason why you may want to use redis pub/sub over socket.io rooms. With Socket.io rooms when you publish to listeners, the (browser)clients recieve the message, with redis it is actually the (redis~on server)clients who recieve messages. For this reason, if you want to inform all (server)clients of information specific to each client and maybe do some processing before passing on to browser clients, you are better off using redis. Using redis you can just fire off an event to generate each users individual data, where as with socket.io you have to actually generate all the users unique data at once, then loop through them and send them their individual data, which almost defeats the purpose of rooms, at least for me.
Unfortunately for my purposes I am stuck with redis for now.
Update 2: Ended up developing a plugin to use only 2 redis connections but still allow for individual client processing, see answer below....
Redis pub/sub is great in case all clients have direct access to redis. If you have multiple node servers, one can push a message to the others.
But if you also have clients in the browser, you need something else to push data from a server to a client, and in this case, socket.io is great.
Now, if you use socket.io with the Redis store, socket.io will use Redis pub/sub under the hood to propagate messages between servers, and servers will propagate messages to clients.
So using socket.io rooms with socket.io configured with the Redis store is probably the simplest for you.
I ended up writing a node plugin to allow for many pub-sub clients but only require 2 redis connections instead of a new one on every single socketio connection, it should work in general, figured someone else may find use for it.
This code assumed you have socket.io running and setup, basically in this example any number of socket.io clients can connect and it will always still only use 2 redis connections, but all clients can subscribe to their own channels. In this example, all clients get a message 'sweet message!' after 10 seconds.
Example with socket.io (utilizing redis pub-sub):
var
RPubSubFactory = require('rpss.js');
var
redOne = redis.createClient(port, host),
redTwo = redis.createClient(port, host);
var pSCFactory = new RPubSubFactory(redOne);
io.sockets.on('connection', function(socket){
var cps = pSCFactory.createClient();
cps.onMessage(function(channel, message){
socket.emit('message', message);
});
io.sockets.on('disconnect', function(socket){
// Dont actually need to unsub, because end() will cleanup all subs,
// but if you need to sometime during the connection lifetime, you can.
cps.unsubscribe('cool_channel');
cps.end();
});
cps.subscribe('cool_channel')
});
setTimeout(function(){
redTwo.publish('cool_channel', 'sweet message!');
},10000);
Actual plugin code:
var RPubSubFactory = function(){
var
len,indx,tarr;
var
dbcom = false,
rPubSubIdCounter = 1,
clientLookup = {},
globalSubscriptions = {};
// public
this.createClient = function()
{
return new RPubSupClient();
}
// private
var constructor = function(tdbcom)
{
dbcom = tdbcom;
dbcom.on("message", incommingMessage);
}
var incommingMessage = function(rawchannel, strMessage)
{
len = globalSubscriptions[rawchannel].length;
for(var i=0;i<len;i++){
//console.log(globalSubscriptions[rawchannel][i]+' incomming on channel '+rawchannel);
clientLookup[globalSubscriptions[rawchannel][i]]._incommingMessage(rawchannel, strMessage);
}
}
// class
var RPubSupClient = function()
{
var
id = -1,
localSubscriptions = [];
this.id = -1;
this._incommingMessage = function(){};
this.subscribe = function(channel)
{
//console.log('client '+id+' subscribing to '+channel);
if(!(channel in globalSubscriptions)){
globalSubscriptions[channel] = [id];
dbcom.subscribe(channel);
}
else if(globalSubscriptions[channel].indexOf(id) == -1){
globalSubscriptions[channel].push(id);
}
if(localSubscriptions.indexOf(channel) == -1){
localSubscriptions.push(channel);
}
}
this.unsubscribe = function(channel)
{
//console.log('client '+id+' unsubscribing to '+channel);
if(channel in globalSubscriptions)
{
indx = globalSubscriptions[channel].indexOf(id);
if(indx != -1){
globalSubscriptions[channel].splice(indx, 1);
if(globalSubscriptions[channel].length == 0){
delete globalSubscriptions[channel];
dbcom.unsubscribe(channel);
}
}
}
indx = localSubscriptions.indexOf(channel);
if(indx != -1){
localSubscriptions.splice(indx, 1);
}
}
this.onMessage = function(msgFn)
{
this._incommingMessage = msgFn;
}
this.end = function()
{
//console.log('end client id = '+id+' closing subscriptions='+localSubscriptions.join(','));
tarr = localSubscriptions.slice(0);
len = tarr.length;
for(var i=0;i<len;i++){
this.unsubscribe(tarr[i]);
}
localSubscriptions = [];
delete clientLookup[id];
}
var constructor = function(){
this.id = id = rPubSubIdCounter++;
clientLookup[id] = this;
//console.log('new client id = '+id);
}
constructor.apply(this, arguments);
}
constructor.apply(this, arguments);
};
module.exports = RPubSubFactory;
I mucked around and tried to improve the efficiency as much as I could, but after doing some different speed tests, I concluded this was the fastest I could get it.
For up-to-date version: https://github.com/Jezternz/node-redis-pubsub

Resources