En la primera publicación vimos: qué es Socket.IO, así como su integración y uso, y las dependencias de Maven. Terminamos con la creación de un servidor con Socket.IO para recibir y enviar mensajes.

Os recuerdo que utilizamos las siguientes tecnologías: 

  • Java 11
  • socket.io 4.0.1
  • engine.io 6.2.1
  • Jetty server 9.4.44.v20210927
  • Junit 5.10.0
  • Mockito 4.0.0
  • Maven
  • Postman

En esta segunda parte vamos a ver a qué niveles podemos crear Eventos y cómo realizarlo. Por otro lado, os explicaré cómo unir y abandonar salas, las distintas maneras de difundir mensajes y también cómo probar todo esto con Postman. 

Eventos

En Socket.IO tenemos varias opciones para generar eventos: a nivel de servidor, a nivel de namespace y a nivel de socket.

Si se crea a nivel de servidor, se escuchará cada conexión que active el evento y se ejecutará la lógica implementada sobre ella.

Por otro lado, si lo hacemos a nivel de namespace, estaremos escuchando el evento que active cualquier socket en ese namespace.

Por último, si el evento se establece a nivel de socket, sólo el listener de ese socket recibirá la señal para ejecutar las instrucciones. Es en este último, en el que nos centraremos.

Creación de eventos

A la hora de crear un evento, lo que tenemos que hacer es obtener el namespace donde será registrado y añadirlo al evento ‘connection’, que se creó cuando instanciamos el objeto SocketIoServer en el apartado “Creación de un servidor Jetty embebido con Socket.IO”.

De esta forma, cada vez que el namespace reciba una petición para realizar una conexión tendremos el socket que la está realizando.

Para mostrar cómo se hace esto, vamos a obtener el namespace ‘/’ del objeto SocketIoServer, que es el de por defecto, vamos a añadir el evento ‘connection’, y vamos a obtener el socket que está realizando la conexión a partir de los argumentos para imprimir sus datos en consola.

SocketIoNamespace namespace = server.namespace("/");
namespace.on("connection", args-> {
  SocketIoSocket socket = (SocketIoSocket) args[0];
  System.out.println("Client " + socket.getId() + " (" + 
socket.getInitialHeaders().values() + ") has connected.");
});

Una vez realizado, podemos incluir un evento en ese socket para que pueda enviar información y el servidor emita el evento al resto de usuarios que estén escuchándolo.

Estos argumentos pueden ser simples como números, cadenas o booleanos, o complejos como JSON o incluso objetos. Para este último, tendremos que mapear los valores antes de trabajar con ellos.

Para ejemplificarlo, crearemos un evento llamado ‘message’ en el socket y mandaremos una cadena como argumento para imprimirla en consola.

SocketIoNamespace namespace = server.namespace("/");
namespace.on("connection", args-> {
  SocketIoSocket socket = (SocketIoSocket) args[0];
  System.out.println("Client " + socket.getId() + " (" + 
socket.getInitialHeaders().values() + ") has connected.");

  socket.on("message", args2 -> {
     String message = (String) args2[0];
     System.out.println("Message from socket: " + message);
  });
});

Salas

Una sala es una división creada por el servidor dentro del espacio de nombres a la que los sockets pueden entrar y salir. Puede utilizarse para difundir eventos a un subconjunto de clientes.

Esta imagen de la página oficial de Socket.IO, lo muestra de forma gráfica:

Integración de SocketIO en parte servidor en lenguaje Java

Imagen 1. Diagrama de funcionamiento de salas 

Unión y abandono de salas

La clase SocketIOSocket que representa una conexión, contiene los métodos ‘joinRoom’ y ‘leaveRoom’. Con estos podemos añadir o eliminar un socket de una o varias salas. Si la sala no existe cuando se vaya a añadir el socket, esta será creada.

En el siguiente ejemplo, crearemos dos eventos: uno para unirnos a una sala y otro para abandonarla. En ambos casos, el primer argumento será el nombre de la sala o salas.

SocketIoNamespace namespace = server.namespace("/");
namespace.on("connection", args-> {
  SocketIoSocket socket = (SocketIoSocket) args[0];
  System.out.println("Client " + socket.getId() + " (" + 
socket.getInitialHeaders().values() + ") has connected.");

  socket.on(«join-room», args2 -> {
   String room = (String) args2[0];
     System.out.println(«Name of the room: « + room);
     socket.joinRoom(room);
  });

  socket.on(«leave-room», args2 -> {
   String room = (String) args2[0];
     System.out.println(«Name of the room: « + room);
     socket.leaveRoom(room);
  });
});

Dado que ambos métodos no devuelven un valor que confirme que la operación se ha realizado correctamente, podemos listar los sockets de una sala y hacer que nuestro socket compruebe si estamos o no en la habitación.

Podemos hacerlo poniendo el siguiente código después de los métodos mencionados anteriormente:

SocketIoSocket[] listClients = 
namespace.getAdapter().listClients(room);
List<SocketIoSocket> listSockets = 
Stream.of(listClients).filter(socket::equals).collect(Collectors.toList
());

Difusión de mensajes

Desde el lado del servidor, podemos enviar mensajes a todos los clientes de diferentes maneras.

También cabría mencionar que, si un cliente no estaba conectado antes de enviar el mensaje, este no lo recibirá, puesto que, se realizan en tiempo real y no se almacenan por defecto. Para ello, tenemos que hacer algo extra, como, por ejemplo, guardarlos en una base de datos, sin embargo, esto no lo vamos a tratar.

Para realizar este envío, vamos a utilizar los objetos SocketIONamespace y SocketIOSocket, y así, podemos mandarlos a nivel de namespace o a nivel de socket.

La diferencia entre ambos es que cuando se emite un mensaje desde el namespace, el emisor también recibe el mensaje, y cuando se hace desde el socket no.

Para realizar esta tarea, utilizaremos el método broadcast(String room, String event, Object… args). Este método puede enviar mensajes a una sala específica o a todos los clientes si enviamos un valor nulo en el parámetro room.

En el siguiente fragmento de código, se muestran varias formas de emitir un mensaje:

//Para enviar mensajes a todos las pesonas incluyendo al emisor
namespace.broadcast(null, "message", "Enviando mensajes a todos las pesonas incluyendo al emisor");

 

//Para enviar mensajes a todas las pesonas incluyendo al emisor
socket.broadcast(null, «message», «Enviando mensajes a todos las pesonas incluyendo al emisor»);

//Para enviar mensajes a una sala específica incluyendo al emisor
namespace.broadcast(«room», «message», «Enviando mensajes a una sala específica incluyendo al emisor «);

//Para enviar mensajes a una sala específica excepto al emisor
socket.broadcast(«room», «message», «Enviando mensajes a una sala específica excepto al emisor»);

Con todo esto, podemos montar nuestro servicio de notificaciones para poder mandar y recibir mensajes de usuarios conectados.

Conexión al servicio de Socket.IO

¿Y ahora cómo lo probamos? Pues tenemos varias opciones:

  • Montarnos una colección en Postman
  • Montarnos un frontal que se conecte al servicio

Nosotros vamos a optar por la primera opción, montar una colección en Postman.

Postman

Para poder conectarnos al servicio creado anteriormente, vamos a crear en Postman una petición de tipo Socket.IO.

Integración de SocketIO en parte servidor en lenguaje Java

Imagen 2. Creación de una petición de tipo Socket.IO 

Una vez creado, nos conectaremos a la IP y el puerto donde tenemos levantado el servicio y presionamos el botón ‘Connect’.

Integración de SocketIO en parte servidor en lenguaje Java. Parte 2/2.

Imagen 3. Conexión al servicio de Socket.IO

Una vez conectado, nos vamos a la pestaña ‘Events’ situada debajo de donde pusimos la URL, y nos ponemos a escuchar el evento ‘message’, que es el que hemos utilizado para ilustrar la difusión de mensajes.

Integración de SocketIO en parte servidor en lenguaje Java

Imagen 4. Pestaña de events

Para realizar una prueba, podemos enviar un mensaje al evento que estamos escuchando y ver si el servidor lo envía. Lo realizaremos desde la pestaña ‘Message’.

Integración de SocketIO en parte servidor en lenguaje Java

Imagen 5. Pestaña de message

En la parte principal, pondremos el texto que queremos enviar y en la caja inferior derecha escribiremos el nombre del evento al que enviaremos el mensaje.

Una vez enviado, en la parte de response, que se encuentra en la parte inferior, podremos ver todos los mensajes enviados y recibidos.

Integración de SocketIO en parte servidor en lenguaje Java

Imagen 6. Pestaña de response

Los mensajes enviados aparecerán con una flecha apuntando hacia arriba, junto al evento y el texto escrito.

Los mensajes recibidos aparecerán con una flecha apuntando hacia abajo, junto al evento y el texto.

Conclusión

En esta segunda publicación hemos podido aprender a:

  • Crear eventos
  • Unir y abandonar salas
  • Difundir mensajes de distintas formas.
  • Utilizar Postman para recibir y enviar mensajes.

Todo lo que hemos mostrado se encuentra integrado en nuestro repositorio de GitLab.

Fuentes