Ejemplos de Node.js Websocket con Socket.io

En los últimos años, comenzó a surgir un nuevo tipo de comunicación en la web y en las aplicaciones móviles, llamados websockets. Este protocolo ha sido largamente esperado y...

¿Qué son los WebSockets?

En los últimos años, comenzó a surgir un nuevo tipo de comunicación en la web y en las aplicaciones móviles, llamado enchufes web. Este protocolo ha sido largamente esperado y finalmente fue estandarizado por el IETF en 2011, allanando el camino para su uso generalizado.

Este nuevo protocolo abre una línea de comunicación mucho más rápida y eficiente con el cliente. Al igual que HTTP, los websockets se ejecutan sobre una conexión TCP, pero son mucho más rápidos porque no tenemos que abrir una nueva conexión cada vez que queremos enviar un mensaje, ya que la conexión se mantiene viva mientras el servidor o cliente quiere.

Aún mejor, dado que la conexión nunca muere, finalmente tenemos comunicación full-duplex disponible para nosotros, lo que significa que podemos enviar datos al cliente en lugar de tener que esperar a que soliciten datos del servidor. Esto permite que los datos se comuniquen de un lado a otro, lo cual es ideal para cosas como aplicaciones de chat en tiempo real o incluso juegos.

¿Cómo funcionan los WebSockets?

En esencia, un websocket es solo una conexión TCP que permite la comunicación full-duplex, lo que significa que cualquier lado de la conexión puede enviar datos al otro, incluso al mismo tiempo.

Para establecer esta conexión, el protocolo en realidad inicia el protocolo de enlace como una solicitud HTTP normal, pero luego se 'actualiza' utilizando la solicitud de actualización Encabezado HTTP, como este:

1
2
3
4
5
6
7
8
GET /ws/chat HTTP/1.1
Host: chat.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: q1PZLMeDL4EwLkw4GGhADm==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 15
Origin: http://example.com

Luego, el servidor devuelve una respuesta HTTP 101 "Protocolos de conmutación", reconociendo que la conexión se actualizará. Una vez realizada esta conexión, cambia a un protocolo binario bidireccional, momento en el que se pueden enviar los datos de la aplicación.

Todo lo que el protocolo tiene que hacer para mantener abierta la conexión es enviar algunos paquetes ping/pong, lo que le dice al otro lado que todavía están allí. Para cerrar la conexión, se envía un paquete simple "cerrar conexión".

Algunos ejemplos de Websocket

De las muchas bibliotecas websocket disponibles para Node.js, elegí usar socket.io a lo largo de este artículo porque parece ser la más popular y es, en mi opinión, el más fácil de usar. Si bien cada biblioteca tiene su propia API única, también tienen muchas similitudes, ya que todas están construidas sobre el mismo protocolo, por lo que esperamos que pueda traducir el código a continuación a cualquier biblioteca que desee usar.

Para el servidor HTTP, utilizaré Expresar, que es el servidor de nodos más popular que existe. Tenga en cuenta que también puede usar el módulo simple http si no necesita todas las características de Express. Aunque, dado que la mayoría de las aplicaciones usarán Express, eso es lo que usaremos también.

Nota: A lo largo de estos ejemplos, he eliminado gran parte del código repetitivo, por lo que parte de este código no funcionará de inmediato. En la mayoría de los casos, puede consultar el primer ejemplo para obtener el código repetitivo.

Estableciendo la conexión

Para que se establezca una conexión entre el cliente y el servidor, el servidor debe hacer dos cosas:

  1. Conéctese al servidor HTTP para manejar conexiones websocket
  2. Sirva la biblioteca de cliente socket.io.js como un recurso estático

En el código a continuación, puede ver que el elemento (1) se realiza en la tercera línea. El elemento (2) lo realiza (de forma predeterminada) la biblioteca socket.io y se sirve en la ruta /socket.io/socket.io.js. De forma predeterminada, todas las conexiones y recursos de websocket se sirven dentro de la ruta /socket.io.

Servidor

1
2
3
4
5
6
7
8
9
var app = require('express')();
var server = require('http').Server(app);
var io = require('socket.io')(server);

app.get('/', function(req, res) {
    res.sendFile(__dirname + '/index.html');
});

server.listen(8080);

El cliente también necesita hacer dos cosas:

  1. Cargue la biblioteca desde el servidor
  2. Llame a .connect() a la dirección del servidor y la ruta del websocket

Cliente

1
2
3
4
<script src="/socket.io/socket.io.js"></script>
<script>
    var socket = io.connect('/');
</script>

Si navega con su navegador a http://localhost:8080 e inspecciona las solicitudes HTTP en segundo plano utilizando las herramientas de desarrollo de su navegador, debería poder ver cómo se ejecuta el protocolo de enlace, incluidas las solicitudes GET y el HTTP resultante. 101 Respuesta de protocolos de conmutación.

Envío de datos del servidor al cliente

Bien, ahora pasemos a algunas de las partes más interesantes. En este ejemplo, le mostraremos la forma más común de enviar datos desde el servidor al cliente. En este caso, enviaremos un mensaje a un canal, al que el cliente puede suscribirse y recibirlo. Entonces, por ejemplo, una aplicación cliente podría estar escuchando en el canal 'anuncios', que contendría notificaciones sobre eventos de todo el sistema, como cuando un usuario se une a una sala de chat.

En el servidor, esto se hace esperando a que se establezca la nueva conexión, luego llamando al método socket.emit() para enviar un mensaje a todos los clientes conectados.

Servidor

1
2
3
io.on('connection', function(socket) {
    socket.emit('announcements', { message: 'A new user has joined!' });
});

Cliente

1
2
3
4
5
6
7
<script src="/socket.io/socket.io.js"></script>
<script>
    var socket = io.connect('/');
    socket.on('announcements', function(data) {
        console.log('Got announcement:', data.message);
    });
</script>

Envío de datos del cliente al servidor

Pero, ¿qué haríamos cuando queramos enviar datos por el otro lado, del cliente al servidor? Es muy similar al último ejemplo, usando los métodos socket.emit() y socket.on().

Servidor

1
2
3
4
5
io.on('connection', function(socket) {
    socket.on('event', function(data) {
        console.log('A client sent us this dumb message:', data.message);
    });
});

Cliente

1
2
3
4
5
<script src="/socket.io/socket.io.js"></script>
<script>
    var socket = io.connect('/');
    socket.emit('event', { message: 'Hey, I have an important message!' });
</script>

Conteo de usuarios conectados

Este es un buen ejemplo para aprender, ya que muestra algunas características más de socket.io (como el evento disconnect), es fácil de implementar y es aplicable a muchas aplicaciones web. Usaremos los eventos conexión y desconexión para contar el número de usuarios activos en nuestro sitio, y actualizaremos a todos los usuarios con el recuento actual.

Servidor

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
var numClients = 0;

io.on('connection', function(socket) {
    numClients++;
    io.emit('stats', { numClients: numClients });

    console.log('Connected clients:', numClients);

    socket.on('disconnect', function() {
        numClients--;
        io.emit('stats', { numClients: numClients });

        console.log('Connected clients:', numClients);
    });
});

Cliente

1
2
3
4
5
6
7
<script src="/socket.io/socket.io.js"></script>
<script>
    var socket = io.connect('/');
    socket.on('stats', function(data) {
        console.log('Connected clients:', data.numClients);
    });
</script>

Una forma mucho más sencilla de rastrear el número de usuarios en el servidor sería simplemente usar esto:

1
var numClients = io.sockets.clients().length;

Pero aparentemente hay algunos asuntos en torno a esto, por lo que es posible que deba realizar un seguimiento del recuento de clientes usted mismo.

Habitaciones y espacios de nombres

Es probable que a medida que su aplicación crezca en complejidad, necesitará más personalización con sus websockets, como enviar mensajes a un usuario específico o a un conjunto de usuarios. O tal vez desee una separación estricta de la lógica entre las diferentes partes de su aplicación. Aquí es donde entran en juego las habitaciones y los espacios de nombres.

Nota: Estas características no son parte del protocolo websocket, sino que socket.io las agrega encima.

De forma predeterminada, socket.io usa el espacio de nombres raíz (/) para enviar y recibir datos. Mediante programación, puede acceder a este espacio de nombres a través de io.sockets, aunque muchos de sus métodos tienen accesos directos en io. Entonces estas dos llamadas son equivalentes:

1
2
io.sockets.emit('stats', { data: 'some data' });
io.emit('stats', { data: 'some data' });

Para crear su propio espacio de nombres, todo lo que tiene que hacer es lo siguiente:

1
2
3
4
5
var iosa = io.of('/wikihtp');
iosa.on('connection', function(socket){
    console.log('Connected to Stack Abuse namespace'):
});
iosa.emit('stats', { data: 'some data' });

Además, el cliente debe conectarse a su espacio de nombres explícitamente:

1
2
3
4
<script src="/socket.io/socket.io.js"></script>
<script>
    var socket = io('/wikihtp');
</script>

Ahora, todos los datos enviados dentro de este espacio de nombres estarán separados del espacio de nombres / predeterminado, independientemente del canal que se use.

Yendo aún más lejos, dentro de cada espacio de nombres puede unirse y salir de 'salas'. Estas salas brindan otra capa de separación además de los espacios de nombres, y dado que un cliente solo puede agregarse a una sala en el lado del servidor, también brindan seguridad adicional. Entonces, si desea asegurarse de que los usuarios no estén husmeando en ciertos datos, puede usar una habitación para ocultarlos.

Para ser agregado a una sala, debe .join():

1
2
3
io.on('connection', function(socket){
    socket.join('private-message-room');
});

Luego, desde allí, puede enviar mensajes a todos los que pertenecen a la sala dada:

1
io.to('private-message-room').emit('some event');

Y finalmente, llama a .leave() para dejar de recibir mensajes de eventos de una habitación:

1
socket.leave('private-message-room');

Conclusión

Esta es solo una biblioteca que implementa el protocolo websockets, y hay muchas más, todas con sus propias características y fortalezas únicas. Aconsejaría probar algunos de los otros (como nodo-websockets) para que tenga una idea de lo que hay.

En solo unas pocas líneas, puede crear algunas aplicaciones bastante poderosas, ¡así que tengo curiosidad por ver qué puede hacer!

¿Tiene algunas ideas geniales o ya creó algunas aplicaciones usando websockets? ¡Cuéntanos en los comentarios! s comentarios!*