Jak mogę wysyłać i odbierać wiadomości WebSocket po stronie serwera?

85
  • Jak mogę wysyłać i odbierać wiadomości po stronie serwera za pomocą WebSocket, zgodnie z protokołem?

  • Dlaczego otrzymuję pozornie losowe bajty na serwerze, kiedy wysyłam dane z przeglądarki na serwer? Czy dane zostały jakoś zakodowane?

  • Jak działa ramka w kierunkach serwer → klient i klient → serwer?

pimvdb
źródło

Odpowiedzi:

154

Uwaga: To jest wyjaśnienie i pseudokod, jak zaimplementować bardzo trywialny serwer, który może obsługiwać przychodzące i wychodzące wiadomości WebSocket zgodnie z ostatecznym formatem ramek. Nie obejmuje procesu uzgadniania. Ponadto ta odpowiedź została udzielona w celach edukacyjnych; nie jest to w pełni funkcjonalna implementacja.

Specyfikacja (RFC 6455)


Wysyłanie wiadomości

(Innymi słowy, serwer → przeglądarka)

Ramki, które wysyłasz, muszą być sformatowane zgodnie z formatem ramek WebSocket. W przypadku wysyłania wiadomości ten format jest następujący:

  • jeden bajt zawierający typ danych (i kilka dodatkowych informacji, które są poza zakresem dla trywialnego serwera)
  • jeden bajt zawierający długość
  • dwa lub osiem bajtów, jeśli długość nie mieści się w drugim bajcie (drugi bajt jest wówczas kodem informującym, ile bajtów jest używanych na długość)
  • rzeczywiste (surowe) dane

Pierwszy bajt będzie 1000 0001(lub 129) dla ramki tekstowej.

Drugi bajt ma ustawiony pierwszy bit, 0ponieważ nie kodujemy danych (kodowanie z serwera do klienta nie jest obowiązkowe).

Konieczne jest określenie długości surowych danych, aby poprawnie przesłać bajty długości:

  • jeśli 0 <= length <= 125nie potrzebujesz dodatkowych bajtów
  • jeśli 126 <= length <= 65535potrzebujesz dwóch dodatkowych bajtów, a drugi bajt to126
  • jeśli length >= 65536potrzebujesz ośmiu dodatkowych bajtów, a drugi bajt to127

Długość należy podzielić na oddzielne bajty, co oznacza, że ​​trzeba przesunąć bit w prawo (o osiem bitów), a następnie zachować tylko osiem ostatnich bitów, wykonując czynność AND 1111 1111(czyli jest 255).

Po bajcie (ach) długości przychodzą surowe dane.

Prowadzi to do następującego pseudokodu:

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)

Otrzymywanie wiadomości

(Innymi słowy, przeglądarka → serwer)

Uzyskane ramki mają następujący format:

  • jeden bajt zawierający typ danych
  • jeden bajt zawierający długość
  • dwa lub osiem dodatkowych bajtów, jeśli długość nie mieści się w drugim bajcie
  • cztery bajty, które są maskami (= klucze dekodujące)
  • rzeczywiste dane

Pierwszy bajt zwykle nie ma znaczenia - jeśli wysyłasz tylko tekst, używasz tylko typu tekstu. W takim przypadku będzie 1000 0001(lub 129).

Drugi bajt i dodatkowe dwa lub osiem bajtów wymagają trochę analizy, ponieważ musisz wiedzieć, ile bajtów jest używanych na długość (musisz wiedzieć, gdzie zaczynają się prawdziwe dane). Sama długość zwykle nie jest konieczna, ponieważ masz już dane.

Pierwszy bit drugiego bajtu jest zawsze, 1co oznacza, że ​​dane są zamaskowane (= zakodowane). Wiadomości od klienta do serwera są zawsze maskowane. Musisz usunąć ten pierwszy kawałek, wykonując secondByte AND 0111 1111. Istnieją dwa przypadki, w których wynikowy bajt nie reprezentuje długości, ponieważ nie mieścił się w drugim bajcie:

  • drugi bajt 0111 1110lub 126oznacza, że ​​jako długość używane są następujące dwa bajty
  • Drugi bajt 0111 1111lub 127oznacza, że ​​na długość użytych jest kolejnych osiem bajtów

Cztery bajty maski są używane do dekodowania faktycznie wysłanych danych. Algorytm dekodowania jest następujący:

decodedByte = encodedByte XOR masks[encodedByteIndex MOD 4]

gdzie encodedBytejest oryginalnym bajtem w danych, encodedByteIndexjest indeksem (przesunięciem) bajtu licząc od pierwszego bajtu rzeczywistych danych , które mają indeks 0. masksjest tablicą zawierającą cztery bajty maski.

Prowadzi to do następującego pseudokodu do dekodowania:

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
źródło
Dlaczego 1000 0001(129) dla ramki tekstowej? Spec mówi, mówi: %x1 denotes a text frame. Więc powinno być 0000 0001( 0x01), czy?
Dennis
3
@Dennis: Kod operacji ramki to 0001, zgodnie z nagłówkiem tej części specyfikacji: „Kod operacji: 4 bity”. Pierwszy bajt składa się z FIN, RSV1-3 i kodu operacji. FIN to 1, RSV1-3 to wszystkie trzy, 0a kod operacji to suma 0001do 1000 0001pierwszego bajtu. Zobacz także grafikę w specyfikacji, która pokazuje, jak bajty są podzielone na różne części.
pimvdb
Masz kilka wierszy o treści „bytesFormatted [2] = (bytesRaw.length >> 56) AND 255” w modelu Serwer-> Klient - czy mógłbyś to dla mnie podzielić? AND wydaje mi się być operatorem logicznym, więc nie mogę oczekiwać, że zwykłe wstawienie liczby po niej zrobi cokolwiek za mnie w C #. Podobnie, nie jestem pewien, co ma oznaczać ">>" w twoim znaczniku - jednak przenosi się do C # ... Cokolwiek to dla mnie znaczy ...: P
DigitalJedi805 Kwietnia
Jeśli ktokolwiek może to dla mnie wyjaśnić, z przyjemnością opublikuję moją implementację C # jako odpowiedź.
DigitalJedi805
1
@Neevek: Chodzi im o to, że same bajty maski muszą być nieprzewidywalne. Jeśli są stałe, nie ma w nich sensu. Zasadniczo, gdy złośliwy użytkownik ma kawałek danych, nie powinien być w stanie ich zdekodować bez masek. Jeśli pozycja masek jest nieprzewidywalna, prawdziwy serwer będzie miał trochę trudności z dekodowaniem :)
pimvdb
26

Implementacja Java (jeśli jest wymagana)

Czytanie: Klient do serwera

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

            }
        }

Pisanie: serwer do klienta

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
źródło
3
Jaka byłaby odpowiednia długość bufora dla operacji odczytu?
jackgerrits
Niestety to nie działa. Właśnie skopiowałem void broadcast (z serwera do klienta) do mojego programu. Gniazdo połączone pomyślnie, wiadomość wysłana do przeglądarki pomyślnie, ale przeglądarka nic nie otrzymała.
nick
18

Implementacja 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;
}
Richarda Astbury'ego
źródło
5
Prawdopodobnie warto zauważyć, że JavaScript w rzeczywistości nie obsługuje przesuwania z liczbami większymi niż 2^31 - 1.
pimvdb
13

Implementacja C #

Przeglądarka -> Serwer

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

Serwer -> Przeglądarka

    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
źródło
1
Funkcja dekodowania zawsze zwraca moją konkretną wiadomość z nieokreślonym dla mnie dodatkiem, jak tutaj, test�c=ܝX[w którym „test” to moja wiadomość. Z czego pochodzi druga część?
Snickbrack
1
Przepraszam za późną odpowiedź. Stworzyłem małą aplikację C # (konsola i sieć), aby wypróbować gniazda internetowe. Możesz je pobrać stąd, aby zobaczyć, jak jest zakodowany. Link: dropbox.com/s/gw8hjsov1u6f7c0/Web%20Sockets.rar?dl=0
Nitij
Nie udało mi się to w przypadku dużych wiadomości. Zamieniłem kod długości> 65535 na: var l = Convert.ToUInt64 (length); var b = BitConverter.GetBytes (l); Array.Reverse (b, 0, b.Length); b.CopyTo (ramka, 2); ... co wydaje się naprawiać rzeczy.
Sean
Dobra robota. Tylko jedno: w DecodeMessage obliczam długość „zdekodowanej” tablicy na podstawie danych o długości ładunku, które są zawarte w ramce danych, ponieważ długość tablicy „bajtów” nie może być dokładna. Długość tablicy „bajtów” zależy od sposobu odczytu strumienia.
user1011138
@Sean, czy możesz mi pokazać pełny przykład rozwiązania problemu z dużą wiadomością? Nie mogę zmienić tego kodu na twoją próbkę.
Ali Yousefi
6

Odpowiedź pimvdb zaimplementowana w Pythonie:

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

Przykład użycia:

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', '!']
Hunter Fernandes
źródło
Próbowałem użyć twojego kodu w moim skrypcie, ale bezskutecznie. Czy mógłbyś być w stanie pomóc? stackoverflow.com/questions/43748377/…
jaka
5

Oprócz funkcji kodowania ramek PHP, poniżej znajduje się funkcja dekodowania:

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

Zaimplementowałem to i inne funkcje w łatwej w użyciu klasie PHP WebSocket tutaj .

Tacticus
źródło
4

Implementacja 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;
}
DanBlack
źródło
4

Dziękuję za odpowiedź, chciałbym dodać do wersji Pythona hfern (powyżej), aby zawierała funkcję wysyłania, jeśli ktoś jest zainteresowany.

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) 

Wykorzystanie do czytania:

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

Wykorzystanie do pisania:

EncodeWebSockSend(sock,"hellooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo")
DR.
źródło
2

Wdrożenie w Go

Kodowanie części (serwer -> przeglądarka)

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
}

Dekoduj część (przeglądarka -> serwer)

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)
}
rmonjo
źródło
2

Clojure, funkcja dekodowania zakłada, że ​​ramka jest wysyłana jako mapa {:data byte-array-buffer :size int-size-of-buffer}, ponieważ rzeczywisty rozmiar może nie być tego samego rozmiaru co tablica bajtów, w zależności od rozmiaru fragmentu strumienia wejściowego.

Kod zamieszczony tutaj: 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))
scape
źródło
0

Implementacja C ++ (nie przeze mnie) tutaj . Zauważ, że gdy twoje bajty przekraczają 65535, musisz przesunąć z długą wartością, jak pokazano tutaj .

pcunite
źródło