Mensajes de browser a browser (Web Socket)

ws_tornadoHe estado trabajando últimamente en un proyecto en el que necesitaba comunicación entre instancias distintas de browsers en tiempos lo más real posible.

La solución se desprende, Web Socket con HTML5 Sonrisa

La lógica que necesitamos en el servidor es simple, transmitir un mensaje que viene de un cliente, al resto de los clientes conectados.

En la parte del servidor quiero la menor lógica posible, así podré reutilizarlo en distintos proyectos. Los mensajes van de clientes a clientes, por lo que la responsabilidad del servidor en este caso debe ser solamente la de comunicación.

Para implementar el servidor he usado Fleck, un Web Socket que ya existe para NET y el cual recomiendo muchísimo, un código claro y fácil de entender si te pica la curiosidad y quieres conocer internamente cómo funciona.

Server

De momento vamos a crear una aplicación de consola para hospedar a nuestro Socket Server.

class Program
{
    static void Main()
    {
        FleckLog.Level = LogLevel.Debug;

        var socketManager = new MessagesWebSocket();
        socketManager.Start();

        Console.ReadLine();

        socketManager.Close();
    }
}

La clase MessagesWebSocket tendría:

class MessagesWebSocket
{
    private readonly List<IWebSocketConnection> _allSockets;
    private readonly WebSocketServer _server;


    public MessagesWebSocket()
    {
        _allSockets = new List<IWebSocketConnection>();
        _server = new WebSocketServer(ConfigurationManager.AppSettings["socketServer"]);
    }

    public void Start()
    {
        _server.Start(socket =>
        {
            socket.OnOpen = () =>
            {
                Console.WriteLine("Open client connection!");
                _allSockets.Add(socket);
            };

            socket.OnClose = () => _allSockets.Remove(socket);
            socket.OnMessage = message => _allSockets.Where(s=> s != socket).ToList().ForEach(s => s.Send(message));
        });
    }

    public void Close()
    {
        _allSockets.ForEach(s => s.Close());
        _server.ListenerSocket.Close();
    }
}

Si has trabajado con Socket verás que no hay mucha diferencia a lo que se hacía hasta ahora, incluso, la clase WebSocketServer de Fleck usa internamente System.Net.Sockets.

Nuestra clase MessagesWebSocket tiene dos métodos, Start & Stop.

Start nos permite abrir la conexión y quedarnos a la escucha de cualquier petición de conexión de algún cliente. Cuando un cliente previamente conectado envía un mensaje al servidor, este simplemente toma el mensaje tal cual y lo envía al resto de clientes conectados.

El servidor no interviene en absoluto en la estructura del mensaje que se envía, justamente lo que necesitamos. Entender los mensajes que se envían los clientes será siempre responsabilidad de ellos.

Client

Si hablamos de clientes WEB, la moda en comunicación se llama JSON! Sonrisa Así que vamos a hacer que este sea el formato que usen los mensajes enviados entre clientes.

Para que todo el tema de web socket y json nos quede fuera de la lógica de cualquier página o proyecto, vamos a encapsular toda la funcionalidad dentro de un objeto en Javascript que nos permita enviar y recibir mensajes sin importarnos cómo.

var WebSocketHelper = function (server, onMessage, onError)
{
    var w = window;
    var connection;

    var reciveData = onMessage || false;
    var error = onError || false;

    // if user is running mozilla then use it's built-in WebSocket
    w.WebSocket = w.WebSocket || w.MozWebSocket;

    // open connection
    try
    {
        connection = new w.WebSocket(server);
    }
    catch (connErr)
    {
        if (error) error("Open connection error: " + connErr);
        return;
    }

    // only for debug propose
    connection.onopen = function ()
    {
        //alert('Connected.');
    };

    connection.onmessage = function (d)
    {
        var result = jQuery.parseJSON(d.data);
        if (reciveData) reciveData(result);

        return false;
    };

    connection.onerror = function (wsErr)
    {
        if (error) error("Connection error: " + wsErr);
        connection = null;
    };

    this.SendCommand = function (obj)
    {
        try
        {
            var jsonString = JSON.stringify(obj);
            connection.send(jsonString);
        }
        catch (sendErr)
        {
            if (error) error("Send message error: " + sendErr);
        }
    };
};

Vamos por parte.

1- Al objeto WebSocketHelper le pasamos tres parámetros: la dirección del servidor, una función (opcional) que se ejecutará cuando nos llegue un nuevo mensaje y otra función (también opcional) que se ejecutará si encontramos algún error.

2- Instanciamos un nuevo objeto WebSocket, y aquí entramos en las diferencias entre browsers de toda la vida. Firefox tiene un objeto MozWebSocket mientras Chrome e Internet Explorer (v10) usan WebSocket.

3- Luego de instanciado el socket, intentamos abrir la conexión con el servidor, si todo sale bien, estamos listos para enviar y recibir datos mediante el socket.

4- El evento onmessage es quien me permite capturar los mensajes que llegan desde el servidor. La función asociada a este evento toma el mensaje recibido y lo transforma a un objeto javascript usando jQuery.parseJSON. Al tener la información recibida como objeto javscript, se ejecuta la función que hemos pasado al WebSocketHelper pasando como parámetro el objeto recibido.

5- Para enviar un objeto al servidor el proceso es parecido. Se usa la función pública SendCommand y se pasa como parámetros el objeto a enviar. Esta función internamente convierte el objeto en string y lo envía mediante el socket al servidor.

¿Cómo funciona esto desde una página?

Primero que todo hay que llegar a un acuerdo entre clientes, podemos enviar cualquier objeto javascript mediante el servidor pero es necesario que los clientes sepan de que va el asunto, porque de lo contrario sería como pedir una cerveza y que de pronto te lleguen melones, quesos, tomates, gambas y sandias ( y lo peor es que intentes algo porque creas que son cervezas).

En mi caso he usado una estructura de objetos muy simple. Internamente cada objeto que envío tiene una propiedad llamada command que define el tipo de mensaje que es.

var commands = { stop: "stop", start: "start", game: "game", reset: "reset", gameover: "gameover" };

Inicializando el socket server y recibiendo datos desde el servidor:

var socket = new WebSocketHelper("ws://192.168.100.64:8181", function (cmd)
{
    switch (cmd.command)
    {
        case commands.gameover:

            // GAME Over
            break;

        case commands.start:
            
            alert("Welcome {0}. Are you ready?".format(cmd.player));
            break;

        default: throw new Error("Invalid Command on socket server.");

    }
});

Enviando mensajes:

socket.SendCommand(
{
    command: commands.start,
    player: 'Pepe el loco',
    device: 1,
    language: 'es'
});

y con esto ya tenemos una comunicación simple de browser a browser.

Más adelante veremos otro ejemplo en el que vamos a darle más responsabilidad al SocketServer enviando comandos recibidos desde una Kinect a clientes Web y ya de paso, convertiremos nuestro WebSocket en un servicio Windows.

Salu2

4 comentarios sobre “Mensajes de browser a browser (Web Socket)”

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *