webSocketを使用して特定の接続ユーザーにメッセージを送信しますか?


82

すべてのユーザーにメッセージをブロードキャストするためのコードを作成しました。

// 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サーバーが特定のクライアント(John)にメッセージを送信したいとします。NodeJsサーバーは、ジョンがどの接続を持っているかをどのように知るのでしょうか?Node.jsサーバーはジョンさえ知りません。表示されるのは接続だけです。

したがって、今では、ユーザーを接続だけで保存するのではなく、オブジェクトを保存する必要があると思います。オブジェクトには、userIdconnectionオブジェクトが含まれます。

考え:

  • ページの読み込みが完了すると(DOM対応)-Node.jsサーバーへの接続を確立します。

  • Node.jsサーバーが接続を受け入れると、一意の文字列を生成してクライアントブラウザに送信します。ユーザー接続と一意の文字列をオブジェクトに保存します。例えば{UserID:"6", value: {connectionObject}}

  • クライアント側では、このメッセージが到着したら、非表示のフィールドまたはCookieに保存します。(NodeJsサーバーへの今後のリクエスト用)


サーバーがJohnにメッセージを送信する場合:

  • 辞書でジョンのユーザーIDを見つけ、対応する接続​​でメッセージを送信します。

  • ここ(メッセージメカニズム)に含まれているasp.netサーバーコードがないことに注意してください。NodeJのみ。*

質問:

これは正しい方法ですか?

回答:


77

これは正しい道であるだけでなく、唯一の道でもあります。基本的に、各接続には一意のIDが必要です。そうでなければ、それらを識別することができません、それはそれと同じくらい簡単です。

今、あなたがそれをどのように表現するかは別のことです。持つオブジェクトを作成idし、connectionプロパティは、それを行うための良い方法(私は間違いなくそれのために行くだろう)です。id接続オブジェクトに直接アタッチすることもできます。

また、ユーザー間の通信が必要な場合は、ターゲットユーザーのIDも送信する必要があります。つまり、ユーザーAがユーザーBにメッセージを送信する場合は、明らかにAがBのIDを知っている必要があります。


1
@RoyiNamir繰り返しますが、選択肢はあまりありません。ブラウザは接続を切断するため、切断と再接続を処理する必要があります。また、何らかのタイムアウトメカニズムを実装して(タイムアウト後にのみサーバー側のラッパーオブジェクトを破棄する)、これが発生したときに不要な通知を回避することもできます(他のユーザーに切断について通知する場合)。
気まぐれな2013年

1
@RoyiNamir 2つの一意の名前について:もう1つのアイデアは、オブジェクトSOCKETS = {};をサーバー側に保持し、指定されたID、つまりSOCKETS["John"] = [];とのリストにソケットを追加することSOCKETS["John"].push(conn);です。ユーザーは複数のタブを開くことができるため、おそらくそれが必要になります。
気まぐれな2013年

8
@freakish:注意してください、これは1つのプロセスアプリにのみ適したソリューションです。複数のプロセス(またはサーバー)を使用してアプリケーションをスケーリングする場合、それらは同じメモリを共有しないため、ローカルに保存されたオブジェクトを使用した反復は機能しません。すべてのローカル接続を直接繰り返し、接続自体にUUIDを保存することをお勧めします。また、スケーリングにはRedisをお勧めします。
Myst 2015

1
接続オブジェクトを使用して特定のクライアントにメッセージを送信する方法を教えてください。上記のように、接続オブジェクトにユーザーIDを保存しました。サーバーから特定のクライアントにメッセージを送信するときに接続オブジェクトを使用する方法を知りたいだけです(私はそのユーザーのIDと接続オブジェクトを持っています)。ありがとう。
バルダン2016

1
@AsishAP、これは長い議論です...方向を理解するためだけにこの記事を読むことから始めて、他の質問をSOで検索するよりも、読む価値のあるものをここに置くことをお勧めします。
Myst 2017年

39

これが簡単なチャットサーバーのプライベート/ダイレクトメッセージングです。

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 installしてインストールしwsます。次に、チャットサーバーを起動するには、1つの[ターミナル]タブでnode server.js(またはnpm start)を実行します。次に、別の[ターミナル]タブで、を実行しますwscat -c ws://localhost:5000/1。ここ1で、は接続ユーザーのユーザーIDです。次に、3番目の[ターミナル]タブで、を実行してwscat -c ws://localhost:5000/2から、ユーザー2から1にメッセージを送信するには、と入力し["1", "Hello, World!"]ます。

欠点

このチャットサーバーは非常にシンプルです。

  • 永続性

    PostgreSQLなどのデータベースにはメッセージを保存しません。したがって、メッセージを送信するユーザーは、メッセージを受信するためにサーバーに接続している必要があります。それ以外の場合、メッセージは失われます。

  • セキュリティ

    安全ではありません。

    • サーバーのURLとアリスのユーザーIDがわかっている場合は、アリスになりすますことができます。つまり、サーバーに彼女として接続して、新しい受信メッセージを受信し、ユーザーIDも知っている任意のユーザーにメッセージを送信できます。安全性を高めるには、接続時に(ユーザーIDではなく)アクセストークンを受け入れるようにサーバーを変更します。次に、サーバーはアクセストークンからユーザーIDを取得し、ユーザーを認証できます。

    • wss://でテストしただけなので、WebSocket Secure()接続をサポートしてlocalhostいるかどうかはわかりませんlocalhost。また、から安全に接続する方法もわかりません。


WebSocketセキュアローカルホスト接続を確立する方法を知っている人はいますか?
ma11hew28 2013年

1
こっちも一緒。ただし、「upgradeReq ...」の部分を「ws.upgradeReq.headers ['sec-websocket-key']」に置き換えると機能します。(github.com/websockets/ws/issues/310経由)
dirkk0 2015

1
WSを実際にキックスタートするための最良の例!ありがとう
NeverEndingQueue 2018

1
素晴らしい例です!私は過去数日間、このような答えを探していました
DIRTYDAVE19年

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、ユーザー名、logintime、logouttimeを保持するデータベーステーブルを作成しました。ユーザーがログインすると、logintimeはcurrect unixtimestampunixになります。また、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

興味深い投稿(私がしていることに似ています)。ディスペンサーをWebSocketに接続するためのAPI(C#)を作成しています。ディスペンサーごとに、WebSocketとDispenserIdを格納するConcurrentDictionaryを作成して、各ディスペンサーがWebSocketを簡単に作成し、スレッドの問題なしに後で使用できるようにします(特定の関数を呼び出します)。 GetSettingsやRequestTicketなどのWebSocketで)。例の違いは、配列の代わりにConcurrentDictionaryを使用して各要素を分離することです(javascriptでそのようなことを試みたことはありません)。宜しくお願いします、


これはコメントであり、回答ではありません。どうぞ、ルールをお読みください。
dpapadopoulos
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.