Теоретические основы протокола WebSockets

Протокол WebSocket-ов строится поверх сетевых соединений TCP/IP, которые характеризуются IP-адресом (или заменяющим его доменным именем), а также номером порта. По такому же принципу работает и протокол HTTP/HTTPS, с которым мы уже практиковались в главе про сетевые функции. Там стандартными номерами портов были 80 (для незащищенных соединений) и 443 (для защищенных). Для WebSocket не существует выделенного номера порта, поэтому поставщики веб-сервиса могут выбрать любой свободный номер. Во всех наших примерах будет использоваться порт 9000.

При задании URL в качестве префиксов протокола WebSocket-ов применяются ws (для незащищенных соединений) и wss (для защищенных).

Формат WebSocket-ов более эффективен в плане передачи данных, чем HTTP, так как использует гораздо меньший объем управляющих данных.

Начальное установление соединение для WebSocket-сервиса полностью повторяет запрос веб-страницы HTTP/HTTPS: нужно отправить GET-запрос со специально подготовленными заголовками. Особенностью этих заголовков является наличие строк:

Connection: Upgrade
Upgrade: websocket

а также некоторых дополнительных, сообщающих версию протокола WebSocket-ов и специальные случайно сгенерированные строки — ключи, участвующие в процедуре "рукопожатия" (handshaking) клиента и сервера.

Sec-WebSocket-Key: ...
Sec-WebSocket-Version: 13

На практике "рукопожатие" заключается в проверке сервером доступности тех опций, которые запросил клиент, и в ответе со стандартными HTTP-заголовками, подтверждающими переключение в режим WebSocket-ов или отклоняющими его. Самой простой причиной отклонения может быть, если вы пытаетесь подключиться по протоколу WebSocket-ов к простому веб-серверу, где WebSocket-сервер не предусмотрен или не поддерживается нужная версия.

Актуальная версия протокола WebSockets известна под символическим именем Hybi и номером 13. Существующая более ранняя и простая версия Hixie может быть полезна для обеспечения обратной совместимости. Далее мы будем использовать только Hybi, хотя реализация Hixie также прилагается.

Успешное подключение обозначается такими HTTP-заголовками в ответе сервера:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: ...

Поле Sec-WebSocket-Accept здесь рассчитывается и заполняется сервером на основе ключа Sec-WebSocket-Key, чтобы подтвердить соответствие протоколу. Все это регламентировано спецификацией RFC6455 и будет поддержано также и в наших MQL-программах.

Для наглядности процедура показана на следующем изображении:

Взаимодействие клиента и сервера по протоколу WebSocket-ов

Взаимодействие клиента и сервера по протоколу WebSocket-ов

После установления WebSocket-соединения клиент и сервер могут обмениваться информацией, упакованной в специальные блоки: фреймы и сообщения. Сообщение может состоять из одного или нескольких фреймов. В принципе, размер фрейма, согласно спецификации, ограничен астрономическим числом 263 байтов (9223372036854775807 ~ 9.22 эксабайт!), но конкретные реализации, разумеется, могут иметь более приземленные ограничения, поскольку данный теоретический лимит не кажется практичным для пересылки одним пакетом.

В любой момент клиент или сервер могут разорвать соединение, предварительно "вежливо попрощавшись" (см. ниже) или просто закрыв сетевой сокет.  

Фреймы бывают разных типов, что задается в их заголовке (длиной от 4 до 16 байтов), который идет в начале каждого фрейма. Перечислим для справки операционные коды (они присутствуют в первом байте заголовка) и назначение фреймов разных типов.

  • 0 — фрейм продолжения (наследует свойства предыдущего фрейма);
  • 1 — фрейм с текстовой информацией;
  • 2 — фрейм с двоичной информацией;
  • 8 — фрейм запроса закрытия и подтверждения закрытия соединения (посылаются для "вежливого прощания");
  • 9 — пинг-фрейм, может периодически посылаться любой из сторон, чтобы убедиться в физическом сохранении соединения;
  • 10 — понг-фрейм, отправляется в ответ на пинг-фрейм.

Последний фрейм в сообщении помечается особым битом в заголовке. Разумеется, когда сообщение состоит из одного фрейма, он же является и последним. Также в заголовке передается длина полезных данных.