Nota: Esta es una explicación y un pseudocódigo sobre cómo implementar un servidor muy trivial que puede manejar los mensajes WebSocket entrantes y salientes según el formato de trama definitivo. No incluye el proceso de apretón de manos. Además, esta respuesta se ha realizado con fines educativos; no es una implementación con todas las funciones.
Especificación (RFC 6455)
Enviando mensajes
(En otras palabras, servidor → navegador)
Los marcos que envía deben formatearse de acuerdo con el formato de marco de WebSocket. Para enviar mensajes, este formato es el siguiente:
- un byte que contiene el tipo de datos (y alguna información adicional que está fuera del alcance de un servidor trivial)
- un byte que contiene la longitud
- ya sea dos u ocho bytes si la longitud no cabe en el segundo byte (el segundo byte es entonces un código que indica cuántos bytes se utilizan para la longitud)
- los datos reales (sin procesar)
El primer byte será 1000 0001
(o 129
) para un marco de texto.
El segundo byte tiene su primer bit configurado en 0
porque no estamos codificando los datos (la codificación del servidor al cliente no es obligatoria).
Es necesario determinar la longitud de los datos brutos para enviar correctamente los bytes de longitud:
- si
0 <= length <= 125
no necesita bytes adicionales
- si
126 <= length <= 65535
necesita dos bytes adicionales y el segundo byte es126
- si
length >= 65536
necesita ocho bytes adicionales y el segundo byte es127
La longitud debe dividirse en bytes separados, lo que significa que deberá desplazar los bits hacia la derecha (con una cantidad de ocho bits) y luego solo retener los últimos ocho bits haciendo AND 1111 1111
(que es 255
).
Después de los bytes de longitud, vienen los datos sin procesar.
Esto conduce al siguiente pseudocódigo:
bytesFormatted[0] = 129
indexStartRawData = -1 // it doesn't matter what value is
// set here - it will be set now:
if bytesRaw.length <= 125
bytesFormatted[1] = bytesRaw.length
indexStartRawData = 2
else if bytesRaw.length >= 126 and bytesRaw.length <= 65535
bytesFormatted[1] = 126
bytesFormatted[2] = ( bytesRaw.length >> 8 ) AND 255
bytesFormatted[3] = ( bytesRaw.length ) AND 255
indexStartRawData = 4
else
bytesFormatted[1] = 127
bytesFormatted[2] = ( bytesRaw.length >> 56 ) AND 255
bytesFormatted[3] = ( bytesRaw.length >> 48 ) AND 255
bytesFormatted[4] = ( bytesRaw.length >> 40 ) AND 255
bytesFormatted[5] = ( bytesRaw.length >> 32 ) AND 255
bytesFormatted[6] = ( bytesRaw.length >> 24 ) AND 255
bytesFormatted[7] = ( bytesRaw.length >> 16 ) AND 255
bytesFormatted[8] = ( bytesRaw.length >> 8 ) AND 255
bytesFormatted[9] = ( bytesRaw.length ) AND 255
indexStartRawData = 10
// put raw data at the correct index
bytesFormatted.put(bytesRaw, indexStartRawData)
// now send bytesFormatted (e.g. write it to the socket stream)
Recibir mensajes
(En otras palabras, navegador → servidor)
Los marcos que obtienes están en el siguiente formato:
- un byte que contiene el tipo de datos
- un byte que contiene la longitud
- ya sea dos u ocho bytes adicionales si la longitud no cabe en el segundo byte
- cuatro bytes que son las máscaras (= claves de decodificación)
- los datos reales
El primer byte generalmente no importa: si solo está enviando texto, solo está usando el tipo de texto. Será 1000 0001
(o 129
) en ese caso.
El segundo byte y los dos u ocho bytes adicionales necesitan algo de análisis, porque necesita saber cuántos bytes se utilizan para la longitud (necesita saber dónde comienzan los datos reales). Por lo general, la longitud en sí no es necesaria, ya que ya tiene los datos.
El primer bit del segundo byte es siempre, lo 1
que significa que los datos están enmascarados (= codificados). Los mensajes del cliente al servidor siempre están enmascarados. Necesitas eliminar ese primer bit haciendo secondByte AND 0111 1111
. Hay dos casos en los que el byte resultante no representa la longitud porque no cabe en el segundo byte:
- un segundo byte de
0111 1110
, o 126
, significa que los siguientes dos bytes se utilizan para la longitud
- un segundo byte de
0111 1111
, o 127
, significa que los siguientes ocho bytes se utilizan para la longitud
Los cuatro bytes de máscara se utilizan para decodificar los datos reales que se han enviado. El algoritmo de decodificación es el siguiente:
decodedByte = encodedByte XOR masks[encodedByteIndex MOD 4]
donde encodedByte
es el byte original en los datos, encodedByteIndex
es el índice (desplazamiento) del byte contando desde el primer byte de los datos reales , que tiene index 0
. masks
es una matriz que contiene los cuatro bytes de máscara.
Esto conduce al siguiente pseudocódigo para decodificar:
secondByte = bytes[1]
length = secondByte AND 127 // may not be the actual length in the two special cases
indexFirstMask = 2 // if not a special case
if length == 126 // if a special case, change indexFirstMask
indexFirstMask = 4
else if length == 127 // ditto
indexFirstMask = 10
masks = bytes.slice(indexFirstMask, 4) // four bytes starting from indexFirstMask
indexFirstDataByte = indexFirstMask + 4 // four bytes further
decoded = new array
decoded.length = bytes.length - indexFirstDataByte // length of real data
for i = indexFirstDataByte, j = 0; i < bytes.length; i++, j++
decoded[j] = bytes[i] XOR masks[j MOD 4]
// now use "decoded" to interpret the received data
1000 0001
(129) para un marco de texto? La especificación dice dice:%x1 denotes a text frame
. Entonces debería ser0000 0001
(0x01
), o?0001
, como se indica en el encabezado de esa parte de las especificaciones: " Código de operación : 4 bits". El primer byte consta de FIN, RSV1-3 y código de operación. FIN es1
, RSV1-3 son los tres0
y el código de operación es el0001
que se suma1000 0001
para el primer byte. También vea la ilustración en la especificación que muestra cómo se dividen los bytes en las diferentes partes.Implementación de Java (si alguno lo requiere)
Lectura: cliente a servidor
Escritura: servidor a cliente
fuente
Implementación de JavaScript:
fuente
2^31 - 1
.Implementación de C #
Navegador -> Servidor
Servidor -> Navegador
fuente
test�c=ܝX[
en el que "prueba" es mi mensaje. ¿De qué viene la otra parte?La respuesta de pimvdb implementada en python:
Un ejemplo de uso:
fuente
Además de la función de codificación de cuadros de PHP, aquí sigue una función de decodificación:
He implementado esta y también otras funciones en una clase PHP de WebSocket fácil de usar aquí .
fuente
Implementación de PHP:
fuente
Gracias por la respuesta, me gustaría agregar la versión de Python de hfern (arriba) para incluir la función de envío si alguien está interesado.
Uso para leer:
Uso para escribir:
fuente
Implementación en Go
Codificar parte (servidor -> navegador)
Decodificar parte (navegador -> servidor)
fuente
Clojure, la función de decodificación asume que el marco se envía como mapa de
{:data byte-array-buffer :size int-size-of-buffer}
, porque el tamaño real puede no ser el mismo que el de la matriz de bytes dependiendo del tamaño del fragmento de su flujo de entrada.Código publicado aquí: https://gist.github.com/viperscape/8918565
fuente
Implementación de C ++ (no por mí) aquí . Tenga en cuenta que cuando sus bytes superan los 65535, debe cambiar con un valor largo como se muestra aquí .
fuente