La tecnología Websockets surgió con la necesidad de aligerar la conexiones HTTP que manejan los navegadores, ideal para ser utilizada en el famoso chip ESP8266. La pareja Websockets y ESP8266 es ya en el mundo IoT un tándem inseparable. Veamos qué son y como programarlos.

HTTP es un sistema de comunicación muy simple basado en petición y respuesta (request and response). El navegador pide un recurso (por ejemplo una página o una imagen) y el servidor la devuelve. Tras esto el servidor cierra unilateralmente la conexión.

Cargar una página web implica abrir tantas conexiones como elementos tenga la página. Y cada conexión ha de negociarse, implicando una carga de cabeceras y solicitudes recurrentes que incrementan la latencia, es decir, una acumulación de retrasos en tareas distintas al propio envío de los datos.

La simpleza en la programación de dispositivos HTTP compensa este incremento en la latencia. La gran ventaja del HTTP es precisamente su sencillez. Pero en entornos con solicitudes constantes de los clientes, en los que el servidor además también quiera establecer comunicación unilateral con el cliente, el protocolo HTTP quedó obsoleto.

Por eso surgió la tecnología Websockets en al año 2011. Veamos una comparativa con HTTP para entender cómo funciona un Websocket y para qué es mejor uno u otro.

Comparativa Websockets (ws://) vs HTTP (http://)

Comparativa Websockets y HTTP. Fuente.

El protocolo Websocket funciona bajo TCP y es independiente del HTTP pese a lo que el nombre pudiera indicar. La única relación de Websocket con HTTP es el handshake, la negociación inicial. Todo lo demás es independiente.

El protocolo HTTP es tipo PULL, el cliente «trae hacia sí» la información desde el servidor. El servidor no puede hacer PUSH con la información, es decir, no la puede «empujar» información hacia el cliente, por usar los conceptos de la terminología anglosajona. El protocolo Websocket permite, además de solicitudes PULL (bajo demanda), el envío de información unilateral PUSH, sin que el cliente la haya requerido explícitamente.

Las conexiones HTTP se crean y se destruyen constantemente, las Websocket permanecen a lo largo del tiempo. Los mensajes en HTTP son unidireccionales y cada extremo en la comunicación tiene un papel muy rígido, mientras que en el Websocket la comunicación es bidireccional y de igual a igual.

La ventaja de Websocket: la rapidez, la inmediatez en la comunicación una vez establecida la comunicación. La desventaja: cierta complejidad adicional en la programación y un mayor consumo de recursos al tener que mantener un número elevado de conexiones siempre abiertas.

La ventaja de HTTP: la relativa sencillez en la programación de la gestión de peticiones y respuestas y su consumo de recursos contenido. La desventaja: la latencia en las comunicaciones y la falta de inmediatez.

Se debe usar HTTP cuando no precisamos inmediatez y cuando las peticiones son ocasionales. Al contrario es mejor usar Websockets.

La domótica y los Websockets

El típico ejemplo en donde es necesario Websockets es en un sistema de domótica. Cuando, por ejemplo, el usuario pulsa en el dispositivo de encender luces, las luces deben encenderse de inmediato, no cinco segundos después de un largo handshake de HTTP.

Además, en domótica, varios dispositivos (pero al fin y al cabo un número pequeño y conocido) estarán conectados a la vez, intercambiando información en tiempo real con el usuario y sus dispositivos fijos y móviles. La sensación de control del usuario será así completa.

Los navegadores actuales soportan Websockets, lo que los convierte en plataformas ideales para, a través de la programación JavaScript, servir de nexo de unión Humano-Domótica. Sin necesidad de programar apps.

Y aquí es donde entran los chips ESP8266. Veamos como funcionan los Websockets en estos pequeños y baratos dispositivos IoT.

Resumen de comandos Websocket para un ESP8266

En el siguiente cuadro vamos a presentar los diferentes elementos de programación usados en el IDE de Arduino para poder programar Websockets en el ESP8266, tanto del lado del ESP8266 servidor, como del cliente.

CLIENTE SERVIDOR
#include <WebSocketsClient.h> #include <WebSocketsServer.h>
WebSocketsClient SOCKET;

Constructor. Preparamos el objeto para conectarse a servidores.

WebSocketsServer SOCKET_SRV(puerto);

Constructor. Se indica el puerto en el que se escuchará constantemente.

SOCKET_SRV.begin();

Con esta orden abrimos el puerto indicado en el constructor.

SOCKET.onEvent (socketCallBackFunction);

Fijamos la función callback, que recibirá todos los eventos de las conexiones enviadas.

SOCKET_SRV.onEvent (socketCallBackFunction);

Fijamos la función callback, que recibirá todos los eventos de las conexiones recibidas.

SOCKET.begin (IP ;PUERTO ,nombre);

Establecemos la comunicación. Se abre la negociación y después de unos segundos tendremos el socket disponible.

Es recomendable no mandar mensajes nada más ejecutar esta instrucción porque no llegarán. Habría que estar atento al evento WStype_CONNECTED para asegurarnos que el socket está abierto.

La función callback del servidor recibe el evento. El evento es del tipo WStype_CONNECTED, por lo que habría que tratarlo convenientemente.

socketCallBackFunctionServer( uint8_t num, WStype_t type, uint8_t * payload, size_t lenght) {...
case WStype_CONNECTED:

 

La función callback del cliente recibe el evento de conexión establecida. El evento es del tipo WStype_CONNECTED. A partir de ese momento el socket está abierto.

socketCallBackFunctionClient (WStype_t type, uint8_t * payload, size_t lenght) {

...

case WStype_CONNECTED:

 

SOCKET.setReconnectInterval(5000);

El cliente mirará cada 5000 milisegundos si el socket está abierto. Si no lo está intentará reconectar.

 

El puede mandar en cualquier momento información al servidor:

SOCKET.sendTXT ("mensaje");

La función callback del server vuelve a recibir el evento. En este caso es del tipo WStype_TXT:

socketCallBackFunctionServer( uint8_t num, WStype_t type, uint8_t * payload, size_t lenght) {
...

case WStype_TXT:

El servidor puede en ese evento responder al cliente (socket número num):

SOCKET_SRV.sendTXT (num, "respuesta del servidor");

O mandar un mensaje de broadcast a todos los clientes conectados:

SOCKET_SRV.broadcastTXT ("Mensaje a todos los clientes");

 

La función callback del cliente recibe el evento WStype_TXT.

socketCallBackFunctionClient (WStype_t type, uint8_t * payload, size_t lenght) {
...
case WStype_TXT:

Aquí debería procesar la solicitud

 

SOCKET_SRV.sendTXT (num, "respuesta del servidor");

El servidor puede mandar al cliente un mensaje en cualquier momento y en cualquier punto del programa, siempre que lleve una tabla de comunicaciones (ver más adelante)

 SOCKET.broadcastTXT ("mensaje para todos los clientes);

Si no lleva esa tabla de comunicaciones, el servidor siempre puede mandar mensajes a todos los clientes que en ese momento estén conectados.

La bidireccionalidad en los websockets: la tabla de comunicaciones

Una de las características de la tecnología websockets es la bidireccionalidad, esto es, el cliente puede mandar un mensaje al servidor, pero el servidor, aparte de responder, también puede manar un mensaje al cliente a iniciativa propia.

En la práctica esto no es tan sencillo. Los clientes cuando abren un socket con un servidor sólo tienen uno abierto por cada objeto WebSocketsClient . Una vez establecida la comunicación, mandar mensajes al servidor es trivial, puesto que la única conexión está encapsulada en el objeto:

SOCKET_CLI.sendTXT ("mensaje");

Sin embargo, del lado del servidor todo es más complicado. Bajo un mismo objeto WebSocketsServer  pueden esconderse un buen montón de conexiones de clientes que, además, perduran en el tiempo. Cuando un servidor manda un mensaje a un cliente hace lo siguiente:

SOCKET_SRV.sendTXT (num, "mensaje");

Cada cliente lleva asociado un número que siempre viene en cada mensaje que nos remite el cliente. No en vano la función callback de los eventos de servidor lo recoge en el parámetro num:

socketCallBackFunctionServer( uint8_t num, WStype_t type, uint8_t * payload, size_t lenght) {  ... }

Es tarea del programador llevar una «tabla de comunicaciones» con cada cliente, de manera que el cliente quede identificado nada más establecerse la comunicación, asociándosele en ese momento el numero que nos suministra el evento. El lugar para hacerlo es dentro de la función callback, el evento WStype_CONNECTED :

socketCallBackFunctionServer( uint8_t num, WStype_t type, uint8_t * payload, size_t lenght) {
   ...
   case WStype_CONNECTED:
   // usar aquí num y payload para identificar al cliente y a la conexión
}

Limitación del número de websockets en el ESP8266

Un ESP8266 puede manejar a la vez 6 sockets TCP, dentro de los cuales estarían los Websockets. El problema de estos últimos es que por su propia naturaleza permanecen abiertos de manera indefinida, mientras que por ejemplo, un socket TCP sobre el que corra una comunicación HTTP, tiene sólo unos segundos de vida, por lo que puede ser reutilizado de manera constante.

La propia limitación de memoria del ESP8266 hace aconsejable no pasar estos límites para evitar cuelgues y problemas diversos, por lo que hay que diseñar los programas con esta limitación en mente.