Concrete Logo
Hamburger button

Implementando Comet com Node.js e Socket.io

  • Blog
  • 26 de Outubro de 2012
Share

Quem desenvolve software para a web, já pode ter se deparado com alguns desafios de implementação, do tipo que envolve informar ao cliente que algo importante, de interesse dele, aconteceu.

Se você já se deparou com isso, lá pela metade da década de 2000 (e não pediu a seu usuário para colocar uma pedra na tecla F5), arrisco dizer que você, para resolver o problema, deve ter optado por implementar Meta refresh ou ainda usar o velho e famoso ajax short polling, também conhecido como modelo “pinga ni mim” 🙂

Fala sério.

Estamos em 2012 e o mundo ainda não acabou! E muito antes disso, já existia um conjunto amplo de técnicas para implementar o conceito de server push, permitindo que o cliente web seja notificado como se deve. Este conjunto de técnicas ou modelo de programação para a web, tem um nome: Comet.

O post Comet para aplicação web em tempo real, muito bem redigido pelo meu amigo Victor Nascimento, apresenta uma das técnicas de server push mais conhecidas e discutidas, o long polling, com uma aplicação de exemplo baseada em Erlang e Chicago Boss.

Neste post, vou abordar uma das formas de implementar Comet, baseada em streaming, através do uso de WebSockets.

O cenário

Imagine uma simples sala de chat, pública, onde qualquer um pode entrar e falar à vontade, como um canal de IRC não moderado. Você acessa, informa seu nome e, em seguida, pode conversar com quem estiver na sala.

Simples, certo?

Se você pretende usar WebSockets para implementar a conversação, usando a API de WebSockets, basta abrir uma conexão:

[sourcecode language=”javascript” autolinks=”false”]
var socket = new WebSocket(‘ws://seu_servidor:porta/servico’);
[/sourcecode]

E quando a conexão estiver aberta, você pode enviar e receber mensagens:

[sourcecode language=”javascript” autolinks=”false”]
// When the connection is open, send some data to the server
socket.onopen = function () {
connection.send(‘Ping’); // Send the message ‘Ping’ to the server
};

// Log errors
socket.onerror = function (error) {
console.log(‘WebSocket Error ‘ + error);
};

// Log messages from the server
socket.onmessage = function (e) {
console.log(‘Server: ‘ + e.data);
};
[/sourcecode]

Fácil, não? Parece que sim.

Mas vale lembrar que tanto a API quanto o protocolo de comunicação WebSocket, o RFC 6455, ainda sofrem alguma revisão e aperfeiçoamento, por seus respectivos mantenedores (W3C e IETF Datatracker). Sendo assim, é possível que sua sala de chat, de uma hora para outra, pare de funcionar, caso o navegador do cliente não suporte (mais) a versão da API e protocolo em uso pela sua aplicação. Ainda há de se considerar que nem todos os navegadores existentes suportam qualquer versão de WebSockets (não é mesmo, Internet Explorer?)

Que chato…

A solução

Pensando em equalizar estas questões e muitas outras não citadas aqui, o pessoal da LearnBoost pensou e projetou o Socket.io. Com ele, você e sua sala de chat, por exemplo, são capazes de pensar no que realmente interessa: Uma sala de chat!

O grande propósito do Socket.io é lidar com as particularidades de cada meio de transporte disponível, para que sua aplicação trafegue informação e consiga fazer server push, mesmo onde determinado transporte, como WebSockets, não esteja disponível, graças ao seu mecanismo interno de fallback, que escolhe o melhor transporte disponível para manter a aplicação funcionando. O Socket.io é composto de dois módulos, cliente e servidor, totalmente escritos em Javascript, sendo o módulo servidor preparado para executar no Node.js, aquele mesmo chamado de “câncer” por aqui. Será mesmo que o Node.js é capaz de tomar conta do organismo da TI mundial? Fica a dica para um próximo e interessante post por aqui 🙂

Agora chega de papo e blah blah blah. Vamos ver como funciona?

O exemplo

Lembram da sala de chat? Construi uma aplicação de exemplo, bem simples (e feia :P), para mostrar a vocês em poucas linhas como a casadinha Node.js e Socket.io é poderosa.

Pré-requisitos

O setup a seguir foi testado no Linux (Ubuntu 12.04), em uma VM Vagrant, mas é compatível com o OSX também.

Vamos lá!

    1 – Para instalar o Node.js, você pode instalar a partir do fonte ou usar um pacote nativo. A escolha é sua. Apenas certifique-se de instalar a última versão estável do Node.js (neste momento, v0.8.12).

    2 – O NPM (Node Package Manager) também é necessário. A forma mais simples que conheço para instalá-lo é, após a instalação no Node.js, executar

    [sourcecode language=”bash” autolinks=”false”]
    curl https://npmjs.org/install.sh | sudo sh
    [/sourcecode]

    Para testar a instalação de ambos, basta executar os comandos abaixo e verificar as versões instaladas:

    [sourcecode language=”bash” autolinks=”false”]
    node -v
    npm -v
    [/sourcecode]

E pra rodar a aplicação?

    1 – No diretório de sua preferência, faça o checkout do projeto no Github:

    [sourcecode language=”bash” autolinks=”false”]
    git clone https://github.com/accbel/nodejs-socketio-example.git
    [/sourcecode]

      NOTE: Se preferir, faça o download e extração do .zip do projeto.

    2 – Dentro do diretório do projeto, execute:

    [sourcecode language=”bash” autolinks=”false”]
    npm install
    [/sourcecode]

      O NPM vai instalar as dependências requeridas pelo projeto. No caso, o Socket.io e o Connect.

    3 – Se tudo deu certo, agora é só executar o servidor:

    [sourcecode language=”bash” autolinks=”false”]
    node server.js
    [/sourcecode]

Se você viu a tela abaixo, no seu terminal, então seu servidor de chat está instalado e funcionando!
[sourcecode language=”bash” autolinks=”false”]
vagrant@precise64:~/workspace/nodejs-socketio-example$ node server.js
info – socket.io started
[/sourcecode]

A partir daí, abra seu navegador e digite o endereço https://localhost:8080/. Uma página muito bonita (só que não!) vai pedir o seu nome e, em seguida, ele vai aparecer no lado esquerdo da tela. Digite uma mensagem e pressione Enter para enviar. Abra várias abas do navegador ou use um navegador diferente e informe outros usuários. Faça os testes.

NOTE: Se você possui mais de uma máquina, em rede local, você pode querer testar a aplicação de um jeito mais “real”, com clientes remotos. Para fazer funcionar desta forma, basta fazer uma pequena alteração em um arquivo do projeto, conforme abaixo:

    1 – No diretório do projeto, procure e abra o arquivo public/javascript/application.js

    2 – Altere a linha 23 do arquivo, para seguir o padrão abaixo

    [sourcecode language=”javascript” autolinks=”false”]
    room.socket = io.connect(‘https://<IP_DO_SERVIDOR>:8080’);
    [/sourcecode]

    3 – Reinicie o servidor (Control+C para interromper)

Só pra reforçar, esse pequeno projeto está no meu Github. Querendo contribuir, será muito bem vindo.

Bem legal, mas como isso tudo funciona?

O que acabamos de fazer aqui é implementar uma pequena aplicação web, com a valiosa ajuda no Node.js, para servir uma página estática HTML e dar suporte para execução do Socket.io. Toda a lógica simples está implementada em um punhado de poucas linhas Javascript.

Basicamente, o servidor web pode ser resumido em um arquivo javascript como abaixo:

[sourcecode language=”javascript” autolinks=”false”]
var server = require(‘http’).createServer()
, io = require(‘socket.io’).listen(server);

server.listen(8080);

io.sockets.on(‘connection’, function (socket) {
socket.on(‘message’, function(data,callback){
socket.broadcast.json.send(data);
});
});
[/sourcecode]

Com apenas 10 linhas de código, temos um servidor HTTP pronto para servir qualquer conteúdo que o programador desejar, além do suporte ao uso de WebSockets ou qualquer outro transporte disponível.

No modelo Comet, toda a comunicação é regida por eventos e, usando Javascript, fica bem fácil programar de forma orientada a eventos. O Socket.io é especialmente preparado para lidar com os principais eventos de uma conversação, como pedido de conexão, desconexão, envio e recebimento de mensagens, etc. Também é possível “emitir e escutar” qualquer tipo de evento personalizado. No código acima, por exemplo, a cada novo pedido de conexão de um cliente (linha 6), um novo socket é criado para representar essa conexão e, neste novo socket, é registrado um listener (linha 7) responsável por cuidar do evento “message”. Ou seja, quando o cliente enviar uma mensagem, o listener se encarrega de enviar esta mensagem aos demais sockets/clientes conectados na aplicação.

Bem simples, não acham?

Já no lado do cliente, o príncipio é o mesmo. Basta obter uma conexão ao serviço, registrar um ou mais listeners para os eventos relevantes e enviar as mensagens, emitindo eventos:

[sourcecode language=”javascript” autolinks=”false” htmlscript=”true”]
<script src="/socket.io/socket.io.js"></script>
<script type="text/javascript">
var room = { … };

room.socket = io.connect(‘https://<IP_DO_SERVIDOR>:8080’);

room.socket.json.send({author: room.owner,message: room.inputMessage});

room.socket.on(‘message’,function(update){
room.printMessage(update.author,update.message);
});
</script>
[/sourcecode]

A mágica por trás do Socket.io acontece na linha 05. Nesse momento, ele vai obter a conexão com o servidor e você, programador, não precisa se preocupar como as mensagens serão enviadas ou recebidas. Tudo é regido por ele, sem impactos de código para lidar com WebSockets ou qualquer outra forma de comunicação.

Daí em diante, o que sua aplicação precisa fazer, para reagir aos eventos emitidos, não passa de pura programação para a Web (HTML, Javascript, CSS). Não tem segredos.

Curtiram??

Se você quiser saber mais sobre os assuntos abordados aqui, visite os links que estão espalhados pelo post. Também recomendo os links abaixo, com tutoriais de introdução ao Node.js e Socket.io: