Using rooms with socket.io - javascript

In the socket.io documentation I see an example of rooms
io.on('connection', function(socket){
socket.on('say to someone', function(id, msg){
socket.broadcast.to(id).emit('my message', msg);
});
});
I have a route /rooms/:roomId.
Is it possible to make the sockets being sent between the server and the client only hits the specific room?
I guess the server should be something like
io.on('connection', function(socket){
socket.on('new msg from client', function(roomId, msg){
io.to(id).emit('new msg from server', msg);
});
});
above and the client should send messages with
socket.emit('new msg from client', roomId, msg);
and get new messages simply with
socket.on('new msg from server', function () {
document.getElementById('msgs').appendChild(...);
});
But will this work? Shouldn't I join the room with socket.join(...) before I can do this?

For a haiku sharing application I made I have something like this:
io.on('connection', function(socket) {
var socket_id = socket.id;
var client_ip = socket.handshake.headers['x-forwarded-for'] || socket.handshake.address.address;
clients.push(socket);
console.info('New client connected (id=' + socket.id + ').');
number_of_clients_connected++;
console.log('[' + client_ip + '] connected, ' + number_of_clients_connected + ' total users online.');
//when a socket is disconnected or closed, .on('disconnect') is fired
socket.on('disconnect', function() {
number_of_clients_connected--;
console.log('[' + client_ip + '] disconnected, ' + number_of_clients_connected + ' total users online.');
//on disconnect, remove from clients array
var index = clients.indexOf(socket);
if (index != -1) {
clients.splice(index, 1);
//console.info('Client gone (id=' + socket.id + ').');
}
});
So it keeps an array of clients and when certain messages need relaying you can specify the client socket ID...
//reads from latest_haikus_cache and sends them
socket.on('request_haiku_cache', function() {
latest_haikus_cache.forEach(function(a_latest_haiku) {
clients[clients.indexOf(socket)].emit('load_haiku_from_cache', a_latest_haiku);
});
});

The server is allowed to broadcast to any room so you can support letting a client ask the server to send to a room without that client being in the room. It really depends upon what you want to do.
So, if you want your server to have this code that lets any client send a message to any room of their choosing:
io.on('connection', function(socket){
socket.on('new msg from client', function(roomId, msg){
io.to(roomId).emit('new msg from server', msg);
});
});
Then, you can indeed do that and it will work. Whether it's appropriate or not is entirely up to your application and whether you want any client to be able broadcast to any room that it has the name of.
But will this work?
Yes, it will work.
Shouldn't I join the room with socket.join(...) before I can do this?
There is no need to have the client join the room unless it wants to receive messages for that room. You don't have be in the room in order to ask the server to send to that room if that's how you want to do it. So, all this is entirely up to your application and what is appropriate.
I have a route /rooms/:roomId.
Is it possible to make the sockets being sent between the server and
the client only hits the specific room?
I could not figure out what this part of your question means. If you care to explain further, I will try to help with this part too.

Related

Socket.io - Cannot send messages to rooms

socket.on('join room', function (data) {
var room = data.room;
var msg = "<span style='color:darkgreen;'>" + data.user + " has joined chat.</span>";
socket.join(room, function () {
console.log(socket.id + " now in rooms ", socket.rooms);
});
io.in(room).emit('system message', msg);
});
If I change io.in(room) to socket.broadcast the system message goes through. For whatever reason though, it will not send messages to specific rooms, it just does nothing, no errors either. Any ideas what's going wrong here?
It seems the code above is on back-end, correct?
Try the following, adapt it with your needs if necessary:
io.on('connection', function (socket) {
// You should expect ONLY one user connected,
// > otherwise, you're creating multiple sessions :)
console.log('a user connected');
socket.on('join room', function (data) {
var room = data.room;
console.log('Room: ', room);
var msg = "<span style='color:darkgreen;'>" + data.user + " has joined chat.</span>";
socket.join(room); // No callback needed
console.log(socket.id + " now in rooms ", socket.rooms);
io.to(room).emit('system message', msg);
// Change it to io.to(room)
});
});
I noticed you're asking to join on front-end using:
$(".channelChange").click( function(evt) {
var socket = io();
$("#messages").html("");
evt.stopPropagation();
if($.inArray(evt.currentTarget, $(this).children())){
console.log("user moved to " + evt.currentTarget.id);
}
setCookie("currentRoom", evt.currentTarget.id, 1);
$(".roomName").html("<span class='3Dtext'>" + evt.currentTarget.id + "</span>");
$("#enter-sound").get(0).play();
getMessages(getCookie("currentRoom"));
// Here you ask to join room
socket.emit('join room', { user: getCookie("username"), room: getCookie("currentRoom") });
})
And receiving the system message on:
$(function(){
var socket = io();
socket.on('system message', function(msg) {
alert("test");
$('#messages').append($('<div class="systemMessage">').html(msg));
var height = $("#messages")[0].scrollHeight;
$.mobile.silentScroll(height);
});
The main problem as you'll see, is that you're calling io() everytime, this created different sessions,
1. Session one joins a room
2. A different session will await for system message
Hence, you need a single io() session, a quick work arround is to make a self invoking function:
const setIo = (function (){
const _io = io();
return () => _io;
})()
And then Replace all io() declarations to: var socket = setIo();
such as:
$(".channelChange").click( function(evt) {
var socket = setIo();
$("#messages").html("");
evt.stopPropagation();
This will create a single io() session instead of different ones, and re-use it everytime you call setIo()
You'll eventually see the alert pop up:
socket.join() is asynchronous. It has not completed yet when you're trying to send to the room, so no message goes to that socket because it isn't yet in the room. So, change this:
socket.join(room, function () {
console.log(socket.id + " now in rooms ", socket.rooms);
});
io.in(room).emit('system message', msg);
to this:
socket.join(room, function () {
console.log(socket.id + " now in rooms ", socket.rooms);
io.in(room).emit('system message', msg);
});
So, in this new version you're not sending to the room until AFTER the .join() has completed rather than before.
You can see a similar example right in the socket.io doc here.

Redis + Socket.io - How to client can join two room the same time?

In my project, I've a user, that user can in two channels , but i want this user can receive notifications for two channels different.
I can ask a question?
How to Client can join multiple room and receive 2 notifications the same time for different two rooms.
I have code here :
client.js
var socket = io.connect('localhost:3000');
socket.on('connect', function(){
socket.emit("subscribe",'test');
});
socket.on('message', function(message) {
console.log('Receive >>', message);
});
server.js
var client = redis.createClient();
io.sockets.on('connection', function (socket) {
var broadcast = function(channel, message){
socket.broadcast.to(channel).emit('message', message);
}
socket.on("subscribe",function(channel){
client.subscribe(channel);
socket.join(channel);
});
socket.on('disconnect', function() {
client.unsubscribe(channel);
client.removeListener(broadcast)
})
client.on("message", broadcast);
});
I wanna : socket.on("subscribe",function(channel){
client.subscribe(channel, channel1, channel2);
socket.join(channel, channel1, channel2);
});
But socket.io not support that.
Please give me an idea or a suggestion. Thanks so much!

Multiple Clients connected to the same Server using UDP in NodeJS

Is it possible to have multiple clients to the same UDP server ?
I'd like to broadcast the same data to all connected clients.
Here would be a starting sample, if it helps somehow ...
// Server
var news = [
"Borussia Dortmund wins German championship",
"Tornado warning for the Bay Area",
"More rain for the weekend"
];
var dgram = require('dgram');
var server = dgram.createSocket("udp4");
server.bind(function() {
server.setBroadcast(true)
server.setMulticastTTL(128);
setInterval(broadcastNew, 3000);
});
function broadcastNew() {
var message = new Buffer(news[Math.floor(Math.random() * news.length)]);
server.send(message, 0, message.length, 5007, "224.1.1.1");
console.log("Sent " + message + " to the wire...");
}
// Client 1
var PORT = 5007;
var dgram = require('dgram');
var client = dgram.createSocket('udp4');
client.on('listening', function() {
var address = client.address();
console.log('UDP Client listening on ' + address.address + ":" + address.port);
client.setBroadcast(true)
client.setMulticastTTL(128);
client.addMembership('224.1.1.1');
});
client.on('message', function(message, remote) {
console.log('A: Epic Command Received. Preparing Relay.');
console.log('B: From: ' + remote.address + ':' + remote.port + ' - ' + message);
});
client.bind(PORT);
// Client 2
// Here would go another client, it is possible ?
Yes, it is possible.
I won't go on a speech about how you should use TCP before UDP and only use UDP when absolutely necessary.
For your problem, the fact is that UDP doesn't have any "connection". You receive messages, you send messages, but there is no "connection".
So what you should do is:
When receiving a message from an incoming client, store the IP/Port used by the client
When wanting to send messages to clients, send to all the IP/Port combinations stored
Periodically remove old clients (for example who didn't send a message in the last 5 minutes)
You can detect when a message is received on a bound socket after the "message" event. Your code would look something like that (helped myself):
// Server
var news = [
"Borussia Dortmund wins German championship",
"Tornado warning for the Bay Area",
"More rain for the weekend"
];
var clients = {};
const dgram = require('dgram');
const server = dgram.createSocket('udp4');
server.on('error', (err) => {
console.log(`server error:\n${err.stack}`);
server.close();
});
server.on('message', (msg, rinfo) => {
console.log(`server got: ${msg} from ${rinfo.address}:${rinfo.port}`);
clients[JSON.stringify([rinfo.address, rinfo.port])] = true;
//use delete clients[client] to remove from the list of clients
});
function broadCastNew() {
var message = new Buffer(news[Math.floor(Math.random() * news.length)]);
for (var client in clients) {
client = JSON.parse(client);
var port = client[1];
var address = client[0];
server.send(message, 0, message.length, port, address);
}
console.log("Sent " + message + " to the wire...");
}
server.on('listening', () => {
var address = server.address();
console.log(`server listening ${address.address}:${address.port}`);
setInterval(broadcastNew, 3000);
});
server.bind(5007);
Now whenever your server gets an UDP message on port 5007, it will add the sender to the list of clients, and every 3 seconds it will send a message to all the clients stored. How to make the sender receive that piece of news is another story, but you can use a tool such as WireShark to confirm yourself that it was correctly sent back.
Here I didn't delete old clients but you probably should include a mechanism to store the last time they contacted you (instead of using = true you can for example store current time, then periodically remove old clients)
Broadcast and Multicast are probably different from what you imagine, broadcast is used for example to send a message to everyone on the local network.

Socket.io: First Client disconnecting on Second connection

I am creating a socket.io app. When I create a connection to the socket on firefox, it established just fine. When I am using chrome to init another connection to the socket, the first client gets disconnected and then the two clients reconnect again (with the same socket.id)
This is the code I have been working with:
app.post('/auth', function(req, res){ // routes.auth(hash, db, io, pseudoArray, connections, Session));
var username = req.body.username,
password = req.body.password;
if (username != "" && password != ""){
authenticate(username, password, db, hash, function(err, user){
if (user) {
// Regenerate session when signing in
// to prevent fixation
console.log('user authenticated');
req.session.regenerate(function(){
req.session.user = user.name;
req.session.success = 'Authenticated as ' + user.name
+ ' click to logout. '
+ ' You may now access /restricted.';
var Session = connect.middleware.session.Session;
io.sockets.on('connection', function (socket) {
var hs = socket.handshake;
console.log('A socket with sessionID ' + hs.sessionID + ' connected!');
socket.set('pseudo', req.session.user, function(){
pseudoArray.push(req.session.user);
socket.emit('pseudoStatus', req.session.user);
console.log("user " + req.session.user + " connected");
});
socket.on('pseudoOk', function(data){
// connections[data] = socket;
connections[data] = socket.id; // connected user with its socket.id
connectedUsers[socket.id] = socket;
console.log('----CONNECTIONS----');
console.log(connections);
console.log('++ USERS ++ ');
console.log(connectedUsers);
console.log('----END CONNECTIONS----');
});
socket.on('disconnect', function () {
console.log('A socket with sessionID ' + hs.sessionID + ' disconnected!');
// clear the socket interval to stop refreshing the session
// clearInterval(intervalID);
});
});
res.cookie('rememberme', '1', { maxAge: 900000, httpOnly: true });
res.redirect('home');
});
} else {
console.log('auth failed');
req.session.error = 'Authentication failed, please check your '
+ ' username and password.'
+ ' (use "tj" and "foobar")';
res.redirect('login');
}
});
} else {
res.redirect('connect');
}
});
Any clues?
The main question is: Will a socket connection on a single computer (localhost) work within two different browsers on the same machine? What I am thinking is that since I am using two browsers on a single machine, I am getting the same socket id for both the browsers. Is that the case?
You're creating an event listener on the socket.io connection event for every POST request to /auth. That's going to result in undefined behaviour and possibly memory leaks. Express and socket.io are two separate subsystems running within the same server application. As such, they shouldn't be mixed like that.
A 'normal' Express/socket.io setup looks like this:
// your Express routes
app.post('/auth', function(req, res) { ... });
...
// socket.io setup (completely separate)
io.sockets.on('connection', function(socket) {
...
});
If you want to share session objects between Express and socket.io: that isn't trivial. Like I said before, both systems should be considered separate from each other. However, there have been some clever people that got it to work. I don't know how up-to-date that blogpost is, though, and it requires quite in-depth knowledge of both Express and socket.io.

How to send message to room clients when a client disconnects

I want the server to send a message to all room clients when one of them disconnects.
Something like this:
socket.on('disconnect', function() {
server.sockets.in(room).emit('bye');
});
But...
How do I know which room to broadcast?
What if the client has joined to multiple rooms?
After inspecting the sockets object, I came up with this solution:
socket.on('disconnect', function() {
var rooms = io.sockets.manager.roomClients[socket.id];
for (var room in rooms) {
if (room.length > 0) { // if not the global room ''
room = room.substr(1); // remove leading '/'
console.log('user exits: '+room);
server.sockets.in(room).emit('bye');
}
}
});
not 100% on this - but give it a try:
when connecting to a room or adding a new user to the mix, remember their username or id or something in the socket:
socket.on('adduser', function(username){
socket.username = username;
socket.join('room');
}
Then listen to leave events on rooms:
socket.room.on('leave', function(){
socket.broadcast.to(this).emit(socket.username + ' says seeya!');
}
It's worth a try anyway - I'm sure something similar will work if this doesn't.

Categories