I'm following a book which has a code snippet for socket.io namespaces. the version specified in the book is socket 0.9
this is the below code for a simple chatroom using namespaces.
exports.initialize = function(server) {
io = io.listen(server);
var chatInfra = io.of("/chat_infra")
.on("connection", function(socket) {
socket.on("set_name", function(data) {
socket.set('nickname', data.name, function() {
socket.emit('name_set', data);
socket.send(JSON.stringify({
type: 'serverMessage',
message: 'Welcome to the most interesting ' +
'chat room on earth!'
}));
socket.broadcast.emit('user_entered', data);
});
});
});
var chatCom = io.of("/chat_com")
.on("connection", function(socket) {
socket.on('message', function(message) {
message = JSON.parse(message);
if (message.type == "userMessage") {
socket.get('nickname', function(err, nickname) {
message.username = nickname;
socket.broadcast.send(JSON.stringify(message));
message.type = "myMessage";
socket.send(JSON.stringify(message));
});
}
});
});
}
i'm trying to recreate the code using the latest socket version. but i'm having trouble getting the socket.nickname variable in the chatCom namespace. the socket.nickname variable in the chatCom namespace returns undefined because of the local scope.
here's my version
exports.initialize = function(server){
io = io(server);
var chatInfra = io.of('/chat_infra');
chatInfra.on('connection',function(socket){
socket.on('set_name',function(data){
socket.nickname = data.name;
console.log(socket.nickname);
socket.emit('name_set',data);
socket.send(JSON.stringify({
type:'serverMessage',
message:'Welcome to the most interesting chat room on earth'
}));
socket.broadcast.emit('user_entered',data);
})
})
var chatCom = io.of('/chat_com');
chatCom.on('connection',function(socket){
socket.on('message',function(message){
message = JSON.parse(message);
if(message.type === 'userMessage'){
message.nickname = socket.nickname;
socket.broadcast.send(JSON.stringify(message));
message.type = 'myMessage';
socket.send(JSON.stringify(message));
}
})
})
}
Is there a way i can access the nickname value in the second namespace?
I'm able to access the same socket instance in both namespaces. but however, the nickname property seems to be erased when i invoke it from a different namespace.
Output of console.log
socket id in /chat_infra is : Zo0kfigUFJOZIgH8AAAB and socket nickname is kj
socket id in /chat_com is : Zo0kfigUFJOZIgH8AAAB and socket nickname is undefine
Sorry that I can't verify this myself right now (and also that I can't comment yet), but hopefully I can help you figure it out.
Both chatInfra and chatCom should have a connected property that is an map of all the current sockets connected to each. If the socket is not shared/reused in each namespace, perhaps they still share the same connection ID. So, you may be able to get the nickname from chatInfra.connected[socket.id].nickname.
If that doesn't work, console.log each of their connected properties and see their differences. Also, socket.io handles JSON natively, so you don't need to JSON.parse and JSON.stringify your objects.
Instead setting nickname to socket, you can set nickname to the client object of socket, such as socket.client.nickname = data.name.
Related
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
I would like to get a multi-process node. Workers are listening clients connections. I need pass sockets to master process because master process emit message to clients. Workers also need socket to emit message to clients.
Socket is a circular object and can't pass to a master process.
My code:
const cluster = require('cluster');
const http = require('http');
var io = require('socket.io');
var users;
var clients = {};
if (cluster.isMaster) {
function messageHandler(msg) {
if (msg.usersarray) {
usersarray = msg.usersarray;
console.log(usersarray);
}else if(msg.socket){
clients[usersarray["manu"][0]] = msg.socket;
clients[usersarray["manu"][0]].emit("hola","hola");
}
}
// Start workers and listen for messages containing notifyRequest
const numCPUs = require('os').cpus().length;
for (var i = 0; i < numCPUs; i++) {
cluster.fork();
}
Object.keys(cluster.workers).forEach((id) => {
cluster.workers[id].on('message', messageHandler);
});
}else {
// Create server & socket
var server = http.createServer(function(req, res){
// Send HTML headers and message
res.writeHead(404, {'Content-Type': 'text/html'});
res.end('<h1>Aw, snap! 404</h1>');
});
server.listen(3000);
io = io.listen(server);
// Add a connect listener
io.sockets.on('connection', function(socket) {
var hs = socket.handshake;
console.log("socket connected");
if(users == undefined){
users = {};
}
if(hs.query.usuario != undefined){
if(users[hs.query.usuario] == undefined){
users[hs.query.usuario] = new Array();
}
users[hs.query.usuario].push(socket.id); // connected user with its socket.id
clients[socket.id] = socket; // add the client data to the hash
process.send({ usersarray: users});
process.send({ socket: socket});
}
// Disconnect listener
socket.on('disconnect', function() {
console.log('Client disconnected.');
});
});
}
in line process.send({ socket: socket}); Node js get error "TypeError: Converting circular structure to JSON"
-I used some module to transform circular object but don't working.
-I tried to pass socket id and then in master process, created new socket with this id but I didn't know to use it.
There is any posibility to pass socket from worker to master process?
Node js version: v5.5.0
Hm, I don't think it is possible what you are trying to do. When you create a cluster it means that you create separate processes (master + workers) which can only talk over the pipe.
Talking over the pipe means they can only send strings to each other. process.send tries to serialize a Javascript object as JSON (--> making a string out of it) using JSON.stringify. JSON for example cannot have functions, circles, etc. I just checked the socket object, it is very complex and contains functions (such as socket.emit()), so you cannot just serialize it and send it over the pipe.
Maybe you can check this or this on how to use clustered WebSockets.
It doesn't seem very trivial.. Maybe you can just pass CPU intensive tasks to some worker processes (via cluster or just spawning them yourself), send the results back to the master and let him do all the communication with the client?
I understand your purpose of broadcasting to all the node worker processes in a cluster, although you can not send socket component as such but there is a workaround for the purpose to be served. I will try an explain with an example :
Step 1: When a client action requires a broadcast :
Child.js (Process that has been forked) :
socket.on("BROADCAST_TO_ALL_WORKERS", function (data)
{
process.send({cmd : 'BROADCAST_TO_ALL_WORKERS', message :data.message});
})
Step 2: On the cluster creation side
Server.js (Place where cluster forking happens):
if (cluster.isMaster) {
for (var i = 0; i < numCPUs; i++) {
var worker = cluster.fork();
worker.on('message', function (data) {
if (data.cmd === "BROADCAST_TO_ALL_WORKERS") {
console.log(server_debug_prefix() + "Server Broadcast To All, Message : " + data.message + " , Reload : " + data.reload + " Player Id : " + data.player_id);
Object.keys(cluster.workers).forEach(function(id) {
cluster.workers[id].send({cmd : "BROADCAST_TO_WORKER", message : data.message});
});
}
});
}
cluster.on('exit', function (worker, code, signal) {
var newWorker = cluster.fork();
newWorker.on('message', function (data) {
console.log(data);
if (data.cmd === "BROADCAST_TO_ALL_WORKERS") {
console.log(data.cmd,data);
Object.keys(cluster.workers).forEach(function(id) {
cluster.workers[id].send({cmd : "BROADCAST_TO_WORKER", message : data.message});
});
}
});
});
}
else {
//Node Js App Entry
require("./Child.js");
}
Step 3: To Broadcast in the child process
-> Put this before io.on("connection") in Child.js
process.on("message", function(data){
if(data.cmd === "BROADCAST_TO_WORKER"){
io.sockets.emit("SERVER_MESSAGE", { message: data.message, reload: data.reload, player_id : data.player_id });
}
});
I hope its clear. Please comment if its confusing ... I will try and make it clear.
I am trying to emit to a particular socket ID:
socket(user[playID]).emit('correct', data);
But I'm getting:
TypeError: object is not a function
if I log out user[playID] I do get a valid socket ID.
Appreciated!
Here is my setup in case I'm missing something:.
// Tell Socket.io to pay attention
servio = io.listen(server);
// Tell HTTP Server to begin listening for connections on port 3250
sock = server.listen(3250);
This should do it
servio.sockets.socket(id).emit('hello');
This answer covers the same/similar topic. In short, consider keeping a reference to the connected clients yourself and emit to them as desired, rather than relying on socket.io's internals, which could change.
Per http://socket.io/docs/rooms-and-namespaces/,
Each Socket in Socket.IO is identified by a random, unguessable,
unique identifier Socket#id. For your convenience, each socket
automatically joins a room identified by this id.
You emit to a room like:
io.to('some room').emit('some event')
If you want to emit to just a specific socket.id, just replace 'some room' with the associated id.
Another way to do this is:
var players = [];
io.sockets.on('connection', function(socket){
socket.on('skt_init', function (data) {
var player = new Object();
player.id = data.id;
player.socket = socket.id;
players.push(player);
});
socket.on('disconnect', function() {
var len = 0;
for(var i=0, len=players.length; i<len; ++i ) {
var p = players[i];
if(p.socket == socket.id){
players.splice(i,1);
break;
}
}
});
socket.on('skt_event', function(data, id_player){
var len = 0;
for(var i=0, len=players.length; i<len; ++i ) {
var p = players[i];
if(p.id == id_player){
io.sockets.socket(p.socket).emit('correct', data);
break;
}
}
});
Hope that helps somewhat.
UPDATE: in socket.io-1.4 you have to prepend "/#" to socket id ( very frustrating that it doesnt work now). you can also view all connected sockets from backend as io.sockets.connected
io.to( "/#" + socket_id).emit("event_name",{data:true})
This should work:
servio.sockets.sockets[playId].emit(...)
Since socket.io doesn't provide a stable API to get the socket from a socket's ID, you can simply (and easily) keep track of them with an object keyed by socket IDs.
sockets_by_id = {}
io.on "connection", (socket)->
sockets_by_id[socket.id] = socket
sockets_by_id[socket_id].emit event, data...
(^CoffeeScript^)
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.
Starting from the official chat example of Node.js.
The user is prompted to emit his name to the server through 'register' (client.html):
while (name == '') {
name = prompt("What's your name?","");
}
socket.emit('register', name );
The server receives the name. I want it to make the name as the identifier of the socket. So when I need to send a message to that user I send it to the socket having his name (Names are stored in a database for the info).
Changes will take place here (server.js):
socket.on('register', function (name) {
socket.set('nickname', name, function () {
// this kind of emit will send to all! :D
io.sockets.emit('chat', {
msg : "naay nag apil2! si " + name + '!',
msgr : "mr. server"
});
});
});
I'm struggling making this works, because I can't go any farer if I can't identify the socket. So any help will be really appreciated.
Update: I understand that nickname is a parameter for the socket, so the question is more specifically: how to get the socket that has "Kyle" as a nickname to emit it a message?
Store your sockets in a structure like this:
var allSockets = {
// A storage object to hold the sockets
sockets: {},
// Adds a socket to the storage object so it can be located by name
addSocket: function(socket, name) {
this.sockets[name] = socket;
},
// Removes a socket from the storage object based on its name
removeSocket: function(name) {
if (this.sockets[name] !== undefined) {
this.sockets[name] = null;
delete this.sockets[name];
}
},
// Returns a socket from the storage object based on its name
// Throws an exception if the name is not valid
getSocketByName: function(name) {
if (this.sockets[name] !== undefined) {
return this.sockets[name];
} else {
throw new Error("A socket with the name '"+name+"' does not exist");
}
}
};