Примечание. Это некоторое объяснение и псевдокод того, как реализовать очень простой сервер, который может обрабатывать входящие и исходящие сообщения WebSocket в соответствии с окончательным форматом кадрирования. Он не включает процесс установления связи. Кроме того, этот ответ был дан в образовательных целях; это не полнофункциональная реализация.
Спецификация (RFC 6455)
Отправка сообщений
(Другими словами, сервер → браузер)
Отправляемые вами кадры должны быть отформатированы в соответствии с форматом кадрирования WebSocket. Для отправки сообщений это следующий формат:
- один байт, который содержит тип данных (и некоторую дополнительную информацию, которая выходит за рамки тривиального сервера)
- один байт, содержащий длину
- два или восемь байтов, если длина не умещается во втором байте (второй байт - это тогда код, указывающий, сколько байтов используется для длины)
- фактические (сырые) данные
Первый байт будет 1000 0001
(или 129
) для текстового фрейма.
Для второго байта установлен первый бит, 0
потому что мы не кодируем данные (кодирование от сервера к клиенту не является обязательным).
Необходимо определить длину необработанных данных, чтобы правильно отправлять байты длины:
- если
0 <= length <= 125
вам не нужны дополнительные байты
- если
126 <= length <= 65535
вам нужны два дополнительных байта, а второй байт126
- если
length >= 65536
вам нужно восемь дополнительных байтов, а второй байт127
Длина должна быть разделена на отдельные байты, что означает, что вам нужно сдвинуть бит вправо (на восемь бит), а затем сохранить только последние восемь бит, выполнив AND 1111 1111
(что есть 255
).
После байта (ов) длины идут необработанные данные.
Это приводит к следующему псевдокоду:
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)
Получение сообщений
(Другими словами браузер → сервер)
Полученные кадры имеют следующий формат:
- один байт, содержащий тип данных
- один байт, содержащий длину
- два или восемь дополнительных байтов, если длина не помещается во втором байте
- четыре байта, которые являются масками (= ключи декодирования)
- фактические данные
Первый байт обычно не имеет значения - если вы просто отправляете текст, вы используете только текстовый тип. Будет 1000 0001
(или 129
) в таком случае.
Второй байт и дополнительные два или восемь байтов нуждаются в некотором разборе, потому что вам нужно знать, сколько байтов используется для определения длины (вам нужно знать, где начинаются реальные данные). Сама длина обычно не нужна, так как у вас уже есть данные.
Первый бит второго байта всегда, 1
что означает, что данные замаскированы (= закодированы). Сообщения от клиента к серверу всегда маскируются. Вам нужно удалить этот первый бит, выполнив secondByte AND 0111 1111
. Есть два случая, когда результирующий байт не представляет длину, потому что он не помещается во второй байт:
- второй байт
0111 1110
или 126
означает, что следующие два байта используются для длины
- второй байт
0111 1111
или 127
означает, что следующие восемь байтов используются для длины
Четыре байта маски используются для декодирования фактически отправленных данных. Алгоритм декодирования следующий:
decodedByte = encodedByte XOR masks[encodedByteIndex MOD 4]
где encodedByte
- исходный байт в данных, encodedByteIndex
- это индекс (смещение) байта, отсчитываемого от первого байта реальных данных , у которых есть индекс 0
. masks
представляет собой массив, содержащий четыре байта маски.
Это приводит к следующему псевдокоду для декодирования:
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) для текстового фрейма? Спецификация говорит говорит:%x1 denotes a text frame
. Так должно быть0000 0001
(0x01
), или?0001
, как указано в заголовке этой части спецификации: « Код операции : 4 бита». Первый байт состоит из FIN, RSV1-3 и кода операции. Плавник1
, RSV1-3 все три0
и опкод это ,0001
который добавляет к1000 0001
для первого байта. Также смотрите иллюстрацию в спецификации, которая показывает, как байты разделяются на разные части.Реализация Java (если требуется)
Чтение: от клиента к серверу
Написание: от сервера к клиенту
источник
Реализация JavaScript:
источник
2^31 - 1
.Реализация C #
Браузер -> Сервер
Сервер -> Браузер
источник
test�c=ܝX[
в котором «тест» - это мое сообщение. Откуда идет другая часть?Ответ pimvdb реализован на Python:
Пример использования:
источник
В дополнение к функции кодирования фреймов PHP, здесь следует функция декодирования:
Я реализовал это , а также другие функции в простом в использовании класса WebSocket PHP здесь .
источник
Реализация PHP:
источник
Спасибо за ответ, я хотел бы добавить в версию Python hfern (см. Выше), чтобы включить функцию отправки, если кому-то интересно.
Использование для чтения:
Использование для записи:
источник
Реализация на Go
Кодировать часть (сервер -> браузер)
Часть декодирования (браузер -> сервер)
источник
Clojure, функция декодирования предполагает, что фрейм отправляется как карта
{:data byte-array-buffer :size int-size-of-buffer}
, потому что фактический размер может отличаться от размера байтового массива в зависимости от размера фрагмента вашего входного потока.Код размещен здесь: https://gist.github.com/viperscape/8918565
источник
Реализация C ++ (не мной) здесь . Обратите внимание, что когда ваши байты превышают 65535, вам нужно сдвинуть с помощью длинного значения, как показано здесь .
источник