【问题标题】:Send message to specific client with socket.io and node.js使用 socket.io 和 node.js 向特定客户端发送消息
【发布时间】:2011-06-06 13:11:23
【问题描述】:

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

client.send(message, receiverSessionId)

但是.send().broadcast() 方法似乎都不能满足我的需求。

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

有什么想法吗?

【问题讨论】:

    标签: javascript node.js websocket socket.io server-side


    【解决方案1】:

    每个套接字都以套接字 id 作为名称加入房间,因此您可以

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

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

    干杯

    【讨论】:

    • 这是最好的答案,适用于较新版本的 socket.io。这里有一个很好的备忘单:stackoverflow.com/questions/10058226/…
    • 请注意,这是发出事件的“广播”类型。因此,如果您尝试为此设置回调,则会出现错误。如果您需要通过回调将事件发送到特定套接字,请使用@PHPthinking 的答案并使用io.sockets.connected[socketid].emit();。用 1.4.6 测试。
    • 但是我得到了这个错误:io.to["JgCoFX9AiCND_ZhdAAAC"].emit("socketFromServe‌​r", info); ^ 类型错误:无法读取未定义的属性“发射”
    • 这对我有用,但必须使用 io.to(id).emit("keyword", data)。
    【解决方案2】:

    最简单、最优雅的方式

    已验证使用 socket.io v3.1.1

    很简单:

    client.emit("your message");
    

    就是这样。好的,但是它是如何工作的呢?

    最小的工作示例

    这是一个简单的客户端-服务器交互示例,其中每个客户端定期接收包含序列号的消息。每个客户端都有一个独特的顺序,这就是“我需要向特定客户端发送消息”发挥作用的地方。

    服务器

    server.js

    const
        {Server} = require("socket.io"),
        server = new Server(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@3.1.1 socket.io-client@3.1.1
    

    运行服务器:

    node server
    

    通过运行打开其他终端窗口并生成任意数量的客户端:

    node client
    

    我还准备了一个要点,完整代码here

    【讨论】:

    • 8000 端口是生产中 socketio 的标准吗?
    • 抱歉,我花了这么长时间才回复,@ingo。 8000 是 Node 社区在测试 websocket 或 HTTP 服务器时使用的通用端口(3000 也很常见)。当然你可以在生产中使用它,但我不确定这有多普遍......无论如何,你可以使用任何端口,只要你的网关/负载均衡器/等为此做好准备。
    • 我是 Python/PHP 程序员,我是套接字和节点的新手,问题是,为什么我们需要每秒增加同一用户的 seq 数?只是为了演示?
    • @Umair 仅用于演示目的,不需要增加序列号。这只是为了表明您可以继续向每个客户发送东西,他们会收到它。
    • 我在这个例子中看不到特定客户端如何仅向另一个客户端发送消息。每个客户端只知道您为演示命名并创建它的自己的序列号,并且没有从该序列号到套接字 id 的映射,这是直接向具有该套接字 id 的客户端发送消息所需的。
    【解决方案3】:

    您还可以保留客户的参考资料。但这会让你的记忆很忙。

    创建一个空对象并将您的客户设置到其中。

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

    【讨论】:

      【解决方案4】:

      那么你必须抓住客户(惊喜),你可以走简单的路:

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

      这可能会破坏,我对此表示怀疑,但io.clients 总是有可能被更改,因此请谨慎使用上述内容

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

      我会使用后一种,因为根据您的应用程序,您可能希望在客户端上拥有更多状态,所以像 clients[id] = {conn: clientConnect, data: {...}} 这样的东西可能会完成这项工作。

      【讨论】:

      • Ivo,您能否指出一个更完整的示例或详细说明一下?我很想了解这种方法,但我不确定我是否能识别您在此示例中使用的变量/对象。在 clients[id] = {conn: clientConnect, data: {...}} 中,clients[id] 是 io 对象的一部分,如上面的 io.clients[sessionID] 所示?还有什么是 clientConnect 对象?谢谢。
      • @ivo-wetzel 嗨,你能帮我谈谈这个话题吗? stackoverflow.com/questions/38817680/…
      【解决方案5】:

      在 1.0 中你应该使用:

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

      【讨论】:

      • 是的!!!,以前 io.sockets.sockets[socketid].emit() 工作,但这给了我在较新版本的 socket.io 中未定义的对象错误。更改为 io.sockets.connected 有效。
      • 对于那些使用 TypeScript 的人来说,根据类型,这是目前的“规范”API。
      • 但是我收到一个错误:io.sockets.connected["JgCoFX9AiCND_ZhdAAAC"].emit("socketFromServer", info); ^ 类型错误:无法读取未定义的属性“发射”
      • 这可能是由于 api 更新并且不再有像 io.sockets.connected["something"] 及其 emit 方法这样的对象。
      【解决方案6】:

      从版本 1.4.5 开始,请确保在 io.to() 中提供正确前缀的 socketId。 我正在使用客户端登录的 socketId 进行调试,它没有前缀,所以我最终永远搜索,直到我发现!因此,如果您拥有的 Id 没有前缀,您可能必须这样做:

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

      【讨论】:

      • 我必须删除前缀才能让它真正起作用!谢谢你的回答。
      【解决方案7】:

      你可以使用

      //只向发送者客户端发送消息

      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 文件中。
      • 嗨,希望你一切都好。我知道你回答这个问题已经有一段时间了,但你的回答对我来说是最有意义的。你能看看我的问题吗?stackoverflow.com/questions/65377025/…
      【解决方案8】:

      你可以这样做

      在服务器上。

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

      【讨论】:

        【解决方案9】:

        无论我们使用什么版本,如果我们只是 console.log() 我们在服务器端 nodejs 代码中使用的“io”对象,[例如io.on('connection', function(socket) {...});],我们可以看到“io”只是一个json对象,其中有很多子对象存储socket id和socket对象。

        我使用的是 socket.io 版本 1.3.5,顺便说一句。

        如果我们查看 io 对象,它包含,

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

        在这里我们可以看到 socketids "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();
        

        所以,我想我们可以通过上面列出的 3 种方式中的任何一种来实现我们的目标,

        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); ^ 类型错误:无法读取未定义的属性“发射”
        • 目前(版本 2.1.1)这仅适用于 sockets.connected,但不适用于其他两个。
        • 有用的答案,让我了解套接字客户端-服务器结构如何更好地工作。谢谢苏曼
        【解决方案10】:

        Socket.IO 允许您为套接字“命名空间”,这实际上意味着分配不同的端点或路径。

        这可能会有所帮助: http://socket.io/docs/rooms-and-namespaces/

        【讨论】:

          【解决方案11】:

          io.sockets.sockets[socket.id].emit(...) 在 v0.9 中为我工作

          【讨论】:

          【解决方案12】:

          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

          【讨论】:

          • 谢谢,它很有帮助。我只需要使用一个数组:io.of('/mynamespace').sockets[socketID].emit(...)(不知道是不是因为我使用了命名空间)
          • 在集群环境下,如何确保套接字所属的正确进程正在发送消息?
          • 由 NGINX 或 HAProxy @Gal Ben-Haim 提供的粘性会话怎么样?
          • var holder = new socketio.RedisStore; ^ TypeError: undefined is not a function at Object. (C:\Users\Dev\Desktop\nouty-server\server.js:108:14) at Module._compile (module.js:460:26) at 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
          • io.sockets.socket(socket_id) 在 socket.io 1.0 中被移除。 github.com/socketio/socket.io/issues/1618#issuecomment-46151246
          猜你喜欢
          • 2012-05-24
          • 2011-10-18
          • 2015-08-06
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-04-05
          • 2012-07-08
          • 1970-01-01
          相关资源
          最近更新 更多