¿Cómo puedo enviar y recibir mensajes WebSocket en el lado del servidor?

Resuelto pimvdb asked hace 12 años • 13 respuestas
  • ¿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?

pimvdb avatar Nov 15 '11 00:11 pimvdb
Aceptado

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 0porque 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 1lo 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, 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 encodedByteestá el byte original de los datos, encodedByteIndexes el índice (desplazamiento) del byte contando desde el primer byte de los datos reales , que tiene índice 0. maskses 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
pimvdb avatar Nov 14 '2011 17:11 pimvdb

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();

}
Haribabu Pasupathy avatar Sep 18 '2012 06:09 Haribabu Pasupathy

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;
}
Richard Astbury avatar May 01 '2012 18:05 Richard Astbury

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;
    }
Nitij avatar Dec 12 '2014 10:12 Nitij