Отправить сообщение конкретному клиенту с помощью socket.io и node.js

192

Я работаю с socket.io и node.js, и до сих пор это кажется довольно хорошим, но я не знаю, как отправить сообщение с сервера конкретному клиенту, что-то вроде этого:

client.send(message, receiverSessionId)

Но ни метод, .send()ни .broadcast()методы, кажется, не удовлетворяют мою потребность.

Что я нашел в качестве возможного решения, так это то, что .broadcast()метод принимает в качестве второго параметра массив SessionIds, на который не отправляется сообщение, поэтому я мог бы передать массив со всеми SessionIds, подключенными в этот момент к серверу, кроме одного Я хотел бы отправить сообщение, но я чувствую, что должно быть лучшее решение.

Любые идеи?

Родольфо Пальма
источник

Ответы:

95

Что ж, вы должны захватить клиента для этого (сюрприз), вы можете пойти простым путем:

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

Что может сломаться, я сомневаюсь в этом, но всегда есть вероятность, что это io.clientsможет измениться, поэтому используйте вышеизложенное с осторожностью

Или вы сами отслеживаете клиентов, поэтому добавляете их в свой собственный clientsобъект в connectionслушателе и удаляете их в disconnectслушателе.

Я бы использовал последний, так как в зависимости от вашего приложения вы, возможно, захотите иметь больше состояний на клиентах, так что что-то подобное clients[id] = {conn: clientConnect, data: {...}}может сделать эту работу.

Иво Ветцель
источник
7
Иво, ты можешь указать на более полный пример или уточнить? Мне не терпится понять этот подход, но я не уверен, что распознаю переменные / объекты, которые вы используете в этом примере. В клиентах [id] = {conn: clientConnect, data: {...}}, являются ли клиенты [id] частью объекта io, как показано в io.clients [sessionID] выше? И что такое объект clientConnect? Спасибо.
AndrewHenderson
@ ivo-wetzel привет, не мог бы ты меня приветствовать на эту тему? stackoverflow.com/questions/38817680/…
Махди Пишгуи
178

Ответ Ivo Wetzel больше не подходит для Socket.io 0.9.

Короче говоря, теперь вы должны сохранить socket.idи использовать io.sockets.socket(savedSocketId).emit(...) для отправки сообщений на него.

Вот как у меня это работает на кластерном сервере Node.js:

Сначала вам нужно установить хранилище Redis как хранилище, чтобы сообщения могли проходить через процессы:

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

});

Я опустил код кластеризации здесь, потому что это делает его более загроможденным, но добавить его тривиально. Просто добавьте все в рабочий код. Больше документов здесь http://nodejs.org/api/cluster.html

Эпели
источник
4
Спасибо, это было полезно. Я просто должен был использовать вместо этого массив: io.of('/mynamespace').sockets[socketID].emit(...)(не знаю, потому что я использую пространство имен)
Adrien Schuler
в кластерной среде, как мне убедиться, что правильный процесс, к которому принадлежит сокет, отправляет сообщение?
Гал Бен-Хаим
Как насчет липкой сессии любезно предоставленной NGINX или HAProxy @Gal Ben-Haim?
matanster
var holder = new socketio.RedisStore; ^ TypeError: undefined не является функцией в Object. <Anonymous> (C: \ Users \ Dev \ Desktop \ nouty-server \ server.js: 108: 14) в Module._compile (module.js: 460: 26) в Object.Module._extensions..js (module.js: 478: 10) в Module.load (module.js: 355: 32) в Function.Module._load (module.js: 310: 12) в Function.Module. runMain (module.js: 501: 10) при запуске (node.js: 129: 16) в node.js: 814: 3
Лукас Бертолло
1
io.sockets.socket(socket_id)удаляется в socket.io 1.0. github.com/socketio/socket.io/issues/1618#issuecomment-46151246
17
98

каждый сокет объединяет комнату с идентификатором сокета для имени, так что вы можете просто

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

документы: http://socket.io/docs/rooms-and-namespaces/#default-room

ура

Матик Коговшек
источник
7
Это лучший ответ и работает с более новыми версиями socket.io. Там в шпаргалку здесь хорошо: stackoverflow.com/questions/10058226/...
blented
4
обратите внимание, что это «широковещательный» тип передачи события. так что если вы попытаетесь установить обратный вызов, у вас будет ошибка. если вам нужно отправить событие в определенный сокет с обратным вызовом, используйте ответ @ PHPthinking и используйте его io.sockets.connected[socketid].emit();. Протестировано с 1.4.6.
tbutcaru
Но я получил эту ошибку: io.to ["JgCoFX9AiCND_ZhdAAAC"]. Emit ("socketFromServe‌r", информация); ^ TypeError: Невозможно прочитать свойство 'emit' из неопределенного
Raz
85

Самое простое, самое элегантное решение

Это так же просто, как:

client.emit("your message");

И это все.

Но как? Приведи пример

То, что нам всем нужно, это на самом деле полный пример, и вот что следует. Это протестировано с самой последней версией socket.io (2.0.3), а также с использованием современного Javascript (который мы все должны сейчас использовать).

Пример состоит из двух частей: сервера и клиента. Каждый раз, когда клиент подключается, он начинает получать от сервера периодический порядковый номер. Новая последовательность запускается для каждого нового клиента, поэтому сервер должен отслеживать их индивидуально. Вот тут-то и вступает в игру «Мне нужно отправить сообщение конкретному клиенту» . Код очень прост для понимания. Давай увидим это.

сервер

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

Сервер начинает прослушивать порт 8000 для входящих соединений. Когда он прибывает, он добавляет этого нового клиента на карту, чтобы он мог отслеживать его порядковый номер. Он также прослушивает disconnectсобытие этого клиента , когда удалит его с карты.

Каждую секунду срабатывает таймер. Когда это происходит, сервер просматривает карту и отправляет каждому клиенту сообщение с его текущим порядковым номером. Затем он увеличивает его и сохраняет номер обратно на карте. Вот и все, что нужно сделать. Очень просто.

клиент

Клиентская часть еще проще. Он просто подключается к серверу и прослушивает seq-numсообщение, распечатывая его на консоли каждый раз, когда оно приходит.

client.js

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

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

Выполнение примера

Установите необходимые библиотеки:

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

Запустите сервер:

node server

Откройте другие окна терминала и создайте столько клиентов, сколько вы хотите, запустив:

node client

Я также подготовил суть с полным кодом здесь .

Лусио Пайва
источник
Является ли порт 8000 нормой для производства в производстве?
Red
1
Извините, что так долго отвечал, @ingo. 8000 - это общий порт, используемый сообществом узлов при тестировании как веб-сокетов, так и HTTP-серверов (3000 также распространено). Конечно, вы можете использовать его в работе, но я не уверен, насколько это распространено ... в любом случае, вы можете просто использовать любой порт на самом деле, если ваши шлюзы / балансировщики нагрузки / и т. Д. Готовы к этому.
Лусио Пайва
Я программист на Python / PHP, и я новичок в сокетах и ​​нодах, вопрос в том, почему мы должны увеличивать seq числа одного и того же пользователя каждую секунду? это только для демо?
Умайр Аюб
1
@ Umair это просто для демонстрационных целей, нет необходимости увеличивать порядковый номер. Это было просто, чтобы показать, что вы можете продолжать посылать вещи каждому клиенту, и они получат его.
Лусио Пайва
В этом примере я не вижу, как определенный клиент отправляет сообщение только и только другому клиенту. Каждый клиент знает только свой собственный порядковый номер, как вы назвали и создали его для демонстрации, и не существует сопоставления с этими порядковыми номерами идентификатору сокета, который необходим для отправки сообщения непосредственно клиенту с этим идентификатором сокета.
Сельчук
36

Ты можешь использовать

// отправляем сообщение только отправителю-клиенту

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

// или вы можете отправить всем слушателям, включая отправителя

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

// отправить всем слушателям кроме отправителя

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

// или вы можете отправить его в комнату

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

DecoderNT
источник
Работая для меня, я также должен был добавить "const socket = require ('socket.io') (http);" в моем файле server.js.
iPzard
36

В 1.0 вы должны использовать:

io.sockets.connected[socketid].emit();
PHPthinking
источник
1
Да, ранее io.sockets.sockets [socketid] .emit () работал, но это дало мне неопределенные объектные ошибки в новой версии socket.io. Переход на io.sockets.connected работает.
Fraggle
Для тех, кто использует TypeScript, в настоящее время это «канонический» API для этого в соответствии с типами.
Ави Черри
Но я получаю сообщение об ошибке: io.sockets.connected ["JgCoFX9AiCND_ZhdAAAC"]. Emit ("socketFromServer", info); ^ TypeError: Невозможно прочитать свойство 'emit' из неопределенного
Raz
это, вероятно, связано с тем, что API обновлен и больше нет объекта типа io.sockets.connected["something"]и его emitметода.
Сельчук
12

Какую бы версию мы не использовали, если мы просто console.log () будем использовать объект "io", который мы используем в нашем коде nodejs на стороне сервера, [например, io.on ('connection', function (socket) {...});] мы можем видеть, что «io» - это просто объект json, и есть много дочерних объектов, в которых хранятся объекты сокета id и сокета.

Я использую socket.io версии 1.3.5, кстати.

Если мы посмотрим на объект io, он содержит,

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

здесь мы можем увидеть сокеты "B5AC9w0sYmOGWe4fAAAA" и т. д. Итак, мы можем сделать,

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

Опять же, при дальнейшей проверке мы можем увидеть такие сегменты, как,

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

Итак, мы можем получить сокет отсюда, выполнив

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

Кроме того, под двигателем мы имеем,

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

Итак, мы также можем написать,

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

Итак, я думаю, что мы можем достичь нашей цели любым из трех способов, которые я перечислил выше,

  1. io.sockets.connected [socketid] .emit (); ИЛИ
  2. io.eio.clients [socketid] .emit (); ИЛИ
  3. io.engine.clients [socketid] .emit ();
Суман Барик
источник
Но я получил эту ошибку: io.eio.clients ["JgCoFX9AiCND_ZhdAAAC"]. Emit ("socketFromServer", info); ^ TypeError: Невозможно прочитать свойство 'emit' из неопределенного
Raz
В настоящее время (с версией 2.1.1) это работает только для sockets.connected, но не для двух других.
Джеймс
10

Ты можешь сделать это

На сервере.

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

А на клиенте это очень просто.

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
источник
7

Начиная с версии 1.4.5, убедитесь, что в io.to () указан правильный префикс socketId. Я брал socketId, в который Клиент вошел для отладки, и он был без префикса, поэтому я продолжал искать вечно, пока не узнал! Так что вам, возможно, придется сделать это следующим образом, если у вашего идентификатора нет префикса:

io.to('/#' + socketId).emit('myevent', {foo: 'bar'});
risuch
источник
6

io.sockets.sockets [socket.id] .emit (...) работал для меня в v0.9

Aljoša
источник
Добро пожаловать в стек переполнения. Этот ответ, кажется, не добавляет много отношения к существующим ответам. Как только у вас появится больше репутации , вы сможете комментировать посты других людей . Это кажется лучше подходит для комментариев.
Джерри
2

Также вы можете сохранить отзывы клиентов. Но это делает вашу память занятой.

Создайте пустой объект и установите в него своих клиентов.

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

затем позвоните конкретному клиенту по идентификатору из объекта

myClientList[specificId].emit("blabla","somedata");
Камуран Сёнечек
источник
0

Socket.IO позволяет вам «именовать» свои сокеты, что по сути означает назначение различных конечных точек или путей.

Это может помочь: http://socket.io/docs/rooms-and-namespaces/

Игорь Т.
источник