Enviar mensaje a un cliente específico con socket.io y node.js

191

Estoy trabajando con socket.io y node.js y hasta ahora parece bastante bueno, pero no sé cómo enviar un mensaje del servidor a un cliente específico, algo como esto:

client.send(message, receiverSessionId)

Pero tampoco el .send() ni los .broadcast()métodos parecen satisfacer mi necesidad.

Lo que he encontrado como una posible solución, es que el .broadcast() método acepta como segundo parámetro una matriz de SessionIds a la que no se envía el mensaje, por lo que podría pasar una matriz con todos los SessionIds conectados en ese momento al servidor, excepto el Deseo enviar el mensaje, pero creo que debe haber una mejor solución.

¿Algunas ideas?

Rodolfo Palma
fuente

Respuestas:

95

Bueno, tienes que agarrar al cliente por eso (sorpresa), puedes seguir el camino simple:

var io = io.listen(server);
io.clients[sessionID].send()

Lo cual puede romperse, lo dudo, pero siempre es una posibilidad que io.clientspueda cambiar, así que use lo anterior con precaución

O puede realizar un seguimiento de los clientes usted mismo, por lo tanto, los agrega a su propio clientsobjeto en el connectionoyente y los elimina en el disconnectoyente.

Usaría el último, ya que dependiendo de su aplicación, es posible que desee tener más estado en los clientes de todos modos, por lo que algo como clients[id] = {conn: clientConnect, data: {...}}podría hacer el trabajo.

Ivo Wetzel
fuente
66
Ivo, ¿puedes señalar un ejemplo más completo o elaborar un poco? Estoy ansioso por entender este enfoque, pero no estoy seguro de reconocer las variables / objetos que está utilizando en este ejemplo. En los clientes [id] = {conn: clientConnect, data: {...}}, ¿los clientes [id] son ​​parte del objeto io como se ve en io.clients [sessionID] arriba? Además, ¿cuál es el objeto clientConnect? Gracias.
AndrewHenderson
@ ivo-wetzel hola, ¿podrías decirme hola sobre este tema? stackoverflow.com/questions/38817680/…
mahdi pishguy
178

La respuesta de Ivo Wetzel ya no parece ser válida en Socket.io 0.9.

En resumen, ahora debe guardar socket.idy usar io.sockets.socket(savedSocketId).emit(...) para enviarle mensajes.

Así es como conseguí que esto funcionara en el servidor Node.js agrupado:

Primero debe configurar la tienda Redis como la tienda para que los mensajes puedan pasar por procesos cruzados:

var express = require("express");
var redis = require("redis");
var sio = require("socket.io");

var client = redis.createClient()
var app = express.createServer();
var io = sio.listen(app);

io.set("store", new sio.RedisStore);


// In this example we have one master client socket 
// that receives messages from others.

io.sockets.on('connection', function(socket) {

  // Promote this socket as master
  socket.on("I'm the master", function() {

    // Save the socket id to Redis so that all processes can access it.
    client.set("mastersocket", socket.id, function(err) {
      if (err) throw err;
      console.log("Master socket is now" + socket.id);
    });
  });

  socket.on("message to master", function(msg) {

    // Fetch the socket id from Redis
    client.get("mastersocket", function(err, socketId) {
      if (err) throw err;
      io.sockets.socket(socketId).emit(msg);
    });
  });

});

Omití el código de agrupación aquí, porque lo hace más abarrotado, pero es trivial agregarlo. Simplemente agregue todo al código del trabajador. Más documentos aquí http://nodejs.org/api/cluster.html

Epeli
fuente
44
Gracias fue útil. Solo tuve que usar una matriz en su lugar: io.of('/mynamespace').sockets[socketID].emit(...)(no sé si es porque estoy usando un espacio de nombres)
Adrien Schuler
en un entorno en clúster, ¿cómo me aseguro de que el proceso correcto al que pertenece el socket está enviando el mensaje?
Gal Ben-Haim
¿Qué tal una sesión adhesiva cortesía de NGINX o HAProxy @Gal Ben-Haim?
matanster
titular var = nuevo socketio.RedisStore; ^ TypeError: undefined no es una función en Object. <anónimo> (C: \ Users \ Dev \ Desktop \ nouty-server \ server.js: 108: 14) en Module._compile (module.js: 460: 26) en Object.Module._extensions..js (module.js: 478: 10) en Module.load (module.js: 355: 32) en Function.Module._load (module.js: 310: 12) en Function.Module. runMain (module.js: 501: 10) al inicio (node.js: 129: 16) en node.js: 814: 3
Lucas Bertollo
1
io.sockets.socket(socket_id)se elimina en socket.io 1.0. github.com/socketio/socket.io/issues/1618#issuecomment-46151246
ImMathan
98

cada socket se une a una habitación con un ID de socket para un nombre, por lo que puede simplemente

io.to(socket#id).emit('hey')

docs: http://socket.io/docs/rooms-and-namespaces/#default-room

Salud

Matic Kogovšek
fuente
77
Esta es la mejor respuesta y funciona con versiones más recientes de socket.io. Aquí hay una buena hoja de trucos: stackoverflow.com/questions/10058226/…
sangrado el
44
tenga en cuenta que este es un tipo de 'emisión' de emisión de un evento. así que si intenta establecer una devolución de llamada para esto, tendrá un error. si necesita enviar un evento a un socket específico con una devolución de llamada, use la respuesta y uso de @ PHPthinking io.sockets.connected[socketid].emit();. Probado con 1.4.6.
tbutcaru
Pero recibí este error: io.to ["JgCoFX9AiCND_ZhdAAAC"]. Emit ("socketFromServe‌ r", info); ^ TypeError: No se puede leer la propiedad 'emitir' de indefinido
Raz
85

La solución más simple y elegante.

Es tan fácil como:

client.emit("your message");

Y eso es.

¿Pero cómo? Dame un ejemplo

Lo que todos necesitamos es, de hecho, un ejemplo completo, y eso es lo que sigue. Esto se prueba con la versión más reciente de socket.io (2.0.3) y también está usando Javascript moderno (que ya deberíamos estar usando todos).

El ejemplo consta de dos partes: un servidor y un cliente. Cada vez que un cliente se conecta, comienza a recibir del servidor un número de secuencia periódica. Se inicia una nueva secuencia para cada nuevo cliente, por lo que el servidor debe realizar un seguimiento de ellos individualmente. Ahí es donde entra en juego el "Necesito enviar un mensaje a un cliente en particular" . El código es muy simple de entender. Vamos a verlo.

Servidor

server.js

const
    io = require("socket.io"),
    server = io.listen(8000);

let
    sequenceNumberByClient = new Map();

// event fired every time a new client connects:
server.on("connection", (socket) => {
    console.info(`Client connected [id=${socket.id}]`);
    // initialize this client's sequence number
    sequenceNumberByClient.set(socket, 1);

    // when socket disconnects, remove it from the list:
    socket.on("disconnect", () => {
        sequenceNumberByClient.delete(socket);
        console.info(`Client gone [id=${socket.id}]`);
    });
});

// sends each client its current sequence number
setInterval(() => {
    for (const [client, sequenceNumber] of sequenceNumberByClient.entries()) {
        client.emit("seq-num", sequenceNumber);
        sequenceNumberByClient.set(client, sequenceNumber + 1);
    }
}, 1000);

El servidor comienza a escuchar en el puerto 8000 las conexiones entrantes. Cuando llega uno, agrega ese nuevo cliente a un mapa para que pueda realizar un seguimiento de su número de secuencia. También escucha el disconnectevento de ese cliente , cuando lo eliminará del mapa.

Cada segundo, se dispara un temporizador. Cuando lo hace, el servidor recorre el mapa y envía un mensaje a cada cliente con su número de secuencia actual. Luego lo incrementa y almacena el número nuevamente en el mapa. Eso es todo lo que hay que hacer. Pan comido.

Cliente

La parte del cliente es aún más simple. Simplemente se conecta al servidor y escucha el seq-nummensaje, imprimiéndolo en la consola cada vez que llega.

client.js

const
    io = require("socket.io-client"),
    ioClient = io.connect("http://localhost:8000");

ioClient.on("seq-num", (msg) => console.info(msg));

Ejecutando el ejemplo

Instale las bibliotecas requeridas:

npm install socket.io
npm install socket.io-client

Ejecute el servidor:

node server

Abra otras ventanas de terminal y genere tantos clientes como desee ejecutando:

node client

También he preparado una esencia con el código completo aquí .

Lucio Paiva
fuente
¿Es el puerto 8000 la norma para socketio en producción?
Rojo
1
Lo siento, me tomó tanto tiempo responder, @ingo. 8000 es un puerto común utilizado por la comunidad Node cuando se prueban tanto Websockets como servidores HTTP (3000 también es común). Por supuesto, podría usarlo en producción, pero no estoy seguro de cuán común es eso ... de todos modos, puede usar cualquier puerto realmente, siempre que sus puertas de enlace / equilibradores de carga / etc. estén preparados para eso.
Lucio Paiva
Soy programador de Python / PHP y soy nuevo en sockets y nodos, la pregunta es, ¿por qué necesitamos incrementar el número de secuencia del mismo usuario cada segundo? ¿es solo para demostración?
Umair
1
@Umair es solo para fines de demostración, no hay necesidad real de incrementar un número de secuencia. Fue solo para mostrar que puede seguir enviando cosas a cada cliente y que lo recibirán.
Lucio Paiva
No veo en este ejemplo cómo un cliente específico envía un mensaje solo y solo a otro cliente. Cada cliente solo conoce su propio número de secuencia como lo nombró y lo creó para la demostración, y no hay un mapa de estos números de secuencia a un ID de socket que sea necesario para enviar un mensaje directo al cliente con ese ID de socket.
Selçuk
36

Puedes usar

// enviar mensaje solo al remitente-cliente

socket.emit('message', 'check this');

// o puede enviar a todos los oyentes, incluido el remitente

io.emit('message', 'check this');

// enviar a todos los oyentes excepto al remitente

socket.broadcast.emit('message', 'this is a message');

// o puedes enviarlo a una habitación

socket.broadcast.to('chatroom').emit('message', 'this is the message to all');

Decodificador NT
fuente
Trabajó para mí, también tuve que agregar "const socket = require ('socket.io') (http);" en mi archivo server.js.
iPzard el
36

En 1.0 deberías usar:

io.sockets.connected[socketid].emit();
Pensamiento de PHP
fuente
1
Sí !!!, anteriormente io.sockets.sockets [socketid] .emit () funcionó, pero esto me dio errores de objeto indefinidos en la versión más reciente de socket.io. El cambio a io.sockets.connected funciona.
Fraggle
Para aquellos que usan TypeScript, esta es actualmente la API 'canónica' para esto de acuerdo con los tipings.
Avi Cherry
Pero recibo un error: io.sockets.connected ["JgCoFX9AiCND_ZhdAAAC"]. Emit ("socketFromServer", info); ^ TypeError: No se puede leer la propiedad 'emitir' de indefinido
Raz
eso probablemente se deba al hecho de que la API se actualizó y ya no hay un objeto como io.sockets.connected["something"]y su emitmétodo.
Selçuk
12

Cualquiera que sea la versión que estemos usando si solo consolamos.log () el objeto "io" que usamos en nuestro código del nodo del lado del servidor, [por ejemplo, io.on ('conexión', función (socket) {...});] , podemos ver que "io" es solo un objeto json y hay muchos objetos secundarios donde se almacenan el id del socket y los objetos del socket.

Estoy usando socket.io versión 1.3.5, por cierto.

Si miramos en el objeto io, contiene,

 sockets:
  { name: '/',
    server: [Circular],
    sockets: [ [Object], [Object] ],
    connected:
     { B5AC9w0sYmOGWe4fAAAA: [Object],
       'hWzf97fmU-TIwwzWAAAB': [Object] },

aquí podemos ver los socketids "B5AC9w0sYmOGWe4fAAAA", etc. Entonces, podemos hacer,

io.sockets.connected[socketid].emit();

Nuevamente, en una inspección adicional podemos ver segmentos como,

 eio:
  { clients:
     { B5AC9w0sYmOGWe4fAAAA: [Object],
       'hWzf97fmU-TIwwzWAAAB': [Object] },

Entonces, podemos recuperar un socket desde aquí haciendo

io.eio.clients[socketid].emit();

Además, debajo del motor tenemos,

engine:
 { clients:
    { B5AC9w0sYmOGWe4fAAAA: [Object],
      'hWzf97fmU-TIwwzWAAAB': [Object] },

Entonces, también podemos escribir,

io.engine.clients[socketid].emit();

Entonces, supongo que podemos lograr nuestro objetivo en cualquiera de las 3 formas que enumeré anteriormente,

  1. io.sockets.connected [socketid] .emit (); O
  2. io.eio.clients [socketid] .emit (); O
  3. io.engine.clients [socketid] .emit ();
Suman Barick
fuente
Pero recibí este error: io.eio.clients ["JgCoFX9AiCND_ZhdAAAC"]. Emit ("socketFromServer", info); ^ TypeError: No se puede leer la propiedad 'emitir' de indefinido
Raz
Actualmente (con la versión 2.1.1) esto solo funcionaba para sockets.connected, pero no para los otros dos.
James
10

Puedes hacerlo

En el servidor

global.io=require("socket.io")(server);

io.on("connection",function(client){
    console.log("client is ",client.id);
    //This is handle by current connected client 
    client.emit('messages',{hello:'world'})
    //This is handle by every client
    io.sockets.emit("data",{data:"This is handle by every client"})
    app1.saveSession(client.id)

    client.on("disconnect",function(){
        app1.deleteSession(client.id)
        console.log("client disconnected",client.id);
    })

})

    //And this is handle by particular client 
    var socketId=req.query.id
    if(io.sockets.connected[socketId]!=null) {
        io.sockets.connected[socketId].emit('particular User', {data: "Event response by particular user "});
    }

Y en el cliente, es muy fácil de manejar.

var socket=io.connect("http://localhost:8080/")
    socket.on("messages",function(data){
        console.log("message is ",data);
        //alert(data)
    })
    socket.on("data",function(data){
        console.log("data is ",data);
        //alert(data)
    })

    socket.on("particular User",function(data){
        console.log("data from server ",data);
        //alert(data)
    })
abhaygarg12493
fuente
7

A partir de la versión 1.4.5, asegúrese de proporcionar un socketId correctamente prefijado en io.to (). Estaba tomando el socketId que el Cliente registró para depurar y no tenía prefijo, así que terminé buscando para siempre hasta que me enteré Por lo tanto, es posible que tenga que hacerlo así si la ID que tiene no tiene el prefijo:

io.to('/#' + socketId).emit('myevent', {foo: 'bar'});
risuch
fuente
6

io.sockets.sockets [socket.id] .emit (...) funcionó para mí en v0.9

aljosa
fuente
Bienvenido a Stack Overflow. Esta respuesta no parece agregar mucha relación a las respuestas existentes. Una vez que tenga más reputación , podrá comentar las publicaciones de otras personas . Esto parece más adecuado para un comentario.
jerry
2

También puede mantener referencias de clientes. Pero esto hace que tu memoria esté ocupada.

Cree un objeto vacío y configure a sus clientes en él.

const myClientList = {};

  server.on("connection", (socket) => {
    console.info(`Client connected [id=${socket.id}]`);
     myClientList[socket.id] = socket;   
  });
  socket.on("disconnect", (socket) => {
    delete myClientList[socket.id];
  });

luego llame a su cliente específico por id desde el objeto

myClientList[specificId].emit("blabla","somedata");
Kamuran Sönecek
fuente