使用webSocket向特定的已连接用户发送消息?


81

我编写了用于向所有用户广播消息的代码:

// websocket and http servers
var webSocketServer = require('websocket').server;

...
...
var clients = [ ];

var server = http.createServer(function(request, response) {
    // Not important for us. We're writing WebSocket server, not HTTP server
});
server.listen(webSocketsServerPort, function() {
  ...
});

var wsServer = new webSocketServer({
    // WebSocket server is tied to a HTTP server. 
    httpServer: server
});

// This callback function is called every time someone
// tries to connect to the WebSocket server
wsServer.on('request', function(request) {
...
var connection = request.accept(null, request.origin); 
var index = clients.push(connection) - 1;
...

请注意:

  • 我没有任何用户参考,只有一个连接。
  • 所有用户连接都存储在中array

目标:假设Node.js服务器希望将消息发送到特定客户端(约翰)。NodeJs服务器如何知道John拥有哪个连接?Node.js服务器甚至都不认识John。它所看到的只是连接。

因此,我相信现在,我不应该仅通过用户连接来存储用户,而是需要存储一个对象,该对象将包含userIdconnection对象。

理念:

  • 页面加载完成(DOM准备就绪)后-建立与Node.js服务器的连接。

  • 当Node.js服务器接受连接时-生成一个唯一的字符串,并将其发送到客户端浏览器。将用户连接和唯一字符串存储在一个对象中。例如{UserID:"6", value: {connectionObject}}

  • 在客户端,当此消息到达时-将其存储在隐藏字段或cookie中。(用于将来对NodeJs服务器的请求)


当服务器要向John发送消息时:

  • 在字典中找到john的UserID并通过相应的连接发送消息。

  • 请注意,此处(在消息机制中)没有inplocate的asp.net服务器代码。仅NodeJ。*

题:

这是正确的方法吗?

Answers:


75

这不仅是正确的方法,而且是唯一的方法。基本上每个连接都需要一个唯一的ID。否则,您将无法识别它们,就这么简单。

现在,您将如何表示它是另一回事。使用idconnection属性创建对象是实现此目的的好方法(我肯定会这么做)。您也可以将id直接附加到连接对象。

还要记住,如果要在用户之间进行通信,则还必须发送目标用户的ID,即,当用户A要向用户B发送消息时,显然,A必须知道B的ID。


1
@RoyiNamir同样,您没有太多选择。浏览器将终止连接,因此您必须处理断开连接和重新连接。另外,您可能希望实现某种超时机制(仅在超时后销毁服务器端的包装对象),以避免在这种情况发生时产生不必要的通知(假设您要通知其他用户有关断开连接的信息)。
奇特

1
@RoyiNamir至于两个唯一名称:另一个想法是将对象保留在SOCKETS = {};服务器端,并在列表中添加给定ID(即SOCKETS["John"] = [];和)的套接字SOCKETS["John"].push(conn);。您可能需要这样做,因为用户可以打开多个选项卡。
奇特

8
@freakish:小心,这仅是一个流程应用程序的好解决方案。如果使用多个进程(或服务器)扩展应用程序,它们将不会共享相同的内存-因此,使用本地存储的对象进行的迭代将无法进行。最好直接迭代所有本地连接,将UUID存储在连接本身上。另外,为了扩展,我建议使用Redis。
Myst

1
您能告诉我如何使用连接对象将消息发送到特定客户端吗?如上所述,我已将用户ID保存在连接对象中。我只想知道从服务器向特定客户端发送消息时如何使用连接对象(我具有该用户的ID和连接对象)。谢谢。
Vardan

1
@AsishAP,这是一个漫长的讨论...我建议您从阅读本文开始,以便定位,而不是在SO中寻找其他问题,这里值得一读
Myst

38

这是一个简单的聊天服务器专用/直接消息传递。

package.json

{
  "name": "chat-server",
  "version": "0.0.1",
  "description": "WebSocket chat server",
  "dependencies": {
    "ws": "0.4.x"
  }
}

server.js

var webSocketServer = new (require('ws')).Server({port: (process.env.PORT || 5000)}),
    webSockets = {} // userID: webSocket

// CONNECT /:userID
// wscat -c ws://localhost:5000/1
webSocketServer.on('connection', function (webSocket) {
  var userID = parseInt(webSocket.upgradeReq.url.substr(1), 10)
  webSockets[userID] = webSocket
  console.log('connected: ' + userID + ' in ' + Object.getOwnPropertyNames(webSockets))

  // Forward Message
  //
  // Receive               Example
  // [toUserID, text]      [2, "Hello, World!"]
  //
  // Send                  Example
  // [fromUserID, text]    [1, "Hello, World!"]
  webSocket.on('message', function(message) {
    console.log('received from ' + userID + ': ' + message)
    var messageArray = JSON.parse(message)
    var toUserWebSocket = webSockets[messageArray[0]]
    if (toUserWebSocket) {
      console.log('sent to ' + messageArray[0] + ': ' + JSON.stringify(messageArray))
      messageArray[0] = userID
      toUserWebSocket.send(JSON.stringify(messageArray))
    }
  })

  webSocket.on('close', function () {
    delete webSockets[userID]
    console.log('deleted: ' + userID)
  })
})

使用说明

要对其进行测试,请运行npm installinstall ws。然后,要启动聊天服务器,请在一个“终端”选项卡中运行node server.js(或npm start)。然后,在另一个“终端”选项卡中,运行wscat -c ws://localhost:5000/1,其中1连接用户的用户ID是。然后,在第三个“终端”选项卡中,运行wscat -c ws://localhost:5000/2,然后将用户发送消息21,输入["1", "Hello, World!"]

缺点

这个聊天服务器非常简单。

  • 坚持不懈

    它不将消息存储到数据库,例如PostgreSQL。因此,您要向其发送消息的用户必须连接到服务器才能接收它。否则,该消息将丢失。

  • 安全

    这是不安全的。

    • 如果我知道服务器的URL和Alice的用户ID,则可以模拟Alice,即以她的身份连接到服务器,从而允许我接收她的新传入消息,并将消息从她发送给我也知道其用户ID的任何用户。为了使其更加安全,请修改服务器以在连接时接受您的访问令牌(而不是您的用户ID)。然后,服务器可以从您的访问令牌中获取您的用户ID并进行身份验证。

    • 我不确定它是否支持WebSocket Secure(wss://)连接,因为我只是在它上进行了测试localhost,而且我不知道如何从localhost


有谁知道如何建立WebSocket安全本地主机连接
2013年

1
同样在这里。但是,如果将“ upgradeReq ...”部分替换为“ ws.upgradeReq.headers ['sec-websocket-key']”,则它可以工作。(通过github.com/websockets/ws/issues/310
dirkk0

1
在实践中启动WS的最佳示例!谢谢
NeverEndingQueue '18

很好的例子!过去几天,我一直在寻找这样的答案
DIRTY DAVE

5

适用于使用ws版本3或更高版本的用户。如果要使用@ ma11hew28提供的答案,只需按以下步骤更改此块。

webSocketServer.on('connection', function (webSocket) {
  var userID = parseInt(webSocket.upgradeReq.url.substr(1), 10)
webSocketServer.on('connection', function (webSocket, req) {
  var userID = parseInt(req.url.substr(1), 10)

ws 软件包已将upgradeReq移至请求对象,您可以检查以下链接以获取更多详细信息。

参考:https : //github.com/websockets/ws/issues/1114


1
您从很多麻烦中解救了我。
假设

1

我想分享我的工作。希望它不会浪费您的时间。

我创建了数据库表,其中包含字段ID,IP,用户名,登录时间和注销时间。当用户登录时,unixtimestamp unix将会是正确的。并且在websocket数据库中启动连接时,将检查最大的登录时间。这将是用户登录。

当用户注销时,它将存储当前的注销时间。用户将成为离开应用程序的人。

每当有新消息时,都会比较Websocket ID和IP,并显示相关的用户名。以下是示例代码...

// when a client connects
function wsOnOpen($clientID) {
      global $Server;
      $ip = long2ip( $Server->wsClients[$clientID][6] );

      require_once('config.php');
      require_once CLASSES . 'class.db.php';
      require_once CLASSES . 'class.log.php';

      $db = new database();
      $loga = new log($db);

      //Getting the last login person time and username
      $conditions = "WHERE which = 'login' ORDER BY id DESC LIMIT 0, 1";
      $logs = $loga->get_logs($conditions);
      foreach($logs as $rows) {

              $destination = $rows["user"];
              $idh = md5("$rows[user]".md5($rows["time"]));

              if ( $clientID > $rows["what"]) {
                      $conditions = "ip = '$ip', clientID = '$clientID'  

                      WHERE logintime = '$rows[time]'";
                      $loga->update_log($conditions);
             }
      }
      ...//rest of the things
} 

0

有趣的帖子(类似于我在做什么)。我们正在制作一个API(用C#编写)以将分配器与WebSockets连接,为每个分配器创建一个ConcurrentDictionary,用于存储WebSocket和DispenserId,从而使每个分配器可以轻松创建WebSocket并随后使用它而不会出现线程问题(调用特定功能)在WebSocket上,例如GetSettings或RequestTicket)。与您的示例不同的是,使用ConcurrentDictionary而不是数组来隔离每个元素(从未尝试在javascript中这样做)。最好的祝福,


这可以是评论,而不是答案。请阅读规则。
dpapadopoulos
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.