Gửi tin nhắn cho một người dùng được kết nối cụ thể bằng cách sử dụng webSocket?


81

Tôi đã viết một mã để phát một thông báo cho tất cả người dùng:

// 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;
...

Xin hãy chú ý:

  • Tôi không có bất kỳ tham chiếu người dùng nào mà chỉ có một kết nối.
  • Tất cả kết nối người dùng được lưu trữ trong một array.

Mục tiêu : Giả sử máy chủ Node.js muốn gửi một thông báo đến một máy khách cụ thể (John). Làm sao máy chủ NodeJs biết John có kết nối nào? Máy chủ Node.js thậm chí không biết John. tất cả những gì nó thấy là các kết nối.

Vì vậy, tôi tin rằng bây giờ, tôi không nên lưu trữ người dùng chỉ bằng kết nối của họ, thay vào đó, tôi cần lưu trữ một đối tượng, nó sẽ chứa userIdconnectionđối tượng.

Ý tưởng:

  • Khi trang hoàn tất tải (DOM sẵn sàng) - thiết lập kết nối với máy chủ Node.js.

  • Khi máy chủ Node.js chấp nhận một kết nối - tạo một chuỗi duy nhất và gửi nó đến trình duyệt máy khách. Lưu trữ kết nối người dùng và chuỗi duy nhất trong một đối tượng. ví dụ{UserID:"6", value: {connectionObject}}

  • Ở phía khách hàng, khi tin nhắn này đến - hãy lưu trữ nó trong một trường ẩn hoặc cookie. (cho các yêu cầu trong tương lai tới máy chủ NodeJs)


Khi máy chủ muốn gửi tin nhắn cho John:

  • Tìm UserID của john trong từ điển và gửi tin nhắn bằng kết nối tương ứng.

  • xin lưu ý rằng không có mã máy chủ asp.net nào được đưa vào đây (trong cơ chế tin nhắn). chỉ NodeJs. *

Câu hỏi:

Đây có phải là cách thích hợp để đi không?

Câu trả lời:


75

Đây không chỉ là con đường đúng đắn mà còn là con đường duy nhất. Về cơ bản mỗi kết nối cần một ID duy nhất. Nếu không bạn sẽ không thể xác định được chúng, đơn giản như vậy thôi.

Bây giờ bạn sẽ thể hiện nó như thế nào, đó là một điều khác. Tạo một đối tượng với idconnectionthuộc tính là một cách tốt để làm điều đó (tôi chắc chắn sẽ sử dụng nó). Bạn cũng có thể đính kèm idtrực tiếp đối tượng kết nối.

Cũng nên nhớ rằng nếu bạn muốn giao tiếp giữa những người dùng, thì bạn cũng phải gửi ID của người dùng đích, tức là khi người dùng A muốn gửi tin nhắn cho người dùng B, thì rõ ràng A phải biết ID của B.


1
@RoyiNamir Một lần nữa, bạn không có nhiều lựa chọn. Trình duyệt sẽ ngắt kết nối, vì vậy bạn phải xử lý việc ngắt kết nối và kết nối lại. Ngoài ra, bạn có thể sẽ muốn thực hiện một số loại cơ chế thời gian chờ (chỉ phá hủy đối tượng wrapper ở phía máy chủ sau khi hết thời gian chờ) để tránh các thông báo không cần thiết khi điều này xảy ra (giả sử bạn muốn thông báo cho người dùng khác về việc ngắt kết nối).
quái đản

1
@RoyiNamir Đối với hai tên duy nhất: ý tưởng khác là giữ đối tượng SOCKETS = {};ở phía máy chủ và thêm một ổ cắm vào danh sách cho một ID cung cấp, tức là SOCKETS["John"] = [];SOCKETS["John"].push(conn);. Bạn có thể sẽ cần điều đó vì một người dùng có thể mở nhiều tab.
quái đản

8
@freakish: Cẩn thận, đây chỉ là giải pháp tốt cho một ứng dụng một quy trình. Nếu bạn mở rộng ứng dụng của mình bằng cách sử dụng nhiều quy trình (hoặc máy chủ), chúng sẽ không chia sẻ cùng một bộ nhớ - do đó, việc lặp lại sử dụng các đối tượng được lưu trữ cục bộ sẽ không hoạt động. Tốt hơn là lặp lại trực tiếp tất cả các kết nối cục bộ, lưu trữ UUID trên chính kết nối đó. Ngoài ra, để mở rộng quy mô, tôi muốn giới thiệu Redis.
Myst

1
Bạn có thể vui lòng cho tôi biết cách gửi tin nhắn đến một khách hàng cụ thể bằng đối tượng kết nối không? Tôi đã lưu id người dùng trong đối tượng kết nối như đã nêu ở trên. Tôi chỉ muốn biết cách sử dụng đối tượng kết nối trong khi gửi tin nhắn từ máy chủ đến máy khách cụ thể (tôi có id và đối tượng kết nối của người dùng đó). Cảm ơn.
Vardan

1
@AsishAP, đây là một cuộc thảo luận dài ... Tôi khuyên bạn nên bắt đầu đọc bài viết này chỉ để có định hướng và hơn là có thể tìm kiếm SO cho các câu hỏi khác, đây là một bài đáng đọc .
Myst

38

Đây là một máy chủ trò chuyện đơn giản nhắn tin riêng tư / trực tiếp.

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

Hướng dẫn

Để kiểm tra nó ra, hãy chạy npm installđể cài đặt ws. Sau đó, để khởi động máy chủ trò chuyện, hãy chạy node server.js(hoặc npm start) trong một tab Đầu cuối. Sau đó, trong tab Terminal khác, hãy chạy wscat -c ws://localhost:5000/1, 1ID người dùng đang kết nối ở đâu . Sau đó, trong tab Terminal thứ ba, hãy chạy wscat -c ws://localhost:5000/2, rồi để gửi tin nhắn từ người dùng 2tới 1, hãy enter ["1", "Hello, World!"].

Thiếu sót

Máy chủ trò chuyện này rất đơn giản.

  • Sự bền bỉ

    Nó không lưu trữ tin nhắn vào cơ sở dữ liệu, chẳng hạn như PostgreSQL. Vì vậy, người dùng mà bạn đang gửi tin nhắn phải được kết nối với máy chủ để nhận nó. Nếu không, tin nhắn sẽ bị mất.

  • Bảo vệ

    Nó không an toàn.

    • Nếu tôi biết URL của máy chủ và ID người dùng của Alice, thì tôi có thể mạo danh Alice, tức là kết nối với máy chủ với tư cách là cô ấy, cho phép tôi nhận các tin nhắn mới đến của cô ấy và gửi tin nhắn từ cô ấy tới bất kỳ người dùng nào có ID người dùng mà tôi cũng biết. Để đảm bảo an toàn hơn, hãy sửa đổi máy chủ để chấp nhận mã thông báo truy cập của bạn (thay vì ID người dùng của bạn) khi kết nối. Sau đó, máy chủ có thể lấy ID người dùng của bạn từ mã thông báo truy cập của bạn và xác thực bạn.

    • Tôi không chắc liệu nó có hỗ trợ wss://kết nối WebSocket Secure ( ) hay không vì tôi chỉ mới thử nghiệm nó localhostvà tôi không chắc cách kết nối an toàn từ đó localhost.


Có ai biết cách tạo kết nối WebSocket Secure localhost không?
ma11hew28

1
Ở đây cũng vậy. Nhưng nếu bạn thay thế phần 'liftReq ...' bằng 'ws.upgradeReq.headers [' sec-websocket-key ']' thì nó hoạt động. (qua github.com/websockets/ws/issues/310 )
dirkk0

1
Ví dụ tốt nhất để bắt đầu WS trong thực tế! Cảm ơn
NeverEndingQueue

1
Ví dụ tuyệt vời! Tôi đã tìm kiếm câu trả lời như thế này trong vài ngày qua
DIRTY DAVE

5

Đối với những người sử dụng wsphiên bản 3 trở lên. Nếu bạn muốn sử dụng câu trả lời do @ ma11hew28 cung cấp, chỉ cần thay đổi khối này như sau.

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 gói đã chuyển nâng cấpReq sang đối tượng yêu cầu và bạn có thể kiểm tra liên kết sau để biết thêm chi tiết.

Tham khảo: https://github.com/websockets/ws/issues/1114


1
bạn đã cứu tôi khỏi rất nhiều rắc rối.
Giả thuyết

1

Tôi muốn chia sẻ những gì tôi đã làm được. Hy vọng nó không lãng phí thời gian của bạn.

Tôi đã tạo bảng cơ sở dữ liệu chứa trường ID, IP, tên người dùng, thời gian đăng nhập và thời gian đăng xuất. Khi người dùng đăng nhập logintime sẽ bị cong unix dấu unix. Và khi kết nối được bắt đầu trong cơ sở dữ liệu websocket sẽ kiểm tra thời gian đăng nhập lớn nhất. Nó sẽ được người dùng đăng nhập.

Và khi người dùng đăng xuất, nó sẽ lưu trữ thời gian đăng xuất không chính xác. Người dùng sẽ trở thành người rời khỏi ứng dụng.

Bất cứ khi nào có tin nhắn mới, ID và IP của Websocket sẽ được so sánh và tên người dùng liên quan sẽ được hiển thị. Sau đây là mã mẫu ...

// 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

bài đăng thú vị (tương tự như những gì tôi đang làm). Chúng tôi đang tạo một API (trong C #) để kết nối bộ phân phối với WebSockets, đối với mỗi bộ phân phối, chúng tôi tạo một ConcurrentDictionary lưu trữ WebSocket và DispenserId giúp mỗi Bộ phân phối dễ dàng tạo một WebSocket và sử dụng nó sau đó mà không gặp vấn đề về chuỗi (gọi các chức năng cụ thể trên WebSocket như GetSettings hoặc RequestTicket). Sự khác biệt đối với bạn ví dụ là việc sử dụng ConcurrentDictionary thay vì một mảng để tách biệt từng phần tử (chưa bao giờ cố gắng làm như vậy trong javascript). Trân trọng,


Đây có thể là một bình luận và không phải là một câu trả lời. Vui lòng, đọc các quy tắc.
dpapadopoulos
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.