Протокол WebSocket, как и любые другие протоколы, имеет свои преимущества и свои недостатки. Именно из-за последних появляются новые версии протоколов, новые протоколы и новые подходы к реализации всего, что вокруг них, а конкретно - клиентских и серверных приложений.
Хороший сервер - это такой сервер, который учитывает особенности протокола. Хороший клиент - то же самое. Здесь под "сервер" и "клиент" подразумевается именно имплементация протокола в виде конкретного софта или библиотеки.
Изучив WebSocket с разных сторон, образовался следующий список проблем, которые в WebSocket есть, а в других популярных и используемых местах их не имеется, либо не несут важных или критичных проблем. Но давайте сперва изучим другую сторону WebSocket, ту, которая лежит в его основе: TCP-keepalive.
TCP KeepAlive
Что такое KeepAlive? Это способ оставлять TCP-соединение открытым долгое время.
Как это делается? Раз в определенный период происходит обмен специальными пакетами, которые озаглавлены в документации "keepalive probes". Выполняется это с помощью PSH- и RST-пакетов.
Как долго может жить KeepAlive соединение? Существует максимальный временной интервал между пакетами с данными, в течение которого соединение может продолжать жить. Если обмен данными происходит в этот период, то следующий период начинается сначала, т.е. KeepAlive-соединение периодически (пусть и редко), обменивающееся внутри себя данными, может жить довольно долго.
В ядре Linux вокруг этого есть три настройки:
root@net-stage:~# sysctl -a | grep tcp_keepalive_ net.ipv4.tcp_keepalive_intvl = 75 net.ipv4.tcp_keepalive_probes = 9 net.ipv4.tcp_keepalive_time = 7200
Здесь показаны их значения по умолчанию. net.ipv4.tcp_keepalive_time - это как раз максимальное время между пакетами с данными.
net.ipv4.tcp_keepalive_intvl - интервал обмена пакетами "keepalive probes" по умолчанию 75 секунд. "net.ipv4.tcp_keepalive_probes" - это количество возможных неотвеченных "keepalive probes" пакетов - по сути попыток возобновить соединение.
Как переводится соединение в режим KeepAlive? На языке C очень просто:
optval = 1; optlen = sizeof(optval); setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &optval, optlen);
Так же можно использовать для своих сокетов свои величины intvl и probes:
int keepcnt = 5; int keepintvl = 120; setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT, &keepcnt, sizeof(int)); //эквивалентно tcp_keepalive_probes setsockopt(sock, IPPROTO_TCP, TCP_KEEPINTVL, &keepintvl, sizeof(int)); //эквивалентно tcp_keepalive_intvl
В комментариях к этим опциям в документации отмечено: "This option should not be used in code intended to be portable." Эти опции требуется применять очень аккуратно: это настройки, которые ДОЛЖНЫ быть одинаковы и на клиенте, и на сервере.
Если величины, например, tcp_keepalive_intvl разойдутся, то клиент, работающий по умолчанию, и сервер, работающий в режиме tcp_keepalive_intvl=25, будут иметь разную информацию о соединении. В этом примере 75/25 - клиент может долго думать, что соединение еще открыто, когда сервер будет уже знать о том, что оно закрылось. Это будет приводить к отправке пакетов в уже закрытое на том конце соединение и потере пакетов.
Но, тем не менее, если вы контролируете и клиентский код, и серверный, то эти величины для вас доступны.
Например, величина 75 секунд может быть слишком велика в определенных ситуациях, например, когда данные требуется передавать быстро, и при их недоставке так же быстро требуется получить ответ об ошибке.
WebSocket
Websocket использует TCP-KeepAlive соединения. В конкретном применении это дает как плюсы, так и минусы. Плюсы очевидны:
Проблемы WebSocket в современном использовании менее очевидны:
Молчаливый отвал соединения. При отправке пакета в WebSocket вы не узнаете о том, доставлен он или нет, пока не пройдет 75 секунд таймаута. Сам протокол ничего не расскажет об этом, а величина по умолчанию 75 секунд велика для быстрого взаимодействия. Например, тот же чат, работающий поверх реальной сети, где клиенты могут "выпадать из сети", усложняется архитектурно: сервер должен внутри себя иметь очередь, он должен иметь unack-буфер и логику переотправки сообщений. Часто по WebSocket разработчики запускают "свои самодельные ping-и" с целью понять, жив он или нет.
Смена сети клиентом. В сетях мобильной связи часто бывает так, что ваш внешний IP, NAT и вообще сеть, в которой присутствует мобильное устройство, меняются. Это даже не зависит от оператора: вы пришли домой, подключился Wi-Fi и весь websocket рухнул очень забавным образом:
Хотите пример такой утечки? Легко:
Повышенная нагрузка на серверы. Клиент ничего не знает о своей смене внешнего IP и сует данные в отпавшее соединение - сервер получает пакеты старого соединения уже с нового IP и отбрасывает их. Если у вас популярное мобильное приложение и, как результат, нагруженный сервер, то маленький чих в сети оператора мобильной связи устроит DDoS атаку на ваш сервер и вам прилетят старые пакеты, реконнекты, данные, вместо того, чтобы просто передать данные 1 раз. Т.е. вам требуется иметь многократный запас пропускной способности на серверах.
Способы решения проблем WebSocket
На данный момент решения для отработки обрыва соединения костыльно-ориентированные:
Универсальных способов решения проблем утечки информации об отправляемых пакетах при смене мобильным клиентом сети до сих пор нет.
Не секрет, что хорошо настроенный сервер "падает" гораздо реже, чем доступ из него в Интернет
Позволяет решать интеллектуальные задачи помимо задач самого мониторинга
И снова о маленьких сетевых фокусах ради надежности работы web-сервисов
Особенности серверных приложений, работающих с сетью IoT-устройств на практике и в теории
В проекте морской навигации есть особенность, грамотная реализация которой и позволяет жить всей системе