He estado haciendo más cosas en Node.Net.

Cliente de Redis:

Por lo pronto he añadido un cliente de Redis (junto al que ya hice de MySQL):
Aquí podéis ver un ejemplo: https://github.com/soywiz/NodeNet/blob/master/NodeNetAsync.Examples/RedisTestProgram.cs

Soporte para WebSockets:

(Conexión bidireccional que permite hacer push desde el server en vez de tener que hacer polling desde el cliente.)

Y por otro lado he añadido soporte parcial de WebSockets. Por ahora únicamente he probado con Google Chrome y únicamente soporta la versión 13 de las especificaciones. He implementado un servidor de chat con alguna cosilla interesante.

https://github.com/soywiz/NodeNet/blob/master/NodeNetAsync.Examples/WebSocketTestProgram.cs](https://github.com/soywiz/NodeNet/blob/master/NodeNetAsync.Examples/WebSocketTestProgram.cs)

Para gestionar una conexión de websocket hay que usar el filtro HttpWebSocket:

Core.Loop(async () =>  
{
    var Server = new HttpServer();  
    var Router = new HttpRouter();  
    
    
    Router.AddRoute("/websocket", new HttpWebSocket<Client>(new WebsocketChatHandler()));  
    
    Server.AddFilterLast(Router);  
    await Server.ListenAsync(80, "127.0.0.1");  
});  

Por un lado el servidor de chat manda cada 4 segundos un mensaje a todos los clientes:

Core.SetInterval(async () =>
{
    await SendMessageToAllAsync(String.Format("Global: Timer Tick 4 seconds. Connected users: {0}", ConnectedSockets.Count));
}, TimeSpan.FromSeconds(4));

Por otro lado el servidor de chat propaga mensajes y acepta comandos especiales, como /help y /nick para establecer el nick. Además se comprueba que el nick no se esté usando ya cuando se vaya a cambiar. Aquí se puede apreciar la potencia de LINQ (con el método de extensión Any que funciona con cualquier IEnumerable):

public bool IsNameUsed(string Name)
{
    return ConnectedSockets.Any(Socket => Socket.Tag.UserName == Name);
}

Por supuesto si miráis el código fuente del ejemplo veréis que es todo código secuencial sin más callbacks que los handlers de los routers y de la conexión/desconexión del websocket.

Vamos una puta maravilla.

El servidor de chat está funcionando, con un timer cada 4 segundos, con clientes conectados al chat y sirviendo 7200 RPS con una concurrencia de 500 ¡y en un solo thread! ¡y escribiendo código secuencial! ¡y con autocompletado!

ab -n 50000 -c 500 http://localhost
Requests per second:    7637.81 [#/sec] (mean)

Y por supuesto durante todo el desarrollo del ejemplo he tenido autocompletado en absolutamente TODO.

Sobre WebSockets:

Implementando WebSockets he podido ver cómo funciona el protocolo. Al menos el de la versión 13. Y ya que estoy voy a colocar aquí unos pequeños detalles sobre la implementación mínima:

http://tools.ietf.org/html/rfc6455

El cliente manda unos headers especiales:

  
Sec-WebSocket-Version: 13  
Sec-WebSocket-Key: Clave_en_base64

El servidor contesta con los siguientes headers:

  
HTTP/1.1 101 Web Socket Protocol Handshake  
Upgrade: WebSocket  
Connection: Upgrade  
Sec-WebSocket-Accept: Base64(SHA1(Encoding.ASCII.GetBytes(Request.Sec-WebSocket-Key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')))  
Access-Control-Allow-Origin: Request.Origin  
Access-Control-Allow-Credentials: true  
Access-Control-Allow-Headers: content-type  

A partir de este momento cualquier parte puede mandar paquetes (5.2.  Base Framing Protocol)
Los paquetes consisten en un header de 2 bytes que especifica el tipo de frame, la longitud del frame, y si hay más frames para el paquete. La longitud de los paquetes se especifica en 7 bits.

public enum OpcodeEnum : byte  
{  
  ContinuationFrame = 0,  
  TextFrame = 1,  
  BinaryFrame = 2,  
  ConnectionClose = 8,  
  Ping = 9,  
  Pong = 10,  
}  
  
IsFinal = (((Header[0] >> 7) & 0x1) != 0);  
Opcode = (OpcodeEnum)((Header[0] >> 0) & 0x7);  
PayloadLength = (Header[1] >> 0) & 0x7F;  
IsMasked = ((Header[1] >> 7) & 0x1) != 0;

El cliente manda los paquetes XOReados con una máscara si el bit IsMasked está habilitado (y debe estarlo por seguridad según las especificaciones). Si IsMasked está seteado, tras el header de 2 bytes habrán 4 bytes que definirán la máscara a XORear.

Se puede ver un ejemplo de escritura y lectura de paquetes aquí, así como el filtro que hace el upgrade del protocolo http a websocket.

https://github.com/soywiz/NodeNet/tree/master/NodeNetAsync/Net/Http/WebSockets](https://github.com/soywiz/NodeNet/tree/master/NodeNetAsync/Net/Http/WebSockets)

Una ayudita:

Os animo a que probéis Node.Net. Y a que colaboréis con el proyecto:

  • Podéis reportar bugs
  • Ayudarme a  corregir problemas
  • Implementar cosas nuevas

Cualquier ayuda es bienvenida :D