¿Enviar un mensaje a usuarios conectados específicos usando webSocket?

81

Escribí un código para transmitir un mensaje a todos los usuarios:

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

Note por favor:

  • No tengo ninguna referencia de usuario, solo una conexión.
  • Todas las conexiones de los usuarios se almacenan en un archivo array.

Objetivo : digamos que el servidor Node.js quiere enviar un mensaje a un cliente específico (John). ¿Cómo sabría el servidor de NodeJs qué conexión tiene John? El servidor de Node.js ni siquiera conoce a John. todo lo que ve son las conexiones.

Entonces, creo que ahora, no debería almacenar usuarios solo por su conexión, en cambio, necesito almacenar un objeto, que contendrá el userIdy el connectionobjeto.

Idea:

  • Cuando la página termine de cargarse (DOM listo), establezca una conexión con el servidor Node.js.

  • Cuando el servidor Node.js acepta una conexión, genera una cadena única y envíala al navegador del cliente. Almacene la conexión del usuario y la cadena única en un objeto. p.ej{UserID:"6", value: {connectionObject}}

  • En el lado del cliente, cuando llegue este mensaje, guárdelo en un campo oculto o en una cookie. (para futuras solicitudes al servidor NodeJs)


Cuando el servidor quiere enviar un mensaje a John:

  • Busque el ID de usuario de John en el diccionario y envíe un mensaje mediante la conexión correspondiente.

  • tenga en cuenta que no se incluye ningún código de servidor asp.net aquí (en el mecanismo de mensajes). solo NodeJs. *

Pregunta:

¿Es este el camino correcto a seguir?

Royi Namir
fuente

Respuestas:

75

Este no es solo el camino correcto a seguir, sino el único camino. Básicamente, cada conexión necesita una identificación única. De lo contrario, no podrá identificarlos, es tan simple como eso.

Ahora, cómo lo representará es algo diferente. Hacer un objeto con propiedades idy connectiones una buena manera de hacerlo (definitivamente lo haría). También puede adjuntar el iddirectamente al objeto de conexión.

También recuerde que si desea comunicación entre usuarios, también debe enviar la identificación del usuario objetivo, es decir, cuando el usuario A quiere enviar un mensaje al usuario B, entonces, obviamente, A tiene que saber la identificación de B.

monstruoso
fuente
1
@RoyiNamir Una vez más, no tienes muchas opciones. El navegador interrumpirá la conexión, por lo que debe manejar la desconexión y la reconexión. Además, probablemente desee implementar algún tipo de mecanismo de tiempo de espera (destruya el objeto contenedor en el lado del servidor solo después del tiempo de espera) para evitar notificaciones innecesarias cuando esto suceda (suponiendo que desee notificar a otros usuarios sobre la desconexión).
extraño
1
@RoyiNamir En cuanto a dos nombres únicos: la otra idea es mantener el objeto SOCKETS = {};en el lado del servidor y agregar un conector a una lista para un ID, es decir, SOCKETS["John"] = [];y SOCKETS["John"].push(conn);. Probablemente lo necesite, ya que un usuario puede abrir varias pestañas.
extraño
8
@freakish: Cuidado, esta es solo una buena solución para una aplicación de un solo proceso. Si escala su aplicación utilizando varios procesos (o servidores), no compartirán la misma memoria, por lo que la iteración con objetos almacenados localmente no funcionará. Es mejor iterar todas las conexiones locales directamente, almacenando el UUID en la propia conexión. Además, para escalar recomendaría Redis.
Myst
1
¿Puede decirme cómo enviar un mensaje a un cliente en particular usando el objeto de conexión? He guardado la identificación de usuario en el objeto de conexión como se indicó anteriormente. Solo quiero saber cómo usar el objeto de conexión mientras envío un mensaje desde el servidor a un cliente en particular (tengo la identificación y el objeto de conexión de ese usuario). Gracias.
Vardan
1
@AsishAP, esta es una discusión larga ... Le recomiendo que comience leyendo este artículo solo para orientarse y que tal vez busque SO para otras preguntas, aquí hay una que vale la pena leer .
Myst
38

Aquí hay un servidor de chat simple de mensajería privada / directa.

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

Instrucciones

Para probarlo, ejecute npm installpara instalar ws. Luego, para iniciar el servidor de chat, ejecute node server.js(o npm start) en una pestaña de Terminal. Luego, en otra pestaña de Terminal, ejecute wscat -c ws://localhost:5000/1, donde 1está el ID de usuario del usuario que se conecta. Luego, en una tercera pestaña de Terminal, ejecute wscat -c ws://localhost:5000/2y luego, para enviar un mensaje del usuario 2a 1, ingrese ["1", "Hello, World!"].

Defectos

Este servidor de chat es muy simple.

  • Persistencia

    No almacena mensajes en una base de datos, como PostgreSQL. Por lo tanto, el usuario al que está enviando un mensaje debe estar conectado al servidor para recibirlo. De lo contrario, el mensaje se pierde.

  • Seguridad

    Es inseguro.

    • Si conozco la URL del servidor y la identificación de usuario de Alice, entonces puedo suplantarme a Alice, es decir, conectarme al servidor como ella, lo que me permite recibir sus nuevos mensajes entrantes y enviar mensajes de ella a cualquier usuario cuya identificación de usuario también conozca. Para hacerlo más seguro, modifique el servidor para que acepte su token de acceso (en lugar de su ID de usuario) al conectarse. Luego, el servidor puede obtener su ID de usuario de su token de acceso y autenticarlo.

    • No estoy seguro de si es compatible con una wss://conexión WebSocket Secure ( ), ya que solo la he probado localhosty no estoy seguro de cómo conectarme de forma segura desde localhost.

ma11hew28
fuente
¿Alguien sabe cómo hacer una conexión localhost segura de WebSocket ?
ma11hew28
1
Igual que aquí. Pero si reemplaza la parte 'upgradeReq ...' con 'ws.upgradeReq.headers [' sec-websocket-key ']', funciona. (a través de github.com/websockets/ws/issues/310 )
dirkk0
1
¡El mejor ejemplo para poner en marcha la WS en la práctica! Gracias
NeverEndingQueue
¡Gran ejemplo! He estado buscando una respuesta como esta durante los últimos días
DAVE SUCIO
5

Para personas que usan la wsversión 3 o superior. Si desea utilizar la respuesta proporcionada por @ ma11hew28, simplemente cambie este bloque de la siguiente manera.

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 El paquete ha movido upgradeReq al objeto de solicitud y puede consultar el siguiente enlace para obtener más detalles.

Referencia: https://github.com/websockets/ws/issues/1114

Dylandy
fuente
1
me salvaste de un montón de problemas.
Hipótesis
1

Me gustaría compartir lo que he hecho. Espero que no pierda su tiempo.

Creé una tabla de base de datos con ID de campo, IP, nombre de usuario, hora de inicio de sesión y hora de salida. Cuando un usuario inicia sesión, el tiempo de inicio de sesión será unixtimestamp unix. Y cuando se inicia la conexión en la base de datos de websocket, comprueba el mayor tiempo de inicio de sesión. Será el usuario que haya iniciado sesión.

Y para cuando el usuario cierre la sesión, se almacenará el tiempo de cierre de sesión actual. El usuario se convertirá en quien dejó la aplicación.

Siempre que hay un mensaje nuevo, se comparan la ID y la IP de Websocket y se muestra el nombre de usuario relacionado. A continuación se muestra un código de muestra ...

// 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
} 
tashi
fuente
0

publicación interesante (similar a la que estoy haciendo). Estamos haciendo una API (en C #) para conectar dispensadores con WebSockets, para cada dispensador creamos un ConcurrentDictionary que almacena el WebSocket y el DispenserId facilitando que cada Dispensador cree un WebSocket y lo use luego sin problemas de subprocesos (invocando funciones específicas en WebSocket como GetSettings o RequestTicket). La diferencia para su ejemplo es el uso de ConcurrentDictionary en lugar de una matriz para aislar cada elemento (nunca intentó hacerlo en javascript). Atentamente,

lobo354
fuente
Esto puede ser un comentario y no una respuesta. Por favor, lea las reglas.
dpapadopoulos