Desenvolvimento

1 mar, 2013

Conectando no Socket.IO – avançado

Publicidade

Há algum tempo, nós vimos o básico da utilização do Socket.IO. Conforme fomos nos aprofundando no tema, descobrimos que Socket.IO nos oferece uma API muito simples, baseada inteiramente em de enviar e receber mensagens e ser notificada dessas mensagens por meio de eventos. Bem, não tem mais nada Socket.IO do que isso. Ele tem várias funcionalidades avançadas que podem vir a ser muito úteis em determinadas situações.

Built-in Events

Este não é exatamente um recurso avançado. Eu simplesmente não tinha tempo para incluí-lo no artigo anterior. De qualquer forma, você viu que tanto o lado do cliente quanto o do servidor tinham um meio de fazer algo depois que a conexão foi feita:

    // Server Side
    io.listen(80);
    io.sockets.on('connection', function(socket){
        . . .
    });

    // Client Side
    socket = io.connect('http://localhost/');
    socket.on('connect', function() {
        . . .
    });

No back end, é necessário que você reaja a isso, ou você não será capaz de fazer nada com a conexão. No front end, no entanto, isso não é necessário, mas geralmente é útil. Além disso, ambas as partes podem também reagir a uma desconexão. O código abaixo aplica-se tanto a front end quanto a back end.

 

    socket.on("disconnect", function() {
        // Let the users know something bad happened or log the disconnect
    });

Não se preocupe em tentar se reconectar ali, pelo menos não imediatamente. O Socket.IO tentará se reconectar um determinado número de vezes por si próprio. Ele aumenta o tempo entre as tentativas a cada tentativa de reconexão. Mas ele desiste depois de um tempo, então você pode querer jogar alguns setTimeout lá para continuar tentando reconectar mais tarde, se quiser. Ah, e por falar nisso, você pode desconectar de propósito, o que não faz com que o cliente tente conectar-se novamente, mas ainda deve disparar o evento “disconnect”:

    // Client Side
    socket.disconnect();

Existe também um evento “message”, que é usado para fazer com que o Socket.IO se adeque melhor à semântica do WebSocket. Dessa forma, todas as mensagens são recebidas e tratadas dentro desse único callback, e você não precisa inventar nomes para elas. Ao usar isso, você também utiliza send em vez de emit para enviar mensagens.

    // Server Side
    io.listen(80);
    io.sockets.on('connection', function (socket) {
        socket.on('message', function (message) {
            console.log(message);
            socket.send('message was received');
        });
    });

    // Client Side
    socket = io.connect('http://localhost/');
    socket.on('connect', function() {
        . . .
    });
    socket.on('message', function (message) {
        console.log(message);
        socket.send('message was received');
    });

Reconhecimento

Às vezes, você quer ter o sistema configurado para ter uma resposta do servidor toda vez que ele recebe suas mensagens. Você pode fazer isso enviando funções para o servidor, o que ele irá, então, chamar “diretamente” e transmitir alguns dados a ele.

    // Server Side
    socket.on("getMessage", function(func) {
        func("The Message");
    });

    // Client Side
    socket.emit("getMessage", function(message) {
        console.log(message);
    });

Quando isso é executado, “The Message” será publicado no console do cliente. Isso pode parecer um pouco com mágica, mas na verdade é apenas um uso inteligente de um proxy. A função em si não está, na verdade, sendo chamada. Uma função diferente está sendo chamada em seu lugar, a qual envia os dados de volta para o cliente e tem a função real chamada lá com os dados que foram enviados. É um sistema muito bom para garantir que você receba a confirmação de quando o servidor recebe seu pedido. Porém, você pode, é claro, apenas reescrever o código como abaixo e conseguir exatamente o mesmo efeito.

    // Server Side
    socket.on("getMessage", function(func) {
        socket.emit("theMessage", "The Message");
    });

    // Client Side
    socket.emit("getMessage");
    socket.on("theMessage", function(message) {
        console.log(message);
    });

É um pouco mais de código e não está totalmente claro a partir da perspectiva do cliente que “theMessage” será enviado de volta imediatamente quando “getMessage” for recebido, mas ainda funciona tão bem quanto.

Armazenamento de dados do cliente

O Socket.IO tem, essencialmente, um tipo especial de armazenamento de sessão que você pode usar para armazenar informações sobre cada socket conectado. É também muito simples de usar, assim como quase tudo mais sobre essa biblioteca.

    // Server Side
    socket.on('set nickname', function (name) {
        socket.set('nickname', name, function () {
            socket.emit('ready');
        });
    });

    socket.on('msg', function () {
        socket.get('nickname', function (err, name) {
            console.log('Chat message by ', name);
        });
    });

    // Client Side
    socket.emit('set nickname', user.nickname);

    socket.on('ready', function () {
        console.log('Connected !');
        socket.emit('msg', message);
    });

Como você pode ver, ele funciona bem para armazenar o nickname de um usuário, de modo que todos em um bate-papo possam saber quem está enviando as mensagens. Basta usar socket.set e socket.get. Certifique-se de que você note que eles são assíncronos, para que eles exijam um callback se você quiser fazer alguma coisa imediatamente depois que o valor for salvo ou recuperado.

Broadcasting

Então você quer estar na televisão? Desculpe, tipo errado de broadcasting. Por broadcasting, eu quero dizer que estou enviando uma mensagem para todos que estão conectados ao servidor. Nós já conversamos sobre isso no artigo anterior, no qual eu disse que no lado do servidor você poderia usar io.sockets.emit em vez de socket.emit para enviar uma mensagem para cada cliente que estiver conectado.

    socket.on('chat message', function(msg) {
        io.sockets.emit('message', msg);
    });

No entanto, existe um problema em potencial com essa configuração: ela envia a mensagem para o cliente original também. O cliente que enviou a “mensagem de bate-papo” provavelmente não precisa recebê-la de volta. Para contornar essa situação, existe um mecanismo que permite que você envie uma mensagem para todos, exceto para o cliente original. Basta usar socket.broadcast.emit. O código acima pode agora ser escrito assim:

    socket.on('chat message', function(msg) {
        socket.broadcast.emit('message', msg);
    });

Segmentação de usuários: rooms e namespaces

Às vezes você não quer todos os usuários agrupados juntos no mesmo pool. Você pode querer enviar mensagens para alguns clientes, mas não para outros. Para isso, temos duas maneiras diferentes de segmentação de usuários: rooms e namespaces.

Rooms

Os usuários podem ser atribuídos a diferentes rooms e, em seguida, podem ser contatados quando bradcasts  são feitas para aquela room. Primeiro, vamos aprender como os clientes podem ser atribuídos e retirados das rooms. Tudo isso é feito no lado do servidor. O cliente nao tem poder real no controle de quais rooms é atribuído, exceto para enviar mensagens às quais o servidor responde alterando se você está em uma room ou não.

    // Server Side
    socket.on('addToRoom', function(roomName) {
        socket.join(roomName);
    });

    socket.on('removeFromRoom', function(roomName) {
        socket.leave(roomName);
    });

Basta usar socket.join e socket.leave para entrar e sair das rooms, respectivamente. Tenho certeza (embora eu não tenha tentado. Você deveria tentar se estiver interessado) de que um socket pode participar de várias rooms ao mesmo tempo. Agora que você está atribuído a uma room, sempre que alguém fizer um broadcast para a room inteira, você será notificado. Veja como você transmitir para rooms:

    // Broadcast to everyone in a room, except you
    socket.broadcast.to("room name").emit("your message");

    // Broadcast to everyone in a room, including you
    io.sockets.in("room name").emit("your message");

 

E isso é praticamente tudo o que existe sobre os rooms!

Namespaces

Namespaces não são tecnicamente destinados para segmentar seus usuários. Em vez disso, eles são usados ​​para permitir que você tenha várias conexões para vários servidores Socket.IO, mas apenas requerem o uso de um único servidor Socket.IO. Em outras palavras, um único servidor atua como vários servidores aos quais você pode se conectar separadamente. Embora a intenção seja diferente, ele trabalha para segregar os usuários.

Vamos configurar o lado do servidor para permitir conexões múltiplas:

    var io = require('socket.io').listen(80);

    var chat = io
        .of('/chat')
        .on('connection', function (socket) {
            // Send message to client like usual
            socket.emit('a message', { that: 'only', socket: 'will get' });
            // Broadcast message to everyone in this namespace
            chat.emit('a message', { everyone: 'in', '/chat': 'will get' });
        });

    var news = io
        .of('/news');
        .on('connection', function (socket) {
            socket.emit('item', { news: 'item' });
        });

Como você pode ver, tudo o que você faz é substituir sockets com of(‘/namespace’) quando você iniciar a chamada on(‘connection’, function(){}). Isso cria um segmento de conexões que você pode manter separadas das outras conexões. Como você pode ver, essa configuração permite que você faça broadcast para todos nesse namespace também.

Agora precisamos que os clientes se conectem a eles separadamente. Basta apenas criar conexões separadas para cada um dos namespaces, e está tudo pronto.

    var chat = io.connect('http://localhost/chat'),
        news = io.connect('http://localhost/news');

    chat.on('connect', function () {
        chat.emit('connectedToChat');
    });

    news.on('news', function () {
        news.emit(' connectedToNews');
    });

Basta adicionar o namespace ao final da URL normal, e você vai se conectar ao servidor Socket.IO com aquele namespace. Você pode tratar chat ou news exatamente da mesma maneira que você usou para tratar socket nos cenários de conexão única.

Configuração

O Socket.IO tem muitas opções configuráveis, então eles implementaram uma forma de configurá-lo. Logo abaixo está  uma forma resumida de como fazê-lo.

    io.configure('production', function(){
        io.enable('browser client etag');
        io.set('log level', 1);

        io.set('transports', ['websocket', 'flashsocket', 'xhr-polling']);
    });

Primeiro, chame io.configure enviando para o ambiente no qual você quer que a configuração seja feita e uma função. Dentro da função, você pode usar io.enable (que apenas define o valor da opção como true), io.disable (que define o valor para false), e io.set (que define o valor para qualquer que seja o segundo argumento) para mudar todas as opções disponíveis para Socket.IO.

Se você quiser mais informações sobre como alterar os ambientes e quais opções estão disponíveis, eu recomendo que você dê uma olhada neste wiki sobre como configurar Socket.IO.

Conclusão

O Socket.IO tem provado ser um ótimo substituto para WebSockets. Ele permite uma personalização incrível e uma API maravilhosamente simples para habilitar comunicação em tempo real de duas formas entre um servidor e o cliente. Definitivamente vale a pena dar uma olhada.

***

Artigo traduzido pela Redação iMasters, com autorização do autor. Publicado originalmente em  http://www.joezimjs.com/javascript/plugging-into-socket-io-advanced/