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,
- Obtener el valor de Sec-WebSocket-Key sin espacios iniciales ni finales de el encabezado de la solicitud
- Concatenar con "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
- Calcular el código SHA-1 y Base64
- 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]); }