如何使用socket.io向特定客户端发送消息

121

我正在使用socket.io + node.js,我知道如何在本地发送消息并使用socket.broadcast.emit()函数进行广播:所有连接的客户端都会收到相同的消息。

现在,我想知道如何向特定客户端发送私人消息,即一对一的客户端流式通信。谢谢。


1
抱歉,psiphi75,但是这个链接并没有回答我的问题,也不是重复的问题。 - Nizar B.
2
@psiphi75,这绝对不是一个重复的问题。 - softvar
1
无法忍受像@psiphi这样的人。你真的是开发者吗?一个HTML5特定的问题如何与独立库相关?就此而言,WebSockets并不是Socket.io。Socket.io是一个可以使用WebSockets的库,但我离题了。这也是关于库发送数据到特定客户端的更具体的问题,而不是关于技术本身的问题。 - Levi Roberts
@Katcha,在“赏金”选项中有一个名为“当前答案已过时”的选项。通常,这种方法应该用于这种情况。 - bugwheels94
显示剩余2条评论
8个回答

325
您可以使用socket.io rooms。从客户端发射一个事件(在本例中为“join”,可以是任何内容),带有任何唯一标识符(电子邮件,ID)。
客户端代码:
var socket = io.connect('http://localhost');
socket.emit('join', {email: user1@example.com});

现在,从服务器端使用这些信息为该用户创建一个唯一的房间。
服务器端:
var io = require('socket.io').listen(80);

io.sockets.on('connection', function (socket) {
  socket.on('join', function (data) {
    socket.join(data.email); // We are using room of socket io
  });
});

现在每个用户都加入了一个以用户电子邮件命名的房间。因此,如果您想向特定用户发送消息,只需

服务器端:

io.sockets.in('user1@example.com').emit('new_msg', {msg: 'hello'});

在客户端上要做的最后一件事情就是监听“new_msg”事件。

客户端:

socket.on("new_msg", function(data) {
    alert(data.msg);
}

我希望你能理解这个想法。


3
请将这行代码“io.socket.in('user1@example.com').emit('new_msg', {msg: 'hello'});”改为如下形式:“io.sockets.in('user1@example.com').emit('new_msg', {msg: 'hello'});”。 - silvesterprabu
50
这个答案比当前被接受的答案好得多。原因如下:1)您不必管理和清理全局客户端数组。2)即使用户在同一页上打开了多个选项卡,它也可以工作。3)它可以轻松扩展到使用 socket.io-redis 在节点群集(多个进程)中工作。 - Daniel Que
2
如何使{email:user1@example.com}对于所有连接都不同,难道所有访问应用程序的客户端都有用户user1@example.com吗?当第二个客户端连接时,我该如何将其设置为用户user2@example.com,以便我可以拥有不同的房间。如果这是一个烦人的问题,那我很抱歉。 - jack blank
2
我喜欢这个解决方案,但我认为安全性受到了威胁。如果我在客户端脚本中更改电子邮件,那该怎么办?现在我能够阅读其他私人消息。你怎么看? - Gopinath Shiva
3
请将以下英文文本翻译成中文。请仅返回已翻译的文本:不必是我提到的任何唯一标识符。 - az7ar
显示剩余12条评论

112

当用户连接时,应向服务器发送消息,其中包含一个类似于电子邮件的唯一用户名。

一个用户名和socket对应关系应该被存储在一个像这样的对象中:

var users = {
    'userA@example.com': [socket object],
    'userB@example.com': [socket object],
    'userC@example.com': [socket object]
}
在客户端,使用以下数据向服务器发送一个对象:
{
    to:[the other receiver's username as a string],
    from:[the person who sent the message as string],
    message:[the message to be sent as string]
}

在服务器端,监听消息。当接收到消息时,将数据发送给接收方。

users[data.to].emit('receivedMessage', data)
在客户端,监听来自服务器的名为“receivedMessage”的事件,并通过读取数据处理它来自谁以及发送的消息。

在客户端,监听来自服务器的名为“receivedMessage”的事件,并通过读取数据处理它来自谁以及发送的消息。


32
如果你使用这个解决方案,那么你需要明白以下几点:
  1. 当用户断开连接时,你需要清理"users"对象。
  2. 它不支持第二个连接——例如来自另一个浏览器的连接。因此,如果用户从另一个浏览器连接,旧连接将被覆盖。
- Vladimir Kurijov
1
你会如何将 socket 对象存储在数据存储中?我假设如果你有多个节点进程,这种方法是行不通的。 - chovy
@chovy 你需要使用 Redis。请查看此链接 https://github.com/LearnBoost/Socket.IO/wiki/Configuring-Socket.IO - Vladimir Kurijov
13
我建议不要使用这种解决方案。我试图克服这种方法的限制,结果浪费了很多时间。请查看@az7ar的解决方案以及这个说明为什么它更好 - Daniel Que
{ to: [接收者的用户名(字符串)],from: [发送消息的人(字符串)], message: [要发送的消息(字符串)]} 为什么是字符串? - user4909217
显示剩余6条评论

45

SURE: 简单来说,

这就是你所需要的:

io.to(socket.id).emit("event", data);

每当用户加入服务器时,将生成套接字详细信息,包括ID。这个ID确实有助于向特定的人发送消息。

首先,我们需要将所有套接字ID存储在数组中,

var people={};

people[name] =  socket.id;

这里的名字是收件人的名字。 示例:

people["ccccc"]=2387423cjhgfwerwer23;

因此,现在我们可以在发送消息时使用接收器名称获取socket.id:

为此,我们需要知道接收方的名称。您需要将接收器名称发射到服务器。

最后一件事是:

 socket.on('chat message', function(data){
io.to(people[data.receiver]).emit('chat message', data.msg);
});

希望这对你有所帮助。

祝好运!!


2
但是如果用户同时与其他两个人聊天,那怎么办?两个用户的消息不会同时出现在两个窗口中吗? - Sharan Mohandas
2
如果有超过两个用户,它将无法正常工作。 - Mohhamad Hasham
1
每次用户连接套接字时,套接字ID都会更改,因此如果用户在多个选项卡中打开您的应用程序,则会收到您存储并发送给其他套接字连接的响应。 - Vikas Kandari
这在socket.io最新版本(3.0.3)中无法工作。 - Deepak saini
这个方法可以跨域发射事件吗?io.to(socket.id).emit("event", data); - maranR
显示剩余3条评论

13
你可以参考socket.io rooms。当你握手成功时,你可以将它们加入到命名的房间中,例如user.#{userid}
之后,你可以通过方便的名称向任何客户端发送私人消息,例如:
io.sockets.in('user.125').emit('new_message', {text: "Hello world"}) 

在上述操作中,我们向用户“125”发送了“new_message”。谢谢。

嗨,伙计,谢谢你的第一个答案,我会尝试一下并告诉你结果的,因为我不想建立一个聊天室,而是像Facebook网络上使用的私人信使。 - Nizar B.
有人能帮我解决这个问题吗?我卡住了。我想添加一个与我的套接字相关联的用户名,并允许我的应用向特定用户发送消息,而不是聊天室,就像Facebook的信使一样。如果您知道,请告诉我!谢谢。 - Nizar B.
首先,您必须了解该用户名必须在所有用户中是唯一的。第二个问题是 - 您是否忘记在客户端订阅发出的消息? - Vladimir Kurijov
这个问题在于你失去了使用回调函数的能力,因为它们在广播中是不允许的,而这实际上就是你在这里所做的事情——向他的私人房间广播。 - bluehallu
@VladimirKurijov,Katcha先生不想使用Rooms,我同意这不是解决他问题的方法。 - miksiii
我认为人们在解释中无法看到“房间”和“名称空间”与真正的私人聊天之间的主要和本质区别,我尝试使用node、socket.io和react进行这项工作已有数月,但未能成功。 - Goran_Ilic_Ilke

6
作为回答的补充,az7ar提供了很好的解释,但是让我用socket.io房间更简单地解释一下。请求一个带有唯一标识符的服务器以加入服务器。我们在这里使用电子邮件作为唯一标识符。

客户端Socket.io

socket.on('connect', function () {
  socket.emit('join', {email: user@example.com});
});

当用户加入服务器时,为该用户创建一个房间。
服务器Socket.io。
io.on('connection', function (socket) {
   socket.on('join', function (data) {    
    socket.join(data.email);
  });
});

现在我们已经完成了连接的设置。让我们向服务器的to房间发射一些东西,这样用户就可以听取。

服务器 Socket.io

io.to('user@example.com').emit('message', {msg: 'hello world.'});

让我们通过监听客户端的事件来完成这个主题。
socket.on("message", function(data) {
  alert(data.msg);
});

来自Socket.IO的参考资料。


3
看起来你的回答是从下面 @az7ar 的 回答 复制过来的,如果你使用了别人的回答并进行了修改,请注明出处。 - Rahul Gaur
谢谢@RahulGaur,是的,它很相似但并非抄袭。但我的答案确实有所改变。他使用in来发出事件。但在我的情况下,我使用to来发出事件。希望你以前见过这个。 - Lalit Mohan
1
很高兴听到这不是抄袭,但它似乎受到启发,所以给予信用是一件好事。 - Rahul Gaur
1
收到,一定会做的。谢谢 @RahulGaur。 - Lalit Mohan

6
在我们公司的一个项目中,我们采用了“房间”方法,它的名称是对话中所有用户的用户ID组合而成的唯一标识符(我们的实现更像Facebook Messenger),例如: | id | name | 1 | Scott | 2 | Susan
“房间”名称将是“1-2”(ID按顺序排列),并且在断开连接时,socket.io会自动清除房间。
这样,您只需向该房间发送消息,并且仅发送给在线(已连接)用户(减少了服务器发送的数据包数量)。

1
我很惊讶没有人看过你的解决方案...我在看到你的答案之后才发布了我的答案... - DragonFire

1
以下是关于 Android 客户端和 Socket IO 服务器的完整解决方案(代码很多但可行)。在使用 socket io 时,Android 和 IOS 的支持似乎存在缺乏,这有点悲剧。
基本上,通过连接来自 mysql 或 mongo 的用户唯一 id 并对其进行排序(在 Android 客户端完成并发送到服务器),创建一个房间名称。因此,每个配对都有一个唯一但在配对中共同的房间名称。然后只需在该房间中聊天即可。 以下是 Android 中创建房间的快速参考。
 // Build The Chat Room
        if (Integer.parseInt(mySqlUserId) < Integer.parseInt(toMySqlUserId)) {
            room = "ic" + mySqlUserId + toMySqlUserId;
        } else {
            room = "ic" + toMySqlUserId + mySqlUserId;
        }

全套作品

包 Json

"dependencies": {
    "express": "^4.17.1",
    "socket.io": "^2.3.0"
  },
  "devDependencies": {
    "nodemon": "^2.0.6"
  }

Socket IO服务器
app = require('express')()
http = require('http').createServer(app)
io = require('socket.io')(http)

app.get('/', (req, res) => {

    res.send('Chat server is running on port 5000')
})

io.on('connection', (socket) => {

    // console.log('one user connected ' + socket.id);

    // Join Chat Room
    socket.on('join', function(data) {

        console.log('======Joined Room========== ');
        console.log(data);

        // Json Parse String To Access Child Elements
        var messageJson = JSON.parse(data);
        const room = messageJson.room;
        console.log(room);

        socket.join(room);

    });

    // On Receiving Individual Chat Message (ic_message)
    socket.on('ic_message', function(data) {
        console.log('======IC Message========== ');
        console.log(data);

        // Json Parse String To Access Child Elements
        var messageJson = JSON.parse(data);
        const room = messageJson.room;
        const message = messageJson.message;

        console.log(room);
        console.log(message);

        // Sending to all clients in room except sender
        socket.broadcast.to(room).emit('new_msg', {
            msg: message
        });

    });

    socket.on('disconnect', function() {
        console.log('one user disconnected ' + socket.id);
    });

});

http.listen(5000, () => {

    console.log('Node app is running on port 5000')
})

安卓Socket IO类
public class SocketIOClient {

    public Socket mSocket;

    {
        try {
            mSocket = IO.socket("http://192.168.1.5:5000");
        } catch (URISyntaxException e) {
            throw new RuntimeException(e);
        }
    }

    public Socket getSocket() {
        return mSocket;
    }
}

安卓活动。
public class IndividualChatSocketIOActivity extends AppCompatActivity {

    // Activity Number For Bottom Navigation Menu
    private final Context mContext = IndividualChatSocketIOActivity.this;

    // Strings
    private String mySqlUserId;
    private String toMySqlUserId;

    // Widgets
    private EditText etTextMessage;
    private ImageView ivSendMessage;

    // Socket IO
    SocketIOClient socketIOClient = new SocketIOClient();
    private String room;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_chat);

        // Widgets
        etTextMessage = findViewById(R.id.a_chat_et_text_message);
        ivSendMessage = findViewById(R.id.a_chat_iv_send_message);

        // Get The MySql UserId from Shared Preference
        mySqlUserId = StartupMethods.getFromSharedPreferences("shared",
                                                              "id",
                                                              mContext);

        // Variables From Individual List Adapter
        Intent intent = getIntent();

        if (intent.hasExtra("to_id")) {

            toMySqlUserId = Objects.requireNonNull(Objects.requireNonNull(getIntent().getExtras())
                                                          .get("to_id"))
                                   .toString();
        }

        // Build The Chat Room
        if (Integer.parseInt(mySqlUserId) < Integer.parseInt(toMySqlUserId)) {
            room = "ic" + mySqlUserId + toMySqlUserId;
        } else {
            room = "ic" + toMySqlUserId + mySqlUserId;
        }

        connectToSocketIO();

        joinChat();

        leaveChat();

        getChatMessages();

        sendChatMessages();

    }

    @Override
    protected void onPause() {
        super.onPause();

    }

    private void connectToSocketIO() {

        socketIOClient.mSocket = socketIOClient.getSocket();
        socketIOClient.mSocket.on(Socket.EVENT_CONNECT_ERROR,
                                  onConnectError);
        socketIOClient.mSocket.on(Socket.EVENT_CONNECT_TIMEOUT,
                                  onConnectError);
        socketIOClient.mSocket.on(Socket.EVENT_CONNECT,
                                  onConnect);
        socketIOClient.mSocket.on(Socket.EVENT_DISCONNECT,
                                  onDisconnect);
        socketIOClient.mSocket.connect();
    }

    private void joinChat() {

        // Prepare To Send Data Through WebSockets
        JSONObject jsonObject = new JSONObject();

        // Header Fields
        try {

            jsonObject.put("room",
                           room);

            socketIOClient.mSocket.emit("join",
                                        String.valueOf(jsonObject));

        } catch (JSONException e) {
            e.printStackTrace();
        }

    }

    private void leaveChat() {
    }

    private void getChatMessages() {

        socketIOClient.mSocket.on("new_msg",
                                  new Emitter.Listener() {
                                      @Override
                                      public void call(Object... args) {
                                          try {
                                              JSONObject messageJson = new JSONObject(args[0].toString());
                                              String message = String.valueOf(messageJson);

                                              runOnUiThread(new Runnable() {
                                                  @Override
                                                  public void run() {
                                                      Toast.makeText(IndividualChatSocketIOActivity.this,
                                                                     message,
                                                                     Toast.LENGTH_SHORT)
                                                           .show();
                                                  }
                                              });
                                          } catch (JSONException e) {
                                              e.printStackTrace();
                                          }
                                      }
                                  });
    }

    private void sendChatMessages() {

        ivSendMessage.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                String message = etTextMessage.getText()
                                              .toString()
                                              .trim();

                // Prepare To Send Data Thru WebSockets
                JSONObject jsonObject = new JSONObject();

                // Header Fields
                try {
                    jsonObject.put("room",
                                   room);

                    jsonObject.put("message",
                                   message);

                    socketIOClient.mSocket.emit("ic_message",
                                                String.valueOf(jsonObject));

                } catch (JSONException e) {
                    e.printStackTrace();
                }

            }
        });
    }

    public Emitter.Listener onConnect = new Emitter.Listener() {
        @Override
        public void call(Object... args) {

            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    Toast.makeText(IndividualChatSocketIOActivity.this,
                                   "Connected To Socket Server",
                                   Toast.LENGTH_SHORT)
                         .show();

                }
            });

            Log.d("TAG",
                  "Socket Connected!");
        }
    };

    private Emitter.Listener onConnectError = new Emitter.Listener() {
        @Override
        public void call(Object... args) {

            runOnUiThread(new Runnable() {
                @Override
                public void run() {

                }
            });
        }
    };
    private Emitter.Listener onDisconnect = new Emitter.Listener() {
        @Override
        public void call(Object... args) {

            runOnUiThread(new Runnable() {
                @Override
                public void run() {

                }
            });
        }
    };

}

Android Gradle
// SocketIO
implementation ('io.socket:socket.io-client:1.0.0') {
    // excluding org.json which is provided by Android
    exclude group: 'org.json', module: 'json'
}

0

不需要创建房间 (因为你只想要向一个人发送消息),我所做的是创建一个内存数组:

clientSessions: Map<string, UserIdentifier> = new Map()

我是根据我的用例来实现这种方式的,我想根据我设置的标识符获取我想要的套接字ID。映射中的键将是我设置的标识符,而值包括连接的套接字的ID。请参见下面的服务器端代码。
客户端:
const socket = io('http://localhost:3200', {query: {randomKeyID: '123'}});

服务器端:

  handleDisconnect(client: Socket) {
    const query = client.handshake.query;
    const randomKeyID = <string> query.randomKeyID
    this.clientSessions.delete(randomKeyID);
    console.log(`Client disconnected: ${client.id}`);
  }

  handleConnection(client: Socket, ...args: any[]) {
    const query = client.handshake.query;
    const user: UserIdentifier = {
      id: client.id,
      randomKeyID: <string> query.randomKeyID
    }
    this.clientSessions.set(user.randomKeyID, user);
    console.log(`Client connected: ${client.id}`);
  }

在服务器端也添加了这个,以便轻松访问websocket (类型Server来自socket.io)

  @WebSocketServer()
  server: Server;

所以,如果我想发送给特定的客户端,我可以:

const { randomKeyID } = this.clientSessions.get(something);
this.server.to(randomKeyID).emit('clientListener', 'Hello');

如果服务器重新启动,客户端将继续搜索您的服务器并建立新连接。服务器重新启动时,您的内存数组将被清除,因此不会有问题。 - heneftees
糟糕,我的代码是在 NestJS 的上下文中编写的;但仍然希望逻辑能够有所帮助! - heneftees

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接