Escribiendo un servidor WebSocket en C#

Esta traducción está incompleta. Por favor, ayuda a traducir este artículo del inglés.

Introducción

Si deseas utilizar la API WebSocket, es conveniente si tienes un servidor. En este artículo te mostraré como puedes escribir uno en C#. Tú puedes hacer esto en cualquier lenguaje del lado del servidor, pero para mantener las cosas simples y más comprensibles, elegí el lenguaje de Microsoft.

Este servidor se ajusta a RFC 6455 por lo que solo manejará las conexiones de Chrome version 16, Firefox 11, IE 10 and superiores.

Primeros pasos

WebSocket se comunica a través de conexiones TCP (Transmission Control Protocol), afortunadamente C# tiene una clase TcpListener la cual hace lo que su nombre sugiere. Esta se encuentra en el namespace System.Net.Sockets.

Es una buena idea usar la instrucción using para escribir menos. Eso significa que no tendrás que re escribir el namespace si utilizas clases de la misma.

TcpListener

Constructor:

TcpListener(System.Net.IPAddress localaddr, int port)

Debes especificar aquí, donde el servidor estará accesible.

Para obtener fácilmente el tipo del primer parámetro, use el método estático Parse de IPAddress.

Métodos:

  • Start()
  • System.Net.Sockets.TcpClient AcceptTcpClient()
    Espera por una conexión TCP, la acepta y la devuelve como un objeto TcpClient.

Aquí está como utilizar lo que hemos aprendido:

​using System.Net.Sockets;
using System.Net;
using System;

class Server {
    public static void Main() {
        TcpListener server = new TcpListener(IPAddress.Parse("127.0.0.1"), 80);

        server.Start();
        Console.WriteLine("El server se ha iniciado en 127.0.0.1:80.{0}Esperando una conexión...", Environment.NewLine);

        TcpClient client = server.AcceptTcpClient();

        Console.WriteLine("Un cliente conectado.");
    }
}

TcpClient

Métodos:

  • System.Net.Sockets.NetworkStream GetStream()
    Obtiene el stream del canal de comunicación. Ambos lados del canal tienen capacidad de lectura y escritura.

Propiedades:

  • int Available
    Este es el número de butes de datos que han sido enviados. Es cero hasta que NetworkStream.DataAvailable es false.

NetworkStream

Métodos:

Write(Byte[] buffer, int offset, int size)

Escribe bytes al buffer, el offset y el size determinan la longitud del mensaje.

Read(Byte[] buffer, int offset, int size)

Lee bytes del buffer, el offset y el size determinan la longitud del mensaje.

Ampliemos nuestro ejemplo anterior.

TcpClient client = server.AcceptTcpClient();

Console.WriteLine("Un cliente conectado.");

NetworkStream stream = client.GetStream();

//enter to an infinite cycle to be able to handle every change in stream
while (true) {
    while (!stream.DataAvailable);

    Byte[] bytes = new Byte[client.Available];

    stream.Read(bytes, 0, bytes.Length);
}

Handshaking

Cuando un cliente se conecta al servidor, envía una solicitud GET para actualizar la conexión al WebSocket con una simple petición HTTP. Esto es conocido como handshaking.

Este código tiene un error. Digamos que client.Available devuelve 2 porque solo el GE está disponible hasta el momento. La expresión regular fallaría a pesar de que los datos son perfectamente válidos.

using System.Text;
using System.Text.RegularExpressions;

Byte[] bytes = new Byte[client.Available];

stream.Read(bytes, 0, bytes.Length);

//translate bytes of request to string
String data = Encoding.UTF8.GetString(bytes);

if (new Regex("^GET").IsMatch(data)) {

} else {

}

Crear la respuesta es más fácil de entender por que debes hacerlo de esta forma.

Debes,

  1. Obtener el valor de Sec-WebSocket-Key sin espacios iniciales ni finales de el encabezado de la solicitud
  2. Concatenar con "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
  3. Calcular el código SHA-1 y Base64
  4. Escribe el valor Sec-WebSocket-Accept en el encabezado como parte de la respuesta HTTP.
if (new Regex("^GET").IsMatch(data)) {
    Byte[] response = Encoding.UTF8.GetBytes("HTTP/1.1 101 Switching Protocols" + Environment.NewLine
        + "Connection: Upgrade" + Environment.NewLine
        + "Upgrade: websocket" + Environment.NewLine
        + "Sec-WebSocket-Accept: " + Convert.ToBase64String (
            SHA1.Create().ComputeHash (
                Encoding.UTF8.GetBytes (
                    new Regex("Sec-WebSocket-Key: (.*)").Match(data).Groups[1].Value.Trim() + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
                )
            )
        ) + Environment.NewLine
        + Environment.NewLine);

    stream.Write(response, 0, response.Length);
}

Decoding messages

Luego de un handshake exitoso el cliente puede enviar mensajes al servidor, pero estos serán codificados.

Si nosotros enviamos "MDN", obtendremos estos bytes:

129 131 61 84 35 6 112 16 109

- 129:

FIN (¿Es el mensaje completo?) RSV1 RSV2 RSV3 Opcode
1 0 0 0 0x1=0001

FIN: Puedes enviar tu mensaje en marcos, pero ahora debe mantener las cosas simples.
Opcode 0x1 significa que es un texto. Lista completa de Opcodes

- 131:

Si el segundo byte menos 128 se encuentra entre 0 y 125, esta es la longitud del mensaje. Si es 126, los siguientes 2 bytes (entero sin signo de 16 bits), si es 127, los siguientes 8 bytes (entero sin signo de 64 bits) son la longitud.

Puedo tomar 128, porque el primer bit siempre es 1.

- 61, 84, 35 y 6 son los bytes de la clave a decodificar. Cambian en cada oportunidad.

- Los bytes codificados restantes son el mensaje.

Algoritmo de decodificación

byte decodificado = byte codificado XOR (posición del byte codificado Mod 4) byte de la clave

Ejemplo en C#:

Byte[] decoded = new Byte[3];
Byte[] encoded = new Byte[3] {112, 16, 109};
Byte[] key = Byte[4] {61, 84, 35, 6};

for (int i = 0; i < encoded.Length; i++) {
    decoded[i] = (Byte)(encoded[i] ^ key[i % 4]);
}

Relacionado

 

Etiquetas y colaboradores del documento

Etiquetas: 
 Colaboradores en esta página: jjmontes
 Última actualización por: jjmontes,