Доклад на РИТ про Веб Сокеты (Web Sockets)
Докладчик: Евгений Лисицкий – ведущий разработчик Спорт Сегодня (www.sports.ru).
Структура доклада:
- Работа интерактивного веба
- Преимущества Веб Сокетов. Сравнение со стандартными технологиями.
- Техническая сторона вопроса
- Примеры реализации
Работа интерактивного веба
На сегодня мы имеем схему передачи данных: сервер, клиент, и среду передачи данных между ними. (слайд 3)
Сервер находится в нашем распоряжении и мы можем делать с ним что угодно, ставить любой софт, менять настройки и так далее.
На сегодня существует масса серверов на различных языках, работающих по событийно-ориентированной модели (event-driven). На слайде представлены наиболее популярные языки, хотя есть и масса других. Практически на каждом языке реализован событийно – ориентированный сервер. (слайд 4)
С клиентом более сложная ситуация, но, в общем, тоже все хорошо: для него существует JavaScript, отличный событийный язык, он настолько хорош, что его применяют для программирования сервера. Например, Node JS. Некоторые трудности могут быть лишь с отдельными браузерами, которые не в полной мере реализуют все возможности или просто содержат ошибки. (слайд 5)
Транспорт — это самое слабое звено в нашей цепи. Проблема совершенно не в том, что HTTP - нерабочий или неотшлифованный протокол, отнюдь нет. Все как раз наоборот — это шикарный, хорошо продуманный протокол, созданный гениями, и имеющий запас прочности на годы вперед. В нем все хорошо, кроме одной-единственной вещи: он создан для другой цели. (слайд 4)
Его предназначение: забирать объекты с сервера, или наоборот отправлять на сервер, но интерактивность не его конек. Поясню, что я подразумеваю под интерактивностью. Для того чтобы обеспечить гибкость и скорость реакции на события, к которым мы привыкли в декск-топных приложениях, нам приходится дробить информацию на мелкие кусочки и очень часто обмениваться ей с сервером. НТТР имеет ряд ограничений, которые не позволяют ему эффективно работать в данном режиме. (слайд 7)
Интерактивность – это (слайд 6)
1) Минимальная латентность - минимальные задержки реакции на действия пользователя. Нажатие кнопки – получение сообщения (молниеносно).
2) Асинхронность – независимость в получении сообщения от пользователя от его текущих рабочих действий и других событий (активности)
3 основных ограничения НТТР при полноценной интерактивной работе (слайд 7):
- Синхронность
- Жесткое распределение «клиент»-ведущий и «сервер»-ведомый
- Низкая эффективность при передаче большого количества маленьких по объему данных
Главной особенностью НТТР является синхронность (слайд 8).
Он работает по модели «запрос-ответ» и не удобен для интерактива, о котором говорили выше. Да, есть различные ухищрения, например, задержка ответа на сервере (long-polling), pipe-lining, streaming, более-менее расширяющие его возможности, и более-менее поддерживаемые сетевой инфраструктурой. Но в основе своей это все тот же «запрос-ответ». Из этого четко вытекает второе ограничение: жесткое распределение ролей на клиент и сервер. Сервер не может по своей инициативе отправить данные клиенту, они могут уйти только как ответ на запрос клиента.
Третья проблема - низкая эффективность при передаче большого количества маленьких по объему данных. Данные по протоколу НТТР снабжаются заголовками (кодировка, размер и т.д.). Объем заголовков составляет от нескольких сотен байт до нескольких килобайт. Это не мешает работе, когда запрашиваются большие страницы, картинки, но когда мы передаем большое количество небольших объектов, накладные расходы на их передачу (те самые заголовки) значительно превышают полезный объем (собственно данные). Таким образом, КПД получается порядка нескольких процентов. Что находится в пределах эффективности паровоза (7%) . (слайд 9)
Преимущество Web Socket.
Что же такого интересного сулит нам технология? На мой взгляд, WebSocket — это самое кардинальное расширение протокола HTTP с его появления. Это сдвиг парадигмы HTTP. Изначально синхронный протокол, построенный по модели «запрос — ответ», становится полностью асинхронным и симметричным. Теперь уже нет клиента и сервера с фиксированными ролями, а есть два равноправных участника обмена данными. Каждый работает сам по себе, и когда надо отправляет данные другому. Отправил — и продолжаешь работу дальше, ничего ждать не надо. Вторая сторона ответит, когда захочет — может не сразу, а может и вообще не ответит. Протокол дает полную свободу в обмене данными, вам решать как это использовать. (слайд 10)
Преимущества технологии Веб-сокет (WebSockets):
· Входит в HTML5, будет принята как стандарт W3C;
· Полноценная двунаправленная передача данных (full-duplex);
· Асинхронность;
· Низкие требования к сетевым ресурсам, максимальный КПД передачи данных, минимум «накладных расходов»;
· Неограниченное время жизни канала в неактивном состоянии;
· Возможность работы с разными доменами;
· Отсутствие ограничения на количество подключений к одному домену.
· Очень простое API (интерфейс)
Раскроем эти преимущества и сравним с классической технологией (Comet), которая частично решает проблемы НТТР.
1) Входит в HTML5, будет принята как стандарт W3C. Это значит, что она будет работать стабильнее, чем существующая технология Комет, которая, по сути, является набором методов, я бы сказал, ухищрений, обходящих описанные проблемы. Также это предполагает, что сетевая структура будет поддерживать Web Socket. Чего нельзя сказать о технологии Comet. Она не будет принята как стандарт.
2) Полноценная двунаправленная передача данных (full-duplex). full-duplex-это одновременный двусторонний обмен данными. По сути своей Web Socket - это открытое ТСР-соединение, к которому мы привыкли за несколько десятков лет вне web. Образно его можно представить как телефон. Сигнал, возникший на одном конце, принимается на другом. При этом соединение симметрично работает в обе стороны. (слайд 10). HTTP можно сравнить с пневмопочтой, которая так же работает в обе стороны, но не одновременно. Если мы хотим, чтобы пневмопочта стала полнодуплексной, нам необходимо две трубы, по одной отправляем данные в одну сторону, по другой в другую.
3) Асинхронность. Я говорил об этом выше. В Web Socket асинхронность реализуется через тот же самый «телефон», т.е. когда мы хотим что-то сообщить другой стороне, мы отправляем пакеты данных и не ожидаем ответа. По мере возникновения событий производится отправка. Другая сторона не обязана отвечать на них сразу, и отвечает по мере необходимости. И посылает свой поток сообщений, в соответствии со своими потребностями.
Как эта функциональность реализуется в Комет? Если мы открываем одно соединение, то мы можем отправлять сообщения, а сервер будет на них отвечать. Здесь есть разные подходы: polling дает быстрый возврат, но как угадать момент, когда надо посылать запрос? Здесь возникает небольшая задержка между моментом возникновения события и получения запроса. Эту проблему может решить long-polling – сервер, получив запрос, не отвечает сразу, а ждет, когда возникнет событие, и только тогда его отправляет. Второй вариант – streaming – сервер отвечает на клиентский запрос порциями, здесь есть возможность отправить несколько ответов на запрос, тем самым, подняв эффективность передачи, но есть и свои проблемы. Оба варианта имеют ограничение в том, что пока у нас отправлен запрос, этот канал занят, а значит, чтобы отправлять дальнейшие запросы, нам нужна вторая «труба» в другую сторону. Такие реализации у Comet есть, но проблема в том, что по стандарту http бразуер не должен открывать больше 2 одновременных соединений к одному домену. Сейчас это число увеличено до 4. Но в любом случае, если ваши соединения постоянно заняты, то вы можете наблюдать проблемы с загрузкой других объектов (например, картинок).
4) Низкие требования к сетевым ресурсам, максимальный КПД передачи данных, минимум «накладных расходов». Для передачи данных в технологии Web Socket не требуются заголовки. Передаваемый пакет представляет собой строку в кодировке UTF-8, в начале которой добавлен нулевой байт 0x00, а в конце – 0xFF. Таким образом, достигается очень эффективная передача данных с КПД до 95%.
В polling-вариантах Comet требуются заголовки, занимающие существенную часть передаваемых данных, и снижающие эффективность до тех самых паровозных 7%. Есть способ этого избежать – streaming. Но у него есть свои проблемы. Например, кеширующие прокси и антивирусы любят буферизовать такие ответы и передавать дальше после накопления определенного объема. Для преодоления этой проблемы придумана технология «двухкилобайтного вантуза» - когда размер ответа искусственно увеличивается до 2КБ незначащими символами, например, пробелами. Но в этом случае говорить о КПД тоже не приходится. В некоторых реализациях Comet сделано определение кеширующего прокси-сервера, и в случае если он обнаружен, то система из режима streaming переключается в long-polling.
5) Неограниченное время жизни канала в неактивном состоянии. Время жизни канала в неактивном состоянии ограничено – а значит, даже если ничего не произошло, то все равно нам надо периодически передавать данные, сбрасывать соединение и устанавливать его заново. То есть все равно мы имеем существенный «холостой ход» и расходование сетевых ресурсов. В отличие от http (Comet) Web Socket не имеют ограничений на время жизни в неактивном состоянии. Это значит, что больше не надо периодически обновлять соединение, т.к. его не вправе закрывать прокси. Значит, соединение может висеть в неактивном виде и не требовать ресурсов. Конечно, можно возразить, что на сервере будут забиваться TCP-сокеты. Для этого достаточно использовать хороший мультиплексор, и нормальный сервер легко потянет до миллиона открытых подключений.
6) Возможность работы с разными доменами. Еще один «камень в ботинке» AJAX-разработчика (Comet) — проблемы с кросс-доменными приложениями. Да, и для них тоже придумана масса способов обхода. Web Sockets не имеет таких ограничений. Ограничения вводятся не по принципу «из-того-же-источника», а из «разрешенного-источника», и определяются не на клиенте, а на сервере. Принимая данные в процессе соединения, сервер получает информацию о том, какая страница к нему обращается. В случае, если домен входит в число разрешенных, то соединение – подтверждается. В обратном случае, соединение сбрасывается.
7) Отсутствие ограничения на количество подключений к одному домену. Как известно в HTTP предусмотрено ограничение на число одновременных открытых сессий к одному серверу. Из-за этого если у вас много различных асинхронных блоков на странице, то вам приходилится делать не только серверный, но и клиентский мультиплексор — именно отсюда идет Bayeux Protocol.
К счастью, это ограничение не распространяется на Web Socket. Открываете столько подключений, сколько вам нужно. Причем эти подключения не учитываются в числе НТТР подключений, а значит, они не будут мешать загрузке страниц и объектов с этого домена. А сколько именно подключений использовать — одно (и через него все мультиплексировать) или же наоборот — на каждый блок свое соединение — решать вам. Исходите из удобства разработки, нагрузки на сервер и клиент.
8) Очень простое API (интерфейс). Весь код для работы с Web Socket полностью помещается на одной странице. Как это работает – мы сейчас подробно рассмотрим и тем самым перейдем к технической стороне вопроса.
Техническая сторона вопроса
Как это работает? Очень просто! Как только ваша страница решила, что она хочет открыть Web Socket на сервер, она создает специальный javascript-объект: (слайд 10)
- <script>
- ws = new WebSocket("ws://site.com/demo");
- // и навешивает на новый объект три колл-бека:
- // первый вызовется, когда соединение будет установлено:
- ws.onopen = function() { alert("Connection opened...") };
- // второй - когда соединено закроется
- ws.onclose = function() { alert("Connection closed...") };
- // и, наконец, третий - каждый раз, когда браузер получает какие-то данные через веб-сокет
- ws.onmessage = function(evt) { $("#msg").append("<p>"+evt.data+"</p>"); };
- </script>
А что при этом происходит в сети?
Все начинается так же как в обычном HTTP-запросе. Браузер подключается по протоколу TCP на 80 порт сервера и дает немного необычный GET-запрос (слайд 11):
GET /demo HTTP/1.1
Upgrade: WebSocket
Connection: Upgrade
Host: site.com
Origin: http://site.com
Если сервер поддерживает Web Socket, то он отвечает таким образом (слайд 12):
HTTP/1.1 101 Web Socket Protocol Handshake
Upgrade: WebSocket
Connection: Upgrade
WebSocket-Origin: http://site.com
WebSocket-Location: ws://site.com/demo
Если браузер это устраивает, то он просто оставляет TCP-соединение открытым. Все, «рукопожатие» совершено, канал обмена данными готов.
Как только одна сторона хочет передать другой какую-то информацию, она отправляет дата-фрейм следующего вида (слайд 13):
0x00, <строка в кодировке UTF-8>, 0xFF
Это и есть реализация того, о чем говорилось выше. Что именно отправлять, разработчики полностью оставили на ваше усмотрение: хотите XML, хотите JSON, да хоть стихи Пушкина.
Каждый раз, когда браузер будет получать такое сообщение, он будет вызывать ваш колл-бек onmessage.
Это демонстрация того, что КПД такого протокола стремится к 95%, а не классический AJAX-запрос, где на каждый пакет данных приходится пересылать несколько килобайт заголовков. Скорость обработки так же стремится к скорости чистого TCP-сокета — ведь все уже готово — соединение открыто — всего лишь байты переслать.
И еще одна вещь, которая меня очень радует - в качестве единственной разрешенной кодировки выбрана UTF-8! Я надеюсь, что через некоторое время мы уйдем от одного из «костылей» веба.
И еще несколько вопросов, которые волнуют программистов:
А картинку можно отправить?
С помощью Web Sockets так же можно передавать и бинарные данные. Для них используется другой дата-фрейм следующего вида (слайд 14):
0x80, <длина - один или несколько байт>, <тело сообщения>
Чтобы не создавать ограничений на длину передаваемого сообщения и в тоже время не расходовать байты нерационально, разработчики использовали оригинальный способ указания длины тела сообщения.
Каждый байт в указании длины рассматривается по частям: самый старший бит указывает является ли этот байт последним (0) либо же за ним есть другие (1), а младшие 7 битов содержат собственно данные. Обрабатывать можно так: как только вы получили признак бинарного дата-фрейма 0x80, вы берете следующий байт и откладываете его в отдельную «копилку», смотрите на следующий байт — если у него установлен старший бит, то переносите и его в «копилку», и так далее, пока вам не встретится байт с 0 старшим битом. Значит это последний байт в указателе длины — его тоже переносите в «копилку». Теперь из всех байтов в «копилке» убираете старшие биты и слепляете остаток. Вот это и будет длина тела сообщения. Еще можно интерпретировать как 7-битные числа без старшего бита (слайд 15).
Например, самую главную картинку веб-дизайна — прозрачный однопиксельный GIF размером 43 байта можно передать так (слайд 16):
0x80, 0x2B, <тело сообщения>
Объект размером 160 байт закодируется 2 байтами длины:
0x80, 0x81, 0x20, <байты объекта>
Пример реализации
Пример реализации – это небольшой чат на Web Socket. Должен оговориться заранее, это демонстрационный пример, дизайн у него довольно спартанский, я намеренно не стал тратить время на украшения и оптимизацию скорости работы, чтобы это не отвлекало от сути дела. Вы можете его увидеть, зайдя по адресу (слайд 17):
http://chat.websockets.ru либо http://sn.im/ws-chat
Что здесь есть?
Все работает по событийно-ориентированной модели — как только возникает событие — оно кодируется в JSON и отправляется на сервер через Web Socket. Сервер его обрабатывает, в процессе обработки возникают другие события, которые рассылаются нужным получателям.
Авторизация сделана на стороне сервера — вы отправляете имя, в ответ приходит подтверждение. После чего вы можете писать в чат. В данном примере сервер подтверждает все логины, без проверок на уникальность и тому подобного, это сделано для удобства слушателей, чтобы вы сейчас могли войти под любым именем, без формулирования уникальных логинов. В реальной жизни не составит труда сделать авторизацию по кукам либо же при их отсутствии запрашивать логин и пароль. После того, как вы авторизовались в чате, всем пользователям рассылаются уведомления, что появился новый пользователь.
Такой способ работы существенно снижает нагрузку на подсистему авторизации, потому что проверка проводится только 1 раз, после чего пользователь может сколь угодно долго сидеть в чате, переходить между комнатами, читать «приват» — и все это через 1 соединение. Одновременно, такой подход улучшает модерацию — ведь теперь, чтобы выкинуть пользователя из чата достаточно просто закрыть его Web Socket. Пользователь в ту же самую секунду будет отключен.
- Войдите или зарегистрируйтесь, чтобы получить возможность отправлять комментарии