使用socket.io和node.js将消息发送到特定客户端


190

我正在使用socket.io和node.js,直到现在看起来还不错,但我不知道如何从服务器向特定客户端发送消息,如下所示:

client.send(message, receiverSessionId)

但是无论是.send()还是.broadcast()方法似乎都无法满足我的需求。

我发现可能的解决方案是,该.broadcast()方法将不发送消息的SessionId数组作为第二个参数,因此我可以将此时连接了所有SessionId的数组传递给服务器,除了那个我希望发送邮件,但是我觉得必须有一个更好的解决方案。

有任何想法吗?

Answers:


95

好吧,您必须为此赢得客户(惊喜),您可以采用以下简单方法:

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

我怀疑这可能会中断,但是它总是有io.clients可能被更改,因此请谨慎使用以上内容

或者您自己跟踪客户端,因此将它们添加到侦听器中自己的clients对象中,connection然后在disconnect侦听器中将其删除。

我将使用后一种,因为根据您的应用程序,您可能仍希望在客户端上具有更多状态,因此clients[id] = {conn: clientConnect, data: {...}}可能会完成一些工作。


6
伊沃,您能指出一个更完整的例子还是详细说明一下?我很想了解这种方法,但是我不确定我是否能识别出在此示例中使用的变量/对象。如上面的io.clients [sessionID]所示,在clients [id] = {conn:clientConnect,数据:{...}}中,clients [id]是io对象的一部分吗?还有什么是clientConnect对象?谢谢。
AndrewHenderson

@ ivo-wetzel嗨,您可以在这个话题上向我致敬吗?stackoverflow.com/questions/38817680/...
马赫迪pishguy

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 2012年

在群集环境中,如何确保套接字所属的正确进程正在发送消息?
Gal Ben-Haim

NGINX或HAProxy @Gal Ben-Haim提供的粘性会话怎么样?
matanster

varholder =新的socketio.RedisStore; ^ TypeError:undefined不是Object的函数。<anonymous>(C:\ Users \ Dev \ Desktop \ nouty-server \ server.js:108:14)位于Module._compile(module.js:460:26)在Function.Module的Module.load(module.js:355:32)处的Object.Module._extensions..js(module.js:478:10)在Module.load(module.js:355:32)。 runMain(module.js:501:10)在启动时(node.js:129:16)在node.js:814:3处运行
Lucas Bertollo 2015年

1
io.sockets.socket(socket_id)已在socket.io 1.0中删除。github.com/socketio/socket.io/issues/1618#issuecomment-46151246
ImMathan

97

每个插槽都以一个带有名称的插槽ID的房间加入房间,因此您可以

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

docs: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”,info); ^ TypeError:无法读取未定义的属性“发射”
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端口是生产中用于套接字的规范吗?
红色

1
抱歉,我花了很长时间回复@ingo。8000是Node社区在测试WebSocket或HTTP服务器时使用的通用端口(3000也很常见)。当然,您可以在生产中使用它,但是我不确定这有多普遍……无论如何,只要您的网关/负载均衡器/等为此做好了准备,您就可以真正使用任何端口。
Lucio Paiva

我是Python / PHP程序员,并且是套接字和节点的新手,问题是,为什么我们需要每秒增加同一用户的seq数?只是为了演示吗?
Umair

1
@Umair仅用于演示目的,不需要增加序列号。这只是为了表明您可以继续向每个客户发送邮件,他们会收到邮件。
Lucio Paiva

在此示例中,我看不到特定客户端仅如何发送消息,并且仅向另一客户端发送消息。每个客户端仅在您命名并为演示创建它时知道他们自己的序列号,并且从此序列号到套接字ID的映射都没有映射,而该映射需要直接向具有该套接字ID的客户端发送消息。
塞尔丘克

35

您可以使用

//仅将消息发送给发送方客户端

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


为我工作,我还必须添加“ const socket = require('socket.io')(http);” 在我的server.js文件中。
iPzard

35

在1.0中,您应该使用:

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

1
是的!,以前的io.sockets.sockets [socketid] .emit()可以工作,但是这在较新版本的socket.io中给了我未定义的对象错误。更改为io.sockets.connected作品。
2015年

对于使用TypeScript的用户,根据类型,这目前是用于此目的的“规范” API。
阿维·切里

但是我收到一个错误:io.sockets.connected [“ JgCoFX9AiCND_ZhdAAAC”]。emit(“ socketFromServer”,info); ^ TypeError:无法读取未定义的属性“发射”
Raz

这可能是由于api已更新,并且不再有类似对象io.sockets.connected["something"]及其emit方法的事实。
塞尔丘克

12

无论我们使用什么版本,只要我们console.log()我们在服务器端nodejs代码中使用的“ io”对象,[例如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] },

在这里我们可以看到套接字ID“ 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:无法读取未定义的属性“发射”
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)
    })

7

从1.4.5版开始,请确保在io.to()中提供正确前缀的socketId。我正在使用客户端登录记录的socketId进行调试,它没有前缀,所以我最终一直寻找直到发现!因此,如果您没有使用ID作为前缀,则可能需要这样做:

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


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

然后通过该对象的ID致电您的特定客户

myClientList[specificId].emit("blabla","somedata");

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.