Давным-давно я публиковал статью на хабре, как написать свой вебсокет-сервер с нуля. Статья переросла в библиотеку. Несколько месяцев я занимался её развитием, ещё несколько лет — поддержкой и багфиксом. Написал модуль интеграции с yii2. Какой-то энтузиаст написал интеграцию с laravel. Моя библиотека совместима с php7. Недавно я решил отказаться от её дальнейшей поддержки (причины ниже), поэтому хочу помочь её пользователям перейти на другую библиотеку.

Прежде чем начать писать свой вебсокет-сервер, я выбирал из готовых продуктов, и на тот момент их было всего два: phpdaemon и ratchet.
1400 звёзд на гитхабе
3600 звёзд на гитхабе
Эти библиотеки были очень монструозны и при этом не соответствовали моим внутренним требованиям:
Таймеры мне нужны были для написания игры на вебсокетах для расчёта взаимодействий между всеми пользователями каждые 0.05 секунды.
В итоге я написал библиотеку для себя и поделился ею с сообществом на гитхабе. Сделал несколько демок (в том числе игру «танчики»). Переписал стороннюю игру (с разрешения авторов) с node.js на свою библиотеку. Делал нагрузочное тестирование. Демки работали годами без перезагрузки. Старался отвечать на тикеты в течения дня. Всё это показывало, что моя библиотека может быть использована на продакшене и многие её использовали.
Была единственная проблема. Мне хватало моей библиотеки для использования в своих проектах, а вот другим нет. Они хотели, чтобы я её развивал, а мне это было не нужно. Кому-то требовалась поддержка windows, а кому-то ssl, pg_notify, safari, pthreads и многое другое. Открытые тикеты с запросами на реализацию различного функционала висят годами.
Не так давно, я решил пересмотреть ещё раз, какие продукты могут быть полезны для пользователей моей библиотеки и был приятно удивлён, что кроме двух проектов, описанных выше появился ещё третий. Он полностью удовлетворял моим запросам и даже больше.
4500 звёзд на гитхабе
Первый его релиз был ещё два года назад, но почему-то всё новые и новые люди начинали пользоваться моей библиотекой для новых проектов. Я ещё могу понять, что ею пользуются на старых проектах (работает — не трогай), но на новых… — для меня это была загадка.
Если загуглить «php websocket», то первая страница — это моя статья на Хабре, а вторая — «Ratchet», который кому-то может показаться сложным и он выберет из-за этого мою библиотеку или вообще откажется от идеи делать вебсокеты.
Что ж, пришло время исправить эту досадную ошибку и донести до как можно большего количества людей о существовании такой библиотеки как Workerman и привести несколько примеров по её использованию.
На главной странице проекта в гитхабе уже есть несколько примеров. Рассмотрим один из них:
Чтобы запустить пример, нужно установить workerwan:
Пример можно запустить с помощью команды
В принципе, используя первый пример можно сделать чат на вебсокетах и других примеров не нужно. Но за несколько лет я понял, что в основном пользователям моей библиотеки был нужен пример того как можно отправить из своего кода на php уведомление выбранному пользователю, а не всем одновременно, как часто бывает в примерах.
Например:
Из двух примеров выше можно собрать один, который будет делать то что нам нужно:
Справедливости ради я решил написать такой же пример для ratchet, но документация мне не помогла, как 3 года назад. Зато на stackoverflow предложили немного костыльный, но рабочий вариант: соединяться из своего php-скрипта по ws-соединению. Конечно это не так же просто как соединиться с tcp-сокетом с помощью stream_socket_client и отправить сообщение с помощью fwrite. Но уже что-то.
Плюс ещё остался для меня незакрытый вопрос: поддерживает ли ratchet возможность запуска нескольких воркеров и если да, то как в таком случае отправлять сообщение одному пользователю, ведь не понятно на каком он воркере. На workerman это можно сделать так.
В общем, я выбрал для себя библиотеку Workerman и рекомендую переходить на неё пользователям моей библиотеки. Все примеры лежат на гитхабе.
Update: в комментариях рекомендуют swoole. Я натыкался на эту библиотеку ранее, но у меня сложилось ложное впечатление, что что она не поддерживает php7 и после этого она выпала из моего круга зрения. А зря. Интересная библиотека.

Прежде чем начать писать свой вебсокет-сервер, я выбирал из готовых продуктов, и на тот момент их было всего два: phpdaemon и ratchet.
phpdaemon
1400 звёзд на гитхабе
- зависит от установки библиотеки libevent
- протоколы: HTTP, FastCGI, FlashPolicy, Ident, Socks4/5.
Ratchet
3600 звёзд на гитхабе
- тянет за собой около десятка зависимостей
- протоколы: websocket, http, wamp
- поддержка windows
- нет ssl
Эти библиотеки были очень монструозны и при этом не соответствовали моим внутренним требованиям:
- отсутствие зависимостей
- наличие таймеров
Таймеры мне нужны были для написания игры на вебсокетах для расчёта взаимодействий между всеми пользователями каждые 0.05 секунды.
В итоге я написал библиотеку для себя и поделился ею с сообществом на гитхабе. Сделал несколько демок (в том числе игру «танчики»). Переписал стороннюю игру (с разрешения авторов) с node.js на свою библиотеку. Делал нагрузочное тестирование. Демки работали годами без перезагрузки. Старался отвечать на тикеты в течения дня. Всё это показывало, что моя библиотека может быть использована на продакшене и многие её использовали.
Была единственная проблема. Мне хватало моей библиотеки для использования в своих проектах, а вот другим нет. Они хотели, чтобы я её развивал, а мне это было не нужно. Кому-то требовалась поддержка windows, а кому-то ssl, pg_notify, safari, pthreads и многое другое. Открытые тикеты с запросами на реализацию различного функционала висят годами.
Не так давно, я решил пересмотреть ещё раз, какие продукты могут быть полезны для пользователей моей библиотеки и был приятно удивлён, что кроме двух проектов, описанных выше появился ещё третий. Он полностью удовлетворял моим запросам и даже больше.
Workerman
4500 звёзд на гитхабе
- отсутствие зависимостей
- протоколы: websocket, http/https, tcp, сustom
- поддержка таймеров
- интеграция с react-компонентами
- поддержка windows
Первый его релиз был ещё два года назад, но почему-то всё новые и новые люди начинали пользоваться моей библиотекой для новых проектов. Я ещё могу понять, что ею пользуются на старых проектах (работает — не трогай), но на новых… — для меня это была загадка.
Если загуглить «php websocket», то первая страница — это моя статья на Хабре, а вторая — «Ratchet», который кому-то может показаться сложным и он выберет из-за этого мою библиотеку или вообще откажется от идеи делать вебсокеты.
Что ж, пришло время исправить эту досадную ошибку и донести до как можно большего количества людей о существовании такой библиотеки как Workerman и привести несколько примеров по её использованию.
На главной странице проекта в гитхабе уже есть несколько примеров. Рассмотрим один из них:
websocket server
<?php require_once __DIR__ . '/vendor/autoload.php'; use Workerman\Worker; // Create a Websocket server $ws_worker = new Worker("websocket://0.0.0.0:8000"); // 4 processes $ws_worker->count = 4; // Emitted when new connection come $ws_worker->onConnect = function($connection) { echo "New connection\n"; }; // Emitted when data received $ws_worker->onMessage = function($connection, $data) { // Send hello $data $connection->send('hello ' . $data); }; // Emitted when connection closed $ws_worker->onClose = function($connection) { echo "Connection closed\n"; }; // Run worker Worker::runAll();
tcp server
<?php require_once __DIR__ . '/vendor/autoload.php'; use Workerman\Worker; // #### create socket and listen 1234 port #### $tcp_worker = new Worker("tcp://0.0.0.0:1234"); // 4 processes $tcp_worker->count = 4; // Emitted when new connection come $tcp_worker->onConnect = function($connection) { echo "New Connection\n"; }; // Emitted when data received $tcp_worker->onMessage = function($connection, $data) { // send data to client $connection->send("hello $data \n"); }; // Emitted when new connection come $tcp_worker->onClose = function($connection) { echo "Connection closed\n"; }; Worker::runAll();
Чтобы запустить пример, нужно установить workerwan:
composer require workerman/workermanПример можно запустить с помощью команды
php test.php start и в консоли мы увидим:----------------------- WORKERMAN -----------------------------
Workerman version:3.3.6 PHP version:7.0.15-0ubuntu0.16.10.4
------------------------ WORKERS -------------------------------
user worker listen processes status
morozovsk none websocket://0.0.0.0:8000 1 [OK]
----------------------------------------------------------------Все команды workerman:
php test.php start
php test.php start -d -демонизировать скрипт
php test.php status
php test.php stop
php test.php restart
php test.php restart -d
php test.php reload
В принципе, используя первый пример можно сделать чат на вебсокетах и других примеров не нужно. Но за несколько лет я понял, что в основном пользователям моей библиотеки был нужен пример того как можно отправить из своего кода на php уведомление выбранному пользователю, а не всем одновременно, как часто бывает в примерах.
Например:
- пользователь #1 лайкает фотографию пользователя #2 и мы хотим отправить пользователю #2 об этом уведомление, если он сейчас на сайте.
- на сайте появилось новое объявление и мы хотим отправить уведомление нашему модератору,
чтобы он его проверил
Из двух примеров выше можно собрать один, который будет делать то что нам нужно:
Отправка сообщения одному пользователю:
код сервера server.php:
код клиента client.html:
код отправки сообщений с нашего сайта send.php:
<?php require_once __DIR__ . '/vendor/autoload.php'; use Workerman\Worker; // массив для связи соединения пользователя и необходимого нам параметра $users = []; // создаём ws-сервер, к которому будут подключаться все наши пользователи $ws_worker = new Worker("websocket://0.0.0.0:8000"); // создаём обработчик, который будет выполняться при запуске ws-сервера $ws_worker->onWorkerStart = function() use (&$users) { // создаём локальный tcp-сервер, чтобы отправлять на него сообщения из кода нашего сайта $inner_tcp_worker = new Worker("tcp://127.0.0.1:1234"); // создаём обработчик сообщений, который будет срабатывать, // когда на локальный tcp-сокет приходит сообщение $inner_tcp_worker->onMessage = function($connection, $data) use (&$users) { $data = json_decode($data); // отправляем сообщение пользователю по userId if (isset($users[$data->user])) { $webconnection = $users[$data->user]; $webconnection->send($data->message); } }; $inner_tcp_worker->listen(); }; $ws_worker->onConnect = function($connection) use (&$users) { $connection->onWebSocketConnect = function($connection) use (&$users) { // при подключении нового пользователя сохраняем get-параметр, который же сами и передали со страницы сайта $users[$_GET['user']] = $connection; // вместо get-параметра можно также использовать параметр из cookie, например $_COOKIE['PHPSESSID'] }; }; $ws_worker->onClose = function($connection) use(&$users) { // удаляем параметр при отключении пользователя $user = array_search($connection, $users); unset($users[$user]); }; // Run worker Worker::runAll();
код клиента client.html:
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <script> ws = new WebSocket("ws://127.0.0.1:8000/?user=tester01"); ws.onmessage = function(evt) {alert(evt.data);}; </script> </head> </html>
код отправки сообщений с нашего сайта send.php:
<?php $localsocket = 'tcp://127.0.0.1:1234'; $user = 'tester01'; $message = 'test'; // соединяемся с локальным tcp-сервером $instance = stream_socket_client($localsocket); // отправляем сообщение fwrite($instance, json_encode(['user' => $user, 'message' => $message]) . "\n");
Справедливости ради я решил написать такой же пример для ratchet, но документация мне не помогла, как 3 года назад. Зато на stackoverflow предложили немного костыльный, но рабочий вариант: соединяться из своего php-скрипта по ws-соединению. Конечно это не так же просто как соединиться с tcp-сокетом с помощью stream_socket_client и отправить сообщение с помощью fwrite. Но уже что-то.
Плюс ещё остался для меня незакрытый вопрос: поддерживает ли ratchet возможность запуска нескольких воркеров и если да, то как в таком случае отправлять сообщение одному пользователю, ведь не понятно на каком он воркере. На workerman это можно сделать так.
В общем, я выбрал для себя библиотеку Workerman и рекомендую переходить на неё пользователям моей библиотеки. Все примеры лежат на гитхабе.
Update: в комментариях рекомендуют swoole. Я натыкался на эту библиотеку ранее, но у меня сложилось ложное впечатление, что что она не поддерживает php7 и после этого она выпала из моего круга зрения. А зря. Интересная библиотека.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Что вы используете для вебсокетов на сервере?
2.39%моя библиотека20
1.19%phpdaemon10
11.71%ratchet98
13.62%workerman114
4.42%самописная библиотека на php37
16.85%библиотека на node.js (socket.io и т.д.)141
5.14%библиотека на другом языке программирования (go и т.д.)43
4.42%другое37
40.26%программирую на php, но не использую вебсокеты337
Проголосовали 837 пользователей. Воздержались 372 пользователя.
