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, 0
ponieważ 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 <= 125
nie potrzebujesz dodatkowych bajtów
- jeśli
126 <= length <= 65535
potrzebujesz dwóch dodatkowych bajtów, a drugi bajt to126
- jeśli
length >= 65536
potrzebujesz 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, 1
co 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 1110
lub 126
oznacza, że jako długość używane są następujące dwa bajty
- Drugi bajt
0111 1111
lub 127
oznacza, ż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 encodedByte
jest oryginalnym bajtem w danych, encodedByteIndex
jest indeksem (przesunięciem) bajtu licząc od pierwszego bajtu rzeczywistych danych , które mają indeks 0
. masks
jest 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
1000 0001
(129) dla ramki tekstowej? Spec mówi, mówi:%x1 denotes a text frame
. Więc powinno być0000 0001
(0x01
), czy?