Wysyłasz wiadomość do określonych podłączonych użytkowników za pomocą webSocket?


82

Napisałem kod do rozgłaszania wiadomości do wszystkich użytkowników:

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

Proszę zauważyć:

  • Nie mam żadnych odniesień użytkownika, ale tylko połączenie.
  • Wszystkie połączenia użytkowników są przechowywane w pliku array.

Cel : Powiedzmy, że serwer Node.js chce wysłać wiadomość do określonego klienta (Jana). Skąd serwer NodeJs wiedział, jakie połączenie ma Jan? Serwer Node.js nawet nie zna Johna. wszystko, co widzi, to połączenia.

Tak, wierzę, że teraz, nie należy przechowywać tylko przez użytkowników, ich połączenia, zamiast, muszę zapisać obiekt, który będzie zawierał userIdi ten connectionobiekt.

Pomysł:

  • Po zakończeniu ładowania strony (DOM ready) - nawiąż połączenie z serwerem Node.js.

  • Gdy serwer Node.js zaakceptuje połączenie - wygeneruj unikalny ciąg i wyślij go do przeglądarki klienta. Przechowuj połączenie użytkownika i unikatowy ciąg w obiekcie. na przykład{UserID:"6", value: {connectionObject}}

  • Po stronie klienta, kiedy ta wiadomość dotrze - przechowuj ją w ukrytym polu lub pliku cookie. (dla przyszłych żądań do serwera NodeJs)


Gdy serwer chce wysłać wiadomość do Jana:

  • Znajdź identyfikator użytkownika jana w słowniku i wyślij wiadomość przez odpowiednie połączenie.

  • proszę zauważyć, że nie ma tu wywoływanego kodu serwera asp.net (w mechanizmie wiadomości). tylko NodeJs. *

Pytanie:

Czy to właściwa droga?

Odpowiedzi:


77

To nie tylko właściwa droga, ale jedyna. Zasadniczo każde połączenie wymaga unikalnego identyfikatora. W przeciwnym razie nie będziesz w stanie ich zidentyfikować, to takie proste.

Teraz, jak to przedstawisz, to inna sprawa. Tworzenie obiektu z właściwościami idi connectionjest dobrym sposobem na zrobienie tego (zdecydowanie bym się na to wybrał). Możesz również dołączyć idbezpośrednio do obiektu połączenia.

Pamiętaj również, że jeśli chcesz komunikować się między użytkownikami, musisz również wysłać identyfikator użytkownika docelowego, tj. Gdy użytkownik A chce wysłać wiadomość do użytkownika B, to oczywiście A musi znać identyfikator B.


1
@RoyiNamir Ponownie, nie masz wielkiego wyboru. Przeglądarka zlikwiduje połączenie, więc musisz poradzić sobie z rozłączeniem i ponownym połączeniem. Również prawdopodobnie będziesz chciał zaimplementować jakiś mechanizm limitu czasu (zniszcz obiekt opakowujący po stronie serwera dopiero po przekroczeniu limitu czasu), aby uniknąć niepotrzebnych powiadomień, kiedy to się stanie (zakładając, że chcesz powiadomić innych użytkowników o rozłączeniu).
dziwaczny

1
@RoyiNamir Co do dwóch unikalnych nazwach: inny pomysł jest utrzymanie obiektu SOCKETS = {};po stronie serwera i dodać do listy gniazdo dla ID dawać, czyli SOCKETS["John"] = [];i SOCKETS["John"].push(conn);. Prawdopodobnie będziesz tego potrzebować, ponieważ użytkownik może otworzyć wiele kart.
dziwaczny

8
@freakish: Ostrożnie, to dobre rozwiązanie tylko dla aplikacji z jednym procesem. Jeśli skalujesz aplikację przy użyciu wielu procesów (lub serwerów), nie będą one współużytkować tej samej pamięci - więc iteracja przy użyciu lokalnie przechowywanych obiektów nie będzie działać. Lepiej jest bezpośrednio iterować wszystkie połączenia lokalne, przechowując identyfikator UUID w samym połączeniu. Również do skalowania polecam Redis.
Myst

1
Czy możesz mi powiedzieć, jak wysłać wiadomość do konkretnego klienta za pomocą obiektu połączenia? Zapisałem identyfikator użytkownika w obiekcie połączenia, jak podano powyżej. Chcę tylko wiedzieć, jak używać obiektu połączenia podczas wysyłania wiadomości z serwera do konkretnego klienta (mam identyfikator i obiekt połączenia tego użytkownika). Dzięki.
Vardan,

1
@AsishAP, to jest długa dyskusja ... Zalecam, abyś zaczął od przeczytania tego artykułu tylko po to, aby się zorientować, a może poszukać SO w innych pytaniach, oto jedno, które warto przeczytać .
Myst

39

Oto proste prywatne / bezpośrednie wiadomości na serwerze czatu.

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

Instrukcje

Aby to przetestować, uruchom, npm installaby zainstalować ws. Następnie, aby uruchomić serwer czatu, uruchom node server.js(lub npm start) w jednej zakładce Terminal. Następnie na innej karcie Terminala uruchom wscat -c ws://localhost:5000/1, gdzie 1jest identyfikator użytkownika łączącego się. Następnie, w trzeciej zakładce Terminal uruchomić wscat -c ws://localhost:5000/2, a następnie, aby wysłać wiadomość od użytkownika 2, aby 1wprowadzić ["1", "Hello, World!"].

Niedociągnięcia

Ten serwer czatu jest bardzo prosty.

  • Trwałość

    Nie przechowuje wiadomości w bazie danych, takiej jak PostgreSQL. Dlatego użytkownik, do którego wysyłasz wiadomość, musi być połączony z serwerem, aby ją odebrać. W przeciwnym razie wiadomość zostanie utracona.

  • Bezpieczeństwo

    To jest niepewne.

    • Jeśli znam adres URL serwera i identyfikator użytkownika Alicji, mogę podszyć się pod Alicję, tj. Połączyć się z serwerem jako ona, co pozwala mi na otrzymywanie jej nowych wiadomości przychodzących i wysyłanie wiadomości od niej do dowolnego użytkownika, którego identyfikator użytkownika również znam. Aby było bezpieczniej, zmodyfikuj serwer, aby akceptował token dostępu (zamiast identyfikatora użytkownika) podczas łączenia. Następnie serwer może uzyskać identyfikator użytkownika z tokena dostępu i uwierzytelnić Cię.

    • Nie jestem pewien, czy obsługuje połączenie WebSocket Secure ( wss://), ponieważ przetestowałem go tylko na localhosti nie jestem pewien, jak bezpiecznie się połączyć localhost.


Czy ktoś wie, jak nawiązać połączenie z hostem lokalnym WebSocket Secure ?
ma11hew28

1
To samo tutaj. Ale jeśli zamienisz część „upgradeReq ...” na „ws.upgradeReq.headers [„ sec-websocket-key ”]”, to zadziała. (via github.com/websockets/ws/issues/310 )
dirkk0

1
Najlepszy przykład na rozpoczęcie WS w praktyce! Dzięki
NeverEndingQueue

1
Świetny przykład! Szukałem takiej odpowiedzi przez ostatnie kilka dni
DIRTY DAVE

5

Dla osób korzystających z wswersji 3 lub nowszej. Jeśli chcesz skorzystać z odpowiedzi udzielonej przez @ ma11hew28, po prostu zmień ten blok na następujący.

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 pakiet przeniósł upgradeReq, aby zażądać obiektu i możesz sprawdzić poniższe łącze, aby uzyskać dalsze szczegóły.

Źródła: https://github.com/websockets/ws/issues/1114


1
uratowałeś mnie od wielu kłopotów.
Hipoteza

1

Chciałbym podzielić się tym, co zrobiłem. Mam nadzieję, że nie marnuje czasu.

Utworzyłem tabelę bazy danych zawierającą identyfikator pola, adres IP, nazwę użytkownika, czas logowania i czas wylogowania. Kiedy użytkownik loguje się na konto, czas logowania będzie aktualny unixtimestamp unix. A gdy połączenie zostanie nawiązane, baza danych Websocket sprawdza najdłuższy czas logowania. Zostanie zalogowany użytkownik.

A gdy użytkownik się wyloguje, zapisze aktualny czas wylogowania. Użytkownik stanie się tym, kto opuścił aplikację.

Za każdym razem, gdy pojawia się nowa wiadomość, identyfikator Websocket i adres IP są porównywane i wyświetlana jest powiązana nazwa użytkownika. Poniżej znajduje się przykładowy kod ...

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

ciekawy post (podobny do tego, co robię). Tworzymy API (w języku C #) do łączenia dozowników z WebSockets, dla każdego dystrybutora tworzymy ConcurrentDictionary przechowujący WebSocket i DispenserId, ułatwiając każdemu Dispenserowi utworzenie WebSocket i późniejsze używanie go bez problemów z wątkami (wywoływanie określonych funkcji w WebSocket, takich jak GetSettings lub RequestTicket). Różnica w twoim przykładzie polega na użyciu ConcurrentDictionary zamiast tablicy do izolowania każdego elementu (nigdy nie próbowałem tego robić w javascript). Z poważaniem,


To może być komentarz, a nie odpowiedź. Proszę, przeczytaj regulamin.
dpapadopoulos
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.