Introdução aos WebSockets: como levar soquetes para a Web

O problema: conexões cliente-servidor e servidor-cliente de baixa latência

A Web foi construída em grande parte em torno do chamado paradigma de solicitação/resposta do HTTP. Um cliente carrega uma página da Web e nada acontece até que o usuário clique na próxima página. Por volta de 2005, o AJAX começou a tornar a Web mais dinâmica. Ainda assim, toda a comunicação HTTP era controlada pelo cliente, o que exigia interação do usuário ou sondagem periódica para carregar novos dados do servidor.

As tecnologias que permitem que o servidor envie dados ao cliente no momento em que sabe que novos dados estão disponíveis já existem há algum tempo. Eles têm nomes como "Push" ou "Comet". Uma das formas mais comuns de criar a ilusão de uma conexão iniciada pelo servidor é chamada de sondagem longa. Com a sondagem longa, o cliente abre uma conexão HTTP com o servidor, que a mantém aberta até enviar a resposta. Sempre que o servidor tem novos dados, ele envia a resposta (outras técnicas envolvem solicitações Flash, XHR multipart e os chamados htmlfiles). A sondagem longa e as outras técnicas funcionam muito bem. Você os usa todos os dias em aplicativos como o Chat do Gmail.

No entanto, todas essas soluções alternativas têm um problema em comum: elas carregam a sobrecarga do HTTP, o que as torna inadequadas para aplicativos de baixa latência. Pense em jogos de tiro em primeira pessoa multiplayer no navegador ou em qualquer outro jogo on-line com um componente em tempo real.

Introdução ao WebSocket: como levar soquetes para a Web

A especificação do WebSocket define uma API que estabelece conexões de "soquete" entre um navegador da Web e um servidor. Em outras palavras, há uma conexão persistente entre o cliente e o servidor, e ambas as partes podem começar a enviar dados a qualquer momento.

Primeiros passos

Para abrir uma conexão WebSocket, basta chamar o construtor WebSocket:

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

Observe o ws:. Este é o novo esquema de URL para conexões WebSocket. Também há wss: para conexão WebSocket segura, da mesma forma que https: é usado para conexões HTTP seguras.

Anexar alguns manipuladores de eventos imediatamente à conexão permite que você saiba quando a conexão é aberta, recebe mensagens recebidas ou apresenta um erro.

O segundo argumento aceita subprotocolos opcionais. Pode ser uma string ou uma matriz de strings. Cada string precisa representar um nome de subprotocolo, e o servidor aceita apenas um dos subprotocolos transmitidos na matriz. O subprotocolo aceito pode ser determinado acessando a propriedade protocol do objeto WebSocket.

Os nomes de subprotocolo precisam ser um dos nomes de subprotocolo registrados no registro da IANA. Atualmente, há apenas um nome de subprotocolo (soap) registrado desde fevereiro 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);
};

Comunicação com o servidor

Assim que tivermos uma conexão com o servidor (quando o evento open for acionado), poderemos começar a enviar dados para o servidor usando o método send('your message') no objeto de conexão. Antes, ele só aceitava strings, mas, na especificação mais recente, também pode enviar mensagens binárias. Para enviar dados binários, use o objeto Blob ou 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);

Da mesma forma, o servidor pode nos enviar mensagens a qualquer momento. Sempre que isso acontece, o callback onmessage é acionado. O callback recebe um objeto de evento, e a mensagem real pode ser acessada pela propriedade data.

O WebSocket também pode receber mensagens binárias na especificação mais recente. Os frames binários podem ser recebidos no formato Blob ou ArrayBuffer. Para especificar o formato do binário recebido, defina a propriedade binaryType do objeto WebSocket como "blob" ou "arraybuffer". O formato padrão é "blob". Não é necessário alinhar o parâmetro binaryType no envio.

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

Outro recurso adicionado recentemente ao WebSocket são as extensões. Com as extensões, é possível enviar frames compactados, multiplexados etc. Para encontrar as extensões aceitas pelo servidor, examine a propriedade "extensions" do objeto WebSocket após o evento "open". Até fevereiro de 2012, não havia uma especificação de extensões publicada oficialmente.

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

Comunicação entre origens

Por ser um protocolo moderno, a comunicação entre origens é integrada ao WebSocket. Embora você ainda precise se comunicar apenas com clientes e servidores em que confia, o WebSocket permite a comunicação entre partes em qualquer domínio. O servidor decide se disponibiliza o serviço para todos os clientes ou apenas para aqueles que residem em um conjunto de domínios bem definidos.

Servidores proxy

Toda nova tecnologia traz novos problemas. No caso do WebSocket, é a compatibilidade com servidores proxy que mediam conexões HTTP na maioria das redes corporativas. O protocolo WebSocket usa o sistema de upgrade HTTP (normalmente usado para HTTP/SSL) para "fazer upgrade" de uma conexão HTTP para uma conexão WebSocket. Alguns servidores proxy não aceitam isso e encerram a conexão. Portanto, mesmo que um determinado cliente use o protocolo WebSocket, talvez não seja possível estabelecer uma conexão. Isso torna a próxima seção ainda mais importante :)

Use WebSockets hoje mesmo

O WebSocket ainda é uma tecnologia recente e não está totalmente implementado em todos os navegadores. No entanto, é possível usar o WebSocket hoje com bibliotecas que usam um dos substitutos mencionados acima sempre que o WebSocket não estiver disponível. Uma biblioteca que se tornou muito conhecida nesse domínio é a socket.io, que vem com uma implementação de cliente e servidor do protocolo e inclui substitutos (a socket.io ainda não oferece suporte a mensagens binárias, de acordo com dados de fevereiro de 2012). Também há soluções comerciais, como o PusherApp, que podem ser facilmente integradas a qualquer ambiente da Web fornecendo uma API HTTP para enviar mensagens WebSocket aos clientes. Devido à solicitação HTTP extra, sempre haverá uma sobrecarga extra em comparação com o WebSocket puro.

O lado do servidor

O uso do WebSocket cria um padrão de uso totalmente novo para aplicativos do lado do servidor. Embora as pilhas de servidores tradicionais, como LAMP, sejam projetadas em torno do ciclo de solicitação HTTP/resposta, elas geralmente não lidam bem com um grande número de conexões WebSocket abertas. Manter um grande número de conexões abertas ao mesmo tempo requer uma arquitetura que receba alta simultaneidade com um baixo custo de desempenho. Essas arquiteturas geralmente são projetadas em torno de threading ou da chamada E/S sem bloqueio.

Implementações do lado do servidor

Versões do protocolo

O protocolo de transmissão (um handshake e a transferência de dados entre cliente e servidor) para WebSocket agora é o RFC6455. As versões mais recentes do Chrome e do Google Chrome para Android são totalmente compatíveis com a RFC6455, incluindo mensagens binárias. Além disso, o Firefox será compatível com a versão 11, e o Internet Explorer com a versão 10. Ainda é possível usar versões mais antigas do protocolo, mas isso não é recomendado, já que elas são vulneráveis. Se você tiver implementações de servidor para versões mais antigas do protocolo WebSocket, recomendamos que faça upgrade para a versão mais recente.

Casos de uso

Use o WebSocket sempre que precisar de uma conexão de latência realmente baixa e quase em tempo real entre o cliente e o servidor. Isso pode envolver repensar como você cria seus aplicativos do lado do servidor com um novo foco em tecnologias como filas de eventos. Alguns exemplos de casos de uso:

  • Jogos multiplayer on-line
  • Aplicativos de chat
  • Ticker de esportes ao vivo
  • Atualização em tempo real de feeds de redes sociais

Demonstrações

Referências