Enviar mensagem para um cliente específico com socket.io e node.js

189

Estou trabalhando com socket.io e node.js e até agora parece muito bom, mas não sei como enviar uma mensagem do servidor para um cliente específico, algo como isto:

client.send(message, receiverSessionId)

Mas .send()nem os .broadcast()métodos nem parecem suprir minha necessidade.

O que eu descobri como uma solução possível é que o .broadcast()método aceita como segundo parâmetro uma matriz de SessionIds para a qual não envia a mensagem, para que eu pudesse passar uma matriz com todos os SessionIds conectados naquele momento ao servidor, exceto aquele Desejo enviar a mensagem, mas sinto que deve haver uma solução melhor.

Alguma ideia?

Rodolfo Palma
fonte

Respostas:

95

Bem, você tem que pegar o cliente para isso (surpresa), você pode seguir o caminho simples:

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

O que pode quebrar, duvido, mas é sempre uma possibilidade que io.clientspode ser alterada; portanto, use o acima com cautela

Ou você mesmo acompanha os clientes, portanto, você os adiciona ao seu próprio clientsobjeto no connectionouvinte e os remove no disconnectouvinte.

Eu usaria o último, já que, dependendo do seu aplicativo, você pode querer ter mais estado sobre os clientes de qualquer maneira, portanto, algo como clients[id] = {conn: clientConnect, data: {...}}pode fazer o trabalho.

Ivo Wetzel
fonte
6
Ivo, você pode apontar para um exemplo mais completo ou elaborar um pouco? Estou ansioso para entender essa abordagem, mas não sei se reconheço as variáveis ​​/ objetos que você está usando neste exemplo. Nos clientes [id] = {conn: clientConnect, data: {...}}, os clientes [id] fazem parte do objeto io, como visto em io.clients [sessionID] acima? Além disso, o que é o objeto clientConnect? Obrigado.
AndrewHenderson
@ ivo-wetzel oi, você poderia me ajudar neste tópico? stackoverflow.com/questions/38817680/…
mahdi pishguy (
178

A resposta de Ivo Wetzel não parece mais válida no Socket.io 0.9.

Em suma, agora você deve salvar socket.ide usar io.sockets.socket(savedSocketId).emit(...) para enviar mensagens a ele.

Foi assim que consegui isso funcionando no servidor Node.js. clusterizado:

Primeiro, você precisa definir o armazenamento Redis como o armazenamento para que as mensagens possam passar por processos:

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);
    });
  });

});

Omiti o código de cluster aqui, porque isso fica mais confuso, mas é trivial adicionar. Basta adicionar tudo ao código do trabalhador. Mais documentos aqui http://nodejs.org/api/cluster.html

Epeli
fonte
4
Graças foi útil. Eu só tinha que usar uma matriz em vez disso: io.of('/mynamespace').sockets[socketID].emit(...)(não sei se é porque eu estou usando um namespace)
Adrien Schuler
no ambiente em cluster, como posso garantir que o processo correto ao qual o soquete pertence está enviando a mensagem?
Gal Ben-Haim
Que tal uma sessão complicada, cortesia de NGINX ou HAProxy @Gal Ben-Haim?
matanster
var holder = novo socketio.RedisStore; ^ TypeError: undefined não é uma função em Object. <anonymous> (C: \ Users \ Dev \ Desktop \ nouty-server \ server.js: 108: 14) em Module._compile (module.js: 460: 26) em Object.Module._extensions..js (module.js: 478: 10) em Module.load (module.js: 355: 32) em Function.Module._load (module.js: 310: 12) em Function.Module. O comando runMain (module.js: 501: 10) na inicialização (node.js: 129: 16) no node.js: 814: 3
Lucas Bertollo
1
io.sockets.socket(socket_id)é removido no socket.io 1.0. github.com/socketio/socket.io/issues/1618#issuecomment-46151246
ImMathan
96

cada soquete se junta a uma sala com um ID de soquete para um nome, para que você possa apenas

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

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

Felicidades

Matic Kogovšek
fonte
7
Esta é a melhor resposta e funciona com as versões mais recentes do socket.io. Há uma boa folha de dicas aqui: stackoverflow.com/questions/10058226/…
blended
4
observe que esse é um tipo de 'transmissão' de emissão de um evento. portanto, se você tentar definir um retorno de chamada para isso, terá um erro. se você precisar enviar um evento para um soquete específico com um retorno de chamada, use a resposta e o uso de @ PHPthinking io.sockets.connected[socketid].emit();. Testado com 1.4.6.
Tbutcaru 25/05
Mas eu recebi este erro: io.to ["JgCoFX9AiCND_ZhdAAAC"]. Emit ("socketFromServe‌r", info); ^ TypeError: Não é possível ler a propriedade 'emit' de indefinido
Raz
85

A solução mais simples e elegante

É tão fácil quanto:

client.emit("your message");

E é isso.

Mas como? Me dê um exemplo

O que todos nós precisamos é de fato um exemplo completo, e é isso que se segue. Isso é testado com a versão mais recente do socket.io (2.0.3) e também usa o Javascript moderno (que todos nós deveríamos estar usando agora).

O exemplo é composto de duas partes: um servidor e um cliente. Sempre que um cliente se conecta, ele começa a receber do servidor um número de sequência periódica. Uma nova sequência é iniciada para cada novo cliente, para que o servidor precise acompanhá-los individualmente. É aí que entra o jogo "Preciso enviar uma mensagem para um cliente em particular" . O código é muito simples de entender. Vamos ver isso.

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);

O servidor começa a escutar na porta 8000 as conexões de entrada. Quando alguém chega, ele adiciona esse novo cliente a um mapa para poder acompanhar seu número de sequência. Ele também escuta o disconnectevento desse cliente , quando o remove do mapa.

A cada segundo, um timer é acionado. Quando isso acontece, o servidor percorre o mapa e envia uma mensagem para cada cliente com seu número de sequência atual. Em seguida, o incrementa e armazena o número de volta no mapa. É só isso. Mole-mole.

Cliente

A parte do cliente é ainda mais simples. Apenas se conecta ao servidor e escuta a seq-nummensagem, imprimindo-a no console toda vez que chega.

client.js

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

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

Executando o exemplo

Instale as bibliotecas necessárias:

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

Execute o servidor:

node server

Abra outras janelas de terminal e crie quantos clientes você desejar executando:

node client

Também preparei uma essência com o código completo aqui .

Lucio Paiva
fonte
A porta 8000 é a norma para socketio em produção?
Vermelho
1
Desculpe, demorei tanto para responder, @ingo. 8000 é uma porta comum usada pela comunidade Node ao testar os websockets ou servidores HTTP (3000 também é comum). É claro que você poderia usá-lo na produção, mas não tenho certeza do quanto isso é comum ... de qualquer maneira, você pode simplesmente usar qualquer porta, desde que seus gateways / balanceadores de carga / etc estejam preparados para isso.
Lucio Paiva
Eu sou programador Python / PHP e sou novo em soquetes e nós, a pergunta é: por que precisamos incrementar o número seq do mesmo usuário a cada segundo? isso é apenas para demonstração?
Umair
1
@Umair é apenas para fins de demonstração, sem necessidade real de incrementar um número de sequência. Foi apenas para mostrar que você pode continuar enviando coisas para cada cliente e eles a receberão.
Lucio Paiva
Não vejo neste exemplo como um cliente específico envia apenas uma mensagem e apenas para outro cliente. Cada cliente conhece apenas seu próprio número de sequência conforme você o nomeou e o criou para demonstração, e não há mapa desses números de sequência para um ID de soquete necessário para enviar uma mensagem diretamente ao cliente com esse ID de soquete.
Selçuk
35

Você pode usar

// envia mensagem apenas para o remetente-cliente

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

// ou você pode enviar para todos os ouvintes, incluindo o remetente

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

// envia para todos os ouvintes, exceto o remetente

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

// ou você pode enviá-lo para uma sala

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

DecoderNT
fonte
Trabalhou para mim, eu também tive que adicionar "const socket = require ('socket.io') (http);" no meu arquivo server.js.
iPzard 01/01/19
35

Na 1.0 você deve usar:

io.sockets.connected[socketid].emit();
PHPthinking
fonte
1
Sim !!!, anteriormente io.sockets.sockets [socketid] .emit () funcionou, mas isso me deu erros de objeto indefinidos na versão mais recente do socket.io. Mudar para io.sockets.connected funciona.
Fraggle
Para aqueles que usam TypeScript, atualmente esta é a API 'canônica', de acordo com as tipagens.
Avi Cereja
Mas eu recebo um erro: io.sockets.connected ["JgCoFX9AiCND_ZhdAAAC"]. Emit ("socketFromServer", info); ^ TypeError: Não é possível ler a propriedade 'emit' de undefined
Raz
isso provavelmente se deve ao fato de a API ter sido atualizada e não existir mais um objeto como io.sockets.connected["something"]e seu emitmétodo.
Selçuk
12

Qualquer que seja a versão que estamos usando, se apenas console.log () o objeto "io" que usamos no código nodejs do servidor, [por exemplo, io.on ('conexão', função (soquete) {...});] , podemos ver que "io" é apenas um objeto json e há muitos objetos filhos nos quais o ID do soquete e os objetos do soquete são armazenados.

Estou usando o socket.io versão 1.3.5, btw.

Se olharmos no objeto io, ele contém,

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

aqui podemos ver os socketids "B5AC9w0sYmOGWe4fAAAA" etc. Então, podemos fazer,

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

Novamente, em uma inspeção mais aprofundada, podemos ver segmentos como,

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

Então, podemos recuperar um soquete daqui fazendo

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

Além disso, sob o motor, temos,

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

Então, também podemos escrever,

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

Acho que podemos alcançar nosso objetivo de qualquer uma das três maneiras listadas acima,

  1. io.sockets.connected [socketid] .emit (); OU
  2. io.eio.clients [socketid] .emit (); OU
  3. io.engine.clients [socketid] .emit ();
Suman Barick
fonte
Mas eu recebi este erro: io.eio.clients ["JgCoFX9AiCND_ZhdAAAC"]. Emit ("socketFromServer", info); ^ TypeError: Não é possível ler a propriedade 'emit' de undefined
Raz
Atualmente (com a versão 2.1.1), isso funcionava apenas para sockets.connected, mas não os outros dois.
James
10

Você consegue fazer isso

No 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 "});
    }

E no cliente, é muito fácil de manusear.

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
fonte
7

A partir da versão 1.4.5, certifique-se de fornecer um socketId com o prefixo correto em io.to (). Eu estava usando o socketId, o cliente logado para depurar e não tinha prefixo, então acabei pesquisando para sempre até descobrir! Portanto, talvez você precise fazer isso assim se o ID que você possui não tiver o prefixo:

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

io.sockets.sockets [socket.id] .emit (...) funcionou para mim na v0.9

aljosa
fonte
Bem-vindo ao Stack Overflow. Esta resposta não parece acrescentar muito em relação às respostas existentes. Depois de ter mais reputação , você poderá comentar nas postagens de outras pessoas . Isso parece mais adequado para um comentário.
Jerry
2

Além disso, você pode manter as referências de clientes. Mas isso deixa sua memória ocupada.

Crie um objeto vazio e defina seus clientes nele.

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];
  });

em seguida, chame seu cliente específico por id do objeto

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

O Socket.IO permite "namespace" seus soquetes, o que significa essencialmente atribuir diferentes pontos de extremidade ou caminhos.

Isso pode ajudar: http://socket.io/docs/rooms-and-namespaces/

Igor T.
fonte