En este artículo haremos un ejemplo del uso del API para WebSockets JSR 356 que es parte de Java EE.

El código fuente está disponible en https://github.com/FlechaRoja/WebSocket

El protocolo de WebSockets fue introducido como parte de una de las iniciativas de HTML 5. Esta es por ende una tecnología estandard que simplifica la conexión y communicación entre clientes y un servidor.

Dado que esta conexión es constante, WebSocket nos presenta una comunicación cliente/servidor que es full duplex, de baja latencia y de bajo nivel que funciona sobre TCP/IP. Esto resulta idóneo para aplicaciones que requieren de una actualización constante desde un servidor, a manera de ejemplo: información de la bolsa de valores, juegos por web, chats, entre otros.

Algunas de las características más notorias del API de Java para WebSocket son:

  • Uso de Anotaciones. Esto permite que los programadores utilicen POJO para interactuar con los eventos del ciclo de vida de WebSocket.

  • Uso de Interfaces. Lo cual permite que se implementen interfaces y métodos para interactuar con los eventos del ciclo de vida de WebSocket.

  • Integración con el stack de Java EE. El uso de CDI. (Contexts and Dependecy Injection)

En este ejemplo, crearemos una aplicación de chat sencilla.

Socket Server

Para empezar, necesitamos crear una clase que sea nuestro servidor central de chat. Para lograrlo creamos una clase que tenga la anotación @ServerEndpoint. En ella indicamos que este endpoint va a responder al URI /servidor

  @ServerEndpoint("/servidor")
  public class ChatWebSocketServer

De seguido debemos implementar el comportamiento de 4 métodos que responden a:

  • @OnOpen Cuando se abre una sesión hacia el servidor.

  • @OnError Cuando se da un error.

  • @OnClose Cuando se cierra la sesión.

  • @OnMessage Cuando se recibe un mensaje.

El resultado final es este:

@ServerEndpoint("/servidor")
public class ChatWebSocketServer {

    private static final Logger LOG = Logger.getLogger(ChatWebSocketServer.class.getName());

    @Inject
    private ChatSessionHandler sessionHandler;

    @OnOpen
    public void onOpen(Session session) {
        sessionHandler.addSession(session);
    }

    @OnError
    public void onError(Throwable t) {
        LOG.log(Level.SEVERE, "Error en ChatWebSocketServer", t);
    }

    @OnClose
    public void onClose(Session session) {
        sessionHandler.removeSession(session);
    }

    @OnMessage
    public void onMessage(String message, Session session) {
        sessionHandler.addMessage(message, session.getId());
    }
}

Notarán que injectamos un ChatSessionHandler. Esta clase es la responsable de llevar el control de las sesiones activas y de distribuir los mensajes entre todos los participantes del chat. En ella tenemos los siguiente métodos:

  • addSession. Este guarda en el Set una nueva sesión participante del chat.

  • removeSession. Este método remueve una sesion del Set. Caso que se da cuando alguien abandona el chat.

  • addMessage. Este método se encarga de distribuir un nuevo mensaje a todos los participantes; es decir, a todas las sesiones que están en el Set. Para facilitar la prueba, agregué el id de la sesión que envió el mensaje.

La clase final se muestra seguidamente.

@ApplicationScoped
public class ChatSessionHandler {

    private static final Logger LOG = Logger.getLogger(ChatSessionHandler.class.getName());

    private final Set<Session> sesiones = new HashSet<>();

    public void addSession(Session session) {
        sesiones.add(session);
    }

    public void removeSession(Session session) {
        sesiones.remove(session);
    }

    void addMessage(String message, String id) {
      sesiones.forEach((session) -> {
          sendToSession(session, message);
      });
    }

    private void sendToSession(Session session, String message) {
        try {
            session.getBasicRemote().sendText(message + " Session [" + session.getId() + "]");
        } catch (IOException ex) {
            sesiones.remove(session);
            LOG.log(Level.SEVERE, "Error al enviar a sesion " + session.getId(), ex);
        }
    }
}

Con esto, terminamos la parte de código Java.

Clientes

Para la parte del cliente; usaremos JSF y una función en Javascript. Esta última lo que hará es conectarse a nuestro WebSocket y así poder enviar y recibir mensajes.

En el código que presentamos abajo lo que hace es establecer comunicación con nuestro WebSocket. Observen que el URI termina en /servidor; pues ese fue el endpoint que definimos y que además usamos el protocolo ws.

var host = "ws://localhost:8080/WebSocketsDemo/servidor";
var wSocket = new WebSocket(host);
var browserSupport = ("WebSocket" in window) ? true : false;

Luego, creamos una función que realiza una inicialización y verifica que el navegador que usamos tenga soporte para WebSockets. No coloque código en la función que se ejecuta al abrir el socket; pero perfectamente se puede emplear para fines específicos de la aplicación que estemos desarrollando.

function initializeReception()
{
    if (browserSupport)
    {
        wSocket.onopen = function ()
        {

        };
    } else
    {
        // No hay soporte, posiblemente un navegador obsoleto

        alert("WebSocket no es soportado en su browser. Utilice uno moderno.");
    }
}

Para poder procesar los mensajes que se reciben se debe definir una función que atienda a onmessage. En nuestro caso, esta función toma el mensaje y lo agrega a un textarea que tenemos definido. El ejemplo es trivial, es simplemente texto, pero perfectamente podría ser un mensaje JSON mucho más elaborado.

wSocket.onmessage = onMessage;    

 function onMessage(evt) {
    var received_msg = evt.data;
    document.getElementById('chatForm:serverMsg').value = received_msg + "\n"+document.getElementById('chatForm:serverMsg').value;
};

Finalmente, para enviar un mensaje a traves del socket, se usa la función send. Eso hace que el mensaje llegue a nuestro endpoint y este a su vez lo distribuye a todos los participantes del chat, los cuales recibirán el mismo por medio de la funcion onMessage y lo mostrarán en pantalla.

function addMsg() {
    wSocket.send(document.getElementById('chatForm:msg').value);
    document.getElementById('chatForm:msg').value="";
}

Nuestra página en JSF invoca a la función de inicialización que definimos en nuestro javascript. Luego, tenemos un form que posee un botón el cual llama a la función para enviar el mensaje cuando se da click sobre él.

<h:body onload="initializeReception()">
      <div class="container">
          <div class="row">
              <div class="col">
                  <img src="images/logo.svg" alt="Flecha Roja Tech"/>
                  <h1>Demo de Java WebSockets</h1>

                  <h:form id="chatForm">
                      <div class="form-group">

                          <h:panelGrid columns="2">
                              <label for="msg">Mensaje</label>
                                  <h:inputText id="msg"   maxlength="100" styleClass="form-control" />
                                  <h:commandButton value="Enviar" onclick="addMsg()" type="button" styleClass="btn btn-primary" />
                          </h:panelGrid>

                          <h:inputTextarea id="serverMsg" cols="90" rows="20" />
                      </div>
                  </h:form>
              </div>
          </div>
      </div>

  </h:body>

Resultado

Conclusión

El uso de WebSockets resulta muy simple de implementar en Java EE. En este ejemplo vimos como puedes hacer un chat sencillo y usar estas ideas para tus propios desarrollos en donde necesites hacer uso de comunicación cliente/servidor bidireccional.

Referencias

Autor

Gerardo Arroyo Arce

Chief Technology Officer.

  • Ingeniero en Software. ITCR.
  • Master en Computación en Informática. UCR.
  • Oracle Certified Expert, JEE 6 Web Services Developer.
  • Oracle Certified Professional, Java SE 7 Programmer.
  • Oracle WebLogic Server 12c Certified Implementation Specialist.

Flecha Roja Technologies, 2017.