how to get socket.io number of clients in room? - javascript

my socket.io version 1.3.5
I want to get number of clients in particular room.
This is my code.
socket.on('create or join', function (numClients, room) {
socket.join(room);
});
I use this code for get clients in room :
console.log('Number of clients',io.sockets.clients(room));

This works for version 3
io.sockets.adapter.rooms.get(roomName).size

To get the number of clients in a room you can do the following:
function NumClientsInRoom(namespace, room) {
var clients = io.nsps[namespace].adapter.rooms[room];
return Object.keys(clients).length;
}
This variable clients will hold a object where each client is a key.
Then you just get the number of clients (keys) in that object.
If you haven't defined a namespace the default one is "/".

Have a counter variable to keep count, increase when someone joins and decrease when people disconnect.
io.on('connection', function (socket) {
var numClients = {};
socket.on('join', function (room) {
socket.join(room);
socket.room = room;
if (numClients[room] == undefined) {
numClients[room] = 1;
} else {
numClients[room]++;
}
});
socket.on('disconnect', function () {
numClients[socket.room]--;
});

This work for me.
// check if your room isn't undefined.
if (io.sockets.adapter.rooms['your room name'])
{
// result
console.log(io.sockets.adapter.rooms['your room name'].length);
}

this work for me
io.in('yourRoom').allSockets().then(result=>{
console.log(res.size) })

Only one that worked for me in 2022 using socket.io 4.5.1:
io._nsps.get('/').adapter.rooms.get('your-room-id').size;
Thanks to #Abhishek Kumar, just making a top level comment for visibility.

In socket.io ^1.4.6
function numClientsInRoom(namespace, room) {
var clients = io.nsps[namespace].adapter.rooms[room].sockets;
return Object.keys(clients).length;
}
You need to add .sockets to rooms[room], because variable clients holds all connection inside sockets variable.

i'm a bit late to the party, but I found a valid solution:
io.in('YOUR_ROOM').engine.clientsCount;

socket.io's rooms is a Map whose elements map to Sets.
You use get(roomId) to get the value of that roomId in a Map and you use .size to the number of elements in a set unlike .length for number of elements in an array.
socket.on("join-room",roomId => {
const room = io.of("/yourNameSpace").adapter.rooms.get(roomId)
if(room === undefined || room.size < 2) {
socket.join(roomId)
}else {
io.of("/yourNameSpace").emit("can't join link")
}
})

io.nsps['/'].adapter.rooms['your room name'].sockets;
If your namespace is not /, change it.

Without Adapter
If you only have one server instance, the easiest way is:
// rooms is a Map object
const rooms = io.of("/").adapter.rooms;
const roomSockets = rooms.get('roomid')
You can see from the official document here
Using An Adapter (Redis)
But if you have multiple server instance and are using Adapters like Redis, this will not work as it will only returns the socket in that instance memory, so based on which server instance you request hit, the result will vary.
To get the actual rooms and sockets, you will need to (for RedisAdapter):
// this is a Set of room ids
const rooms = await io.of("/").adapter.allRooms();
// or get the sockets directly if you know the room id
const roomSockets = await io.of("/").adapter.sockets(new Set(['room id']);
You can see more methods here

Latest Documentation (v4)
// return all Socket instances in the "room1" room of the main namespace
const sockets = await io.in("room1").fetchSockets();
try sockets.size or sockets.length to get the actual count

Related

socket.io send data to matching socket's

when a user connects to my socket
I add to a session map:
io.on('connection', function (socket) {
sessionMap.set(socket.id,socket);
}
my session Map
var SessionMap = {};
module.exports = {
set: function(key,value){
SessionMap[key] = value;
},
get: function(key){
return SessionMap[key]
},
delete: function(key){
delete SessionMap[key]
},
all: function(){
return SessionMap
}
}
And also save my user socket id in a class player:
socket.on('addPlayer-Queue', (result) => {
sessionMap.set(socket.id,socket);
queue.addPlayer(new Player({
id: result.id,
name: result.name,
mmr: result.mmr
}, socket.id));
And I have a function that selects two players that are connected (where I save in an array) and create a "battle" and then I wanted to send to the socket that was selected / matched for this battle
the battle dice
This is my role that selects both players and creates a battle:
searching() {
const firstPlayer = this.getRandomPlayer();
const secondPlayer = this.players.find(
playerTwo =>
playerTwo.mmr < this.calculateLessThanPercentage(firstPlayer) &&
playerTwo.mmr > this.calculateGreaterThanPercentage(firstPlayer) &&
playerTwo.id != firstPlayer.id
);
if (!secondPlayer) {
return null;
}
const matchedPlayers = [firstPlayer, secondPlayer];
this.removePlayers(matchedPlayers);
return new Match(matchedPlayers);
}
}
And also when connecting I use a set interval to be performing this function every 1 second
But my difficulty is how I would send the data from this battle to the corresponding socket's
my relation socket with player
When a player enters my event I create a player by going through socket id
And I also make a session map of this socket
sessionMap.set(socket.id,socket);
my class player:
class Player {
constructor(player,socketId) {
this.id = player.id
this.socketId = socketId
this.name = player.name
this.mmr = player.mmr
}
}
module.exports = Player;
const getMatchConfigurationFor = player => {
/* configure and return the payload notifying the player of the match */
}
const configurePlayersForNewMatch = () => matchedPlayers.forEach(player =>
sessionMap.get(player.socketid)
.broadcast.to(player.socketid)
.emit(messageTags.MATCH_CONFIGURATION,
getMatchConfigurationFor(player)))
regarding where to do this work .. the single responsibility principle says that a function should have a singular clear purpose. So the search method should search for matching players, not configure the match. You should do this work in another function that is called while configuring the match, which itself is called after the search returns successfully. I've provided the wrapper function for that here: it is written in a fashion to expect the relevant pieces are in scope. You could rewrite it as a proper function with parameters if you prefer.
This is a work in progress solution for Felipe, posted by request.
After a match is found, you'd probably want to emit a MatchFound object to both clients detailing information about the match (including information about their opponent). Once a client gets this, you can initiate anything the client needs for a match (load a level, display names, or a lobby).

Socket undefined, send don't send if undefined

Hello I wrote the following function to get someone his socket;
var getSocket = function(persId){
var socketid = users[persId].socket;
if(io.sockets.connected[socketid]){
return io.sockets.connected[socketid];
}
}
They are being emitted like so;
getSocket(372).emit({ ... });
Now if an user has disconnected before the socket is sent out it will result in a undefined socket error, which may cause issues.
is there any way by modifying the function (without having to check for if getSocket(372)) to make it not throw out an error? it's causing problems right now.
Thanks!
What you should do instead, is use socket.io function, that will check for that already.
io.to(room).emit()
On connection just join the user to a room named 372 (I believe that's the user id), and then emit to it:
io.on('connection', socket => {
const room = 372; // Get user id somehow
socket.join(room);
});
// Somewhere
io.to(372).emit(..)
Answering your specific question, you can return an object with an emit nop method. Also you have to check if the user exist before accessing to .socket. You can do that using destructuring.
const { socket: socketId } = users[persId] || {};
The full function should be:
const getSocket = function(persId){
const { socket: socketId } = users[persId] || {};
if(socketId && io.sockets.connected[socketId]){
return io.sockets.connected[socketId];
}
return { emit: () => {} }
}
And now you don't have to perform a check, since .emit will always exist.
getSocket(23123213).emit(); // Will no throw if 23123213 doesn't exist

socket.io broadcast only to users who are in room A and B

Is it possible to make socket.io broadcast to all users of a namespace who are in both room A and room B but not those who are just in room A or room B?
If not, how would I go about implementing this myself? Is there a way to retrieve all users in a namespace who are in a given room?
I am working with socket.io 1.0 in node
Edit:
If there is no native method, how would I go about to create my own syntax such as:
socket.broadcast.in('room1').in('room2').emit(...)?
You can look up all the users of a room using (ref How to update socket object for all clients in room? (socket.io) )
var clients = io.sockets.adapter.room["Room Name"]
So given two arrays for your 2 rooms' roster list, you can compute the intersection using something like the answer here (ref: Simplest code for array intersection in javascript)
And finally you can take that list of users in both rooms and emit events using (ref: How to update socket object for all clients in room? (socket.io) )
//this is the socket of each client in the room.
var clientSocket = io.sockets.connected[clientId];
//you can do whatever you need with this
clientSocket.emit('new event', "Updates");
The alternate ofcourse is to have hidden rooms, where you maintain a combination of all rooms, and add users to those rooms behind the scenes, and then you are able to just simply emit to those hidden rooms. But that suffers from an exponential growth problem.
There is no in-built way to do this. So first let's look up how the broadcast works:
https://github.com/Automattic/socket.io/blob/master/lib/namespace.js 206...221-224...230
this.adapter.broadcast(packet, {
rooms: this.rooms,
flags: this.flags
});
Now we know every broadcast creates a bunch of temp objects, indexOf lookups, arguments slices... And then calls the broadcast method of the adapter. Lets take a look at that one:
https://github.com/Automattic/socket.io-adapter/blob/master/index.js 111-151
Now we are creating even more temp objects and loop through all clients in the rooms or all clients if no room was selected. The loop happens in the encode callback. That method can be found here:
https://github.com/socketio/socket.io-parser/blob/master/index.js
But what if we are not sending our packets via broadcast but to each client separately after looping through the rooms and finding clients that exist both in room A and room B?
socket.emit is defined here: https://github.com/Automattic/socket.io/blob/master/lib/socket.js
Which brings us to the packetmethod of the client.js:
https://github.com/Automattic/socket.io/blob/master/lib/client.js
Each directly emitted packet will be separately encoded, which again, is expensive. Because we are sending the exact same packet to all users.
To answer your question:
Either change the socket.io adapter class and modify the broadcast method, add your own methods to the prototype or roll your own adapter by inheriting from the adapter class). (var io = require('socket.io')(server, { adapter: yourCustomAdapter });)
Or overwrite the joinand leave methods of the socket.js. Which is rather convenient considering that those methods are not called very often and you don't have the hassle of editing through multiple files.
Socket.prototype.join = (function() {
// the original join method
var oldJoin = Socket.prototype.join;
return function(room, fn) {
// join the room as usual
oldJoin.call(this, room, fn);
// if we join A and are alreadymember of B, we can join C
if(room === "A" && ~this.rooms.indexOf("B")) {
this.join("C");
} else if(room === "B" && ~this.rooms.indexOf("A")) {
this.join("C");
}
};
})();
Socket.prototype.leave = (function() {
// the original leave method
var oldLeave = Socket.prototype.leave;
return function(room, fn) {
// leave the room as usual
oldLeave.call(this, room, fn);
if(room === "A" || room === "B") {
this.leave("C");
}
};
})();
And then broadcast to C if you want to broadcast to all users in A and B.
This is just an example code, you could further improve this by not hard coding the roomnames but using an array or object instead to loop over possible room combinations.
As custom Adapter to make socket.broadcast.in("A").in("B").emit()work:
var Adapter = require('socket.io-adapter');
module.exports = CustomAdapter;
function CustomAdapter(nsp) {
Adapter.call(this, nsp);
};
CustomAdapter.prototype = Object.create(Adapter.prototype);
CustomAdapter.prototype.constructor = CustomAdapter;
CustomAdapter.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;
for (var id in room) {
if (room.hasOwnProperty(id)) {
if (~except.indexOf(id)) continue;
socket = self.nsp.connected[id];
if (socket) {
ids[id] = ids[id] || 0;
if(++ids[id] === rooms.length){
socket.packet(encodedPackets, packetOpts);
}
}
}
}
}
} 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);
}
}
}
});
};
And in your app file:
var io = require('socket.io')(server, {
adapter: require('./CustomAdapter')
});
io.sockets.adapter.room["Room A"].forEach(function(user_a){
io.sockets.adapter.room["Room B"].forEach(function(user_b){
if(user_a.id == user_b.id){
user_a.emit('your event', { your: 'data' });
}
});
});

How to make changes to a Javascript object from Node.js/Socket.io listener?

Sorry if the title is too vague, I'm not really sure what the problem is here.
I am attempting to make a basic program which allows users to start and join rooms. When the start button is pressed, a room is started with a random string code which other users can then use to join. I store the member counter for each room in a object 'rooms' using the room code as a key and the count as a value. However, when trying to increment the value, it returns the value as NaN.
I have attached my code below, it uses express 4.10.2 and socket.io 1.3.5:
javascript in index.html, enclosed in the body tags
var socket = io.connect();
//possible characters in room code
var chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
//for generating random room codes
function randomString(c,length){
var str = ''
for(var i=0; i<length;i++){
str += c[Math.round(Math.random()*(c.length-1))]
}
return str;
}
$('#join').submit(function(){
socket.emit('joinroom', $('#code').val());
});
$('#start').submit(function(){
socket.emit('startroom', randomString(chars,5));
});
//listener for 'updatecounter', updates room member count
socket.on('updatecounter', function(count){
$('#roomcount').append('<p>'+count.toString()+'</p>');
});
//listener for 'updateroom' - should update room code at top
socket.on('updateroom', function(code){
$('#roomcode').append('<h1>'+count.toString()+'</h1>');
});
The section of my app.js in question:
//available rooms
var rooms = {};
io.sockets.on('connection', function(socket){
// when the client emits 'joinroom', this listens and executes
socket.on('startroom', function(room){
console.log('Starting room: '+room);
//add to serverside list of rooms
rooms[room]=1;
socket.room = room;
socket.join(room);
});
// when the client emits 'joinroom', this listens and executes
socket.on('joinroom', function(room){
console.log('Joining room '+room);
//leave previous room
socket.leave(socket.room);
//join new room
socket.join(room);
//update socket session room title
socket.room = room;
//increase serverside counter
rooms[room]++;
});
socket.on('disconnect', function(){
//decrement counter
rooms[socket.room]--;
//check if room is now empty
if(rooms[socket.room]===0){
delete rooms[socket.room]
}
//leave the room
socket.leave(socket.room);
});
});
Any help is appreciated, I'm a beginner to JS as well as Node.js and Socket.IO.
Your mistake may be relevant with 'updateroom' listener on client. Your parameter name is 'code' but you append count. This is wher you get NaN value?
Turns out the error was caused by a form submission in my HTML code causing a page refresh which caused a disconnect/reconnect from the server.

Emiting with exclusion in Socket.io

Is there a way to emit a message via socket.io while excluding some socket id?
I know about the existence of rooms, but that's a no-no for me.
If what I'm trying is impossible, what should I do between those two things:
a) Iterate over all users that I want to send a message (in fact, all of them except 1) and do a emit for each socket
or
b) Just emit the message to everyone and do something hacky on the client side to "ignore" the message.
EDIT: I can't do a broadcast because the message is generated from the server side (so there is no client interaction).
Rooms - are not the way to accomplish of what you are trying to do, as they are meant to be used only to limit groups of people, but not specific.
What you are looking for is simple collection of users and ability to set some sort of filter on them.
Iteration through list of sockets - is not that critical, as when you broadcast - it does iteration anyway.
So here is small class that keeps sockets, removes them when needed (disconnect) and sends message to all with optional exception of single socket.
function Sockets() {
this.list = [ ];
}
Sockets.prototype.add = function(socket) {
this.list.push(socket);
var self = this;
socket.on('disconnect', function() {
self.remove(socket);
});
}
Sockets.prototype.remove = function(socket) {
var i = this.list.indexOf(socket);
if (i != -1) {
this.list.splice(i, 1);
}
}
Sockets.prototype.emit = function(name, data, except) {
var i = this.list.length;
while(i--) {
if (this.list[i] != except) {
this.list[i].emit(name, data)
}
}
}
Here is example of usage, user sends 'post' message with some data, and server just sends it to all in collection except of original sender:
var collection = new Sockets();
io.on('connection', function(socket) {
collection.add(socket);
socket.on('post', function(data) {
collection.emit('post', data, socket);
});
});
In fact it can be used as rooms as well.

Categories

Resources