Простая реализация long polling механизма на PHP
Итак, что же из себя представляет Long Polling?
Выглядит это примерно следующим образом:
1) Клиент отсылает на сервер обычный ajax-запрос
2) Сервер, вместо того, чтобы быстро обработать этот запрос и отправить ответ клиенту, запускает цикл, в каждой итерации которого следит за возникновением событий (другой клиент добавил запись или удалил).
3) При возникновении события сервер генерирует ответ и отсылает его клиенту, таким образом завершая запрос.
4) Клиент, получив ответ от сервера, запускает обработчик события и параллельно отправляет очередной «длинный» запрос серверу.
То есть всё довольно просто и понятно. Однако, при реализации возникает несколько вопросов, которые нужно решить.
Шаги к практике
В первую очередь возникает вопрос о том, как скрипты будут взаимодействовать друг с другом. Ведь на каждый запрос клиента к серверу создаётся независимая копия PHP-скрипта. То есть нужна какая-то общая память для хранения данных о событиях.
Традиционный вариант — запускается демон, который оперирует событиями и держит соединение с клиентами. Использование демона естественным образом решает проблему памяти. Клиенты обращаются для получения событий не на веб-сервер, а к демону. В то же время, клиент, инициализирующий событие, также сообщает демону, что произошло событие. И таким образом всё крутится в памяти этого демона. который вообще может быть написан на чём угодно.
Но нам, в виду шила в одном месте и желания обойтись без сторонних приложений, демон на чём угодно не подойдёт. А писать демона на PHP, на мой вкус, не слишком интересно. У хостера могут быть проблемы с изменением максимального времени работы скрипта, а ещё продумывать интерфейсы взаимодействия клиентов с этим демоном… Нет, мы пойдём другим путём.
А остаётся только решить, как реализовать общую память в самом PHP. В голову приходят варианты хранения событий в файлах и БД, но хочется-то, чтобы всё было поближе — в памяти. Memcache нам, увы недоступен, а что тогда делать? А есть в php такое понятие как shared memory, точнее понятие-то не является особенностью этого языка, но нам дают возможность этим пользоваться. Механизмы этой технологии позволяют нам создать ячейку в оперативной памяти и использовать её в своих целях. Вот её я и буду использовать.
Практика
Попробуем представить интерфейс класса, который будет реализовывать нужный нам функционал.
Какие нужны методы?
1) Естественно, метод «прослушки», который будет вызываться, когда клиенты будут посылать polling-запросы, и следить за возникающими событиями. Я назвал этот метод listen.
2) Кроме того нам понадобится метод, который будет создавать событие. Этот метод носит имя push.
3) Вполне вероятно, что мы захотим обработать данные о событии, прежде чем отправить результат клиенту. Поэтому я добавил метод, который регистрирует событие и связывает с ним обработчик. Называется метод registerEvent.
В итоге получаем такой интерфейс класса:
Несколько примечаний:
1) В методе registerEvent поддерживается только по одному обработчику на событие по причине того, что возвращаемое обработчиком значение возвращается клиенту как данные события. В одном обработчике можно вызвать несколько функций и из результата их работы собрать ответ клиенту. Впрочем переделать код для поддержки нескольких обработчиков можно без особых затруднений.
2) В методе listen используется параметр lastQueryTime, который позволяет обработать события, возникшие раньше, чем поступил запрос на прослушку. Это может пригодиться когда нагрузка на сеть высока и между завершением одного polling-запроса от клиента и началом другого может пройти заметный промежуток времени, в который возникают события.
3) В приведённом коде не учитывается одновременный доступ к памяти. А вообще. для более надёжной работы желательно использовать семафоры.
4) В методе listen используется вызов функции sleep(1). Это нужно для того, чтобы уменьшить количество холостых итераций. Частоты обновления раз в секунду вполне достаточно для имитации реалтайма.
Ну и на клиенте всё также предельно просто. Нам нужен метод, который будет посылать polling-запрос серверу, ждать ответ, запускать обработчики событий и заново посылать запрос. Ну и метод, регистрирующий сами обработчики событий.
Исходники:
Описанный класс Polling на PHP.
Если кому-нибудь понадобится, приведу в порядок и выложу клиентскую часть.
Длинные опросы
Длинные опросы – это самый простой способ поддерживать постоянное соединение с сервером, не используя при этом никаких специфических протоколов (типа WebSocket или Server Sent Events).
Его очень легко реализовать, и он хорошо подходит для многих задач.
Частые опросы
Самый простой способ получать новую информацию от сервера – периодический опрос. То есть, регулярные запросы на сервер вида: «Привет, я здесь, у вас есть какая-нибудь информация для меня?». Например, раз в 10 секунд.
В ответ сервер, во-первых, помечает у себя, что клиент онлайн, а во-вторых посылает весь пакет сообщений, накопившихся к данному моменту.
Это работает, но есть и недостатки:
- Сообщения передаются с задержкой до 10 секунд (между запросами).
- Даже если сообщений нет, сервер «атакуется» запросами каждые 10 секунд, даже если пользователь переключился куда-нибудь или спит. С точки зрения производительности, это довольно большая нагрузка.
Так что, если речь идёт об очень маленьком сервисе, подход может оказаться жизнеспособным, но в целом он нуждается в улучшении.
Длинные опросы
«Длинные опросы» – гораздо лучший способ взаимодействия с сервером.
Они также очень просты в реализации, и сообщения доставляются без задержек.
Как это происходит:
- Запрос отправляется на сервер.
- Сервер не закрывает соединение, пока у него не возникнет сообщение для отсылки.
- Когда появляется сообщение – сервер отвечает на запрос, посылая его.
- Браузер немедленно делает новый запрос.
Для данного метода ситуация, когда браузер отправил запрос и удерживает соединение с сервером в ожидании ответа, является стандартной. Соединение прерывается только доставкой сообщений.
Если соединение будет потеряно, скажем, из-за сетевой ошибки, браузер немедленно посылает новый запрос.
Примерный код клиентской функции subscribe , которая реализует длинные опросы:
Функция subscribe() делает запрос, затем ожидает ответ, обрабатывает его и снова вызывает сама себя.
Архитектура сервера должна быть способна работать со многими ожидающими подключениями.
Некоторые серверные архитектуры запускают отдельный процесс для каждого соединения. Для большого количества соединений будет столько же процессов, и каждый процесс занимает значительный объём памяти. Так много соединений просто поглотят всю память.
Часто такая проблема возникает с бэкендом, написанными на PHP или Ruby, но технически дело не в языке, а в реализации. На большинстве современных языков можно написать подходящий сервер, но на некоторых это проще сделать.
Что такое Long-Polling, WebSockets, SSE и Comet
Недавно один из подписчиков спросил, как работают уведомления вконтакте? Или других высоконагруженных проектах, ведь они приходят мгновенно и, если использовать обычный ajax, то сервер достаточно быстро упадет. Вот о том, какие существуют способы реализации данного функционала, мы и поговорим в сегодняшней статье.
Для начала рассмотрим, как вообще идет http запрос при обычной работе
Regular http
1. Клиент посылает запрос на вебстраницу к серверу
2. Сервер формирует ответ
3. Сервер посылает ответ клиенту
AJAX Polling
1. Клиент посылает запрос на вебстраницу серверу, используя обычный http(regular http)
2. Запрошенная страница выполняет JavaScript, который запрашивает файл от сервера через какой-то интервал времени(например, 0.5 секунд)
3. Сервер формирует ответ для каждого запроса и отсылает его обратно
AJAX Long-Polling
1. Клиент запрашивает страницу у сервера, используя обычный http
2. Запрошенная страница выполняет JavaScript, который запрашивает файл от сервера.
3. Сервер НЕ реагирует на запрошенную информацию и ждет, пока не появится новой информации
4. Когда появляется новая информация, сервер отсылает ее клиенту
5. Клиент получает новую информацию и НЕМЕДЛЕННО отсылает другой запрос серверу, запуская процесс ожидания на нем снова.
HTML5 Server Sent Events(SSE)
1. Клиент запрашивает страницу у сервера, используя обычный http
2. Запрошенная страница выполняет JavaScript, который открывает соединение с сервером
3. Сервер посылает событие клиенту, когда появляется новая информация
- Трафик в реальном времени от сервера клиенту, главным образом, то, что вам нужно
- Вы захотите использовать сервер, который имеет цикл событий
- Нет возможности соединиться с сервером с другого домена
HTML5 WebSockets
1. Клиент запрашивает страницу у сервера, используя обычный http
2. Запрошенная страница выполняет JavaScript, который открывает соединение с сервером
3. Сервер и клиент могут посылать друг другу сообщения, когда новая информация доступна(либо на сервере, либо на клиенте)
- Трафик в реальном времени от сервера к клиенту и от клиента к серверу
- Вы захотите использовать сервер, который имеет цикл событий
- Есть возможность соединиться с сервером с другого домена
- Есть возможность использовать третью сторону веб-сокет сервера, например, Pusher
Comet
Comet — это коллекция техник, которые были до HTML5 и которые используют потоковую передачу и долгосрочный опрос для достижения приложения, работающего в реальном времени.
Итак, мы рассмотрели возможные способы реализации данного функционала. У каждого способа есть как свои плюсы, так и свои минусы. Однако, сейчас лучшим и быстро развивающимся способом считается WebSockets. Но помните, что HTML5 поддерживают только современные браузеры и про старые, увы, придется забыть, если вы будете использовать сокеты.
В ближайшее время я буду писать статьи про каждый из этих способов, так что подписывайтесь на обновления сайта, чтобы не пропустить! 🙂
Спасибо за внимание и удачного вам кодинга!
Копирование материалов разрешается только с указанием автора (Михаил Русаков) и индексируемой прямой ссылкой на сайт (http://myrusakov.ru)!
Добавляйтесь ко мне в друзья ВКонтакте: http://vk.com/myrusakov.
Если Вы хотите дать оценку мне и моей работе, то напишите её в моей группе: http://vk.com/rusakovmy.
Если Вы не хотите пропустить новые материалы на сайте,
то Вы можете подписаться на обновления: Подписаться на обновления
Если у Вас остались какие-либо вопросы, либо у Вас есть желание высказаться по поводу этой статьи, то Вы можете оставить свой комментарий внизу страницы.
Порекомендуйте эту статью друзьям:
Если Вам понравился сайт, то разместите ссылку на него (у себя на сайте, на форуме, в контакте):
Она выглядит вот так:
Комментарии ( 4 ):
Вот теперь ясно! Спасибо 🙂
Ну, є одна проблема.. php створений, щоб помирати.. тому, треба обробляти на node.js і т.д, або ж якщо хочеться, щоб менше витрачало ресурсів — с++. І це.. це діло потрібно проксувaти nginx
тоесть непрерывного айфрейма больше не существует по вашему?
Было бы не плохо посмотреть пример
Для добавления комментариев надо войти в систему.
Если Вы ещё не зарегистрированы на сайте, то сначала зарегистрируйтесь.
Polling и Long polling
Мы сейчас живем в мире realtime приложений, где для актуальной доставки информации используются быстрые и удобные socket соединения, которые позволяют отправить новое событие напрямую на устройство клиента.
Но настроить socket-соединение не всегда очень просто. Иногда это не позволяет хостинг, а иногда просто нет ресурсов на поддержку инфраструктуры, тестирование отказоустойчивости и безопасности. В таком случае можно воспользоваться поллингом.
polling — голосование, опрашивание. Само название намекает на то, что будет происходить регулярный опрос чего-то. В нашем случае, сервера.
Избирательный участок, где происходит настоящий polling:)
Голосовать мы, конечно же, не будем. А вот опрашивать сервер будем еще как. Идея достаточно простая — регулярно опрашивать сервер на предмет новых изменений.
Обычный polling
Например мы пишем мессенджер и хотим доставлять пользователям новые сообщения. В случае обычного polling клиентское приложение (телефон/браузер) будет кидать запрос GET /messages каждые N секунд (например каждые 2 секунды).
Диаграмма общения между клиентом и сервером в случае polling
Решение «в лоб» и приводит к необходимому результату — пользователь получает свои сообщения не познее 2х секунд после их появления. Но минусы достаточно очевидны:
- Надо все же ждать 2 секунды до получения некоторых сообщений
- Нагрузка на сервер неоправданно высока. Необходимо каждые 2 секунды создавать соединение с сервером, инициализировать приложение и тд. Все этоприводит к накладным расходам
Тут как раз на сцену выходит long polling
Что такое long polling?
Это все тот же polling, но уже с немного другой идеей. В рамках long polling клиент отправляет запрос на сервер с заранее заданным timeout (обычно 30 сек). Сервер обрабатывает этот запрос в режиме бесконечного цикла с указанным timeout и, в рамках цикла, ожидает необходимые для пользователя обновления. Как только обновление появляется сервер сразу же отдает ответ и закрывает соединение.
Общение клиента с серверов в long polling
Таким образом мы лишаемся двух минусов polling и отдаем пользователю обновления с минимальной задержкой. У long polling, конечно же есть свои минусы и для решения их уже есть websockets.