Il problema: connessioni client-server e server-client a bassa latenza
Il web è stato in gran parte costruito attorno al cosiddetto paradigma richiesta/risposta di HTTP. Un client carica una pagina web e non succede nulla finché l'utente non fa clic sulla pagina successiva. Intorno al 2005, AJAX ha iniziato a rendere il web più dinamico. Tuttavia, tutte le comunicazioni HTTP erano gestite dal client, il che richiedeva l'interazione dell'utente o il polling periodico per caricare nuovi dati dal server.
Le tecnologie che consentono al server di inviare dati al client nel momento in cui sa che sono disponibili nuovi dati sono in circolazione da un po' di tempo. Hanno nomi come "Push" o "Comet". Una delle compromissioni più comuni per creare l'illusione di una connessione avviata da un server è chiamata "long polling". Con il polling lungo, il client apre una connessione HTTP al server che la mantiene aperta fino all'invio della risposta. Ogni volta che il server ha effettivamente nuovi dati, invia la risposta (altre tecniche prevedono Flash, richieste XHR multipart e i cosiddetti file HTML). Il polling lungo e le altre tecniche funzionano abbastanza bene. Li usi ogni giorno in applicazioni come la chat di Gmail.
Tuttavia, tutte queste soluzioni alternative condividono un problema: comportano l'overhead di HTTP, il che non le rende adatte per le applicazioni a bassa latenza. Ad esempio, sparatutto in prima persona multiplayer nel browser o qualsiasi altro gioco online con un componente in tempo reale.
Introduzione a WebSocket: portare le socket sul web
La specifica WebSocket definisce un'API che stabilisce connessioni "socket" tra un browser web e un server. In parole povere: esiste una connessione persistente tra il client e il server ed entrambe le parti possono iniziare a inviare dati in qualsiasi momento.
Per iniziare
Puoi aprire una connessione WebSocket semplicemente chiamando il costruttore WebSocket:
var connection = new WebSocket('ws://html5rocks.websocket.org/echo', ['soap', 'xmpp']);
Osserva l'ws:
. Questo è il nuovo schema di URL per le connessioni WebSocket. Esiste anche wss:
per la connessione WebSocket sicura, nello stesso modo in cui https:
viene utilizzato per le connessioni HTTP sicure.
L'associazione immediata di alcuni gestori eventi alla connessione ti consente di sapere quando la connessione è aperta, se sono stati ricevuti messaggi in arrivo o se si è verificato un errore.
Il secondo argomento accetta sottoprotocolli facoltativi. Può essere una stringa o un array di stringhe. Ogni stringa deve rappresentare il nome di un sottoprotocollo e il server accetta solo uno dei sottoprotocolli passati nell'array. Il sottoprotocollo accettato può essere determinato accedendo alla proprietà protocol
dell'oggetto WebSocket.
I nomi di sottoprotocollo devono essere uno dei nomi di sottoprotocollo registrati nel registro IANA. Al momento, a febbraio 2012 è stato registrato un solo nome di sottoprotocollo (sapone).
// 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);
};
Comunicazione con il server
Non appena abbiamo una connessione al server (quando viene attivato l'evento open
), possiamo iniziare a inviare dati al server utilizzando il metodo send('your message')
sull'oggetto connessione. In precedenza supportava solo le stringhe, ma nella versione più recente può inviare anche messaggi binari. Per inviare dati binari, puoi utilizzare l'oggetto 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);
Allo stesso modo, il server potrebbe inviarci messaggi in qualsiasi momento. Ogni volta che si verifica questa situazione, viene attivato il callback onmessage
. Il callback riceve un oggetto evento e il messaggio effettivo è accessibile tramite la proprietà data
.
WebSocket può anche ricevere messaggi binari nell'ultima versione della specifica. I frame binari possono essere ricevuti in formato Blob
o ArrayBuffer
. Per specificare il formato del file binario ricevuto, imposta la proprietà binaryType dell'oggetto WebSocket su "blob" o "arraybuffer". Il formato predefinito è "blob". Non è necessario allineare il parametro binaryType al momento dell'invio.
// 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
};
Un'altra funzionalità di WebSocket appena aggiunta sono le estensioni. Con le estensioni, sarà possibile inviare frame compressi, multiplexati e così via. Puoi trovare le estensioni accettate dal server esaminando la proprietà estensioni dell'oggetto WebSocket dopo l'evento di apertura. A febbraio 2012 non è ancora stata pubblicata una specifica ufficiale per le estensioni.
// Determining accepted extensions
console.log(connection.extensions);
Comunicazione cross-origin
Essendo un protocollo moderno, la comunicazione multiorigine è integrata direttamente in WebSocket. Anche se devi comunque assicurarti di comunicare solo con client e server attendibili, WebSocket consente la comunicazione tra le parti su qualsiasi dominio. Il server decide se rendere disponibile il proprio servizio a tutti i client o solo a quelli che si trovano in un insieme di domini ben definiti.
Server proxy
Ogni nuova tecnologia comporta una nuova serie di problemi. Nel caso di WebSocket, è la compatibilità con i server proxy che mediano le connessioni HTTP nella maggior parte delle reti aziendali. Il protocollo WebSocket utilizza il sistema di upgrade HTTP (normalmente utilizzato per HTTP/SSL) per eseguire l'upgrade di una connessione HTTP a una connessione WebSocket. Alcuni server proxy non lo accettano e interrompono la connessione. Pertanto, anche se un determinato client utilizza il protocollo WebSocket, potrebbe non essere possibile stabilire una connessione. Questo rende ancora più importante la sezione successiva :)
Utilizzare WebSocket oggi
WebSocket è ancora una tecnologia giovane e non completamente implementata in tutti i browser. Tuttavia, puoi utilizzare WebSocket oggi con librerie che utilizzano uno dei metodi di riserva sopra menzionati quando WebSocket non è disponibile. Una libreria che è diventata molto popolare in questo dominio è socket.io, che viene fornita con un client e un'implementazione server del protocollo e include i fallback (socket.io non supporta ancora la messaggistica binaria a partire da febbraio 2012). Esistono anche soluzioni commerciali come PusherApp che possono essere facilmente integrate in qualsiasi ambiente web fornendo un'API HTTP per inviare messaggi WebSocket ai client. A causa della richiesta HTTP aggiuntiva, ci sarà sempre un overhead aggiuntivo rispetto a WebSocket puro.
Lato server
L'utilizzo di WebSocket crea un nuovo modello di utilizzo per le applicazioni lato server. Sebbene gli stack di server tradizionali come LAMP siano progettati in base al ciclo di richiesta/risposta HTTP, spesso non gestiscono bene un numero elevato di connessioni WebSocket aperte. Mantenere aperte contemporaneamente un numero elevato di connessioni richiede un'architettura che supporti una concorrenza elevata a un costo prestazionale ridotto. Queste architetture sono in genere progettate in base a threading o alla cosiddetta I/O non bloccante.
Implementazioni lato server
- Node.js
- Java
- Ruby
- Python
- Erlang
- C++
- .NET
Versioni del protocollo
Il protocollo Wire (un handshake e il trasferimento di dati tra client e server) per WebSocket ora è RFC6455. Le versioni più recenti di Chrome e Chrome per Android sono completamente compatibili con RFC6455, inclusa la messaggistica binaria. Inoltre, Firefox sarà compatibile con la versione 11 e Internet Explorer con la versione 10. Puoi comunque utilizzare le versioni precedenti del protocollo, ma non è consigliabile perché sono note per essere vulnerabili. Se hai implementazioni del server per versioni precedenti del protocollo WebSocket, ti consigliamo di eseguire l'upgrade alla versione più recente.
Casi d'uso
Utilizza WebSocket ogni volta che hai bisogno di una connessione quasi in tempo reale con una latenza molto bassa tra il client e il server. Tieni presente che ciò potrebbe comportare un ripensamento del modo in cui crei le tue applicazioni lato server concentrandoti su tecnologie come le code di eventi. Ecco alcuni casi d'uso di esempio:
- Giochi online multiplayer
- Applicazioni di chat
- Ticker sportivo in tempo reale
- Aggiornamenti in tempo reale degli stream social
Demo
- Plink
- Dipingi con me
- Pixelatr
- A tratti
- Crocifisso online multiplayer di massa
- Server di ping (utilizzato negli esempi precedenti)
- Esempio HTML5demos