Informe Proyecto Programación de Sistemas: WebSocket

Integrantes

Resumen

Este informe presenta el trabajo realizado para el proyecto WebSocket en el contexto del ramo de Programación de Sistemas (ELO330) con el profesor Sr. Agustín González. Aquí se detallan tanto el protocolo como la API de WebSocket, además de presentarse los programas diseñados especialmente para la demostración de esta simple pero poderosa tecnología.

Introducción

Desde hace bastantes años ha sido posible la comunicación entre cliente y servidor utilizando el protocolo HTTP. Sin embargo, esta comunicación si bien es full duplex y bidireccional, no permite que el servidor le envíe contenido por cuenta propia al cliente. Esto ha sido "solucionado" en múltiples oportunidades a través de aplicaciones que simulan (desde el punto de vista del usuario y/o programador) una conexión única en la que el servidor puede enviar contenido de manera asíncronica al cliente. Si bien estas soluciones logran el cometido de comunicar al cliente con el servidor en la medida que cada uno lo requiera, no son soluciones escalables y mucho menos baratas o fáciles de implementar. La solución que nace es el protocolo de comunicación WebSocket, el cual busca crear un canal de comunicación único entre el par cliente-servidor y sobre el que estos se comunican de forma asincrónica y en la medida que cada uno necesite hacerlo. Para este trabajo se estudió el protocolo y a través del API en javascript de WebSocket y el servidor web Tornado, se implementaron dos aplicaciones que utilizan esta tecnología. La primera es la presentación misma a realizar en clases y la segunda es un juego que permite que diversos usuarios puedan mover cajas desde sus propios terminales de forma tal que los demás clientes puedan ver reflejado en sus terminales estos movimientos.

En lo que viene a continuación, se presentará el protocolo y API WebSocket, se dará a conocer la forma en que opera el protocolo, la construcción de los mensajes, el como opera la API y los eventos que están permitidos. Además se mostrará la forma en que se construyo el servidor y como opera a grandes rasgos Tornado. Finalmente se explicará lo esencial de los códigos, la lógica detrás de ellos y las bibliotecas usadas en la implementación de ambos programas. Siempre con el objetivo en mente de poder demostrar lo fácil que es implementar esta reciente y poderosa tecnología que promete cambiar la forma en que se diseñan aplicaciones y juegos.

Creación de una aplicación que utilice WebSocket

Background del trabajo: Protocolo y API WebSocket

Websocket nace como una una tecnología para proporcionar una comunicación bidireccional y full-duplex sobre un único canal TCP. Si bien ya existía HTTP, a pesar de que se hace sobre un canal TCP, los requerimientos son hechos solamente por el cliente y el servidor se limita exclusivamente a responder y enviar lo que se pide. WebSocket permite que tanto el cliente como el servidor puedan comunicarse indistintamente y de forma asincrónica, haciendo posible que tanto el servidor como el cliente puedan enviar información a medida de lo que el flujo de ejecución así lo vaya requiriendo. Websocket es un protocolo que actualmente se encuentra en proceso de revisión y estandarización por la IETF y además existe una API, la cual por su parte está siendo revisada y mejorada por la W3C.

El protocolo Websocket esta diseñado para ser implementado en clientes y servidores web, pero además puede ser usado con cualquier otro servidor y/o cliente. Se relaciona con HTTP en que inicia su vida como una conexión HTTP pero que luego realiza un upgrade para convertirse en conexión de websocket. Esto por un lado asegura compatibilidad con el mundo pre-WebSocket y por otro lado recicla la conexión TCP, no haciendo necesaria la creación de una nueva. Por omisión, la conexión se realiza sobre el puerto 80 para HTTP y 443 para HTTPS y el handshake es iniciado por el cliente y aceptado por el servidor. En cuanto a la finalización de la conexión, esta puede ser iniciada por cualquiera de las partes y quien recibe la petición de cierre hace un echo de la petición y cierra conexión.

La forma en que se gesta el requerimiento de conexión WebSocket es la siguiente:

  1. Se envía al servidor el requerimiento de HTTP, seguido de la petición de Upgrade a WebSocket
  2. Si el servidor entiende el protocolo, envía un mensaje de aceptación. Si no lo acepta envía un mensaje de rechazo.

El mensaje que envía el cliente, se encuentra ejemplificado en las siguientes lineas:

GET ws://echo.websocket.org/?encoding=text HTTP/1.1 
Origin: http://websocket.org 
Cookie: __utma=99as 
Connection: Upgrade 
Host: echo.websocket.org 
Sec-WebSocket-Key: uRovscZjNol/umbTt5uKmw== 
Upgrade: websocket 
Sec-WebSocket-Version: 13

Se puede ver que las 3 primeras lineas son iguales a HTTP, pero que luego en Connection pide un Upgrade que es interpretado por el servidor y con el cual crea la conexión. El resto del mensaje es interpretado por el servidor y con el cual se definen los protocolos de traspaso de información y a través del cual se verifica que el cliente y el servidor son quienes dicen ser.

Una vez recibido por el servidor el mensaje de conexión, si es capaz de comprender el protocolo y de crear la conexión, responde lo siguiente:

HTTP/1.1 101 WebSocket Protocol Handshake 
Date: Fri, 10 Feb 2012 17:38:18 GMT 
Connection: Upgrade 
Server: Kaazing Gateway 
Upgrade: WebSocket 
Access-Control-Allow-Origin: http://websocket.org 
Access-Control-Allow-Credentials: true 
Sec-WebSocket-Accept: rLHCkw/SKsO9GAH/ZSFhBATDKrU= 
Access-Control-Allow-Headers: content-type
En este mensaje, el servidor le indica el estado 101 que significa que la conexión ha sido creada exitosamente. Además le envía un string codificado en base64 para evitar suplantaciones. Luego de esto, la conexión HTTP se cierra y es remplazada por la conexión WebSocket sobre la misma conexión TCP/IP subyacente.

La API de websocket nació como una parte importante de HTML5 y con el tiempo y el explosivo crecimiento e interés en la tecnología, se separó oficialmente de la actualización de HTML para formar una propia especificación sostenida por el W3C y en vías de revisión y estandarización. Esta API reemplaza soluciones "parche" que buscaban lo mismo pero de formas mucho más arcaicas y rudimentarias. Para poder iniciar una conexión, basta con crear un objeto indicando la URL a la cual se desea conectar. Para poder recibir un mensaje, basta con asociar una serie de "event listeners" para poder manejar cada fase de la conexión de la forma en que sea necesaria y para poder enviar un mensaje basta llamar a "send" con el mensaje a enviar como único parámetro. Finalmente se llama a "close" para cerrar la conexión. A continuación, se muestran 3 "event listeners" y dos instrucciones que definen la API:

Explicación de las aplicaciones y sus respectivos códigos

Server

Para demostrar la utilización y las ventajas de WebSocket decidimos crear una aplicación que hiciera uso de sus principales características. La aplicación consiste en una pagina web en la que se muestran varios rectángulos de colores. Cada rectángulo representa a un usuario conectado a la pagina. Además los usuarios pueden arrastrar el rectángulo que les pertenece con el mouse. La pagina web debe mostrar el desplazamiento de los rectángulos de los demás usuarios.

Para poder intercambiar la información del movimiento de los bloques es necesario que un servidor WebSocket reenvíe la posición de cada rectángulo a los demás clientes. Inicialmente creímos que el servidor web Apache tenía soporte para WebSocket. Pero rápidamente nos dimos cuenta de que necesitaríamos otro servidor ya que actualmente Apache no soporta WebSocket. Finalmente decidimos usar el servidor web Tornado. Este servidor funciona como una biblioteca para Python. Por lo tanto es necesario crear un programa en Python que instancie los objetos necesarios para ofrecer el servicio que se desea.

La parte esencial de nuestro código de servidor es la siguiente:

application = tornado.web.Application([
	(r"/ws", SquareWS),
	(r'/(.*)', tornado.web.StaticFileHandler, {'path': './static', 'default_filename': "index.html"}),
])

if __name__ == "__main__":
	application.listen(80)
	tornado.ioloop.IOLoop.instance().start()

La primera parte de este código crea una instancia de la clase Application. Los parámetros que se pasan al constructor de esta clase indican a la aplicación que los requerimientos al path /ws se deben atender por una instancia de la clase SquareWS y los requerimientos que pidan cualquier otro path deben entregar los archivos que contiene la carpeta ./static. De esta forma los clientes pueden acceder a los archivos de la carpeta static. Y cuando un cliente quiere establecer una conexión WebSocket con el servidor este hace un requerimiento al path /ws.
La segunda parte del código hace que el servidor escuche el puerto 80 e inicie su ejecución.

Además para reenviar la posición de un cliente a los demás se ha sobrescrito el método on_message de la clase SquareWS. El método bcast lo hemos escrito para que envíe el mismo mensaje a todos los clientes conectados.

def on_message(self, message):
	self.bcast ('pos ' + message + ' ' + self.colorStr ())

Para coordinar a todos los clientes hemos ideado los siguientes prefijos para los mensajes intercambiados:

Cuadrados

Las partes relevantes del código del cliente son las siguientes:

websocket = new WebSocket("ws://kiroh.dyndns.org/ws");
websocket.onmessage = check_socket;

La primera linea del código crea una nueva instancia de la clase WebSocket. El argumento indica el protocolo y la dirección a la cual se desea conectar. Es interesante notar que la clase WebSocket es parte de las clases que vienen predefinidas en el intérprete javascript del navegador.

La segunda linea indica que el evento onmessage del objeto websocket debe ser atendido por la función check_socket. La función check_socket recibe mensajes del servidor y ejecuta diferentes acciones dependiendo del prefijo que tenga el mensaje. (init, pos, create, del)

Finalmente el cliente envía la posición en que se encuentra el mouse cada vez que este se mueve. Para enviar estos mensajes se usa la linea: websocket.send(message);

Ligas

El código completo de nuestro proyecto se encuentra en el siguiente enlace:

codigo_proyecto_websocket.zip

Conclusiones

Si bien WebSocket pudiera parecer una solución trivial, ha pasado mucho tiempo sin que exista una solución definitiva al problema de la comunicación asincrónica entre servidores y paginas web. Además WebSocket puede usarse en aplicaciones que no están relacionadas con la web permitiendo solucionar el problema de acceder a una conexión por socket a través del puerto 80 en ambientes en el que otros puertos podrían estar bloqueados.

Para demostrar las características de WebSocket hemos logrado programar el ejemplo de los Cuadrados. En el cual hemos mantenido una conexión por socket con flujos de datos bidireccionales, asincrónicos y a través del puerto 80 (mismo puerto en el que funciona el servidor web de nuestro ejemplo).

Creemos haber cumplido con el objetivo de la demostración. Sin embargo sería posible mejorar varios aspectos en el trabajo realizado tales como: el rendimiento de la aplicación enviando mensajes mas cortos y menos frecuentemente, y la interfaz gráfica de la aplicación.

Referencias