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?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 to1
, RSV1-3 to wszystkie trzy,0
a kod operacji to suma0001
do1000 0001
pierwszego bajtu. Zobacz także grafikę w specyfikacji, która pokazuje, jak bajty są podzielone na różne części.Implementacja Java (jeśli jest wymagana)
Czytanie: Klient do serwera
Pisanie: serwer do klienta
źródło
Implementacja JavaScript:
źródło
2^31 - 1
.Implementacja C #
Przeglądarka -> Serwer
Serwer -> Przeglądarka
źródło
test�c=ܝX[
w którym „test” to moja wiadomość. Z czego pochodzi druga część?Odpowiedź pimvdb zaimplementowana w Pythonie:
Przykład użycia:
źródło
Oprócz funkcji kodowania ramek PHP, poniżej znajduje się funkcja dekodowania:
Zaimplementowałem to i inne funkcje w łatwej w użyciu klasie PHP WebSocket tutaj .
źródło
Implementacja PHP:
źródło
Dziękuję za odpowiedź, chciałbym dodać do wersji Pythona hfern (powyżej), aby zawierała funkcję wysyłania, jeśli ktoś jest zainteresowany.
Wykorzystanie do czytania:
Wykorzystanie do pisania:
źródło
Wdrożenie w Go
Kodowanie części (serwer -> przeglądarka)
Dekoduj część (przeglądarka -> serwer)
źródło
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
źródło
Implementacja C ++ (nie przeze mnie) tutaj . Zauważ, że gdy twoje bajty przekraczają 65535, musisz przesunąć z długą wartością, jak pokazano tutaj .
źródło