Introducción a los WebSockets: incorporación de sockets a la Web

HTML5 Rocks

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

Internet se ha creado en gran parte a partir del llamado paradigma solicitud/respuesta de HTTP. Un cliente carga una página web y no ocurre nada hasta que el usuario hace clic en la página siguiente. Allá por 2005, AJAX empezó a hacer que Internet pareciera más dinámico. Aún así, todas las comunicaciones HTTP eran dirigidas por el cliente, lo que requería la interacción del usuario o hacía necesario preguntarle periódicamente cada vez que se cargaban nuevos datos del servidor.

Hace ya algún tiempo que existen tecnologías que permiten al servidor enviar datos al cliente en el mismo momento que detecta que hay nuevos datos disponibles. Se conocen 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 denomina Long Polling (sondeo largo). Con el Long Polling, el cliente abre una conexión HTTP con el servidor, el cual la mantiene abierta hasta que se envíe una respuesta. Cada vez que el servidor tenga datos nuevos, enviará la respuesta (otras técnicas implican Flash, solicitudes XHR de varias partes y los denominados htmlfiles). El Long Polling y las otra técnicas funcionan bastante bien. Se utilizan todos los días en aplicaciones como el chat de Gmail.

Sin embargo, todas estas soluciones comparten un problema: el exceso del HTTP, lo que no las hace aptas para aplicaciones de baja latencia. Piensa por ejemplo en los juegos multijugador de tiro en primera persona del navegador o en cualquier otro juego online con un componente en tiempo real.

Introducción a los WebSockets: incorporación de sockets a la Web

La especificación WebSocket define un API que establece conexiones "socket" entre un navegador web y un servidor. Dicho con otras palabras: existe una conexión persistente entre el cliente y el servidor, y ambas partes pueden empezar a enviar datos en cualquier momento.

Introducción

Para abrir una conexión WebSocket, solo tienes que ejecutar el constructor WebSocket:

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

Observa los elementos ws:. Este es el nuevo esquema de URL para las conexiones WebSocket. También hay wss: para conexiones WebSocket seguras, de la misma forma que se utiliza https: para las conexiones HTTP seguras.

Adjuntar inmediatamente varios controladores a la conexión te permite saber cuándo está abierta la conexión, cuándo ha recibido mensajes entrantes o cuándo hay un error.

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

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

// 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

Cuando se establezca una conexión con el servidor (cuando el evento open se active), podemos empezar a enviar datos al servidor con el método send('your message') en el objeto de conexión. Antes solo se admitían cadenas, pero la última especificación también permite 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);

De la misma forma, el servidor puede enviarnos mensajes en cualquier momento. Cada vez que esto ocurra, se activa la devolución de llamada onmessage. La devolución de llamada recibe un objeto "event", lo que permite acceder al mensaje actual mediante la propiedad data.

La última especificación de WebSocket también permite recibir mensajes binarios. Los marcos binarios se pueden recibir en formato Blob o ArrayBuffer. Para especificar el formato del binario recibido, establece la propiedad binaryType del objeto WebSocket en "blob" o en "arraybuffer". El formato predeterminado es "blob" (no es necesario alinear el parámetro binaryType al enviar).

// 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 recién incorporada a WebSocket son las extensiones. Las extensiones permiten enviar marcos comprimidos, multiplexados, etc. Para enviar extensiones que acepte el servidor, examina la propiedad de las extensiones del objeto WebSocket después de un evento "open". A fecha de febrero de 2012 no se ha publicado ninguna especificación oficial de extensiones.

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

Comunicaciones de origen cruzado

Las comunicaciones de origen cruzado son un protocolo moderno creado directamente para WebSocket. Aunque sigue siendo necesario que te asegures de que solo te comunicas con clientes y servidores de confianza, WebSocket permite la comunicación entre las partes de cualquier dominio. El servidor decide si desea poner su servicio a disposición de todos los clientes o solo de los que están alojados en un grupo de dominios bien definidos.

Servidores proxy

Toda nueva tecnología trae consigo una nueva serie de problemas. En el caso de WebSocket, se trata de la compatibilidad con los servidores proxy que median las conexiones HTTP en la mayoría de las redes corporativas. El protocolo WebSocket utiliza el sistema de actualización de HTTP (normalmente utilizado para HTTP/SSL) para "actualizar" una conexión HTTP a una conexión WebSocket. A algunos servidores proxy no les gusta esto y cancelan la conexión. Por tanto, aunque un cliente dado utilice el protocolo WebSocket, es posible que no pueda establecer una conexión. Esto hace que la siguiente sección sea aún más importante :)

Uso actual de WebSockets

WebSocket sigue siendo una tecnología joven y no está implementada completamente en todos los navegadores. Sin embargo, actualmente puedes utilizar WebSocket con las bibliotecas que usan una de las alternativas mencionadas anteriormente si WebSocket no está disponible. Una biblioteca que se ha hecho muy popular en este dominio es socket.io, que viene con una implementación cliente-servidor del protocolo e incluye alternativas (socket.io no es compatible con mensajes binarios a fecha de febrero de 2012). También hay soluciones comerciales como PusherApp, que se puede integrar fácilmente en cualquier entorno web con un API HTTP que envíe mensajes WebSocket a los clientes. Debido a la solicitud HTTP extra, siempre habrá un exceso en comparación con el WebSocket puro.

En el servidor

Al utilizar WebSocket, se crea un patrón de uso completamente nuevo para las aplicaciones de servidor. Aunque las pilas de servidor tradicionales como LAMP están diseñadas a partir del ciclo de solicitud-respuesta de HTTP, a menudo dan problemas si hay muchas conexiones WebSocket abiertas. Mantener un gran número de conexiones abiertas de forma simultánea requiere una arquitectura capaz de recibir un alto nivel de concurrencia sin consumir mucho rendimiento. Estas arquitecturas suelen estar basadas en subprocesos o el denominado sistema E/S asíncrono.

Implementaciones en el servidor

Versiones de protocolos

El protocolo de transmisión (un protocolo de enlace y la transferencia de datos entre cliente y servidor) para WebSocket ahora es RFC6455. Las últimas versiones de Chrome y Chrome para Android son completamente compatibles con RFC6455, incluidos los mensajes binarios. Además, Firefox es compatible a partir de la versión 11 e Internet Explorer a partir de la versión 10. Puedes seguir utilizando versiones más antiguas del protocolo, pero no es recomendable, ya que se ha demostrado que son vulnerables. Si has implementado versiones anteriores del protocolo WebSocket en servidores, te recomendamos que las actualices a la última versión.

Casos prácticos

Utiliza WebSocket siempre que necesites una conexión casi a tiempo real y de latencia baja entre el cliente y el servidor. Ten en cuenta que esto podría significar tener que replantearte cómo has desarrollado tus aplicaciones de servidor, adoptando un nuevo enfoque en tecnologías como las colas de eventos. Estos son algunos ejemplos de casos prácticos:

  • Juegos online multijugadores
  • Aplicaciones de chat
  • Rotativos de información deportiva
  • Actualizaciones en tiempo real de las actividades de tus amigos

Demos

Referencias

Comments

0