Saltar al contenido
Portada » Websockets en Java

Websockets en Java

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

TemaDescripción
SocketsAprende lo básico sobre sockets en Java, construye tus protocolos.
WebsocketsConstruye un endpoint en Java al que conectar mediante Websockets.
FTPCódigo para implementar en Java un cliente FTP
IMAPCliente IMAP escrito en Java.
SMB/jCIFSUso de jCIFS para el acceso a recursos de red compartidos mediante SAMBA
Send Mail JavaEnvío de correos electrónicos a través de JavaMail API

Recursos básicos Java

AsuntoDescripción
Tutorial básico y sintaxisTutorial 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 LambdaAquí te enseñamos las nociones más importantes para arrancas con funciones lambda
PalíndromosPrograma de ejemplo para el uso de palíndromos en Java.
Máquina Virtual de JavaTe explicamos el funcionamiento de la máquina virtual de java (Java Virtual Machine – JVM)
JDK, JRE y JVMDiferencias entre el JDK, JRE y JVM.
Mejores libros Java en EspañolHazte con los mejores libros Java para aprender paso a paso y profundizar en las mejores prácticas
TensorFlowManejo del API de TensorFlow para la construcción de grafos de operaciones y su ejecución
Tutorial Log4jTutorial para el manejo de Log4j, herramienta ágil y flexible para la gestión de Logs en Java
Java SecurityEntiende y aplica las posibilidades que da Java para mantener la seguridad
Tutorial JConsoleAprende los conceptos básicos de monitorización de procesos Java con JConsole
JavaFXTutorial de JavaFX, librería gráfica moderna para construcción de GUIs en móvil, escritorio y web.
Estructuras de datos en JavaExplicación y ejemplos de las estructuras de datos más importantes: listas, pila, cola, arbol.
JavaapiConjunto de clases, interfaces, métodos y paquetes que forman parte de la plataforma Java estándar
Algoritmo HuffmanMétodo eficiente para codificar datos, asignando códigos más cortos a los caracteres más frecuentes