
Los websockets en Java proveen una alternativa a la limitación en la comunicación eficiente entre servidor y navegador web, ofreciendo una conexión bi-direccional, full-duplex, en tiempo real cliente/servidor. Los websockets corren sobre TCP, gracias a este protocolo ofrece baja latencia y reduce la sobrecarga de cada mensaje.
JSR 356 – Java API Websockets
JSR 356 es la especificación del API de Java para el manejo de websockets. Ofrece componentes tanto para el cliente como el servidor:
- Servidor: Se encuentran todas las clases en el paquete javax.websocket.server.
- Cliente: El paquete javax.websocket incluye todas las clases.
Aplicación de Chat usando Websockets
Para demostrar el uso de websockets construiremos una pequeña aplicación de chat. Cualquier usuario podrá abrir el chat desde un navegador web, hacer login y empezar a comunicarse con cualquiera que esté conectado.
1. Dependencias Maven
Las dependencias maven que tenemos incluir en el pom.xml son las siguientes:
<dependency>
<groupId>javax.websocket</groupId>
<artifactId>javax.websocket-api</artifactId>
<version>1.1</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.0</version>
</dependency>
2. Configuración del endpoint
Hay dos formas de configurar endpoints, basado en anotaciones y basado en extensiones. Puedes optar por extender la clase javax.websocket.Endpoint o utilizar anotaciones a nivel de métodos. Normalmente es más común el uso de anotaciones por limpieza en el código.
Los eventos se gestionan usando las siguientes anotaciones:
- @ServerEndpoint: Una clase que utilice esta anotación será un servidor Websocket escuchando en una URI específica.
- @ClientEndpoint: Una clase que use esta anotación será tratada como un cliente Websocket.
- @OnOpen: Un método marcado con esta anotación se invoca por el container cuando una nueva conexión Websocket se inicia.
- @OnMessage: Un método Java anotado recibe la información del Websocket container cuando el mensaje se envía al endpoint.
- @OnError: Los métodos marcados con esta anotación se invoca cuando hay algún problema en la comunicación.
- @OnClose: Cuando ponemos esta anotación a un método, se invocará cuando se cierra la conexión del Websocket.
3. Código del Endpoint Servidor
Declaramos la clase anotándola como @ServerEndpoint para que actúe como edpoint Websocket. También debe especificarse la URI de escucha:
@ServerEndpoint(value = "/chat/{nombreusuario}")
public class ServidorChat{
private Session sesion;
private static Set<ServidorChat> chatEndpoints = new CopyOnWriteArraySet<>();
private static HashMap<String, String> usuarios = new HashMap<>();
@OnOpen
public void abrir(Session sesion, @PathParam("nombreusuario") String nombreusuario) throws IOException {
this.sesion = sesion;
chatEndpoints.add(this);
usuarios.put(sesion.getId(), nombreusuario);
Message mensaje = new Message();
mensaje.setFrom(nombreusuario);
mensaje.setContent("Conectado");
broadcast(mensaje);
}
@OnMessage
public void recibirMensaje(Session sesion, Message mensaje) throws IOException {
mensaje.setFrom(usuarios.get(sesion.getId()));
broadcast(mensaje);
}
@OnClose
public void cierre(Session sesion) throws IOException {
chatEndpoints.remove(this);
Message mensaje= new Message();
mensaje.setFrom(usuarios.get(sesion.getId()));
mensaje.setContent("Desconectado");
broadcast(mensaje);
}
@OnError
public void gestionErrores(Session sesion, Throwable throwable) {
// Gestion de errores en la conexion
}
private static void broadcast(Message mensaje)
throws IOException, EncodeException {
chatEndpoints.forEach(endpoint -> {
synchronized (endpoint) {
try {
endpoint.sesion.getBasicRemote().
sendObject(mensaje);
} catch (IOException | EncodeException e) {
e.printStackTrace();
}
}
});
}
}
Cuando un nuevo usuario se loguea (@OnOpen), se mapea inmediatamente a una estructura de datos de usuarios activos. A continuación se envía un mensaje a todos los endpoints haciendo un broadcast.
Este método también se usa cuando un nuevo mensaje se envía (@OnMessage) por cualquiera de los usuarios conectados, que al final es el propósito principal del chat.
Cuando algún usuario ya no está conectado al chat, el método @OnClose limpia el endpoint y notifica a todos los usuarios la desconexión.
4. Formatos de Mensaje
La especificación Websockets soporta el transporte de datos de texto y binarios. Añade también la posibilidad de trabajar con objetos de Java y mensaje de medición de salud (ping-pong):
- Texto: Cualquier dato tipo texto, primitiva String.
- Datos Binarios: Representado por java.nio.ByteBuffer o byte[].
- Objetos Java: Utilizando codificadores y decodificadores.
- Ping-Pong: Usando la clase javax.websocket.PongMessage como ACK.
En nuestra aplicación usaremos objetos Java por lo que implementaremos el codificador y decodificador correspondiente.
4.1 Codificador
El codificador coge un objeto Java y produce un formato que pueda enviarse por red como JSON, XML o binario. Necesitamos las siguientes clases:
public class Message {
private String from;
private String to;
private String content;
}
public class CodificadorMensaje implements Encoder.Text<Message> {
private static Gson gson = new Gson();
@Override
public String encode(Message mensaje) throws EncodeException {
return gson.toJson(mensaje);
}
@Override
public void init(EndpointConfig endpointConfig) {
// codigo a medida
}
@Override
public void destroy() {
// cierre de recursos
}
}
4.2 Decodificador
El decodificador hace la operación opuesta, transforma un formato transmitido en un objeto Java.
public class Decodificador implements Decoder.Text<Message> {
private static Gson gson = new Gson();
@Override
public Message decode(String cadena) throws DecodeException {
return gson.fromJson(cadena, Message.class);
}
@Override
public boolean willDecode(String cadena) {
return (cadena != null);
}
@Override
public void init(EndpointConfig endpointConfig) {
// codigo a medida
}
@Override
public void destroy() {
// cierre de recursos
}
}
4.3 Unificando
A nivel del @ServerEndpoint incluimos lo siguiente:
@ServerEndpoint(
value="/chat/{nombreusuario}",
decoders = MessageDecoder.class,
encoders = MessageEncoder.class )
Más algoritmos de redes
Tema | Descripción |
Sockets | Aprende lo básico sobre sockets en Java, construye tus protocolos. |
Websockets | Construye un endpoint en Java al que conectar mediante Websockets. |
FTP | Código para implementar en Java un cliente FTP |
IMAP | Cliente IMAP escrito en Java. |
SMB/jCIFS | Uso de jCIFS para el acceso a recursos de red compartidos mediante SAMBA |
Send Mail Java | Envío de correos electrónicos a través de JavaMail API |
Recursos básicos Java
Asunto | Descripción |
Tutorial básico y sintaxis | Tutorial básico Java y sintaxis. Aprende los fundamentos del lenguaje. |
Hilos (Threads) | Aprende a manejar hilos y las cuestiones básicas de la concurrencia |
Funciones Lambda | Aquí te enseñamos las nociones más importantes para arrancas con funciones lambda |
Palíndromos | Programa de ejemplo para el uso de palíndromos en Java. |
Máquina Virtual de Java | Te explicamos el funcionamiento de la máquina virtual de java (Java Virtual Machine – JVM) |
JDK, JRE y JVM | Diferencias entre el JDK, JRE y JVM. |
Mejores libros Java en Español | Hazte con los mejores libros Java para aprender paso a paso y profundizar en las mejores prácticas |
TensorFlow | Manejo del API de TensorFlow para la construcción de grafos de operaciones y su ejecución |
Tutorial Log4j | Tutorial para el manejo de Log4j, herramienta ágil y flexible para la gestión de Logs en Java |
Java Security | Entiende y aplica las posibilidades que da Java para mantener la seguridad |
Tutorial JConsole | Aprende los conceptos básicos de monitorización de procesos Java con JConsole |
JavaFX | Tutorial de JavaFX, librería gráfica moderna para construcción de GUIs en móvil, escritorio y web. |
Estructuras de datos en Java | Explicación y ejemplos de las estructuras de datos más importantes: listas, pila, cola, arbol. |
Javaapi | Conjunto de clases, interfaces, métodos y paquetes que forman parte de la plataforma Java estándar |
Algoritmo Huffman | Método eficiente para codificar datos, asignando códigos más cortos a los caracteres más frecuentes |