¿Cómo puedo enviar y recibir mensajes WebSocket en el lado del servidor?
¿Cómo puedo enviar y recibir mensajes en el lado del servidor usando WebSocket, según el protocolo?
¿Por qué recibo bytes aparentemente aleatorios en el servidor cuando envío datos desde el navegador al servidor? ¿Los datos están codificados de alguna manera?
¿Cómo funciona el encuadre en las direcciones servidor → cliente y cliente → servidor?
Nota: Esta es una explicación y un pseudocódigo sobre cómo implementar un servidor muy trivial que pueda manejar mensajes WebSocket entrantes y salientes según el formato de encuadre 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 fotogramas que envíe deben formatearse según el formato de fotogramas de WebSocket. Para el envío de 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 sin procesar para enviar los bytes de longitud correctamente:
- 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 conservar solo los últimos ocho bits haciendo AND 1111 1111
(que es 255
).
Después de los bytes de longitud vienen los datos sin procesar.
Esto lleva 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 fotogramas que obtienes tienen el siguiente formato:
- un byte que contiene el tipo de datos
- un byte que contiene la longitud
- dos u ocho bytes adicionales si la longitud no cabía en el segundo byte
- cuatro bytes que son las máscaras (= claves de decodificación)
- los datos reales
El primer byte normalmente no importa: si solo envías texto, solo estás 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). La longitud en sí normalmente no es necesaria ya que ya tienes los datos.
El primer bit del segundo byte es siempre 1
lo que significa que los datos están enmascarados (= codificados). Los mensajes del cliente al servidor siempre están enmascarados. Debes eliminar ese primer bit haciendo secondByte AND 0111 1111
. Hay dos casos en los que el byte resultante no representa la longitud porque no cabía en el segundo byte:
- un segundo byte de
0111 1110
, o126
, significa que los siguientes dos bytes se utilizan para la longitud - un segundo byte de
0111 1111
, o127
, 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
está el byte original de los datos, encodedByteIndex
es el índice (desplazamiento) del byte contando desde el primer byte de los datos reales , que tiene índice 0
. masks
es una matriz que contiene los cuatro bytes de máscara.
Esto lleva 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
Implementación de Java (si alguno lo requiere)
Lectura: Cliente a Servidor
int len = 0;
byte[] b = new byte[buffLenth];
//rawIn is a Socket.getInputStream();
while(true){
len = rawIn.read(b);
if(len!=-1){
byte rLength = 0;
int rMaskIndex = 2;
int rDataStart = 0;
//b[0] is always text in my case so no need to check;
byte data = b[1];
byte op = (byte) 127;
rLength = (byte) (data & op);
if(rLength==(byte)126) rMaskIndex=4;
if(rLength==(byte)127) rMaskIndex=10;
byte[] masks = new byte[4];
int j=0;
int i=0;
for(i=rMaskIndex;i<(rMaskIndex+4);i++){
masks[j] = b[i];
j++;
}
rDataStart = rMaskIndex + 4;
int messLen = len - rDataStart;
byte[] message = new byte[messLen];
for(i=rDataStart, j=0; i<len; i++, j++){
message[j] = (byte) (b[i] ^ masks[j % 4]);
}
parseMessage(new String(message));
//parseMessage(new String(b));
b = new byte[buffLenth];
}
}
Escritura: Servidor a Cliente
public void brodcast(String mess) throws IOException{
byte[] rawData = mess.getBytes();
int frameCount = 0;
byte[] frame = new byte[10];
frame[0] = (byte) 129;
if(rawData.length <= 125){
frame[1] = (byte) rawData.length;
frameCount = 2;
}else if(rawData.length >= 126 && rawData.length <= 65535){
frame[1] = (byte) 126;
int len = rawData.length;
frame[2] = (byte)((len >> 8 ) & (byte)255);
frame[3] = (byte)(len & (byte)255);
frameCount = 4;
}else{
frame[1] = (byte) 127;
int len = rawData.length;
frame[2] = (byte)((len >> 56 ) & (byte)255);
frame[3] = (byte)((len >> 48 ) & (byte)255);
frame[4] = (byte)((len >> 40 ) & (byte)255);
frame[5] = (byte)((len >> 32 ) & (byte)255);
frame[6] = (byte)((len >> 24 ) & (byte)255);
frame[7] = (byte)((len >> 16 ) & (byte)255);
frame[8] = (byte)((len >> 8 ) & (byte)255);
frame[9] = (byte)(len & (byte)255);
frameCount = 10;
}
int bLength = frameCount + rawData.length;
byte[] reply = new byte[bLength];
int bLim = 0;
for(int i=0; i<frameCount;i++){
reply[bLim] = frame[i];
bLim++;
}
for(int i=0; i<rawData.length;i++){
reply[bLim] = rawData[i];
bLim++;
}
out.write(reply);
out.flush();
}
Implementación de JavaScript:
function encodeWebSocket(bytesRaw) {
var bytesFormatted = new Array();
bytesFormatted[0] = 129;
if (bytesRaw.length <= 125) {
bytesFormatted[1] = bytesRaw.length;
} else if (bytesRaw.length >= 126 && bytesRaw.length <= 65535) {
bytesFormatted[1] = 126;
bytesFormatted[2] = ( bytesRaw.length >> 8 ) & 255;
bytesFormatted[3] = ( bytesRaw.length ) & 255;
} else {
bytesFormatted[1] = 127;
bytesFormatted[2] = ( bytesRaw.length >> 56 ) & 255;
bytesFormatted[3] = ( bytesRaw.length >> 48 ) & 255;
bytesFormatted[4] = ( bytesRaw.length >> 40 ) & 255;
bytesFormatted[5] = ( bytesRaw.length >> 32 ) & 255;
bytesFormatted[6] = ( bytesRaw.length >> 24 ) & 255;
bytesFormatted[7] = ( bytesRaw.length >> 16 ) & 255;
bytesFormatted[8] = ( bytesRaw.length >> 8 ) & 255;
bytesFormatted[9] = ( bytesRaw.length ) & 255;
}
for (var i = 0; i < bytesRaw.length; i++) {
bytesFormatted.push(bytesRaw.charCodeAt(i));
}
return bytesFormatted;
}
function decodeWebSocket (data) {
var datalength = data[1] & 127;
var indexFirstMask = 2;
if (datalength == 126) {
indexFirstMask = 4;
} else if (datalength == 127) {
indexFirstMask = 10;
}
var masks = data.slice(indexFirstMask,indexFirstMask + 4);
var i = indexFirstMask + 4;
var index = 0;
var output = "";
while (i < data.length) {
output += String.fromCharCode(data[i++] ^ masks[index++ % 4]);
}
return output;
}
Implementación de C#
Navegador -> Servidor
private String DecodeMessage(Byte[] bytes)
{
String incomingData = String.Empty;
Byte secondByte = bytes[1];
Int32 dataLength = secondByte & 127;
Int32 indexFirstMask = 2;
if (dataLength == 126)
indexFirstMask = 4;
else if (dataLength == 127)
indexFirstMask = 10;
IEnumerable<Byte> keys = bytes.Skip(indexFirstMask).Take(4);
Int32 indexFirstDataByte = indexFirstMask + 4;
Byte[] decoded = new Byte[bytes.Length - indexFirstDataByte];
for (Int32 i = indexFirstDataByte, j = 0; i < bytes.Length; i++, j++)
{
decoded[j] = (Byte)(bytes[i] ^ keys.ElementAt(j % 4));
}
return incomingData = Encoding.UTF8.GetString(decoded, 0, decoded.Length);
}
Servidor -> Navegador
private static Byte[] EncodeMessageToSend(String message)
{
Byte[] response;
Byte[] bytesRaw = Encoding.UTF8.GetBytes(message);
Byte[] frame = new Byte[10];
Int32 indexStartRawData = -1;
Int32 length = bytesRaw.Length;
frame[0] = (Byte)129;
if (length <= 125)
{
frame[1] = (Byte)length;
indexStartRawData = 2;
}
else if (length >= 126 && length <= 65535)
{
frame[1] = (Byte)126;
frame[2] = (Byte)((length >> 8) & 255);
frame[3] = (Byte)(length & 255);
indexStartRawData = 4;
}
else
{
frame[1] = (Byte)127;
frame[2] = (Byte)((length >> 56) & 255);
frame[3] = (Byte)((length >> 48) & 255);
frame[4] = (Byte)((length >> 40) & 255);
frame[5] = (Byte)((length >> 32) & 255);
frame[6] = (Byte)((length >> 24) & 255);
frame[7] = (Byte)((length >> 16) & 255);
frame[8] = (Byte)((length >> 8) & 255);
frame[9] = (Byte)(length & 255);
indexStartRawData = 10;
}
response = new Byte[indexStartRawData + length];
Int32 i, reponseIdx = 0;
//Add the frame bytes to the reponse
for (i = 0; i < indexStartRawData; i++)
{
response[reponseIdx] = frame[i];
reponseIdx++;
}
//Add the data bytes to the response
for (i = 0; i < length; i++)
{
response[reponseIdx] = bytesRaw[i];
reponseIdx++;
}
return response;
}