Как работает js: websocket и http/2+sse. что выбрать?
Содержание:
- Специальные требования к серверу
- Performance is not scalability
- Краткая история веб-приложений реального времени
- Массовый реконнект
- Или getting started with WebSocket PHP без phpDaemon
- HTTP Streaming
- Простой клиент веб-сокетов
- A simple example
- Сервер
- Все определения WSS
- Что означает WSS в тексте
- Opening a websocket
- データ転送
- Example¶
- Connection close
- Low-level API¶
- Установление WebSocket-соединения
- Реализация сервера на PHP
Специальные требования к серверу
В нашем случае лучше всего использовать сервер на основе цикла событий. Например, NodeJS, Kestrel или Twisted. Идея состоит в том, что при использовании потокового решения будет один поток на соединение. То есть, 1000 соединений = 1000 потоков. В решении на основе цикла событий у нас будет один поток для 1000 соединений.
- Вы можете принимать запросы EventSource только в том случае, если HTTP-запрос говорит, что он может принимать MIME-тип event-stream;
- Необходимо вести список всех подключенных пользователей, чтобы запускать новые события;
- Вы должны прослушивать сброшенные соединения и удалять их из списка подключенных пользователей;
- Вы должны поддерживать историю сообщений, чтобы при повторном подключении клиентов можно было отправить им пропущенные сообщения.
Мы получили все, чтобы приложение работало эффективно. Но столкнулись с некоторыми проблемами:
- Устаревшие прокси-серверы в некоторых случаях удаляют HTTP-соединения после короткого таймаута. Чтобы защитить соединения, авторы могут включать строку комментариев (начинающуюся с символа «:») каждые 15 секунд или около того.
- Авторы, желающие связать соединения источника событий друг с другом или с определенными ранее документами, могут обнаружить, что использование IP-адресов не работает. Отдельные клиенты могут иметь несколько IP-адресов (из-за наличия нескольких прокси-серверов) и отдельные IP-адреса могут иметь несколько клиентов (из-за совместного использования прокси-сервера). Лучше включать в документ уникальный идентификатор и передавать его как часть URL-адреса при установлении соединения.
- Использование chunked transfer encoding может уменьшить надежность HTTP протокола, если блокирование выполняется другим слоем, не подозревающим о требованиях к синхронизации. Если эта проблема возникнет, блокирование может быть отключено для обслуживания потоков событий.
- Клиенты, которые поддерживают ограничение на подключение к серверу через протокол HTTP, могут столкнуться с трудностями при открытии нескольких страниц сайта, если на каждой из этих страниц есть источник событий, расположенный в том же домене. Можно избежать этого, применяя механизм уникальных доменных имен для каждого соединения и разрешая пользователям включать функции EventSource для каждой страницы.
- Поддержка браузера и полифиллы: Microsoft Edge не поддерживает эту реализацию. Но существует полифиллы, которые позволяют решить данную проблему. Тем не менее, самый важный сегмент для SSE — это мобильные устройства, где браузеры IE / Edge распространены незначительно.
Некоторые из доступных полифиллов:
· Yaffle.
· amvtek.
· remy.
Бесплатное подключение и другие функции
Пользовательские агенты, работающие в контролируемых средах, могут отключить управление соединением с прокси-сервером в сети. В такой ситуации считается, что пользовательский агент включает как программное обеспечение мобильного устройства, так и сетевой прокси-сервер.
Например, браузер на мобильном устройстве, установив соединение, может обнаружить, что он находится в поддерживаемой сети, и попросить прокси-сервер сети взять на себя управление созданным соединением. Последовательность действий в такой ситуации может быть следующей:
- Браузер подключается к удаленному HTTP-серверу и запрашивает ресурс, указанный автором в конструкторе EventSource.
- Сервер отправляет случайные сообщения.
- В промежутке между двумя сообщениями браузер обнаруживает, что он неактивен, за исключением активности сети, связанной с поддержанием TCP- соединения, и решает переключиться в спящий режим для экономии энергии.
- Браузер отключается от сервера.
- Браузер связывается с сервисом в сети и просит, чтобы служба «push proxy» поддерживала соединение.
- Служба «push proxy» связывается с удаленным HTTP-сервером и запрашивает ресурс, указанный в конструкторе EventSource (возможно, включая HTTP-заголовок последнего события и т. д.).
- Браузер позволяет мобильному устройству перейти в спящий режим.
- Сервер отправляет другое сообщение.
- Служба «push proxy» использует технологию OMA push для передачи события на мобильное устройство, которое выходит из спящего режима на время, достаточное для обработки события. Затем возвращается в спящий режим.
Подобный подход может снизить объем передаваемых данных и привести к значительной экономии энергии.
Помимо реализации существующего API и формата передаваемых данных ext/event-stream также могут поддерживаться форматы фреймворка событий, определенные другими спецификациями.
Performance is not scalability
Горизонтальное масштабирование
Производительность — конечно, это первое требование, это логично.
Сохранение порядка сообщений — тоже важное свойство, которое нам нужно.
Масштабируемость брокера. Мы хотим, чтобы брокер сам по себе масштабировался и был бы отказоустойчивым.
Миллионы топиков
Мы хотим, чтобы он поддерживал миллионы топиков одновременно, потому что это частый use case — когда у каждого соединения есть свой персональный канал. Мы хотим рассылать сообщения конкретному пользователю. И если у вас миллион WebSocket-соединений, появляется миллион топиков в брокере.
Кэш/стрим сообщений. Мы хотим, чтобы брокер поддерживал кэш или стрим сообщений в топике/канале. Что это такое и от чего это спасает, поговорим чуть позже.
Возможность писать процедуры — и это большой бонус. Я расскажу, как мне это помогло в Centrifugo и в мессенджере Авито.
Опции брокера сообщений
Redis
- Redis производительный, в том числе у него производительный PUB/SUB;
- Он стабильный и, самое главное, предсказуемый;
- У него есть Sentinel для High Availability;
- Он позволяет писать атомарные LUA-процедуры;
- Структуры данных позволяют хранить кэш сообщений. Опять возник этот магический кэш сообщений, но мы к нему скоро вернемся.
- Используйте одно или пул соединений между WebSocket-сервером и вашим брокером.
- Не используйте новое соединение на каждый коннект! Я видел часто в примерах на GitHub, как пишут код: пришло новое WebSocket-соединение, открываем новое PUB/SUB соединение с Redis или с каким-то другим брокером. Так делать не надо, это антипаттерн и это не масштабируется.
- Используйте максимально эффективный формат сериализации сообщений для общения между WebSocket-сервером и вашим брокером. Здесь не надо задумываться о том, чтобы формат был человеко-читаемым, потому что его не увидят ни ваши фронтенд-разработчики, ни тестировщики. Это сугубо внутренняя вещь, и вы можете делать ее максимально эффективной.
Краткая история веб-приложений реального времени
Интернет был построен на представлении о том, что забота браузера– запрос данных с сервера, а забота сервера – обслуживание этих запросов. Эта парадигма не подвергалась сомнению несколько лет. Но с появлением AJAX в 2005 году многие начали работать над созданием двунаправленных соединений.
Веб-приложения значительно увеличивались в размере. Сдерживающим фактором для их роста была традиционная модель HTTP. Чтобы преодолеть это, были созданы несколько стратегий, позволяющих серверам «проталкивать» (push) данные клиенту. Одной из наиболее популярных стала стратегия «длинного опроса». Она подразумевает поддержание HTTP- соединения открытым до тех пор,пока у сервера есть данные для отправки клиенту.
Но все эти технологии приводят к перегрузке HTTP. Каждый раз, когда вы делаете запрос HTTP, набор заголовков и cookie передаются на сервер. Они накапливаются в большие массивы информации, которые нужно передать. Это увеличивает время ожидания, что может быть критично для равномерной работы приложения.
Чтобы решить данную проблему, нужен был способ создания постоянного соединения с минимальными задержками, которое могло бы поддерживать транзакции, инициированные как клиентом, так и сервером. Это как раз то, что предоставляют веб-сокеты.
Массовый реконнект
Exponential backoff на клиенте. Rate limiter на WebSocket-сервере.JWT-аутентификация как способ не нагружать бэкенд сессий.Добивайтесь максимальной производительности соединений.
smart-batchingрепозиторииКэш сообщений, чтобы убрать пиковую нагрузку с СУБД.
- Мы добавляем сообщения в структуру данных List. Как вы видите, здесь уже три сообщения. У каждого сообщения при этом есть инкрементальный номер. Мы его увеличиваем атомарно.
- Далее мы публикуем сообщение в PUB/SUB Redis и в этот момент оно улетает в PUB/SUB. В свою очередь, PUB/SUB долетает до клиента (если он подключен). Как только клиент реконнектится, они передают номер сообщения, которое видели последним.
- Идем в Redis, смотрим в Lists. Если есть сообщения, восстанавливаем их клиенту из этой структуры данных, как будто он даже не был отключен.
- Так клиент получает весь стрим сообщений, которые ему были высланы в интервале отсутствия и так снимается пиковая нагрузка с СУБД.
Время ответа RPC при раскладке.
Или getting started with WebSocket PHP без phpDaemon
Здравствуйте! Простите за столь длинный заголовок, но, надеюсь, что новичкам вроде меня будет легче найти эту статью, ведь мне ничего подобного найти не удалось. Несколько недель назад я принял решение переработать игровой клиент и сервер своей игры Growing Crystals с AJAX, на WebSocket, но всё оказалось не просто непросто, а очень сложно. Поэтому я и решил написать статью, которая бы помогла самым что ни на есть начинающим разработчикам на WebSocket + PHP сэкономить несколько дней времени, максимально подробно объясняя каждый свой шаг по настройке и запуску первого WebSocket скрипта на PHP.
Что у меня есть: Денвер на локальной машине, на нём я веду разработку проекта и дешевый PHP хостинг, на котором я публикую свой проект для того, чтобы получить обратную связь от Интернет-пользователей.
Что я хочу: Без установки phpDaemon (phpd), NodeJS и прочих вещей на локальную машину и хостинг, продолжить разработку своего проекта, но теперь с WebSocket, в этой статье разберем простой WebSocket эхо сервер.
Чего я не хочу: Говоря о NodeJS, не хочется переписывать серерную логику с PHP на другой язык, тем более устанавливать NodeJS, хотя и люблю JavaScript больше чем PHP.
Важно: не путайте демона написанного на php, с фреймворком асинхронных приложений phpDaemon. Который, конечно же, обязательно потребуется в случае развития проекта и многократного роста нагрузки на хостинг
Но для начала работы с WebSocket на дешевом хостинге можно обойтись и без него.
HTTP Streaming
HTTP Streaming — provides a long-lived connection for instant and continuous data push (Image from realtimeapi.io)
The client makes an HTTP request, and the server trickles out a response of indefinite length (it’s like polling infinitely).HTTP streaming is performant, easy to consume and can be an alternative to WebSockets.
Issue: Intermediaries can interrupt the connection (e.g. timeout, intermediaries serving other requests in a round-robin manner). In such cases, it cannot guarantee the complete realtimeness.
00:00:00 CLIENT-> I need cakes 00:00:01 SERVER-> Wait for a moment.00:00:01 SERVER-> Cake-1 is in process.00:00:02 SERVER-> Have cake-1.00:00:02 SERVER-> Wait for cake-2.00:00:03 SERVER-> Cake-2 is in process.00:00:03 SERVER-> You must be enjoying cake-1.00:00:04 SERVER-> Have cake-2.00:00:04 SERVER-> Wait for cake-3.00:00:05 CLIENT-> Enough, I'm full.
Простой клиент веб-сокетов
С точки зрения веб-страницы функциональность веб-сокетов легко понять и использовать. Первый шаг — это создать объект WebSocket и передать ему URL. Код для этого подобен следующему:
Строка URL начинается с текста ws://, который идентифицирует подключение типа веб-сокет. Этот URL указывает файл веб-приложения на сервере (в данном случае это сценарий socketServer.php).
Стандарт веб-сокетов также поддерживает URL, которые начинаются с текста wss://, что указывает на требование использовать безопасное, зашифрованное подключение (точно так же, как и при запросе веб-страницы указывается URL, начинающийся с https:// вместо http://).
Веб-сокеты могут подключаться не только к своему веб-серверу. Веб-страница может открыть подключение к серверу веб-сокетов, исполняющемуся на другом веб-сервере, не требуя для этого никаких дополнительных усилий.
Само обстоятельство создания объекта WebSocket понуждает страницу пытаться подключиться к серверу. Дальше надо использовать одно из четырех событий объекта WebSocket: onOpen (при установлении подключения), onError (когда возникает ошибка), onClose (при закрытии подключения) и onMessage (когда страница получает сообщение от сервера):
Например, в случае успешного подключения неплохо бы отправить соответствующее подтверждающее сообщение. Такое сообщение доставляется с помощью метода send() объекта WebSocket, которому в качестве параметра передается обычный текст. Далее приведена функция, которая обрабатывает событие onopen и отправляет сообщение:
Предположительно, веб-сервер получит это сообщение и даст на него ответ.
События onError и onClose можно использовать для отправки извещений посетителю веб-страницы. Но безоговорочно самым важным является событие onMessage, которое срабатывает при получении новых данных от сервера. Опять же, код JavaScript для обработки этого события не представляет никаких сложностей — мы просто извлекаем текст сообщения из свойства data:
Если веб-страница решит, что вся ее работа выполнена, она может закрыть подключение, используя метод disconnect():
Из этого обзора веб-сокетов можно видеть, что использование сервера веб-сокетов стороннего разработчика не представляет никаких трудностей — нам нужно лишь знать, какие сообщения отправлять, а какие — ожидать.
Чтобы заставить подключение веб-сокетов работать, выполняется большой объем работы за кулисами. Прежде всего, веб-страница устанавливает связь по обычному стандарту HTTP. Потом это подключение нужно повысить до подключения веб-сокетов, позволяющего свободную двустороннюю связь. На этом этапе возможны проблемы, если между компьютером клиента и веб-сервером находится прокси-сервер (как, например, в типичной корпоративной сети). Прокси-сервер может отказаться сотрудничать и разорвет подключение. Эту проблему можно решить, обнаруживая неудачное подключение (посредством события onError объекта WebSocket) и применяя один из заполнителей (polyfills) для сокетов, описанных на веб-сайте GitHub. Эти заполнители применяют метод опроса, чтобы эмулировать подключение веб-сокетов.
A simple example
To open a websocket connection, we need to create using the special protocol in the url:
There’s also encrypted protocol. It’s like HTTPS for websockets.
Always prefer
The protocol is not only encrypted, but also more reliable.
That’s because data is not encrypted, visible for any intermediary. Old proxy servers do not know about WebSocket, they may see “strange” headers and abort the connection.
On the other hand, is WebSocket over TLS, (same as HTTPS is HTTP over TLS), the transport security layer encrypts the data at sender and decrypts at the receiver. So data packets are passed encrypted through proxies. They can’t see what’s inside and let them through.
Once the socket is created, we should listen to events on it. There are totally 4 events:
- – connection established,
- – data received,
- – websocket error,
- – connection closed.
…And if we’d like to send something, then will do that.
Here’s an example:
For demo purposes, there’s a small server server.js written in Node.js, for the example above, running. It responds with “Hello from server, John”, then waits 5 seconds and closes the connection.
So you’ll see events → → .
That’s actually it, we can talk WebSocket already. Quite simple, isn’t it?
Now let’s talk more in-depth.
Сервер
Чат будет работать по такому принципу: сообщения между сервером и клиентом передаются в формате JSON с указанием, какое «действие» (action) выполняет это сообщение. Таким образом можно разделить сообщения на типы: служебные, публичные, приватные и т.д., и с лёгкостью дополнять различные типы служебной и иной сопутствующей информацией.
Такой подход позволит в будущем при необходимости произвести «безболезненную» модернизацию чата. Например, если сервер начнёт отправлять сообщения неизвестного действия, то клиент просто не будет на них реагировать, но можно оповестить пользователя о том, что он использует устаревшую версию клиента.
Наш простейший чат будет поддерживать следующие действия:
-
авторизация пользователя
action = Authorized
При подключении пользователя к чату сервер предварительно проверяет свободность выбранного никнейма. Если никнейм занят, то приписываем к нему номер (2, 3, 4 и т.д.). Если никнейм свободен, то отправляем пользователю сообщение «Authorized», в котором передаются данные, с которыми он был авторизован в чате и список пользователей чата.
Дополнительно пользователь может выбрать цвет отображения своего имени и указать, к какому полу (М/Ж) относится. -
оповещение всех пользователей о присоединении нового участника к чату
action = Connected
После авторизации нового пользователя сервер отправляет всем участникам сообщение «Connected», в котором передаются данные авторизованного пользователя. -
оповещение всех пользователей при выходе участника из чата
action = Disconnected
При выходе пользователя из чата все участники оповещаются сообщением «Disconnected». -
отправка сообщения в общий чат
action = PublicMessage
Если пользователь отправляет сообщение в чат без указания адресата, то такое сообщение определяется как «Публичное» и рассылается всем участникам. -
отправка приватного сообщения
action = PrivateMessage
Если пользователь отправляет сообщение в чат с указанием адресата, то такое сообщение определяется как «Приватное» и отправляется только адресату. -
проверка пользователей на потерю соединений
action = Ping
Сервер рассылает всем участникам служебное сообщение «Ping» с определённым интервалом и ожидает от каждого ответное сообщение «Pong». -
оповещение пользователей о потере соединения участником
action = ConnectionLost
Если сервером несколько раз подряд не будет получено ответное на «Ping» сообщение от участника, то участник считается отключившимся, всем остальным участникам рассылается сообщение «ConnectionLost» с данными “отвалившегося” пользователя.
Все определения WSS
Акроним | Определение |
---|---|
WSS | Web Security Suite |
WSS | Web Services симпозиум |
WSS | Windaroo Государственная школа |
WSS | WindowsSoundSystem |
WSS | Woongoolba Государственная школа |
WSS | Worongary Государственная школа |
WSS | Безопасность веб-служб |
WSS | Безопасность системы водоснабжения |
WSS | Ван станции наборы |
WSS | Веб службы Планировщик |
WSS | Веб-самообслуживания |
WSS | Вес воспринимая систему |
WSS | Вестсайдская история |
WSS | Взвешенный сигнал подпространство |
WSS | Воде и санитарным услугам |
WSS | Водоснабжение и санитария |
WSS | Войны экономия штамп |
WSS | Всемирная встреча на высшем уровне |
WSS | Выбор состояния ожидания |
WSS | Датчик скорости колеса |
WSS | Длина волны селективный выключатель |
WSS | Еженедельного статистического приложения |
WSS | Западная Государственная школа |
WSS | Мир общество позвоночника |
WSS | Моделирование Спутниковое WDS |
WSS | Ну стимуляции услуги |
WSS | Оптовая доставка система |
WSS | Оружие системы моделирования |
WSS | Почему так грустно |
WSS | Рабочая станция-сервер |
WSS | Сайт для хранения оружия |
WSS | Самообслуживание на рабочем месте |
WSS | Сварные специальные разделы |
WSS | Свинг западного общества |
WSS | Секция стандартизации оружия |
WSS | Сервер подписки Погода |
WSS | Синдром морщинистой кожи |
WSS | Система беспроводного абонента |
WSS | Система поддержки вафельные |
WSS | Система поддержки войны/оружие |
WSS | Система программного обеспечения рабочих станций |
WSS | Системы Уолл-стрит |
WSS | Системы веб-хранилища |
WSS | Службы Windows Sharepoint Services |
WSS | Смарт-стандарты работы |
WSS | Смотреть станции стандартов |
WSS | Средневзвешенная склоне спектральной |
WSS | Стандарта сервера приложений WebSphere |
WSS | Уайт Салфер Спрингс |
WSS | Уивер — синдром Смит |
WSS | Уинстон-Сейлем круизной железнодорожная компания |
WSS | Услуги во всем мире ценных бумаг |
WSS | Что она сказала |
WSS | Широкий космических систем |
WSS | Широкий экран сигнализации |
WSS | Широком смысле стационарные |
WSS | Широкополосные стерео сигнала |
WSS | Эскадрилья Погода |
Что означает WSS в тексте
В общем, WSS является аббревиатурой или аббревиатурой, которая определяется простым языком. Эта страница иллюстрирует, как WSS используется в обмена сообщениями и чат-форумах, в дополнение к социальным сетям, таким как VK, Instagram, Whatsapp и Snapchat. Из приведенной выше таблицы, вы можете просмотреть все значения WSS: некоторые из них образовательные термины, другие медицинские термины, и даже компьютерные термины. Если вы знаете другое определение WSS, пожалуйста, свяжитесь с нами. Мы включим его во время следующего обновления нашей базы данных. Пожалуйста, имейте в информации, что некоторые из наших сокращений и их определения создаются нашими посетителями. Поэтому ваше предложение о новых аббревиатур приветствуется! В качестве возврата мы перевели аббревиатуру WSS на испанский, французский, китайский, португальский, русский и т.д. Далее можно прокрутить вниз и щелкнуть в меню языка, чтобы найти значения WSS на других 42 языках.
Opening a websocket
When is created, it starts connecting immediately.
During the connection the browser (using headers) asks the server: “Do you support Websocket?” And if the server replies “yes”, then the talk continues in WebSocket protocol, which is not HTTP at all.
Here’s an example of browser headers for request made by .
- – the origin of the client page, e.g. . WebSocket objects are cross-origin by nature. There are no special headers or other limitations. Old servers are unable to handle WebSocket anyway, so there are no compatibility issues. But header is important, as it allows the server to decide whether or not to talk WebSocket with this website.
- – signals that the client would like to change the protocol.
- – the requested protocol is “websocket”.
- – a random browser-generated key for security.
- – WebSocket protocol version, 13 is the current one.
WebSocket handshake can’t be emulated
We can’t use or to make this kind of HTTP-request, because JavaScript is not allowed to set these headers.
If the server agrees to switch to WebSocket, it should send code 101 response:
Here is , recoded using a special algorithm. The browser uses it to make sure that the response corresponds to the request.
Afterwards, the data is transfered using WebSocket protocol, we’ll see its structure (“frames”) soon. And that’s not HTTP at all.
There may be additional headers and that describe extensions and subprotocols.
For instance:
-
means that the browser supports data compression. An extension is something related to transferring the data, functionality that extends WebSocket protocol. The header is sent automatically by the browser, with the list of all extensions it supports.
-
means that we’d like to transfer not just any data, but the data in SOAP or WAMP (“The WebSocket Application Messaging Protocol”) protocols. WebSocket subprotocols are registered in the IANA catalogue. So, this header describes data formats that we’re going to use.
This optional header is set using the second parameter of . That’s the array of subprotocols, e.g. if we’d like to use SOAP or WAMP:
The server should respond with a list of protocols and extensions that it agrees to use.
For example, the request:
Response:
Here the server responds that it supports the extension “deflate-frame”, and only SOAP of the requested subprotocols.
データ転送
WebSocket 通信は “フレーム” (データフラグメント、どちら側からでも送信でき、いくつかの種類があります)で構成されます。 :
- “text frames” – 関係者が互いに送信するテキストデータを含んでいます。
- “binary data frames” – 関係者が互いに送信するバイナリデータを含んでいます。
- “ping/pong frames” は接続確認に使用されます。サーバから送信され、ブラウザは自動でそれらに応答します。
- “connection close frame” やその他いくつかのサービスフレームもあります。
ブラウザでは、テキストフレームまたはバイナリフレームのみを直接扱います。
WebSocket メソッドはテキストまたはバイナリデータを送信できます。
呼び出しは、文字列または や などを含むバイナリ形式の が許可されます。設定は必要ありません。任意のフォーマットで送信するだけでOKです。
データを受信したとき、テキストは常に文字列として来ます。また、バイナリデータの場合は 、 形式のいずれかを選択することができます。
これは プロパティで設定されます。デフォルトは なので、バイナリデータは オブジェクトで来ます。
Blob は高レベルのバイナリオブジェクトで、, 等といったタグと直接統合されます。そのため、これは妥当なデフォルト値です。ただし、バリナリ処理の場合に個々のバイトデータにアクセスする必要があれば、 に変更することができます。
Example¶
Here’s a WebSocket server example. It reads a name from the client and sends a
message.
#!/usr/bin/env python import asyncio import websockets @asyncio.coroutine def hello(websocket, path): name = yield from websocket.recv() print("< {}".format(name)) greeting = "Hello {}!".format(name) yield from websocket.send(greeting) print("> {}".format(greeting)) start_server = websockets.serve(hello, 'localhost', 8765) asyncio.get_event_loop().run_until_complete(start_server) asyncio.get_event_loop().run_forever()
Here’s a corresponding client example.
#!/usr/bin/env python import asyncio import websockets @asyncio.coroutine def hello(): websocket = yield from websockets.connect('ws://localhost:8765/') name = input("What's your name? ") yield from websocket.send(name) print("> {}".format(name)) greeting = yield from websocket.recv() print("< {}".format(greeting)) asyncio.get_event_loop().run_until_complete(hello())
Connection close
Normally, when a party wants to close the connection (both browser and server have equal rights), they send a “connection close frame” with a numeric code and a textual reason.
The method for that is:
- is a special WebSocket closing code (optional)
- is a string that describes the reason of closing (optional)
Then the other party in event handler gets the code and the reason, e.g.:
Most common code values:
- – the default, normal closure (used if no supplied),
- – no way to set such code manually, indicates that the connection was lost (no close frame).
There are other codes like:
- – the party is going away, e.g. server is shutting down, or a browser leaves the page,
- – the message is too big to process,
- – unexpected error on server,
- …and so on.
The full list can be found in .
WebSocket codes are somewhat like HTTP codes, but different. In particular, any codes less than are reserved, there’ll be an error if we try to set such a code.
Low-level API¶
Exceptions
- exception
-
Exception raised when a handshake request or response is invalid.
- exception
-
Exception raised when an operation is forbidden in the current state.
- exception
-
Exception raised when an URI is invalid.
Opening handshake
The module deals with the WebSocket opening
handshake according to .
It provides functions to implement the handshake with any existing HTTP
library. You must pass to these functions:
- A set_header function accepting a header name and a header value,
- A get_header function accepting a header name and returning the header
value.
The inputs and outputs of get_header and set_header are
objects containing only ASCII characters.
Some checks cannot be performed because they depend too much on the
context; instead, they’re documented below.
To accept a connection, a server must:
- Read the request, check that the method is GET, and check the headers with
, - Send a 101 response to the client with the headers created by
if the request is valid; otherwise, send a 400.
To open a connection, a client must:
- Send a GET request to the server with the headers created by
, - Read the response, check that the status code is 101, and check the headers
with .
- (set_header)
-
Build a handshake request to send to the server.
Return the key which must be passed to .
- (get_header)
-
Check a handshake request received from the client.
If the handshake is valid, this function returns the key which must be
passed to .Otherwise, it raises an exception and the server
must return an error, usually 400 Bad Request.This function doesn’t verify that the request is an HTTP/1.1 or higher GET
request and doesn’t perform Host and Origin checks. These controls are
usually performed earlier in the HTTP request handling code. They’re the
responsibility of the caller.
- (set_header, key)
-
Build a handshake response to send to the client.
key comes from .
- (get_header, key)
-
Check a handshake response received from the server.
key comes from .
If the handshake is valid, this function returns .
Otherwise, it raises an exception.
This function doesn’t verify that the response is an HTTP/1.1 or higher
response with a 101 status code. These controls are the responsibility of
the caller.
Data transfer
The module implements data framing as specified in
.
It deals with a single frame at a time. Anything that depends on the sequence
of frames is implemented in .
- class (fin, opcode, data)
-
-
Alias for field number 2
-
Alias for field number 0
-
Alias for field number 1
-
- (reader, mask, *, max_size=None)
-
Read a WebSocket frame and return a object.
reader is a coroutine taking an integer argument and reading exactly this
number of bytes, unless the end of file is reached.mask is a telling whether the frame should be masked, ie.
whether the read happens on the server side.If max_size is set and the payload exceeds this size in bytes,
is raised.This function validates the frame before returning it and raises
if it contains incorrect values.
- (frame, writer, mask)
-
Write a WebSocket frame.
frame is the object to write.
writer is a function accepting bytes.
mask is a telling whether the frame should be masked, ie.
whether the write happens on the client side.This function validates the frame before sending it and raises
if it contains incorrect values.
- (data)
-
Parse the data in a close frame.
Return (code, reason) when code is an and reason a
.Raise or if the
data is invalid.
- (code, reason)
-
Serialize the data for a close frame.
This is the reverse of .
URI parser
The module implements parsing of WebSocket URIs
according to .
- (uri)
-
This function parses and validates a WebSocket URI.
If the URI is valid, it returns a namedtuple (secure, host, port,
resource_name)Otherwise, it raises an exception.
Установление WebSocket-соединения
Протокол работает над TCP.
Это означает, что при соединении браузер отправляет по HTTP специальные заголовки, спрашивая: «поддерживает ли сервер WebSocket?».
Если сервер в ответных заголовках отвечает «да, поддерживаю», то дальше HTTP прекращается и общение идёт на специальном протоколе WebSocket, который уже не имеет с HTTP ничего общего.
Пример запроса от браузера при создании нового объекта :
Описания заголовков:
- GET, Host
- Стандартные HTTP-заголовки из URL запроса
- Upgrade, Connection
- Указывают, что браузер хочет перейти на websocket.
- Origin
- Протокол, домен и порт, откуда отправлен запрос.
- Sec-WebSocket-Key
- Случайный ключ, который генерируется браузером: 16 байт в кодировке Base64.
- Sec-WebSocket-Version
- Версия протокола. Текущая версия: 13.
Все заголовки, кроме и , браузер генерирует сам, без возможности вмешательства JavaScript.
Такой XMLHttpRequest создать нельзя
Создать подобный XMLHttpRequest-запрос (подделать ) невозможно, по одной простой причине: указанные выше заголовки запрещены к установке методом .
Сервер может проанализировать эти заголовки и решить, разрешает ли он с данного домена .
Ответ сервера, если он понимает и разрешает -подключение:
Здесь строка представляет собой перекодированный по специальному алгоритму ключ . Браузер использует её для проверки, что ответ предназначается именно ему.
Затем данные передаются по специальному протоколу, структура которого («фреймы») изложена далее. И это уже совсем не HTTP.
Также возможны дополнительные заголовки и , описывающие расширения и подпротоколы (subprotocol), которые поддерживает данный клиент.
Посмотрим разницу между ними на двух примерах:
-
Заголовок означает, что браузер поддерживает модификацию протокола, обеспечивающую сжатие данных.
Это говорит не о самих данных, а об улучшении способа их передачи. Браузер сам формирует этот заголовок.
-
Заголовок говорит о том, что по WebSocket браузер собирается передавать не просто какие-то данные, а данные в протоколах SOAP или WAMP («The WebSocket Application Messaging Protocol»). Стандартные подпротоколы регистрируются в специальном каталоге IANA.
Этот заголовок браузер поставит, если указать второй необязательный параметр :
При наличии таких заголовков сервер может выбрать расширения и подпротоколы, которые он поддерживает, и ответить с ними.
Например, запрос:
Ответ:
В ответе выше сервер указывает, что поддерживает расширение , а из запрошенных подпротоколов – только SOAP.
Соединение можно открывать как или как . Протокол представляет собой WebSocket над HTTPS.
Кроме большей безопасности, у есть важное преимущество перед обычным – большая вероятность соединения. Дело в том, что HTTPS шифрует трафик от клиента к серверу, а HTTP – нет
Дело в том, что HTTPS шифрует трафик от клиента к серверу, а HTTP – нет.
Если между клиентом и сервером есть прокси, то в случае с HTTP все WebSocket-заголовки и данные передаются через него. Прокси имеет к ним доступ, ведь они никак не шифруются, и может расценить происходящее как нарушение протокола HTTP, обрезать заголовки или оборвать передачу.
А в случае с весь трафик сразу кодируется и через прокси проходит уже в закодированном виде. Поэтому заголовки гарантированно пройдут, и общая вероятность соединения через выше, чем через .
Реализация сервера на PHP
Исходники простого WebSocket echo-сервера выложил сюда.
Код хорошо документирован, но я всё же опишу некоторые тонкости реализации.
Чтобы «поднять» WebSocket сервер нужно создать обычный TCP-сервер.
В PHP TCP-сервер реализуется через «stream_socket» или через PHP расширение «sockets».
Различия между ними в том, что «stream_socket» работает на встроенных функциях PHP для работы с потоками, «sockets» же работает через модуль PHP и повторяет функции для работы с сокетами в языке «C».
Я выбрал «sockets».
Процесс реализован через «while» с задержкой 0.2 секунды.
Процесс не форкается и сообщения выбрасывает в консоль, поэтому запускать необходимо только через консоль.
Для того, чтобы обслуживать несколько клиентов одновременно, сокет делаю неблокирующим и через «socket_select»
каждые 0.2 секунды прослушиваю сокет.
При рукопожатии проверяю только наличие заголовков.
Фреймы парсю через «pack/unpack».
Сервер не понимает фрагментированных фреймов.
Сервер выдаёт только незамаскированные сообщения, т.к. некоторые браузеры не понимают замаскированных сообщений.
Сервер реагирует только на текстовые фреймы и фрейм закрытия соединения, бинарные фреймы не понимает.
Ну собственно всё, удачи в исследовании этого не простого протокола.