«

»

Sep 06

Um chat em node.js com websockets – o servidor

Este post continua a explicação do post anterior, então ele continua sendo o quarto na série de posts sobre Node.js mas é o segundo na série de posts sobre o chat escrito com WebSockets, então agora vamos ver como escrever o servidor do chat com node.js, o cliente foi criado no post anterior, se você não lembra pode ir la dar uma olhada.

Pronto, agora que você voltou vamos falar do servidor, o código do servidor ficou bem menor do que o cliente, em parte graças aos modulos node-static e websocket-server que foram utilizados, já falamos antes sobre o node-static, e vou falar um pouquinho sobre o outro neste post, mas deixei os links caso queiram mais informações ou resolvam utilizar estes modulos.

Como tenho feito nos últimos posts, vou inserir o código pronto do servidor e comentar abaixo, se você não gosta muito desta abordagem, avise nos comentários que tento pensar em uma forma melhor de fazer isto.

samples/node_chat/chatserver.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
var sys = require('sys'),
    http = require("http"),
    nodeStatic = require("node-static"),
    ws = require('websocket-server');
 
function ChatServer(){
  var self = this;
  self.users = [];
  self.fileServer = new nodeStatic.Server("public");
  self.httpServer = http.createServer(function (request, response) {
    request.addListener('end', function () {
      self.fileServer.serve(request, response);
    })
  });
  self.server = ws.createServer({server: self.httpServer});
 
  self.server.on("connection", function(connection){
    var this_user = "user" + self.users.length;
    self.users.push(this_user);
    console.log("New connection, total users: " + users.length);
    self.broadcastMessage(this_user,"just connected");
    self.broadcastUsersList();
    connection.on("message", function(msg){
      if(msg.indexOf("/nick")==0){
        new_user = msg.substr(5);
        self.users.splice(self.users.indexOf(this_user),1,new_user);
        self.broadcastMessage(this_user,"is now known as " + new_user);
        this_user = new_user;
        self.broadcastUsersList();
      }else{
        self.broadcastMessage(this_user,msg);
      }
    });
    connection.on("close",function(){
      self.users.splice(self.users.indexOf(this_user),1,new_user);
      self.broadcastMessage(this_user, "just left")
      self.broadcastUsersList();
    });
  });
}
ChatServer.prototype.start = function(){
  this.httpServer.listen(3040);
}
ChatServer.prototype.broadcastUsersList = function(){
  this.server.broadcast(JSON.stringify({users: this.users}));
}
ChatServer.prototype.broadcastMessage = function(login,message){
  this.server.broadcast(JSON.stringify({message:message,user:login}));
}
var cs = new ChatServer();
cs.start();

As primeiras 4 linhas do código fazem os “requires” necessários para a aplicação, as linhas 50 e 51 criam uma instância de ChatServer e iniciam o servidor com o método start, e agora vamos ao código real entre as linhas 5 e 49, isto quer dizer que para criar o servidor do nosso char só foram necessárias 44 linhas de código javascript não muito organizado mas razoavelmente simples :D

Vou falar do que ficou fora do grande bloco monolitico central primeiro, entre as linhas 41 e 43 no método start, o servidor começa a ouvir na porta 3040.

Entre as linhas 44 e 46, no método broadcastUsersList, o utilizamos o método broadcast do WebSocket server para enviar uma mensagem para todos os usuários conectados, a mensagem é composta de um objeto javascript que tem uma propriedade users que aponta para a listagem de nomes de usuários. Este objeto é transformado em String para a transferência utilizando o método JSON.stringify nativo do Node.JS.

Entre as linhas 47 e 49, no método broadcastMessage a mesma técnica é utilizada mas criando um objeto com a propriedade message e user, ou seja uma mensagem e o usuário que a enviou.

Agora rumo ao grande bloco monolitico do inicio do arquivo, ou seja o contrutor da classe ChatServer.

Na linha 9, é criado o servidor node-static para servir o conteúdo do diretório public, que é um diretório que contem apenas os dois arquivos criados no post anterior, o chat.html e chat.js.

Entre as linhas 10 e 14 criamos o servidor HTTP e adicionamos os listeners necessários para configurar o node-static como já foi feito em outro post sobre node.js aqui no blog.

Na linha 15 o servidor websocket é criado e o servidor HTTP é passado como parâmetro, desta forma eles podem funcionar junto e vão poder compartilhar a mesma porta TCP.

E agora que começa realmente o código do chat, vamos utilizar bastante closures, ou seja blocos de código que tem refrência ao contexto do local onde eles foram criados, isto vai simlificar bastante o código, mas também vai definir o aninhamento do código que criamos.

Você não é obrigado a implementar desta forma, eu apenas achei que assim o código ficaria mais conciso e fácil de explicar no blog, provavelmente em uma aplicação real eu teria escrito isto de forma mais modular.

Mas voltando ao código, na linha 17 adicionamos um listener no evento “connection” que vai ser chamado sempre que um usuário conectar no servidor. Este listener recebe o objeto connection recem criado como parâmetro, e é nese objeto que ouviremos pelos próximos eventos.

Nas linhas 18 e 19 criamos um nome de usuário sequencial e adicionamos este nome a lista de usuários existentes. Perceba que o algoritmo tem uma falha grave, se dois usuários conectarem, e o primeiro desconectar, o próximo a conectar vai receber o mesmo nome do segundo, este seria um problema grave em uma aplicação real, mas no exemplo feito apenas para o blog não é um problema grave, e pode ser fácilmente corrigido com uma variável utilizada como contador.

Na linha 20 imprimimos o número de usuários no console.

Nas linhas 21 e 22 enviamos uma mensagem para todos os usuários informando do novo usuário conectado e atualizamos as listagens de usuários dos clientes utilizando os métodos que já vimos acima.

Entre as linhas 23 e 33 adicionamos o listener para o evento “message” que vai ser disparado sempre que o usuário desta conexão enviar uma mensagem para o servidor. o “if” na linha 24 verifica se o usuário quer mudar de nick, caso positivo substitui o nick antigo na lista de usuários pelo novo, envia uma mensagem para todos os usuários avisando da mudança de nick e envia a lista de usuários atualizada para todos conectados ao servidor.

Caso a mensagem não seja uma troca de nick, apenas repassa a mensagem para todos os usuários na linha 31.

Entre as linhas 34 e 38, ouvimos o evento “close” que vai ser chamado sempre que um usuário desconectar, neste caso removemos o nick do usuário da lista de usuários, enviamos uma mensagem para todos os usuários informando que este desconectou e enviamos a lista de usuários atualizada para todos ainda conectados.

Bom, este foi todo o código necessário para criar o servidor para o chat em node.js, no próximo post na próxima semana vamos ver o código necessário para criar um servidor para o chat em Ruby com EventMachine, então poderemos comparar as duas implementação.

Tanto em Ruby quanto em node.js temos várias formas de escrever o código, eu tentei utilizar a mesma abordagem de closures para escrever os dois códigos, mesmo podendo não ser a melhor abordagem em ambas as linguagens, isto tornara os dois códigos mais próximos, facilitando a comparação.

Se você tem perguntas ou comentários sobre este exemplo de chat em node.js, por favor deixe nos comentários, se tem outros comentários também ficarei feliz em ouvir.