Frage Wie kann ich WebSocket-Nachrichten auf der Serverseite senden und empfangen?


  • Wie kann ich auf der Serverseite mit WebSocket Nachrichten senden und empfangen, wie im Protokoll beschrieben?

  • Warum bekomme ich scheinbar zufällige Bytes auf dem Server, wenn ich Daten vom Browser an den Server sende? Sind die Daten irgendwie codiert?

  • Wie funktioniert das Framing in den Server → Client- und Client → Server-Anweisungen?


75


Ursprung


Antworten:


Hinweis: Dies ist eine Erklärung und ein Pseudocode, um einen sehr trivialen Server zu implementieren, der eingehende und ausgehende WebSocket-Nachrichten gemäß dem endgültigen Rahmenformat verarbeiten kann. Es beinhaltet nicht den Handshake-Vorgang. Außerdem wurde diese Antwort für Bildungszwecke gegeben; Es ist keine voll funktionsfähige Implementierung.

Spezifikation (RFC 6455)


Nachrichten senden

(Mit anderen Worten, Server → Browser)

Die Frames, die Sie senden, müssen entsprechend dem WebSocket-Rahmenformat formatiert sein. Für das Senden von Nachrichten gilt folgendes Format:

  • ein Byte, das den Typ der Daten enthält (und einige zusätzliche Informationen, die für einen trivialen Server nicht verfügbar sind)
  • ein Byte, das die Länge enthält
  • entweder zwei oder acht Bytes, wenn die Länge nicht in das zweite Byte passt (das zweite Byte ist dann ein Code, der angibt, wie viele Bytes für die Länge verwendet werden)
  • die eigentlichen (Roh-) Daten

Das erste Byte wird sein 1000 0001 (oder 129) für einen Textrahmen.

Das zweite Byte hat sein erstes Bit gesetzt 0 weil wir die Daten nicht kodieren (die Kodierung von Server zu Client ist nicht obligatorisch).

Es ist notwendig, die Länge der Rohdaten zu bestimmen, um die Längenbytes korrekt zu senden:

  • ob 0 <= length <= 125, Sie benötigen keine zusätzlichen Bytes
  • ob 126 <= length <= 65535, benötigen Sie zwei zusätzliche Bytes und das zweite Byte ist 126
  • ob length >= 65536, benötigen Sie acht zusätzliche Bytes und das zweite Byte ist 127

Die Länge muss in einzelne Bytes aufgeteilt werden, was bedeutet, dass Sie nach rechts verschieben müssen (mit einer Menge von acht Bits) und dann nur die letzten acht Bits beibehalten AND 1111 1111 (welches ist 255).

Nach dem Längenbyte (s) kommen die Rohdaten.

Dies führt zu folgendem Pseudocode:

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)

Nachrichten empfangen

(Mit anderen Worten, Browser → Server)

Die Frames, die Sie erhalten, haben das folgende Format:

  • ein Byte, das den Datentyp enthält
  • ein Byte, das die Länge enthält
  • entweder zwei oder acht zusätzliche Bytes, wenn die Länge nicht in das zweite Byte passte
  • vier Bytes, die die Masken sind (= Dekodierschlüssel)
  • die tatsächlichen Daten

Das erste Byte spielt normalerweise keine Rolle - wenn Sie nur Text senden, verwenden Sie nur den Texttyp. Es wird sein 1000 0001 (oder 129) In diesem Fall.

Das zweite Byte und die zusätzlichen zwei oder acht Bytes benötigen ein Parsing, da Sie wissen müssen, wie viele Bytes für die Länge verwendet werden (Sie müssen wissen, wo die echten Daten beginnen). Die Länge selbst ist normalerweise nicht notwendig, da Sie die Daten bereits haben.

Das erste Bit des zweiten Bytes ist immer 1was bedeutet, dass die Daten maskiert (= codiert) sind. Nachrichten vom Client zum Server werden immer maskiert. Sie müssen dieses erste Bit entfernen, indem Sie tun secondByte AND 0111 1111. Es gibt zwei Fälle, in denen das resultierende Byte die Länge nicht darstellt, da es nicht in das zweite Byte passte:

  • ein zweites Byte von 0111 1110, oder 126, bedeutet, dass die folgenden zwei Bytes für die Länge verwendet werden
  • ein zweites Byte von 0111 1111, oder 127, bedeutet, dass die folgenden acht Bytes für die Länge verwendet werden

Die vier Maskenbytes werden zum Decodieren der tatsächlichen Daten verwendet, die gesendet wurden. Der Algorithmus zum Decodieren ist wie folgt:

decodedByte = encodedByte XOR masks[encodedByteIndex MOD 4]

woher encodedByte ist das ursprüngliche Byte in den Daten, encodedByteIndex ist der Index (Offset) des Bytes ab dem ersten Byte der realen Daten, die Index hat 0. masks ist ein Array, das die vier Maskenbytes enthält.

Dies führt zu folgendem Pseudocode zum Decodieren:

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

141



Java-Implementierung (falls erforderlich)

Lesen: Client zu Server

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

            }
        }

Schreiben: Server zu Client

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

}

23



JavaScript-Implementierung:

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

14



C # Implementierung

Browser -> Server

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

Server -> Browser

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

11



Pimvdb Antwort in Python implementiert:

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

Ein Anwendungsbeispiel:

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', '!']

5



Zusätzlich zur PHP-Rahmencodierungsfunktion folgt hier eine Decodierungsfunktion:

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

Ich habe diese und auch andere Funktionen in einer einfach zu bedienenden WebSocket PHP-Klasse implementiert Hier.


5



PHP-Implementierung:

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

4



Implementierung in Go

Teil verschlüsseln (Server -> Browser)

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
}

Decode-Teil (Browser -> Server)

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

2



Clojure, die Dekodierungsfunktion nimmt an, dass der Rahmen als Karte von gesendet wird {:data byte-array-buffer :size int-size-of-buffer}, da die tatsächliche Größe möglicherweise nicht die gleiche Größe wie das Byte-Array hat, abhängig von der Chunk-Größe Ihres Eingabestreams.

Code hier gepostet: 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))

2