HTML5: WebWorkers y WebSockets

Webworkers

Los WebWorkers nos permiten ejecutar procesos en paralelo sin impactar en el rendimiento del navegador.

Sin Web Workers por ejemplo, las tareas JavaScript pueden bloquear otros JavaScript de la página y por tanto el Navegador se puede colgar.

JavaScript es un entorno de subproceso único, es decir, que no se pueden ejecutar varias secuencias de comandos al mismo tiempo. Si tenemos que manipular eventos, manejar grandes cantidades de datos y manipular objetos DOM, no podemos hacerlo de forma simultánea debido a las limitaciones de JavaScript. La ejecución de comandos se realiza en un único subproceso.

Con Web Workers:

  • Podemos realizar procesos background
  • Podemos ejecutar procesos de forma paralela

Los Web Workers permiten realizar acciones con tiempos de ejecución muy largos para gestionar tareas pesadas en segundo plano, pero sin bloquear la interfaz de usuario.

Podemos usarlos para multitud de operaciones que en la actualidad consumirían recursos de navegación.

Tienen algunas limitaciones, ya que se ejecutan en una SANDBOX que no permite acceder a todos los recursos disponibles a los JavaScript normales.

  • No se puede acceder al objet window (window.document)
  • No hay acceso directo a la página WEB ni al DOM API
  • Se puede usar las funciones de timing
  • Tienen otras limitaciones de acceso.
  • Debemos comunicarnos con ellos a través de mensajes.

Para trabajar con WebWorkers, lo primero es comprobar que tenemos soporte para ellos.

Ejemplo

//Comprobar que nuestro navegador soporta WebWorkers.
if (typeof(Worker) !== "undefined") {
    document.getElementById("support").innerHTML =“ A tu navegador no le gustan los WebWorkers”;
}

Crear un WebWorker

Los Web Workers se ejecutan en un subproceso aislado. Por tanto, es necesario que el código que ejecutan se encuentre en un archivo independiente.

Sin embargo, antes de hacer esto, lo primero que hay que hacer es crear un nuevo objeto Worker en la página principal. El constructor debe tener el nombre de la secuencia de comandos del Worker.

Los pasos para crear un WebWorker son los siguientes:

  • Creamos un objeto WebWorker y le pasamos el fichero JavaScript a ejecutar. Si el archivo especificado existe, el navegador generará un nuevo subproceso que lo descargará de forma asíncrona. El Worker no empezará a trabajar hasta que el archivo se haya descargado al completo.
var worker = new Worker('fichero.js');

 

  • Lo arrancamos con el método PostMessage
worker.postMessage();

Trabajar con un WebWorker

El trabajo entre un Worker y su programa principal se hace mediante eventos. Se usa el método postMessage().

Este método hacepta normalmente una cadena de caracteres, aunque muchos  Navegadores modernos soportan la posibilidad de incluir in objeto JSON.

En el programa principal debemos activar un Listener que responda al evento “message” y que capture lo que pueda llegar desde el worker. Por ejemplo:

worker.addEventListener('message', f1(e) {
  console.log('Respuesta del Worker: ', e.data);
}, false);

 

Como vemos se puede acceder a los datos del evento a través del parámetro de la función f1. En este caso nos limitamos a sacarlo por el log.

Por otro lado, en el worker debemos incluir el Listener para que esté pendiente de recepcionar los mensajes que le llegan y responder a ellos. Por ejemplo algo como el siguiente código

self.addEventListener('message', f1_worker(e) {
  self.postMessage(e.data);
}, false);

 

Los mensajes que van del programa principal a los Workers y viceversa no son compartidos, en realidad se copias. Los objetos transferidos se serializan y des-serializan en cada fase del proceso.

Por lo tanto, el Worker no comparte la misma instancia que el programa principal, por lo que el resultado final es la creación de un duplicado en cada transferencia.

Para parar el woker usamos dos posibles opciones:

  • worker.terminate() desde la página principal
  • self.close()dentro del propio Worker.

El siguiente ejemplo muestra el ciclo de vida de una llamada a un  WebWorker desde un programa principal.

//Crear un nuevo worker
w1 = new Worker(“trabajo_worker.js"); 
//Mandar un mensaje al worker
w1.postMessage(“Empieza a currar hombre”); 
//Añadir un event listener
w1.addEventListener("message", messageHandler, true); 
//Procesar mensajes entrantes
function messageHandler(e) {
    // procesamos mensajes del worker
} 
//Gestión de errores
w1.addEventListener("error", errorHandler, true); 
//Parar el worker
w1.terminate();

 

 

El siguiente ejemplo muestra un programa principal que llama a un webworker que devuelve el dato que se le pasa en mayúsculas

Programa principal:

<script>
//Crear un nuevo worker
w1 = new Worker("trabajo_worker.js"); 
//Mandar un mensaje al worker
w1.postMessage("pedro"); 
//Añadir un event listener
w1.addEventListener("message", m1, true); 
//Procesar mensajes entrantes
function m1(e) {
    // procesamos mensajes del worker
    console.log('Ese nombre en mayúsculas es: ', e.data);
} 
//Gestión de errores
w1.addEventListener("error", errorHandler, true); 
//Parar el worker
w1.terminate();
</script>

 

El werworker sería el siguiente código:

self.addEventListener('message'       f1(e) {
self.postMessage(e.data.toUpperCase());
    }, false); 

 

Otros ejemplos de uso. Se puede invocar a un worker dentro de otro worker, es decir podemos tener subworkers. A la hora de utilizar los Subworkers es necesario tener en cuenta los siguientes aspectos:

  • Los Subworkers deben situados en el mismo origen que la página principal.
  • La resolución de las URI de los Subworkers está relacionada con la ubicación de su Worker principal.

 

//Trabajar  con elWorkerfunction messageHandler(e) {    postMessage(“mensaje del worker: ” + e.data);} addEventListener(“message”, messageHandler, true);//Podemos llamar a un worker dentro de otro workervar sub1 = new Worker(“subWorker.js”);

Gestión de errores

Al igual que cualquier programa JavaScript, es necesario gestionar los errores que se producen en los Web Workers.

Podemos hacerlo controlando el evento ErrorEvent. La interfaz incluye tres propiedades útiles para descubrir la causa del error:

  • Filename: el nombre del fichero  Worker que causó el error
  • Lineno: el número de línea donde se produjo el error
  • Message: descripción del error

A continuación, se muestra un ejemplo sobre cómo configurar un gestor de eventos onerror para controlar el error.

 <output id=”error” style=”color: red;”></output>

<output id=”result”></output>

<script>

function onError(e) {

document.getElementById(‘error’).textContent = [

'ERROR: Línea ', e.lineno, ' en ', e.filename, ': ', e.message].join(”);

}

function onMsg(e) {

document.getElementById(‘result’).textContent = e.data;

}

var worker = new Worker(‘workerError.js’);

worker.addEventListener(‘message’, onMsg, false);

worker.addEventListener(‘error’, onError, false);

worker.postMessage(); // Arrancar el worker sin un mensaje.

</script>

El worker asociado sería el siguiente:

self.addEventListener('message', function(e) {
  postMessage(1/x); };

 

El resultado sería aparecido al siguiente

 ERROR: Línea 2 en workerError.js: Uncaught ReferenceError: x is not defined

Websocket

Las aplicaciones actuales demandan una latencia cercana a 0. También necesitan comunicación bidireccional. En principio, la funcionalidad actual está basada en la arquitectura request/response de HTTP.

Por ejemplo, un cliente carga una página WEB y no sucede nada hasta que el usuario pulsa algo para pasar a la página siguiente. La incorporación de AJAX permitió disponer de páginas más dinámicas. De todas formas es necesario interactuar y cargar nuevos datos del servidor de forma periódica.

HTTP  es half-duplex, de forma que la información de Header se manda en cada petición y respuesta HTTP lo que produce una sobrecarga indeseada

Si observamos un request HTTP típico

GET /PollingStock//PollingStock HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
Referer: http://localhost:8080/PollingStock/
Cookie: showInheritedConstant=false; showInheritedProtectedConstant=false; showInheritedProperty=false; showInheritedProtectedProperty=false; showInheritedMethod=false; showInheritedProtectedMethod=false; showInheritedEvent=false; showInheritedStyle=false; showInheritedEffect=false;

Y un response

HTTP/1.x 200 OK
X-Powered-By: Servlet/2.5
Server: Sun Java System Application Server 9.1_02
Content-Type: text/html;charset=UTF-8
Content-Length: 321
Date: Sat, 07 Nov 2009 00:32:46 GMT

El Overhead innecesario en este ejemplo alcanza 871 bytes. Puede ser mayor y lo peor es que no escala.

WebSocket define una API para establecer conexiones de tipo “socket” entre un navegador y el servidor. Por tanto existe una conexión persistente entre el cliente y el servidor y ambas partes pueden enviar datos en cualquier momento.

Algunos ejemplos de su uso pueden ser las siguientes:

  • Aplicaciones financieras
  • Sociales
  • Juegos online
  • Otras

 

El websocket tiene las siguientes características

  • Protocolos W3C API y IETF Protocol
  • Full-duplex, single socket
  • Comunican las páginas con un Servidor remoto
  • Atraviesa firewalls, proxies, y routers
  • Comparte el puerto con el HTTP
  • Reduce de forma drástica el overhead

La conexión se establece a través del upgrade del protocolo HTTP al protocolo WebSocket usando la misma conexión

Una vez actualizado, los data frames de  WebSocket se pueden enviar de un lado a otro entre el cliente y el servidor en modo full-duplex.

Las Frames se pueden enviar en modo full-duplex en cualquier dirección y en cualquier momento.

Creación de un websocket

Como siempre, lo primero que debemos averiguar es si disponemos de soporte para WebSocket

//Checking for browser support
if (window.WebSocket) {
    document.getElementById("support").innerHTML =
     "HTML5 WebSocket is supported";
  } else {
     document.getElementById("support").innerHTML =
      "HTML5 WebSocket is not supported";
  }

Se puede crear una conexión Websocket llamando al constructor correspondiente:

var con1 = new WebSocket('ws://ejemplo.curso.com', ['soap']);

Se usa el esquema “ws” para conexiones de este tipo. También podemos observar los subprotocolos que se han asignado a la conexión que debe ser uno de los registrados y permitidos por el estándar. Ahora mismo solo se soporta “soap”.

Podemos usar distintos eventos para controlar la gestión de la conexión:

  • Onopen: cuando se establece la conexión
  • Onerror: cuando se produce un error
  • Onmessage: cuando se recibe un mensaje del servidor

Algunos ejemplos:

Con1.onerror = f1(error) {
  console.log('Se ha producido el error:' + error);
}; 
Con1.onmessage = f1 (e) {
  console.log('Mensaje del servidor: ' + e.data);
};

 

Trabajar con WEBSOCKET

Una vez establecida la conexión con el servidor, podemos enviar un mensaje mediante el método send. Tiene el siguiente formato:

conexion.send('your message');

Aunque por ahora solo se soportan cadenas, algunos navegadores están empezando a trabajar con objetos lo que permite una mayor versatilidad.

En el siguiente ejemplo vemos como mandar un mensaje cuando hemos establecido la conexión:

Con1.onopen = f1 () {
  Con1.send('Esto es una prueba'); // Mandar el mensaje
};

Por supuesto, el servidor puede también mandar mensajes. Para recibirlos habilitamos el método callbak “onmessage” como hemos visto en el punto anterior. Este método recibe un objeto de tipo evento con los datos asociados.

Un ejemplo completo de gestión de websocket

//Crear WebSocket
var sock1 = new WebSocket("ws://www.websocket.org”); 
// Asociar un Listener
Sock1.onopen = function(evt) {
    alert("Conexión abierta…”);
}; 
//Capturar mensajes del ervisor
Sock1.onmessage = function(evt) {
    alert(“Received message: “ + evt.data);
}; 
//Al cerrar la conexión
Sock1.onclose = function(evt) {
    alert(“Conexión cerrada…”);
}; 
// Mandar datos
Sock1.send(“Saludos!”); 
//Cerramos el WebSocket
Sock1.close();

 

 

 

 

 

 

 

18
jun 2013
POSTED BY
POSTED IN Tic-tek
DISCUSSION 0 Comments

Comments are closed.