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 서버는 John이 어떤 연결을 가지고 있는지 어떻게 알 수 있습니까? Node.js 서버는 John조차 모릅니다. 보이는 것은 연결뿐입니다.

따라서 이제는 연결만으로 사용자를 저장해서는 안되며, 대신 userIdconnection개체를 포함 할 개체를 저장해야 합니다.

생각:

  • 페이지로드가 완료되면 (DOM 준비)-Node.js 서버에 대한 연결을 설정합니다.

  • Node.js 서버가 연결을 수락하면 고유 한 문자열을 생성하여 클라이언트 브라우저로 보냅니다. 사용자 연결 및 고유 문자열을 개체에 저장합니다. 예 :{UserID:"6", value: {connectionObject}}

  • 클라이언트 측에서이 메시지가 도착하면 숨겨진 필드 또는 쿠키에 저장하십시오. (향후 NodeJs 서버에 대한 요청)


서버가 John에게 메시지를 보내려고 할 때 :

  • 사전에서 john의 UserID를 찾아 해당 연결로 메시지를 보냅니다.

  • 여기 (메시지 메커니즘에)에 등록 된 asp.net 서버 코드가 없습니다. NodeJ 만. *

질문:

이것이 올바른 방법입니까?

답변:


77

이것은 올바른 방법 일뿐만 아니라 유일한 방법입니다. 기본적으로 각 연결에는 고유 한 ID가 필요합니다. 그렇지 않으면 그것들을 식별 할 수 없을 것입니다. 그것은 그렇게 간단합니다.

이제 그것을 어떻게 표현할지는 다른 것입니다. idconnection속성을 사용 하여 개체를 만드는 것은 이를 수행하는 좋은 방법입니다 (확실히 갈 것입니다). id연결 개체에 직접 연결할 수도 있습니다 .

또한 사용자 간의 통신을 원할 경우 대상 사용자의 ID도 전송해야합니다. 즉, 사용자 A가 사용자 B에게 메시지를 보내고 싶을 때 A는 B의 ID를 알아야합니다.


1
@RoyiNamir 다시 말하지만, 선택의 여지가별로 없습니다. 브라우저가 연결을 종료하므로 연결 해제 및 재 연결을 처리해야합니다. 또한 이런 일이 발생할 때 불필요한 알림을 피하기 위해 (다른 사용자에게 연결 해제에 대해 알리고 싶다고 가정 할 때) 일종의 타임 아웃 메커니즘 (타임 아웃 후에 만 ​​서버 측에서 래퍼 객체를 파괴)을 구현하고 싶을 것입니다.
freakish

1
@RoyiNamir 두 개의 고유 한 이름에 대해 : 다른 아이디어는 SOCKETS = {};서버 측에 객체를 유지하고 주어진 ID, 즉 SOCKETS["John"] = [];SOCKETS["John"].push(conn);. 사용자가 여러 탭을 열 수 있으므로 필요할 것입니다.
freakish

8
@freakish : 조심하세요. 이것은 하나의 프로세스 앱을위한 좋은 솔루션입니다. 여러 프로세스 (또는 서버)를 사용하여 애플리케이션을 확장하면 동일한 메모리를 공유하지 않으므로 로컬에 저장된 객체를 사용하는 반복이 작동하지 않습니다. 모든 로컬 연결을 직접 반복하여 연결 자체에 UUID를 저장하는 것이 좋습니다. 또한 확장을 위해 Redis를 권장합니다.
Myst

1
연결 개체를 사용하여 특정 클라이언트에 메시지를 보내는 방법을 알려주시겠습니까? 위에서 설명한대로 연결 개체에 사용자 ID를 저장했습니다. 서버에서 특정 클라이언트로 메시지를 보내는 동안 연결 개체를 사용하는 방법을 알고 싶습니다 (해당 사용자의 ID와 연결 개체가 있습니다). 감사.
Vardan

1
@AsishAP, 이것은 긴 토론입니다 ... 이 기사 를 읽는 것으로 시작하여 다른 질문을 찾고 다른 질문을 검색 하는 것이 좋습니다 . 여기에 읽을만한 가치가 있습니다.
Myst

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. 그런 다음 채팅 서버를 시작하려면 하나의 터미널 탭에서 실행 node server.js(또는 npm start)합니다. 그런 다음 다른 터미널 탭에서을 실행합니다 wscat -c ws://localhost:5000/1. 여기서는 1연결하는 사용자의 사용자 ID입니다. 그런 다음 세 번째 터미널 탭에서을 실행 wscat -c ws://localhost:5000/2한 다음 사용자로부터에 메시지를 보내 2려면 1을 입력 ["1", "Hello, World!"]합니다.

단점

이 채팅 서버는 매우 간단합니다.

  • 고집

    PostgreSQL과 같은 데이터베이스에 메시지를 저장하지 않습니다. 따라서 메시지를 보내는 사용자는 메시지를 수신하기 위해 서버에 연결되어 있어야합니다. 그렇지 않으면 메시지가 손실됩니다.

  • 보안

    안전하지 않습니다.

    • 서버의 URL과 Alice의 사용자 ID를 알고 있으면 Alice를 가장 할 수 있습니다. 즉, Alice로 서버에 연결하여 그녀의 새 수신 메시지를 수신하고 그녀의 메시지를 내가 아는 사용자 ID를 가진 모든 사용자에게 보낼 수 있습니다. 보안을 강화하려면 연결할 때 사용자 ID 대신 액세스 토큰을 허용하도록 서버를 수정하십시오. 그러면 서버는 액세스 토큰에서 사용자 ID를 가져와 인증 할 수 있습니다.

    • WebSocket Secure ( wss://) 연결을 지원하는지 확실하지 않습니다. 에서만 테스트했기 때문에에서 localhost안전하게 연결하는 방법도 모르겠습니다 localhost.


누구든지 WebSocket Secure localhost 연결 을 만드는 방법을 알고 있습니까?
ma11hew28

1
여기도 마찬가지입니다. 그러나 'upgradeReq ...'부분을 'ws.upgradeReq.headers ['sec-websocket-key ']'로 바꾸면 작동합니다. ( github.com/websockets/ws/issues/310을 통해 )
dirkk0

1
실제로 WS를 시작하는 가장 좋은 예! 감사합니다
NeverEndingQueue 2018

1
좋은 예! 나는 지난 몇 일 동안이 같은 대답을 찾고 있었어요
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

흥미로운 게시물 (내가하는 일과 유사). 디스펜서를 WebSocket과 연결하는 API (C #)를 만들고 있습니다. 각 디스펜서에 대해 WebSocket과 DispenserId를 저장하는 ConcurrentDictionary를 생성하여 각 Dispenser가 WebSocket을 쉽게 생성하고 나중에 스레드 문제없이 사용할 수 있도록합니다 (특정 함수 호출). GetSettings 또는 RequestTicket과 같은 WebSocket에서). 예제의 차이점은 배열 대신 ConcurrentDictionary를 사용하여 각 요소를 분리하는 것입니다 (자바 스크립트에서는 이러한 작업을 시도하지 않음). 친애하는,


이것은 답변이 아닌 주석 일 수 있습니다. 제발, 규칙을 읽으십시오.
dpapadopoulos 19.06.12
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.