websockets event codes encoding protocols websocket decoding

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



websocket readystate (11)

Además de la función de codificación de cuadros de PHP, aquí sigue una función de decodificación:

function Decode($M){ $M = array_map("ord", str_split($M)); $L = $M[1] AND 127; if ($L == 126) $iFM = 4; else if ($L == 127) $iFM = 10; else $iFM = 2; $Masks = array_slice($M, $iFM, 4); $Out = ""; for ($i = $iFM + 4, $j = 0; $i < count($M); $i++, $j++ ) { $Out .= chr($M[$i] ^ $Masks[$j % 4]); } return $Out; }

Implementé esto y también otras funciones en una clase de PHP WebSocket fácil de usar here .

  • ¿Cómo puedo enviar y recibir mensajes del lado del servidor usando WebSocket, según el protocolo?

  • ¿Por qué obtengo bytes aparentemente aleatorios en el servidor cuando envío datos desde el navegador al servidor? Es la información codificada de alguna manera?

  • ¿Cómo funciona el encuadre en las direcciones del servidor → cliente y cliente → servidor?


Clojure, la función de decodificación asume que el marco se envía como un mapa de {:data byte-array-buffer :size int-size-of-buffer} , porque el tamaño real puede no ser del mismo tamaño que el byte-array dependiendo del tamaño del fragmento de su inputstream.

Código publicado aquí: https://gist.github.com/viperscape/8918565

(defn ws-decode [frame] "decodes websocket frame" (let [data (:data frame) dlen (bit-and (second data) 127) mstart (if (== dlen 127) 10 (if (== dlen 126) 4 2)) mask (drop 2 (take (+ mstart 4) data)) msg (make-array Byte/TYPE (- (:size frame) (+ mstart 4)))] (loop [i (+ mstart 4), j 0] (aset-byte msg j (byte (bit-xor (nth data i) (nth mask (mod j 4))))) (if (< i (dec(:size frame))) (recur (inc i) (inc j)))) msg)) (defn ws-encode [data] "takes in bytes, return websocket frame" (let [len (count data) blen (if (> len 65535) 10 (if (> len 125) 4 2)) buf (make-array Byte/TYPE (+ len blen)) _ (aset-byte buf 0 -127) ;;(bit-or (unchecked-byte 0x80) (unchecked-byte 0x1) _ (if (= 2 blen) (aset-byte buf 1 len) ;;mask 0, len (do (dorun(map #(aset-byte buf %1 (unchecked-byte (bit-and (bit-shift-right len (*(- %2 2) 8)) 255))) (range 2 blen) (into ()(range 2 blen)))) (aset-byte buf 1 (if (> blen 4) 127 126)))) _ (System/arraycopy data 0 buf blen len)] buf))


Gracias por la respuesta, me gustaría agregar a la versión de Python de hfern (arriba) para incluir la función de envío si a alguien le interesa.

def DecodedWebsockRecieve(stringStreamIn): byteArray = stringStreamIn datalength = byteArray[1] & 127 indexFirstMask = 2 if datalength == 126: indexFirstMask = 4 elif datalength == 127: indexFirstMask = 10 masks = [m for m in byteArray[indexFirstMask : indexFirstMask+4]] indexFirstDataByte = indexFirstMask + 4 decodedChars = [] i = indexFirstDataByte j = 0 while i < len(byteArray): decodedChars.append( chr(byteArray[i] ^ masks[j % 4]) ) i += 1 j += 1 return ''''.join(decodedChars) def EncodeWebSockSend(socket,data): bytesFormatted = [] bytesFormatted.append(129) bytesRaw = data.encode() bytesLength = len(bytesRaw) if bytesLength <= 125 : bytesFormatted.append(bytesLength) elif bytesLength >= 126 and bytesLength <= 65535 : bytesFormatted.append(126) bytesFormatted.append( ( bytesLength >> 8 ) & 255 ) bytesFormatted.append( bytesLength & 255 ) else : bytesFormatted.append( 127 ) bytesFormatted.append( ( bytesLength >> 56 ) & 255 ) bytesFormatted.append( ( bytesLength >> 48 ) & 255 ) bytesFormatted.append( ( bytesLength >> 40 ) & 255 ) bytesFormatted.append( ( bytesLength >> 32 ) & 255 ) bytesFormatted.append( ( bytesLength >> 24 ) & 255 ) bytesFormatted.append( ( bytesLength >> 16 ) & 255 ) bytesFormatted.append( ( bytesLength >> 8 ) & 255 ) bytesFormatted.append( bytesLength & 255 ) bytesFormatted = bytes(bytesFormatted) bytesFormatted = bytesFormatted + bytesRaw socket.send(bytesFormatted)

Uso para leer:

bufSize = 1024 read = DecodedWebsockRecieve(socket.recv(bufSize))

Uso para escribir:

EncodeWebSockSend(sock,"hellooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo")


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; }


Implementación de C ++ (no por mí) here . Tenga en cuenta que cuando sus bytes son más de 65535, debe cambiar con un valor largo como se muestra here .


Implementación de Java (si se 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 PHP:

function encode($message) { $length = strlen($message); $bytesHeader = []; $bytesHeader[0] = 129; // 0x1 text frame (FIN + opcode) if ($length <= 125) { $bytesHeader[1] = $length; } else if ($length >= 126 && $length <= 65535) { $bytesHeader[1] = 126; $bytesHeader[2] = ( $length >> 8 ) & 255; $bytesHeader[3] = ( $length ) & 255; } else { $bytesHeader[1] = 127; $bytesHeader[2] = ( $length >> 56 ) & 255; $bytesHeader[3] = ( $length >> 48 ) & 255; $bytesHeader[4] = ( $length >> 40 ) & 255; $bytesHeader[5] = ( $length >> 32 ) & 255; $bytesHeader[6] = ( $length >> 24 ) & 255; $bytesHeader[7] = ( $length >> 16 ) & 255; $bytesHeader[8] = ( $length >> 8 ) & 255; $bytesHeader[9] = ( $length ) & 255; } $str = implode(array_map("chr", $bytesHeader)) . $message; return $str; }


Implementación en Go

Codificar parte (servidor -> navegador)

func encode (message string) (result []byte) { rawBytes := []byte(message) var idxData int length := byte(len(rawBytes)) if len(rawBytes) <= 125 { //one byte to store data length result = make([]byte, len(rawBytes) + 2) result[1] = length idxData = 2 } else if len(rawBytes) >= 126 && len(rawBytes) <= 65535 { //two bytes to store data length result = make([]byte, len(rawBytes) + 4) result[1] = 126 //extra storage needed result[2] = ( length >> 8 ) & 255 result[3] = ( length ) & 255 idxData = 4 } else { result = make([]byte, len(rawBytes) + 10) result[1] = 127 result[2] = ( length >> 56 ) & 255 result[3] = ( length >> 48 ) & 255 result[4] = ( length >> 40 ) & 255 result[5] = ( length >> 32 ) & 255 result[6] = ( length >> 24 ) & 255 result[7] = ( length >> 16 ) & 255 result[8] = ( length >> 8 ) & 255 result[9] = ( length ) & 255 idxData = 10 } result[0] = 129 //only text is supported // put raw data at the correct index for i, b := range rawBytes { result[idxData + i] = b } return }

Parte de decodificación (navegador -> servidor)

func decode (rawBytes []byte) string { var idxMask int if rawBytes[1] == 126 { idxMask = 4 } else if rawBytes[1] == 127 { idxMask = 10 } else { idxMask = 2 } masks := rawBytes[idxMask:idxMask + 4] data := rawBytes[idxMask + 4:len(rawBytes)] decoded := make([]byte, len(rawBytes) - idxMask + 4) for i, b := range data { decoded[i] = b ^ masks[i % 4] } return string(decoded) }


La respuesta de pimvdb implementada en python:

def DecodedCharArrayFromByteStreamIn(stringStreamIn): #turn string values into opererable numeric byte values byteArray = [ord(character) for character in stringStreamIn] datalength = byteArray[1] & 127 indexFirstMask = 2 if datalength == 126: indexFirstMask = 4 elif datalength == 127: indexFirstMask = 10 masks = [m for m in byteArray[indexFirstMask : indexFirstMask+4]] indexFirstDataByte = indexFirstMask + 4 decodedChars = [] i = indexFirstDataByte j = 0 while i < len(byteArray): decodedChars.append( chr(byteArray[i] ^ masks[j % 4]) ) i += 1 j += 1 return decodedChars

Un ejemplo de uso:

fromclient = ''/x81/x8c/xff/xb8/xbd/xbd/xb7/xdd/xd1/xd1/x90/x98/xea/xd2/x8d/xd4/xd9/x9c'' # this looks like "?ŒOÇ¿¢gÓ ç/Ð=«ož" in unicode, received by server print DecodedCharArrayFromByteStreamIn(fromclient) # [''H'', ''e'', ''l'', ''l'', ''o'', '' '', ''W'', ''o'', ''r'', ''l'', ''d'', ''!'']


Nota: Esta es una explicación y pseudocódigo sobre cómo implementar un servidor muy trivial que pueda manejar los mensajes entrantes y salientes de WebSocket 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 marcos que está enviando deben formatearse de acuerdo con el formato de marco 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
  • dos u ocho bytes si la longitud no cabe en el segundo byte (el segundo byte es 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 puesto a 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 es 126
  • si length >= 65536 , necesita ocho bytes adicionales, y el segundo byte es 127

La longitud tiene que dividirse en bytes separados, lo que significa que tendrá que desplazarse hacia la derecha (con una cantidad de ocho bits), y luego solo retendrá los últimos ocho bits haciendo AND 1111 1111 (que es 255 ) .

Después del (de los) byte (s) de longitud aparecen los datos brutos.

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 marcos que obtienes están en 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 cabe en el segundo byte
  • cuatro bytes que son las máscaras (= claves de descodificació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 algunos análisis, ya que necesita saber cuántos bytes se utilizan para la longitud (necesita saber dónde comienzan los datos reales). La longitud en sí misma no suele ser necesaria ya que ya tienes los datos.

El primer bit del segundo byte es siempre 1 que significa que los datos están enmascarados (= codificados). Los mensajes del cliente al servidor siempre están enmascarados. secondByte AND 0111 1111 eliminar ese primer bit haciendo secondByte AND 0111 1111 . Hay dos casos en los que el byte resultante no representa la longitud porque no encajaba en el segundo byte:

  • un segundo byte de 0111 1110 , o 126 , significa que se utilizan los siguientes dos bytes para la longitud
  • un segundo byte de 0111 1111 , o 127 , significa que se utilizan los siguientes ocho bytes para la longitud

Los cuatro bytes de máscara se utilizan para decodificar los datos reales que se han enviado. El algoritmo para decodificar 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 recuento de bytes desde el primer byte de los datos reales , que tiene el í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