На днях опубликовал свой класс для развёртывания веб-сокет сервера.
Веб-сокеты — это технология, позволяющая устанавливать непрерывное соединение между клиентом и сервером. Особенность такой системы также в том, что сервер может по своей инициативе отправлять данные одному или нескольким клиентам. Это позволяет создавать real-time мессенджеры, онлайн-игры и прочие проекты.
PHP, казалось бы, не подходит для такой цели. Он обычно используется для создания динамических веб-страниц и работает по принципу «открыл-ответил-закрыл». Что же, стереотипы пора ломать.
Данный класс избавляет вас от необходимости скачивания сторонних демонов или написания сервера самостоятельно. Всё, что вам потребуется — подключить класс и написать функции приёма сообщений, при желании ещё и сердцебиения.
Чтобы использовать класс, просто включите его в ваш скрипт
Сразу должен предупредить, что запускаться сервер будет не через браузер, как вы привыкли, а через консоль. В репозиторий на GitHub я включил примеры BAT и SH файлов для исполнения.
Поскольку сервер работает неограниченное время, крайне желательно перед запуском сервера отключить буферизацию вывода и убрать лимит по времени.
Чтобы уберечь сервер от падений, напишите данный код:
Для начала нужно создать экземпляр данного класса:
Как видите, во время создания надо указать протокол, IP и порт будущего сервера. Обязательно убедитесь, что данный порт открыт и IP правильный.
При желании можете записывать логи в файл:
Дальше надо создать обработчик сообщений клиента. Сразу обязан предупредить, что javascript-программисты сейчас возрадуются, а к остальным просто просьба не брать близко к сердцу.
Наследуя добрые традиции JS, вы должны записать в переменную handler анонимную функцию. Она будет вызываться каждый раз, когда клиент отправит сообщение серверу. Если вы собираетесь прямо оттуда отправлять ответ, не забывайте кодировать данные при помощи статической функции WebSocket::encode.
Немножко отклонений. Соединение — это ресурс. Его можно читать и дополнять так же, как и обычный открытый файл — при помощи fwrite и fread.
Теперь о функции «сердцебиения». Данная функция будет вызываться во время каждой итерации (чаще, чем раз в секунду).
Во время вызова функции в первом аргументе будет содержаться массив со всеми установленными соединениями. «Сердцебиение» может использоваться для регулярных проверок и других подобных целей.
Последнее, что нам пригодится — собственно запустить сервер. Делается это при помощи метода runServer:
После этого будет запущен вечный цикл. Ваш веб-сокет сервер готов.
Буду очень благодарен любым отзывам или предложениям. Скрипт будет дополняться по мере возможности.
Веб-сокеты — это технология, позволяющая устанавливать непрерывное соединение между клиентом и сервером. Особенность такой системы также в том, что сервер может по своей инициативе отправлять данные одному или нескольким клиентам. Это позволяет создавать real-time мессенджеры, онлайн-игры и прочие проекты.
PHP, казалось бы, не подходит для такой цели. Он обычно используется для создания динамических веб-страниц и работает по принципу «открыл-ответил-закрыл». Что же, стереотипы пора ломать.
Данный класс избавляет вас от необходимости скачивания сторонних демонов или написания сервера самостоятельно. Всё, что вам потребуется — подключить класс и написать функции приёма сообщений, при желании ещё и сердцебиения.
Чтобы использовать класс, просто включите его в ваш скрипт
require_once('../src/websocket.class.php');
Сразу должен предупредить, что запускаться сервер будет не через браузер, как вы привыкли, а через консоль. В репозиторий на GitHub я включил примеры BAT и SH файлов для исполнения.
Поскольку сервер работает неограниченное время, крайне желательно перед запуском сервера отключить буферизацию вывода и убрать лимит по времени.
Чтобы уберечь сервер от падений, напишите данный код:
error_reporting(E_ERROR);
set_time_limit(0);
ob_implicit_flush();
Для начала нужно создать экземпляр данного класса:
$socket = new WebSocket('tcp://', '127.0.0.1', '7777');
Как видите, во время создания надо указать протокол, IP и порт будущего сервера. Обязательно убедитесь, что данный порт открыт и IP правильный.
При желании можете записывать логи в файл:
$socket->setOutput('ws-log.txt');
Дальше надо создать обработчик сообщений клиента. Сразу обязан предупредить, что javascript-программисты сейчас возрадуются, а к остальным просто просьба не брать близко к сердцу.
$socket->handler = function($connection, $data)
{
fwrite($connection, WebSocket::encode($data));
};
Наследуя добрые традиции JS, вы должны записать в переменную handler анонимную функцию. Она будет вызываться каждый раз, когда клиент отправит сообщение серверу. Если вы собираетесь прямо оттуда отправлять ответ, не забывайте кодировать данные при помощи статической функции WebSocket::encode.
Немножко отклонений. Соединение — это ресурс. Его можно читать и дополнять так же, как и обычный открытый файл — при помощи fwrite и fread.
Теперь о функции «сердцебиения». Данная функция будет вызываться во время каждой итерации (чаще, чем раз в секунду).
$socket->hearthbeat = function($connects)
{
}
Во время вызова функции в первом аргументе будет содержаться массив со всеми установленными соединениями. «Сердцебиение» может использоваться для регулярных проверок и других подобных целей.
Последнее, что нам пригодится — собственно запустить сервер. Делается это при помощи метода runServer:
$socket->runServer();
После этого будет запущен вечный цикл. Ваш веб-сокет сервер готов.
Буду очень благодарен любым отзывам или предложениям. Скрипт будет дополняться по мере возможности.
andrewnester
Чем Вас Ratchet не устроил?
mikechips
Да я и не пробовал его. Просто написал своё.
andrewnester
Хорошее решение
mikechips
Если честно, во время написания своего я не слышал о Ратчет… у меня тут минимализм, а там мощная продуманная платформа. Кому что надо…
andrewnester
Предположим с клиента делается запрос на получение каких-то данных, серверу предположим надо на это 500мс. Таких запросов одновременно от 20,50,100 клиентов. Что будет с Вашим сервером?
mikechips
Я тестил лишь при 15-ти реальных пользователях, всё работало бесперебойно. Тесты потом ещё сделаю
igordata
Интересно, что с ним будет при over 9000 подключениях =)
andrewnester
итак, код сервера
Результаты тестов
grayfolk
Если уж на то пошло, то
mikechips
Да, вы правы, пожалуй. Правда есть извращенцы, которые запускают сокет-сервер через браузер… он работать-то будет, но тайм-лимит там будет стоять…
haskel
Продолжайте экспериментировать, все равно потом прийдете к уже обкатанным решениям, но обкатанные решения не дадут опыт и глубины понимания темы.
AlexLeonov
PSR, неймспейсы и composer придумали трусы, для рисковых парней есть require_once?
Каждый раз, когда вижу такое, смотрю на календарь. Нет, я не ошибся. У меня 2015 год. А у вас?
zviryatko
Мм… Это еще что, мне тут недавно объясняли что namespace это прямая замена для require.
Temirkhan
Может Вы не так поняли, и человек имел ввиду autoload, основанный на namespace'ах?
zviryatko
Естественно autoload, только он имел ввиду что лучше использовать require вместо use. А на вопрос когда таких строк становится слишком много отвечал что подключает через require файл со списком других require. А про пользу расширения пространства имен он и слышать не хотел.
mikechips
Проекты вообще разные, в некоторых аутолоадер не используется, и тем более в данном случае существует лишь один класс… аутолоадер принципиально не нужен, если у кого-то проекты с множеством файлов — пусть используют свою автозагрузку, я ведь не могу предугадать все случаи
zelenin
require_once('../src/websocket.class.php');
после этого сразу перешел к комментариям :-)
Temirkhan
Как говорил Печкин, в целях повышения самообразования, что не так с конструкцией require?)
zelenin
неиспользование в проекте composer
Metus
А почему, если человеку не нужны сторонние библиотеки, для использования одного класса в примере нужно обязательно использовать композер?
Тем более что composer -V:
Composer version 1.0-dev (b4bd5774dc7c2a3be18d31d55583492a7c7c2ebe) 2015-09-16 10:01:25
zelenin
1. а) потому что его библиотеку нельзя установить через композер б) потому что никто не будет использовать его библиотеку без других
2. ой-ой, как страшно — станадарт де-факто в индустрии представляется дев-версией :-(
Mendel
это ТЕСТОВЫЙ пример, хоть и оформленный в виде библиотеки.
очень хорошо что не потянешь через композер. Будешь думать лишний раз — использовать ли этот концепт-пруф на продакте.
zelenin
хоть где-то найдите в первых пяти абзацах упоминание про тестовый пример и призыв не использовать это для реальных преоктов. Все выглядит ровно наоборот — вот моя либа, вот пример использования.
Mendel
ну это да, но на это топикстартеру уже указали и не раз, и с более конкретными замечаниями, а не с оформлением…
zelenin
это не оформление — это уровень.
Mendel
Ну меня лет семь назад тоже так носом тыкали в мои велосипеды. Тогда не понимал…
Иногда можно и так писать, даже профессионалу. Но нужно понимать КОГДА это можно.
К примеру в документациях часто ужасы пишут, но это примеры, а не сниппеты.
Вот у меня сейчас простенький «проект» килобайт на 150 кода.
Примерно вот так вот и написан. Никаких автозагрузчиков, нормального наследования, структуры, слоев, MVC? о чем вы?)
И да, я даже в некоторых реальных задачах его использую. Но это даже не прототип. Я просто разбираюсь с форматами, протоколами и т.п. Там реально в живой проект уйдут одни константы и тому подобное.
Я конечно соблюдаю стандарты кодирования которые я сам же себе и принимал, но тут руку сбивать себе дороже.
Но в плане структуры…
zelenin
не передергивайте — тут человек все-таки вывалил на обзор общественности свою библиотеку, предлагая ее к использованию.
Mendel
Согласен, но человеку уже столько раз это сказали, что смысл тыкать7)
SerafimArts
Дабы не перелопачивать исходник, небольшой вопрос — какой из протоколов вебсокетов он поддерживает? RFC6455, Hixie76, HyBi10?
Ramzeska
Увы это велосипед. PHP отличный язык, но не предназначен работать 24/7, не поддерживает многопоточность и рано или позно начнет память течь, а сервер грохнется. Есть куда более живучие решения на node.js или go.
В защиту скажу что это полная ерунда на вебсокет-сервере запускать долгие задачи — вебсокет должен быстро пулять сообщения от отправителя (сервера, демона, пользователей). На этом его задача должна заканчиваться.
Кстати ваше решение как раз будет очень неплохим для кастомных вещей типа веб-консоли или чего-то подобного, где не нужен хайлоад и нужна развертка с минимумом зависимостей.
Rathil
Не согласен. Дописывали phpDaemon до нормального состояния, 3 года назад. Даже род нагрузкой, спустя месяц, и на мегобайт не протек.
Ramzeska
Я пробовал phpdaemon, код там ужасен, высыпал тонну ошибок, когда я наконец-то его починил, то понял что он не поддерживает современные протоколы(на тот момент). Я забил на него и перешел на торнадо.
SerafimArts
Не предназначен был во времена 4ки, когда память действительно подтекала.
Ramzeska
Память в редких случаях течет и сейчас. Тут вопрос о чистоте кода. При написании таких приложений надо в циклах проверять память и если что-то течет — то бегать по коду и искать место где поставить unset(). Сборщик мусора в php совсем не такой как в других языках — он от всего не спасет.
SerafimArts
Имею ввиду что в 4ке без контроля пользователя (разработчика), а своими силами улетучивалась. Да и наличие объектного пула (начиная с php 5.3 вроде, по аналогии с интеджер-пулом джавы), всяких Spl структур, вроде ObjectStorage, FixedArray, Heap и т.д. добавляют намного больше контроля над памятью в «реалтайм» приложениях.
symbix
Вебсокет-сервер на Ratchet запущен уже третий месяц, ничего не течет, что я делаю не так?
Ramzeska
Какой пиковый онлайн?
symbix
Реально пока было чуть больше 200, на нагрузочных тестах тянуло 3k на куда более слабом сервере, чем тот, что в продакшене — а для задачи этого с запасом.
Отсутствию циклических ссылок уделялось особое внимание, каэш.
SerafimArts
Хм, а как так получилось в тестах 3к, если ратчет отсекает больше 255 одновременных подключений по-дефолту? Треды\форки? Или какой-то хитрый способ запиливания нескольких процессов с разными айпишниками, балансировщиком и общением через шаред мемори?
AlexGx
Ваши представления о пхп устарели, для долгих задач есть очереди и микросервисы. Память не течет, но и назвать пхп идеальным решением для сокетов тоже нельзя.
ошибся веткой, комментарий для Ramzeska
Ramzeska
Может я действительно уже устарел и не в курсе? В php нет инструментов ни для многопоточной работы, ни для микропотоков (типа goroutines)
Расскажите как в одном php процессе обрабатывать websocket сообщения и выполнять долгие задачи не повесив всех клиентов?
Mendel
форк к примеру.
Но тут правильно сказали, что вебсокет не разумно загружать тяжелыми задачами, лучше выполнять их отдельно.
Вы в аяксе как делаете? отправил запрос, когда пришел ответ, обработали? ну вот так и тут делайте.
демон принимает запросы извне, и передает им ответы. Если добавить тут еще один интерфейс работающий с бекэендом выполняющим тяжелые задачи, то ничего так уж принципиально не поменяется…
SerafimArts
1) Генераторы\корутины (yield\Generator) http://php.net/manual/ru/language.generators.syntax.php
2) Thread http://php.net/manual/ru/class.thread.php
3) event, libebent, etc
4) pcntl fork
5) exec\system на крайний случай + redis\shared memory\etc
Ramzeska
1. Генераторы мягко говоря не особо то
2. pthread — интересная библиотека, давно появилась? Норм работает? Память не жрет?
3. libevent — очень узкая специализация, да и работать с ней не очень то просто и удобно… зато тянет очень много коннектов
4. ну форк понятно, жрет много памяти, 10к клиентов так не подключить
5. exec/system кстати я бы не называл крайним случаем — наилучший вариант поднять rabbitmq или redis pub/sub и на него цеплять кучу микросервисов.
Мой вебсокет-сервер именно так и работает — все сообщения летают через редисовый канал, две-три штуки втыкаются в редис и распределяют клиентов между собой. Аналогично к редису цепляются мелкие демоны, выполняющие разные задания. Пхп из фронтенда кидает сообщения в редис, а вебсокет-серверы сами разберутся кому что доставить.
SerafimArts
Ну «на крайний» я погорячился в случае п.5, действительно. Сами года 2 назад организовывали вебсокеты таким же образом. Только в том случае были несколько воркеров Event Machine'ы (специфика окружения), общение через редиску как раз, у неё эвенты есть и перманентная работа в памяти, так что работает (работало) «на ура».
pewpew
Автор — молодец.
Хвалю за велосипедостроение. Я считаю, любой велосипед — это тернистый, но СВОЙ путь к пониманию темы.
На продакшин такое я бы ставить рискнул, но разобраться с веб сокетами на привычном языке (если до этого не было опыта) вполне позволяет.
AmdY
Не хочется разочаровывать вашу веру в людей, но я слазил посмотреть исходники и сразу напрягла помесь английских комментатриев с русскими. Погулив нашёл ряд похожих материалов от разных авторов
http://bb3x.ru/blog/delaem-vebsoketyi-na-php-s-nulya/
https://gist.github.com/Xeoncross/8b5fc1deb46f13f23ffa
https://github.com/brebvix/php-websocket/blob/master/socket.php
…
pewpew
Нда… Это почти копипаста.
Причём первый код опубликован от некоей Анны, второй — копипаста первого на гитхабе год назад. Написаны функционально.
Третей — обремлён в класс за авторством какого-то Назара полгода назад и почти полностью совпадает с кодом топикстартера.
Лицензии не указаны, и автор счёл, что код можно использовать как угодно, опубликовав свой вариант под лицензией MIT.
Куда катится мир…
mikechips
Если честно, я просто использовал один урок, на его основе создав шаблон. Решения выше впервые вижу
Rhaps107
На половине методов нет модификаторов доступа, опечатка в слове heartbeat, реализация только одного протокола вебсокетов, без возможности расширения, использование цикла вместо event polling (увеличивает нагрузку), из композера поставить нельзя, я правильно понимаю?
Все бы ничего, но для публикации не очень, т.к. уже были статьи про вебсокеты с хорошими решениями, на которых в т.ч. я разбирал эту тематику. А для собственного ознакомления с темой — норм.
Groove
Как отправить запрос с клиента и уведомить всех других подписчиков канала — это ясно.
Как поймать и обработать на сервере то, что было отослано клиентом — тоже понятно.
Это все делает BrainSocket.
А вот как сделать так, чтобы с сервера отправить сообщение в канал
Пример: кроном раз в минуту забирается результаты матчей с удаленного сервера. Записываются в базу.
Те, кто откроет страницу — им результаты покажутся из сгенерированной страницы.
Как уведомить тех, кто не обновлял страницу?
На реалплексоре Котерова такую задачу решали на раз-два, но таскать его и устанавливать за собой проблематичнее, чем установка того же BrainSocket из композера и запуск сервера.
Groove
toster.ru/q/248997 перенес в вопросы ответы коммент
andrewnester
Как насчет просто отправить данные клиенту по открытому сокет-каналу? Есть еще такая вещь broadcast сообщения