Para atender a essa necessidade, o WebSocket, um protocolo de comunicação popular finalizado em 2011, permite que sites enviem e recebam dados sem demora. Com o WebSockets, você pode construir jogos multiplayer, aplicativos de bate-papo e software de colaboração que funcionem…
A comunicação perfeita é uma obrigação na web moderna. À medida que as velocidades da internet aumentam, esperamos nossos dados em tempo real. Para atender a essa necessidade, o WebSocket, um protocolo de comunicação popular finalizado em 2011, permite que sites enviem e recebam dados sem demora. Com o WebSockets, você pode construir jogos multiplayer, aplicativos de bate-papo e software de colaboração que funcionam na web aberta.
Construí vários projetos com WebSockets antes de começar a me perguntar o que exatamente estava acontecendo sob o capô. Essa pergunta me levou a um buraco de coelho de pesquisa, e estou animado para compartilhar o que aprendi com você. Neste artigo, vamos:
explorar o problema Que os WebSockets resolvem e olham para alternativas
olhar sob o capô para entender como WebSockets funcionam
revisar algum código simples para um aplicativo alimentado pelo WebSocket
falar através de algumas implementações do mundo real
No final desta peça, você deve se sentir confortável discutindo como os WebSockets funcionam, e talvez até inspirado a usá-lo em seu próximo projeto.
Mensagens na internet
Vamos começar com o básico: o WebSocket é uma tecnologia que permite ao cliente estabelecer comunicação bidirecional (“full-duplex”) com o servidor. (Uma rápida revisão: o cliente é o aplicativo no computador de um usuário, e o servidor é o computador remoto que armazena o site e os dados associados).
A palavra-chave nessa definição é bidirecional:com o WebSocket, tanto o cliente quanto o servidor podem acionar a comunicação entre si, e ambos podem enviar mensagens, ao mesmo tempo. Por que isso é importante? Para apreciar totalmente o poder do WebSocket, vamos dar um passo atrás e olhar para algumas maneiras comuns de que os computadores podem obter dados do servidor.
Solicitação-resposta
Em um sistema HTTP tradicional, que é usado pela maioria dos sites hoje, um servidor web foi projetado para receber e responder a solicitações de clientes via mensagens HTTP. Essa comunicação tradicional só pode ser iniciada em uma direção: do cliente ao servidor. O código do servidor define que tipo de solicitações o servidor deve esperar e como responder a cada uma delas. Uma metáfora comum para este tipo de comunicação é uma cozinha de restaurante. É mais ou menos assim:
Você (o cliente) faz um pedido (uma solicitação HTTP) que um garçom leva para a cozinha (o servidor).
A cozinha recebe o pedido e verifica se eles sabem como fazê-lo (o servidor processa a solicitação).
Se a cozinha sabe como fazer o prato, eles preparam o pedido (o servidor busca dados de um banco de dados ou ativos do servidor).
Se a cozinha não reconhecer a ordem ou não for permitida a servi-la, eles enviam o garçom de volta com más notícias (se o servidor não sabe ou não responder à solicitação, ele envia de volta um código de erro, como um 404).
De qualquer forma, o garçom retorna para você (você recebe uma resposta HTTP com um código associado, como 200 OK ou 403 Proibido).
O importante a notar aqui é que a cozinha não tem ideia de quem vem a ordem. A maneira técnica de dizer isso é que “HTTP é apátrida”: trata cada novo pedido como completamente independente. Temos maneiras de contornar isso — por exemplo, os clientes podem enviar cookies que ajudam o servidor a identificar o cliente, mas as próprias mensagens HTTP são distintas e são lidas e cumpridas de forma independente.
Eis o problema: a cozinha não pode mandar um garçom para você; ele só pode dar ao garçom um prato, ou más notícias, quando você enviar o garçom mais. A cozinha não tem nenhum conceito de você— apenas as ordens que entram. Na fala do servidor, a única maneira de os clientes obterem informações atualizadas do servidor é enviar solicitações.
Imagine um aplicativo de bate-papo onde você está falando com um amigo. Você envia uma mensagem para o servidor, como uma solicitação com algum texto como carga útil. O servidor recebe sua solicitação e armazena a mensagem. Mas não tem como falar com o computador do seu amigo. O computador do seu amigo também precisa enviar uma solicitação para verificar novas mensagens; só então o servidor pode enviar sua mensagem.
Do jeito que está, você e seu amigo — ambos os clientes — precisam verificar constantemente as atualizações do servidor, introduzindo atrasos estranhos entre cada mensagem. Isso é bobagem, certo? Quando você envia uma mensagem, você quer que o servidor ping seu amigo imediatamente para dizer “Ei, você tem uma mensagem! Aqui está!” A resposta à solicitação HTTP funciona muito bem quando você precisa carregar uma página estática, mas é insuficiente quando sua comunicação é sensível ao tempo.
Votação curta
Uma solução simples para este problema é uma técnica chamada votação curta. Basta ter o cliente pingando o servidor repetidamente, digamos, a cada 500ms (ou sobre algum atraso fixo). Dessa forma, você recebe novos dados a cada 500ms. Há algumas desvantagens óbvias para isso: há um atraso de 500ms, ele consome recursos do servidor com uma enxurrada de solicitações, e a maioria das solicitações retornará vazia se os dados não forem atualizados com frequência.
Votação longa
Outra solução alternativa para a demora no recebimento de dados é uma técnica chamada votação longa. Neste método, o servidor recebe uma solicitação, mas não responde a ela até receber novos dados de outra solicitação. A votação longa é mais eficiente do que pingar o servidor repetidamente, uma vez que salva o incômodo de analisar cabeçalhos de solicitação, consultar novos dados e enviar respostas muitas vezes vazias. No entanto, o servidor deve agora acompanhar várias solicitações e seu pedido. Além disso, as solicitações podem ser realizadas e novas solicitações precisam ser emitidas periodicamente.
Eventos enviados pelo servidor (SSE) / EventSource
Outra técnica para enviar mensagens é a API de Eventos Enviadospelo Servidor, que permite ao servidor empurrar atualizações para o cliente aproveitando a interface JavaScript EventSource. EventSource abre uma conexão persistente e unidirecional com o servidor em HTTP usando um cabeçalho especial de fluxo de texto/fluxo de eventos e ouve mensagens, que são tratadas como eventos JavaScript pelo seu código.
Isso é quase o que estamos procurando — agora podemos receber atualizações do servidor! Por serem unidirecionais, os Eventos Enviados pelo Servidor (SSE) são ótimos para aplicativos onde você não precisa enviar dados ao servidor — por exemplo, um feed de notícias no estilo Twitter ou um painel em tempo real de cotações de ações. Outro profissional é que os Eventos Enviados pelo Servidor funcionam sobre HTTP e a API é relativamente fácil de usar. No entanto, o SSE não é suportado por navegadores mais antigos, e a maioria dos navegadores limita o número de conexões SSE que você pode fazer ao mesmo tempo. Mas, isso ainda não é suficiente para o nosso aplicativo de bate-papo: é ótimo receber atualizações em tempo real, mas gostaríamos de poder enviá-las também.
WebSockets
Portanto, precisamos de uma maneira de enviar informações para o servidor, e receber atualizações do servidor quando as atualizações chegam. Isso nos traz de volta à comunicação bidirecional (“full-duplex”) que mencionamos anteriormente. Digite websocket! Suportada por quase todos os navegadores modernos,a API do WebSocket nos permite abrir exatamente esse tipo de conexão bidirem com o servidor. Além disso, o servidor pode acompanhar cada cliente e enviar mensagens para um subconjunto de clientes. Legal! Com essa capacidade podemos convidar todos os nossos amigos para o nosso aplicativo de bate-papo e enviar mensagens para todos eles, alguns deles, ou apenas para o seu melhor amigo.
WebSockets sob o capô
Então, como exatamente essa magia funciona? Não se intimide com a configuração — bibliotecas modernas do WebSocket, como socket.io abstraem grande parte da configuração, mas ainda é útil entender como a tecnologia funciona. Se, no final desta seção, você estiver interessado em ainda mais detalhes, confira o surpreendentemente legível WebSocket RFC.
Na última seção, mencionamos HTTP várias vezes. HTTP é um protocolo, um conjunto de regras para como os computadores se comunicam na web. É composto pelas solicitações e respostas, cada uma das quais contém uma linha de solicitação (“GET /assets/icon.png”), cabeçalhos e um corpo de mensagem opcional (usado, por exemplo, solicitações POST para enviar alguns dados para o servidor).
WebSocket é outro protocolo para enviar e receber mensagens. Tanto o HTTP quanto o WebSockets enviam mensagens através de uma conexão TCP (Transmission Control Protocol), que é um padrão de camada de transporte que garante que os fluxos de bytes, enviados em pacotes, sejam entregues de forma confiável e previsível de um computador para outro. Assim, http e WebSockets usam o mesmo mecanismo de entrega no nível de pacote/byte, mas os protocolos para estruturar as mensagens são diferentes.
Para estabelecer uma conexão WebSocket com o servidor, o cliente primeiro envia uma solicitação de “aperto de mão” HTTP com um cabeçalho de atualização, especificando que o cliente deseja estabelecer uma conexão WebSocket. A solicitação é enviada para a ws: ou wss:: URI (análogo a http ou https). Se o servidor for capaz de estabelecer uma conexão WebSocket e a conexão for permitida (por exemplo, se a solicitação vier de um cliente autenticado ou whitelist), o servidor envia uma resposta bem-sucedida de aperto de mão, indicada pelo código HTTP 101 Switching Protocols.
Uma vez que a conexão é atualizada, o protocolo muda de HTTP para WebSocket, e enquanto os pacotes ainda são enviados pelo TCP, a comunicação agora está de acordo com o formato de mensagem Do WebSocket. Uma vez que o TCP, o protocolo subjacente que transmite pacotes de dados, é um protocolo full-duplex, tanto o cliente quanto o servidor podem enviar mensagens ao mesmo tempo. As mensagens podem ser fragmentadas, por isso é possível enviar uma mensagem enorme sem declarar o tamanho de antemão. Nesse caso, o WebSockets divide-o em quadros. Cada quadro contém um pequeno cabeçalho que indica o comprimento e o tipo de carga e se este é o quadro final.
Um servidor pode abrir conexões do WebSocket com vários clientes — até mesmo várias conexões com o mesmo cliente. Ele pode então enviar uma mensagem, alguns ou todos esses clientes. Na prática, isso significa que várias pessoas podem se conectar ao nosso aplicativo de bate-papo, e podemos enviar mensagens a algumas delas de cada vez.
Finalmente, quando estiver pronto para fechar a conexão, o cliente ou o servidor podem enviar uma mensagem “próxima”.
Ufa— bom trabalho seguindo junto! Vamos fazer uma pausa rápida para esticar, e então olhar para algum código de amostra.
Um pequeno exemplo
Agora que você está todo solto de alongamento (eu não estava brincando antes!), vamos mudar as engrenagens de uma visão geral técnica para olhar um pouco de código. Isso será muito menos complexo — como mencionei antes, o tooling moderno nos salva de ter que nos preocupar com aperto de mão ou armar OpCodes.
Na parte frontal, usaremos o JavaScript para estabelecer uma conexão com um servidor habilitado para WebSockets e, em seguida, ouvir mensagens como eventos JavaScript — da mesma forma que você ouviria eventos gerados pelo usuário, como cliques e teclas. Em baunilha JavaScript,fazemos isso criando um novo objeto WebSocket e adicionando ouvintes de eventos para eventos abertos, de mensagem e próximos. Também usamos o método de envio para enviar dados para o servidor.
const socket = new WebSocket(‘ws://localhost:8080’);
socket.addEventListener(‘open’, function (event) {
socket.send(‘Hello Server!’);
});
socket.addEventListener(‘message’, function (event) {
console.log(‘Message from server ‘, event.data);
});
socket.addEventListener(‘close’, function (event) {
console.log(‘The connection has been closed’);
});
No servidor, também precisamos ouvir solicitações do WebSocket. No Node.js, por exemplo, podemos usar o popular pacote ws para abrir uma conexão e ouvir mensagens:
const WebSocket = require(‘ws’);
const ws = new WebSocket.Server({ port: 8080 });
ws.on(‘connection’, function connection(wsConnection) {
wsConnection.on(‘message’, function incoming(message) {
console.log(`server received: ${message}`);
});
wsConnection.send(‘got your message!’);
});
Embora neste exemplo, estejamos enviando strings, um caso de uso comum do WebSockets é enviar dados JSON stringified ou até mesmo dados binários,permitindo que você estruture suas mensagens no formato conveniente para você.
Para um exemplo mais completo, Socket.io, uma estrutura frontal popular para fazer e gerenciar conexões WebSocket, tem um fantástico passo a passo para a construção de um aplicativo de bate-papo Node/JavaScript. Esta biblioteca alterna automaticamente entre WebSockets e votação longa, e também simplifica a transmissão de mensagens para grupos de usuários conectados.
Na natureza
Embora você possa escrever manualmente o código do servidor WebSocket, o WebSockets convenientemente já está incorporado em frameworks populares. Já tocamos na biblioteca Socket.io na parte da frente. Algumas outras implementações de WebSockets em projetos maiores são:
ActionCable em Ruby on Rails, uma estrutura ruby completa
Canais em Django, uma estrutura python completa
Gorila na língua Go
Meteor, uma estrutura JavaScript de pilha completa baseada em WebSockets em vez de HTTP
Apollo, um servidor GraphQL que ajuda a buscar dados em tempo real usando WebSockets
Então, que tipos de projetos podem se beneficiar do WebSockets? Qualquer projeto que exija comunicação bidirecional em tempo real é um ótimo caso de uso. Aqui estão apenas alguns tipos de aplicativos frequentemente alimentados por WebSockets:
Aplicativos de bate-papo: um WebSockets de implementação conceitualmente simples; os usuários enviam mensagens para um servidor, o que instantaneamente empurra essas mensagens para o destinatário. Sem atraso! O servidor também pode armazenar grupos de conexões em canais, permitindo que você envie mensagens para várias pessoas ao mesmo tempo, ou veja mensagens de várias pessoas em uma sala, como um canal Do Slack.
Jogos multiplayer: Um padrão comum para jogos multiplayer é ter um servidor armazenando um estado de jogo que serve como fonte da verdade. Os jogadores tomarão ações ou farão movimentos que são enviados para o servidor, que atualiza o estado do jogo, e empurra-o para todos os jogadores. Com HTTP, cada jogador precisa solicitar regularmente o estado do jogo. Com websockets, cada movimento é instantaneamente transmitido para todos os jogadores.
Aplicativos de colaboração: Precisa trabalhar em um documento ou tela compartilhado? Você pode seguir o padrão acima para permitir que vários usuários desenhe ou digite um documento e atualize-o instantaneamente para todos os conectados.
Ferramentas para desenvolvedores: Ferramentas de integração contínua como o CircleCI usam webSockets para notificá-lo instantaneamente quando uma compilação terminar. Precisa enviar métricas ao seu cliente em torno do desempenho do site e contagem de visitantes? Abra uma conexão WebSocket e envie atualizações assim que o servidor as receber.
Aplicativos dependentes de localização: Atualize o servidor quando as coordenadas gps do usuário tiverem sido alteradas e envie ao usuário novos dados com base em suas coordenadas atuais.
Uma recapitulação em tempo real
Espero que agora você esteja vendido no WebSockets! Cobrimos muito terreno: rastreamos o problema que o WebSockets resolve e as soluções alternativas, exploramos como o WebSockets opera sob o capô e até examinamos alguns códigos de amostra e casos de uso comum.
O protocolo WebSocket é uma ferramenta maravilhosa para construir aplicativos de comunicação em tempo real, mas como todas as ferramentas, não é uma bala de prata. Uma conexão WebSocket é feita para ser persistente, por isso pode ser um exagero para aplicativos mais simples. Para um feed de notícias de uma direção, o feed de métricas ou qualquer aplicativo onde você precisa atualizar o cliente, mas não receber informações em troca, os Eventos enviados pelo servidor ou chamadas HTTP simples são mais rápidos e simples de configurar. No entanto, para jogos multiplayer e aplicativos colaborativos, o WebSockets abre um mundo de possibilidades. A web em tempo real está evoluindo, e o protocolo WebSocket é uma engrenagem crucial em sua evolução. Vá em frente e use-o para o bem!