Автор оригинала: David Wong.
Websocket RFC GitHub Перевод на китайский
Документ Websocket RFC
Реализация протокола websocket рабочего человека
Протокол состоит из открытого рукопожатия, за которым следует базовое обрамление сообщения, многоуровневый TCP
На основе механизма браузера реализуется двусторонняя связь между клиентом и сервером
- От рукопожатия клиента
GET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== Origin: http://example.com Sec-WebSocket-Protocol: chat, superchat Sec-WebSocket-Version: 13
- Рукопожатие с сервера
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= //Optional header, indicating the client allowed to pass Sec-WebSocket-Protocol: chat
Выше, порядок головы не имеет значения
Как только и клиент, и сервер отправят сигнал подтверждения, если подтверждение будет успешным, начнется часть передачи данных. Это канал связи между двумя сторонами, независимый от другой стороны, и данные могут быть отправлены по желанию.
Ответ сервера не является случайным. Пожалуйста, обратитесь к странице 6/7 документа RFC для получения следующих правил:
- Получить запрошенные клиентом
Sec-Websocket-КлючЗначения полей, удалив закрывающие пробелы - Соединен с глобальным уникальным идентификатором
258EAFA5-E914-47DA-95CA-C5AB0DC85B11 sha1Шифрование (короткий формат)- Шифрование Base64
Описание программы PHP:
$client_key = 'dGhlIHNhbXBsZSBub25jZQ=='; $client_key = trim($client_key); $guid = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; $key = $client_key . $guid; $key = sha1($key , true); $key = base64_encode($key);
Значение, полученное из приведенных выше результатов, состоит в том, что сервер возвращается к клиенту для рукопожатия Sec-Websocket-Принять Значение поля заголовка
Получено 0x8 После контрольного кадра связь может быть разорвана немедленно или после получения оставшихся данных.
- Может быть текст сообщения с указанием причины сообщения, который может быть записан в виде журнала.
- Приложение не должно отправлять больше кадров данных после отправки кадров.
- Если конечная точка получает закрытый кадр и ранее не отправляла закрытый кадр, она должна отправить закрытый кадр.
- После получения заключительного кадра конечная точка может отложить ответ на заключительный кадр и продолжить отправку или получение кадров данных, но это не гарантирует, что конечная точка, отправившая заключительный кадр, продолжит обработку данных.
- Конечная точка, которая отправляет и получает закрытый кадр, считается закрытым
websocketСоединением, которое должно закрыть базовоеTCPСоединение.
На основе кадров, а не потоковых/текстовых или двоичных кадров
Для требований клиента
- Рукопожатие должно быть действительным HTTP-запросом
- Запрошенный метод должен быть
ПОЛУЧИТЬИHTTPВерсия должна быть 1.1 - Запрошенный
ЗАПРОС-URIДолжен соответствовать требованиям, указанным в документе (подробнее см. стр. 13) - Запрос должен содержать
Хостзаголовок - Запрос должен содержать
Обновление: websocketЗаголовок, значение должно бытьwebsocket - Запрос должен содержать
Подключение: ОбновлениеЗаголовок, значение должно бытьОбновление - Запрос должен содержать
Sec-WebSocket-Ключзаголовок - Запрос должен содержать
Sec-WebSocket-Версия: 13Заголовок, значение должно быть13 - Запрос должен содержать
Источникзаголовок - Запрос может содержать
Sec-WebSocket-ПротоколРуководитель, укажите дополнительное соглашение - Запрос может содержать
Sec-WebSocket-Расширения, предусматривающие продление соглашения - Запрос может содержать другие поля, такие как
cookieи т.д.
Если сервер не соответствует вышеуказанным требованиям, клиент отключится
- Если ответ не содержит
Sec-WebSocket-ПротоколПодпротокол, указанный в, клиент разъединенный - Если ответ
HTTP/1.1 101 Коммутационные протоколыКод состояния не101, клиент разъединенный
Для требований к серверу
- Если запрос
HTTP/1.1Или вышеПОЛУЧИТЬЗапрос, содержитЗАПРОС-URIОн должен быть правильно проанализирован в соответствии с требованиями документа - Поле хоста должно быть проверено
ОбновлениеЗначение поля заголовка должно быть без учета регистравеб-карманСек-WebSocket-ключДлина декодирования D составляет16 байтSec-WebSocket-ВерсияЗначение должно быть13ХостЕсли ссылки не включены, их не следует интерпретировать как поведение, инициированное браузеромSec-WebSocket-ПротоколДля подпротокола запроса клиента, указанного в, сервер должен организовать и ответить в порядке приоритета- Необязательные другие поля
Требования к ответу:
- Поле Проверка
Происхождение, возвращает соответствующий код ошибки, если запрос не соответствует требованиям (например: 403) Sec-WebSocket-КлючЗначение равно единице.база 64Зашифрованное значение не должно определяться сервером, оно используется только для создания сервера рукопожатие- Проверка
Sec-WebSocket-ВерсияЦенность, если нет13, возвращается соответствующий код ошибки (например:HTTP/1.1 Требуется обновление 426) - Проверка имени ресурса
- Проверка Подпротокола
- Проверка расширений
Если вышеуказанная проверка пройдена, сервер принимает ссылку. Затем ответ должен соответствовать следующим требованиям. Подробнее см. стр. 23:
- Требуется, строка состояния
HTTP/1.1 101 Протоколы коммутации - Требуется, заголовок обновления протокола
Обновление: websocket - Обязательно, указывает поле заголовка обновления подключения
Подключение: Обновлять - Необходимо,
Sec-WebSocket-ПринятьПоле заголовка, пожалуйста, обратитесь к Обзор протокола Часть - Необязательно:
Sec-WebSocket-Протоколыглава
Полный код ответа выглядит следующим образом (отвечайте в строгом соответствии со следующим форматом!! Приказ главы не имеет значения! Главное-обратить внимание на следующий разрыв строки! Строго контролируйте количество!):
HTTP/1.1 101 Switching Protocols\r\n Connection: Upgrade\r\n Upgrade: websocket\r\n Sec-WebSocket-Accept: 3nlEzv+LqVBYnTHclAqtk62uOTQ=\r\n //The following header field is optional Sec-WebSocket-Protocols: chat\r\n\r\n
Пара деталей передачи данных положение Сгруппировались!! Поскольку это битная инкапсуляция данных на уровне слоя, поэтому, если она будет удалена напрямую, полученные данные будут обработаны и должны быть расшифрованы. На следующем рисунке показан Формат передачи данных :
0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-------+-+-------------+-------------------------------+ |F|R|R|R| opcode|M| Payload len | Extended payload length | |I|S|S|S| (4) |A| (7) | (16/64) | |N|V|V|V| |S| | (if payload len==126/127) | | |1|2|3| |K| | | +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + | Extended payload length continued, if payload len == 127 | + - - - - - - - - - - - - - - - +-------------------------------+ | |Masking-key, if MASK set to 1 | +-------------------------------+-------------------------------+ | Masking-key (continued) | Payload Data | +-------------------------------- - - - - - - - - - - - - - - - + : Payload Data continued ... : + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Payload Data continued ... | +---------------------------------------------------------------+
1. Введение в значение специальных терминов
- 1 бит,НОРМАЛЬНО
- Каждый 1 бит, rsv1, rsv2, rsv3
4 бита, код операции (определен в ABNF ниже)
- % X0 последовательный кадр
- %X1 текстовый фрейм
- %X2 двоичный кадр
- %X3 – % X7 зарезервированный кадр
- %X8 ссылка отключена
- %%x9 розовый
- %xA понг
- %Xb-f зарезервированная рамка управления
- Все вышеперечисленные значения являются шестнадцатеричными
1 бит, маска
- Для данных, отправляемых клиентом на сервер, необходимо установить значение 1
- Другими словами, данные замаскированы
Пожалуйста, обратитесь к документу RFC (стр. 31) для конкретного диапазона 7 бит, 7 + 16 бит, 7 + 64 бит и длины полезной нагрузки
- Полезная нагрузка Длина полезной нагрузки + Длина полезной нагрузки приложения
- Длина данных полезной нагрузки + длина данных приложения
- Длина расширенных данных может быть равна 0, поэтому, когда расширенные данные, длина приложения полезной нагрузки
- Единица измерения длины данных полезной нагрузки составляет
Байт
0/4 байта, маскирующий ключ
- Данные, отправляемые клиентом на сервер, маскируются длиной 32 бита
- Данные, отправляемые сервером клиенту, не маскируются и имеют длину 0 бит
байт x + y, Данные полезной нагрузки
- Данные полезной нагрузки
x Байт, Данные Расширения
- Расширенные данные
байт y, Данные приложения
- Прикладные данные
2. понимание
На рисунке показано следующее websocket Данные, передаваемые по протоколу, являются websocket Данные, обрабатываемые протоколом, не могут быть получены напрямую Эффективные данные 。 Если вы хотите получить достоверные данные, вам необходимо следовать websocket Толкованию положений соглашения.
На рисунке слева направо расположите от высокого положения к низкому положению.
Что такое низкое и высокое??
Это похоже на десятичное число, если есть такое описание: 3 Представляет собой немного, 2 Десять, 1 Это означает 100 цифр. Какой там номер?? Ответ: 123 。
Это легко понять, Индивидуальный, 10, 100 Он описывает порядок расположения; аналогично, в области программы также описывается порядок от низкого к высокому! Однако Индивид, 10, 100 То, что описано, является 10 двоичной системой И Низкий, высокий То, что описано, – это 2 двоичная система Конкретное описание: Бит 0, бит 1, бит 2 И так далее. Порядок в текущем примере от низкого до высокого ), вот описание изображения:
Понял Низкий, высокий , последовательность данных, описанная на рисунке выше, понятна.
как всем известно, Бит (бит) Это минимальная единица хранения в памяти, и ее можно хранить только 0,1 Два значения. Поэтому для того, чтобы получить и установить значение бита, но необходима операция. Потому что это позиция Поэтому содержание, описанное на рисунке, основано на дополнении.
Данные, отправляемые клиентом на сервер, маскируются! Его необходимо проанализировать, и процесс обработки данных должен быть проанализирован:
//Parsing client encrypted data according to websocket specification
function decode(string $buffer){
//Buffer [0] gets the first byte, 8bit
//Compared with that picture, it shows fin + rsv1 + rsv2 + RSV 3 + opcode
//Why to convert to ASCII value
//To ensure that the bit operation result is correct!
//For details of PHP bit operation, please refer to: https://note.youdao.com/share/? Id = 927bfc2f40a8d62f4c9165de30a41e75 & type = note#/
//Here's a simple explanation
//The following code will have a code like $first_byte > > 7
//In PHP, the operands are treated as integers (int)
//So if you don't convert to ASCII, the process will be
// (int) $buffer[0] >> 7
//Such a result will be wrong!!
//Ord ((int) $buffer [0])! = = ord ($buffer [0]) is the best proof
//Because ASCII values are not the same, binary values (strictly speaking, I think it should be said: complement) are not the same
//This violates the websocket protocol
//Causes parsing errors
$first_byte = ord($buffer[0]);
//Buffer [1] gets the second byte, 8bit
//Compared with that picture, it shows mask + payload len
$second_byte = ord($buffer[1]);
//Get the first value on the left
$fin = $first_byte >> 7;
//To get the value represented by payload len
//Bit 7 needs to be set to 0
//Because bit 7 is the mask, bits 0 - 6 are the complement of payaod len
//So to get the value of payload len
// 0111 1111 => 127
$payload_len = $second_byte & 127;
//The data sent by the client to the server is masked
//So get the client data after mask key + mask processing
//Get mask key + payload data
if ($payload_len === 127) {
//If payload len = 127 bytes
//Payload len itself occupies 7 bits
//Extended payload light occupies 64bit
$mask_key = substr($buffer , 10 , 4);
$encoded_data = substr($buffer , 14);
} else if ($payload_len === 126) {
//If payload len = 126 byte
//Payload length itself occupies 7 bits
//Extended payload light occupies 16bit
$mask_key = substr($buffer , 4 , 4);
$encoded_data = substr($buffer , 8);
} else {
//If payload len = 126 byte
//Payload length itself occupies 7 bits
//Extended payload light occupies 0 bit
$mask_key = substr($buffer , 2 , 4);
$encoded_data = substr($buffer , 6);
}
//Decoding payload data
$decoded_data = "";
//Decode each payload data
//Decoding rules are detailed in RFC documents
for ($index = 0; $index < count($encoded_data); ++$index)
{
$k = $index % 4;
$valid_data = $encoded_data[$index] ^ $mask_data[$k];
$decoded_data .= $valid_data;
}
//This is the real data sent by the client!!
return $decoded_data;
}Вместо этого, если сервер хочет отправить данные в websocket Клиент, данные также должны быть обработаны соответствующим образом! Поток обработки:
//Encapsulate the message sent to the client according to the websocket specification
function encode($msg){
if (!is_scalar($msg)) {
Print_r ("only scalar data can be sent");
}
//Data length
$len = strlen($msg);
//Only transfer text frame here! First byte, text frame 1000 0001 = > 129
//If you need a binary frame, for example, to transfer large files, please implement it separately
$first_byte = chr(129);
if ($len <= 125) {
//Payload length = maximum range supported by 7bit!
$second_byte = chr($len);
} else {
if ($len <= 65535) {
//Payload length = 7, extended payload length = 16bit, maximum supported range 65535
//Finally, 16bit is interpreted as an unsigned integer, sorted as: big endian byte order (network byte order)
$second_byte = chr(126) . pack('n' , $len);
} else {
// payload length = 7,extended payload length = 64bit
//The last 64 bits are interpreted as unsigned integers, big endian (network byte order)
$second_byte = chr(127) . pack('J' , $len);
}
}
//Note that the data sent to the client does not need to be processed
//Check websocket document for details!!
$encoded_data = $first_byte . $second_byte . $buffer;
//This is the data sent to the client!
return $encoded_data;
}Цель фрагментации
Основная цель сегментации сообщений-разрешить отправку сообщения неизвестного размера при запуске сообщения, но не требует буферизации всего сообщения; сообщение без сегментации должно буферизировать все сообщение, чтобы получить размер сообщения;
Требования к фрагментации:
- Первый сегмент,, за которым следует несколько сегментов,, заканчивающихся на сегментах,
- Расширенные данные могут находиться в любом разделе
- Управляющий кадр может быть введен в середину сообщения фрагментации, а сам управляющий кадр не должно быть сегментировано
- Фрагменты сообщения должны быть доставлены получателю в том порядке, в котором они были отправлены отправителем
- Сообщение во фрагменте не должно чередоваться с другим сообщением во фрагменте, если не согласовано расширение для интерпретации чередования.
- Сервер Websocket должен иметь возможность обрабатывать управляющий кадр в середине сообщения о фрагментации
- Отправитель может создавать фрагменты любого размера для неконтролируемых сообщений (неконтролируемых кадров)
- Не удается обработать кадр управления
- Если используются какие-либо зарезервированные битовые значения и значение этих значений неизвестно промежуточному программному обеспечению, промежуточное программное обеспечение не должно изменять фрагментацию сообщения.
- В контексте подключения было согласовано расширение, и промежуточное программное обеспечение не знает семантики согласованного расширения. Промежуточное программное обеспечение не должно изменять фрагментацию какого-либо сообщения. Аналогично, промежуточное программное обеспечение, которое не видит подтверждение связи websocket (и не информируется о его содержимом) и приводит к подключению websocket, не должно изменять фрагментацию любых сообщений ссылки.
- Из-за этих правил все фрагменты сообщения имеют один и тот же тип, заданный с помощью кода операции первого фрагмента. Поскольку фрейм управления не может быть фрагментирован, тип фрагментации, используемый в сообщении, должен быть текстовым, двоичным или зарезервированным кодом операции.
Получен ping(0x9) Контрольный кадр, должен вернуть pong(0xa) Контрольный кадр, указывающий, что процесс все еще продолжается!! На самом деле это тест на сердцебиение
- Может быть получен по адресу
ping(0x9)После контрольного кадра он возвращается в виде ответного сообщения. - Он также может быть отправлен в одном направлении
pongКадр, указывающий, что процесс отправки все еще продолжается, как одностороннее сердцебиение
- 1000, обычно закрыт
- 1001 , уходя
- 1003, закрывающее соединение
- 1004, зарезервированный
- 1005, зарезервированный
- 1006, зарезервированный
- 1007, конечная точка завершает соединение, поскольку она получила сообщение, которое не соответствует типу сообщения.
- 1008, конечная точка завершает связь, потому что она получила сообщение, нарушающее ее правила.
- 1009 , конечная точка завершает связь, потому что полученное сообщение слишком велико
- 1010, конечная точка, завершающая связь из – за проблем с расширением
- 1011, конечная точка, завершающая связь, непредвиденная ошибка
- 1015, зарезервированный
- … опущено, подробности см. в документе RFC
Приведенное выше личное понимание предназначено только для справки. Если есть какая-либо ошибка, пожалуйста, исправьте ее. Продолжение следует
Оригинал: “https://developpaper.com/websocket-protocol/”