Neste tutorial iremos abordar alguns conceitos do Socket.io criando um simples sistema de chat para browser.
O que é Socket.io?
Socket.io é um uma biblioteca Javascript feita para construir aplicações real-time, possibilitando uma comunicação bi-direcional entre cliente e servidor. O socket.io utiliza as especificações de Web Sockets (para quem quer saber mais, recomendo dar uma olhada neste ótimo artigo da HTML5 Rocks).
O Socket.io roda, no lado do servidor, em NodeJS, e, no lado do cliente, ele roda diretamente no browser, possibilitando uma enorme gama de possibilidades de aplicações, como jogos, sistemas de notificações, real-time analytics e sistemas de chats e conversas em tempo real.
Setando o projeto
Primeiramente, temos que instalar algumas bibliotecas que iremos utilizar no projeto, para isso usarei o yarn.
Em primeiro lugar, vou adicionar ao projeto a biblioteca do Socket.io que rodará do lado do servidor.
yarn add socketio
Também iremos utilizar o express:
yarn add express
Também precisamos adicionar o Socket.io para o cliente (você pode utilizar a CDN oficial disponibilizada no site deles também):
yarn add socket.io-client
E, por último, usarei a biblioteca jQuery para manipular a DOM.
yarn add jquery
Fazendo o HTML+CSS
Vamos criar um arquivo index.html e já deixar preparado o nosso template do sistema de chat.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Simple chat</title> <link rel="stylesheet" href="assets/css.css"> </head> <body> <div class="nickname_container" id="nick"> <span>Type your nickname:</span> <form id="submit"><input type="text" id="nickname" /></form> </div> <div id="chat" hidden> <div class="menu" => <div class="name" id="name">Alex</div> <div class="last" id="time">18:09</div> </div> <ol class="chat"> </ol> <input class="textarea" type="text" placeholder="Type here!" id="textarea" /> </div> <script src="node_modules/jquery/dist/jquery.min.js"></script> <script src="node_modules/socket.io-client/socket.io.js"></script> <script src="assets/js.js"></script> </body> </html>
Repare que eu também criei o diretório assets, e criei os arquivos css.css e js.js.
Não entrarei na parte do CSS, pois o foco aqui é o javascript, mas você pode ver o resultado no github. Eu utilizei como base este pen para construir o layout.
Server-side
Vamos iniciar com a criação da parte de servidor do Socket.io, ou seja, iremos lidar com os eventos server-side.
Iniciaremos criando um arquivo app.js no diretório raíz e importaremos os módulos e faremos algumas operações iniciais:
var app = require('express')(); var http = require('http').Server(app); var io = require('socket.io')(http); var clients = {}; app.get('/', function(req, res){ res.send('server is running'); }); //SocketIO vem aqui http.listen(3000, function(){ console.log('listening on port 3000'); });
Este script implementa um servidor Node utilizando os módulos http e express (para roteamento).
A variável clientes que está sendo criada servirá para armazenar nossa lista de clientes.
Agora iremos adicionar o nosso primeiro evento do Socket.io, que será o connection, que dispara a cada vez que um cliente se conecta ao socket.
io.on("connection", function (client) { console.log('user connected'); });
Para nossa sala de chat, precisaremos implementar outros 3 eventos: join, send e disconnect:
io.on("connection", function (client) { client.on("join", function(name){ console.log("Joined: " + name); clients[client.id] = name; client.emit("update", "You have connected to the server."); client.broadcast.emit("update", name + " has joined the server.") }); client.on("send", function(msg){ console.log("Message: " + msg); client.broadcast.emit("chat", clients[client.id], msg); }); client.on("disconnect", function(){ console.log("Disconnect"); io.emit("update", clients[client.id] + " has left the server."); delete clients[client.id]; }); });
O evento join deverá ser disparado quando o cliente entrar no servidor, adicionando o id do cliente no array e emitindo dois novos eventos, nomeando-os de update.
Note que há uma diferença entre o método client.emit e o client.broadcast.emit. O client.emit enviará a notificação somente para o cliente atual, ou seja, o cliente que acabou de entrar na sala de chat. O client.broadcast.emit irá emitir para todos os clientes conectados, com exceção do que está executando a ação. Se utilizássemos o método io.emit, a mensagem seria enviada a todos os clientes conectados ao socket. Abaixo uma série de exemplos de métodos disponíveis:
// enviar apenas para o cliente atual client.emit('message', "this is a test"); // enviar para todos os clientes, inclusive o atual io.emit('message', "this is a test"); // enviar para todos os clientes, exceto o atual client.broadcast.emit('message', "this is a test"); // enviar para todos os clientes (com exceção do atual) para uma sala específica socket.broadcast.to('game').emit('message', 'nice game'); // enviar para todos os clientes em uma sala específica io.in('game').emit('message', 'cool game'); // enviar para o atual, caso ele esteja na sala client.to('game').emit('message', 'enjoy the game'); // enviar para todos os clientes em um namespace 'namespace1' io.of('namespace1').emit('message', 'gg'); // enviando para um socketid individual client.broadcast.to(socketid).emit('message', 'for your eyes only');
Com todos esses métodos, conseguiríamos implementar salas específicas, mensagens individuais, etc. Porém nosso foco é mostrar a parte mais básica e entender o funcionamento.
Client-side
Com nosso servidor concluido e rodando, vamos passar para a parte de client-side de nossa aplicação de chat. Vamos ao js.js.
Primeiramente, inicializaremos o socket.io e criaremos uma variável ready, setada como false. Esta variável será responsável por indicar se o usuário já informou ou não o seu nickname.
$(document).ready(function(){ var socket = io.connect("https://localhost:3000"); var ready = false; });
Com esta implementação, já conseguimos disparar o evento connection em nosso servidor. Porém, precisamos fazer com que o servidor receba a informação cada vez que um novo usuário entrar na sala informando o seu nickname.
$("#submit").submit(function(e) { e.preventDefault(); $("#nick").fadeOut(); $("#chat").fadeIn(); var name = $("#nickname").val(); var time = new Date(); $("#name").html(name); $("#time").html('First login: ' + time.getHours() + ':' + time.getMinutes()); ready = true; socket.emit("join", name); });
A função jQuery acima captura a submissão do formulário de nickname, fecha a tela de seleção de nick, mostra a tela de chat, seta a variável ready para true e executa um comando de socket, o socket.emit, que informa para o nosso servidor que um novo usuário acabou de entrar na sala.
Nada irá acontecer, pois ainda não temos o receptor do evento update, que está sendo disparado no nosso servidor, então vamos criá-lo:
socket.on("update", function(msg) { if (ready) { $('.chat').append('<li class="info">' + msg + '</li>') } });
Este código fará com que, a cada vez que o servidor emitir um update, o jQuery adicione uma nova linha no chat com a mensagem retornada.
Agora, iremos fazer com que nossa aplicação envie as mensagens ao servidor a cada vez que o cliente apertar o enter no input de texto:
$("#textarea").keypress(function(e){ if(e.which == 13) { var text = $("#textarea").val(); $("#textarea").val(''); var time = new Date(); $(".chat").append('<li class="self"><div class="msg"><span>' + $("#nickname").val() + ':</span> <p>' + text + '</p><time>' + time.getHours() + ':' + time.getMinutes() + '</time></div></li>'); socket.emit("send", text); } });
E, para concluir, precisamos fazer com que o socket.io observe todas as mensagens referente ao chat em si, e adicione à DOM:
socket.on("chat", function(client,msg) { if (ready) { var time = new Date(); $(".chat").append('<li class="other"><div class="msg"><span>' + client + ':</span><p>' + msg + '</p><time>' + time.getHours() + ':' + time.getMinutes() + '</time></div></li>'); } });
Conclusão
Na minha opinião, as sockets são uma das melhores funcionalidades do HTML5, e possuem uma infinidade de aplicação. O ganho de performance é espetacular se bem aplicado, uma vez que evita o uso de requisições HTTP em aplicações onde a necessidade de atualização é grande (baixa latência).
Disponibilizei o código do tutorial no github para quem se interessar, e estou aberto a tirar dúvidas.