Presentamos WebSockets: Lleva los sockets a la Web

El problema: Conexiones cliente-servidor y servidor-cliente de baja latencia

La Web se creó, en gran medida, en torno al paradigma de solicitud y respuesta de HTTP. Un cliente carga una página web y, luego, no ocurre nada hasta que el usuario hace clic en la página siguiente. Alrededor de 2005, AJAX comenzó a hacer que la Web se sintiera más dinámica. Aun así, toda la comunicación HTTP estaba dirigida por el cliente, lo que requería la interacción del usuario o un sondeo periódico para cargar datos nuevos desde el servidor.

Las tecnologías que permiten que el servidor envíe datos al cliente en el momento exacto en que sabe que hay datos nuevos disponibles existen desde hace bastante tiempo. Se conocen con nombres como "Push" o "Comet". Uno de los trucos más comunes para crear la ilusión de una conexión iniciada por el servidor se llama sondeo largo. Con el sondeo largo, el cliente abre una conexión HTTP al servidor, que la mantiene abierta hasta que envía la respuesta. Cuando el servidor tiene datos nuevos, envía la respuesta (otras técnicas implican solicitudes Flash, XHR de varias partes y los llamados htmlfiles). El sondeo largo y las otras técnicas funcionan bastante bien. Los usas todos los días en aplicaciones como el chat de Gmail.

Sin embargo, todas estas soluciones alternativas comparten un problema: conllevan la sobrecarga de HTTP, lo que no las hace adecuadas para aplicaciones de baja latencia. Piensa en juegos de disparos en primera persona multijugador en el navegador o cualquier otro juego en línea con un componente en tiempo real.

Presentamos WebSocket: Lleva los sockets a la Web

La especificación de WebSocket define una API que establece conexiones de "socket" entre un navegador web y un servidor. En pocas palabras: Existe una conexión persistente entre el cliente y el servidor, y ambas partes pueden comenzar a enviar datos en cualquier momento.

Cómo comenzar

Para abrir una conexión de WebSocket, simplemente llama al constructor de WebSocket:

var connection = new WebSocket('ws://html5rocks.websocket.org/echo', ['soap', 'xmpp']);

Observa el objeto ws:. Este es el nuevo esquema de URL para las conexiones de WebSocket. También existe wss: para la conexión segura de WebSocket, de la misma manera que se usa https: para las conexiones HTTP seguras.

Adjuntar algunos controladores de eventos de inmediato a la conexión te permite saber cuándo se abre la conexión, cuándo se reciben mensajes entrantes o si hay un error.

El segundo argumento acepta subprotocolos opcionales. Puede ser una cadena o un array de cadenas. Cada cadena debe representar un nombre de subprotocolo, y el servidor solo acepta uno de los subprotocolos pasados en el array. Para determinar el subprotocolo aceptado, accede a la propiedad protocol del objeto WebSocket.

Los nombres de los subprotocolos deben ser uno de los nombres de subprotocolos registrados en el registro de la IANA. Actualmente, solo hay un nombre de subprotocolo (soap) registrado desde febrero de 2012.

// When the connection is open, send some data to the server
connection.onopen = function () {
connection.send('Ping'); // Send the message 'Ping' to the server
};

// Log errors
connection.onerror = function (error) {
console.log('WebSocket Error ' + error);
};

// Log messages from the server
connection.onmessage = function (e) {
console.log('Server: ' + e.data);
};

Comunicación con el servidor

En cuanto tengamos una conexión con el servidor (cuando se active el evento open), podremos empezar a enviar datos al servidor con el método send('your message') en el objeto de conexión. Antes solo admitía cadenas, pero en la especificación más reciente, ahora también puede enviar mensajes binarios. Para enviar datos binarios, puedes usar un objeto Blob o ArrayBuffer.

// Sending String
connection.send('your message');

// Sending canvas ImageData as ArrayBuffer
var img = canvas_context.getImageData(0, 0, 400, 320);
var binary = new Uint8Array(img.data.length);
for (var i = 0; i < img.data.length; i++) {
binary[i] = img.data[i];
}
connection.send(binary.buffer);

// Sending file as Blob
var file = document.querySelector('input[type="file"]').files[0];
connection.send(file);

Del mismo modo, el servidor podría enviarnos mensajes en cualquier momento. Cada vez que esto sucede, se activa la devolución de llamada de onmessage. La devolución de llamada recibe un objeto de evento, y se puede acceder al mensaje real a través de la propiedad data.

WebSocket también puede recibir mensajes binarios en la especificación más reciente. Los marcos binarios se pueden recibir en formato Blob o ArrayBuffer. Para especificar el formato del objeto binario recibido, establece la propiedad binaryType del objeto WebSocket en "blob" o "arraybuffer". El formato predeterminado es "blob". (No es necesario que alinees el parámetro binaryType en el envío).

// Setting binaryType to accept received binary as either 'blob' or 'arraybuffer'
connection.binaryType = 'arraybuffer';
connection.onmessage = function(e) {
console.log(e.data.byteLength); // ArrayBuffer object if binary
};

Otra función que se agregó recientemente a WebSocket son las extensiones. Con las extensiones, será posible enviar marcos comprimidos, multiplexados, etcétera. Puedes encontrar las extensiones aceptadas por el servidor examinando la propiedad de extensiones del objeto WebSocket después del evento de apertura. Hasta febrero de 2012, no se había publicado oficialmente ninguna especificación de extensiones.

// Determining accepted extensions
console.log(connection.extensions);

Comunicación de origen cruzado

Como es un protocolo moderno, la comunicación entre dominios está integrada en WebSocket. Si bien debes asegurarte de comunicarte solo con clientes y servidores de confianza, WebSocket permite la comunicación entre partes de cualquier dominio. El servidor decide si pone su servicio a disposición de todos los clientes o solo de aquellos que residen en un conjunto de dominios bien definidos.

Servidores proxy

Cada nueva tecnología trae consigo un nuevo conjunto de problemas. En el caso de WebSocket, es la compatibilidad con los servidores proxy que median las conexiones HTTP en la mayoría de las redes de las empresas. El protocolo WebSocket usa el sistema de actualización de HTTP (que normalmente se usa para HTTP/SSL) para "actualizar" una conexión HTTP a una conexión WebSocket. Algunos servidores proxy no admiten esta opción y descartarán la conexión. Por lo tanto, incluso si un cliente determinado usa el protocolo WebSocket, es posible que no se pueda establecer una conexión. Esto hace que la siguiente sección sea aún más importante :)

Usa WebSockets hoy mismo

WebSocket sigue siendo una tecnología nueva y no se implementó por completo en todos los navegadores. Sin embargo, puedes usar WebSocket hoy mismo con bibliotecas que usen una de las alternativas mencionadas anteriormente cuando WebSocket no esté disponible. Una biblioteca que se volvió muy popular en este dominio es socket.io, que incluye una implementación del protocolo para el cliente y el servidor, y cuenta con resguardos (socket.io aún no admite la mensajería binaria desde febrero de 2012). También hay soluciones comerciales, como PusherApp, que se pueden integrar fácilmente en cualquier entorno web proporcionando una API de HTTP para enviar mensajes de WebSocket a los clientes. Debido a la solicitud HTTP adicional, siempre habrá una sobrecarga adicional en comparación con WebSocket puro.

El lado del servidor

El uso de WebSocket crea un patrón de uso completamente nuevo para las aplicaciones del servidor. Si bien las pilas de servidores tradicionales, como LAMP, están diseñadas en torno al ciclo de solicitud/respuesta HTTP, a menudo no manejan bien una gran cantidad de conexiones WebSocket abiertas. Mantener una gran cantidad de conexiones abiertas al mismo tiempo requiere una arquitectura que reciba una alta simultaneidad con un bajo costo de rendimiento. Estas arquitecturas suelen diseñarse en torno a subprocesos o a la llamada E/S sin bloqueo.

Implementaciones del servidor

Versiones del protocolo

El protocolo de conexión (un protocolo de enlace y la transferencia de datos entre el cliente y el servidor) para WebSocket ahora es RFC6455. Las versiones más recientes de Chrome y Chrome para Android son totalmente compatibles con RFC6455, incluidos los mensajes binarios. Además, Firefox será compatible con la versión 11, e Internet Explorer, con la versión 10. Aún puedes usar versiones anteriores del protocolo, pero no se recomienda, ya que se sabe que son vulnerables. Si tienes implementaciones de servidor para versiones anteriores del protocolo WebSocket, te recomendamos que lo actualices a la versión más reciente.

Casos de uso

Usa WebSocket siempre que necesites una conexión de latencia realmente baja y casi en tiempo real entre el cliente y el servidor. Ten en cuenta que esto puede implicar que debas replantearte cómo compilar tus aplicaciones del servidor con un nuevo enfoque en tecnologías como las colas de eventos. Estos son algunos ejemplos de casos de uso:

  • Juegos multijugador en línea
  • Aplicaciones de chat
  • Marcador de deportes en vivo
  • Actualización en tiempo real de los feeds de redes sociales

Demostraciones

Referencias