Запостил на github https://github.com/anlide/websocket
Ещё надо будет с двумя вопросами разобраться — это socket_select под нагрузкой и причина закрытия сокета. Ещё расставить кучу комментариев по коду и вопрос можно будет закрывать.
В вашем коде я вижу 3 реализации WebSocket — socket_select, event, libevent. Чем отличаются event и libevent? Какой лучше выбрать? Почему?
На первый взгляд libevent больше нравится — потому что PhpStorm о нём знает, а про event — нет. :)
В первом примере данные хранятся в самих потоках, там же запись в них. Далее чтение этих данных из основного потока, я так понимаю без физического копирования данных.
Во втором примере данные хранятся в экземплярах класса Connect и конкретная задача их успешно читает, но это чтение данных внутри одного потока. А запись осуществляется дёрганьем метода, а не напрямую.
Но я собственно на такое чтение данных и не жаловался. Как мне в дочернем потоке почитать данные из родительского потока не дублируя при этом данные в физической памяти? Как мне из дочернего потока изменить данные в родительском потоке? Без этого для моей задачи использовать pthreads бессмысленно. И если погуглить «c++ многопоточность память» то видно, что в C++ таких проблем нет.
К чему этот разговор вообще веду. Вот у меня есть реализованый механизм распределения задач в несколько процессов, которые общаются через сокеты. Общаются посылая сигналы друг-другу. Это собственно первая проблема — что общаться они должны только сигналами. Это очень неудобно и громоздко. Большие куски данных хранятся там где собственно используются (например данные по конкретному игроку, а они довольно объёмные), если другому процессу нужны данные из этого процесса — то он их тянет из БД или процесса, поработает с ними и удаляет из своей памяти. Это вторая проблема — хотелось жеж бы просто по id пользователя обратится куда-то в памяти, получить нужные данные и всё, не дублируя объёмные данные, не реализуя сигналы. Кроме того, есть большой массив констант типа тексты заданий, куча настроек игрового процесса и т.д. — это тоже желательно не дублировать, один экземпляр на процесс.
Так вот я надеялся, что pthread решат эти две проблемы. Но похоже он не решает ни того ни другого и добавляют ещё одну. Я надеялся, что у меня будет одна большая переменная с текстами-настройками игры, будет один большой массив с игроками. И потоки будут просто получать задачи и указатели на память и работать над ними. Не дублируя данные. Код был бы маленький и очень простой. Память использовалась бы максимально эффективно. Процессоры при необходимости загружались бы на максимум. Новые фичи писать в такой системе было бы легко.
Но что же это получается с pthreads — поток должен к себе полностью скопировать всё, обработать, изменить и отправить обратно? Опять сигналы вместо прямого обращения к данным? Далее по конкретному игроку — пока он онлайн, для него формируется большой кэш данных, который ещё и меняется постоянно, его тоже надо весь скопировать в поток, а потом записать обратно? И бонусная проблема — тексты-настройки игры тоже надо копировать целиком в поток?
Уже разобрались. Пол года назад, последняя версия safary под windows была пятёрка. Под ней нужен старый протокол. Кстати Ratchet его поддерживает.
Но кстати я изучил код Ratchet и он не поддерживает не шифрованные (masked) данные. А в протоколе указано как поддерживать это (и PhpDeamon и код приведенный тут умеет это). Может ещё чего всплывёт по ходу ковыряния его исходника.
Ну вобщем я статью обновлю — как все вылезшие подробности прояснятся.
<?php
class Connect extends Worker {
public function getStorage() {
if (!self::$storage) {
self::$storage = array();
for ($i = 0; $i < 1000000; $i++) self::$storage[$i] = rand(0, 256 * 256 - 1);
}
var_dump(memory_get_usage());
return self::$storage;
}
protected static $storage;
}
class Query extends Threaded {
public function run() {
$this->data = $this->worker->getStorage();
sleep(5);
var_dump(memory_get_usage());
var_dump(count($this->data));
}
protected $data;
}
$pool = new Pool(4, "Connect", []);
$pool->submit
(new Query());
$pool->submit
(new Query());
$pool->submit
(new Query());
$pool->submit
(new Query());
$pool->submit
(new Query());
$pool->submit
(new Query());
$pool->shutdown();
Я взял этот пример и чуть подправил его. И попробовал в разном месте по разному расставить memory_get_usage.
И выяснилось, что если мы записываем ответ getStorage в локальную переменную, например $data — то расхода памяти нет. Как будто ссылка передалась.
Если записать в $this->data — то данные полностью копируются.
Попытка использовать "&" приводит к крашу CLI.
Возвращаясь к $data я попробовал перезаписать данные пустым массивом — не получается. То есть локальная переменная становится пустым массивом, а в Connect::$storage продолжает лежать большой кусок данных.
Вывод какой — если используется Pool, то конкретные задачи могут получить данные только для чтения из Worker без потери памяти. Запись в Worker невозможно.
Вот рассмотрел повнимательнее. Попробовал. Воодушевился, собрался переписывать всё с использованием потоков. Но…
В документации и примерах нет информации как ворочать большие объёмы данных между потоками. Или я не нашёл?
$var = true;
class Test extends Thread {
public function run() {
global $var;
$var = false;
}
}
$test = new Test();
$test->start();
$test->join();
var_dump($var);
Приведенный код не работает — то есть выводит «true», хотя я ожидаю «false».
Мне очень хотелось бы, чтобы переменная $var не дублировалась, не синхронизировалась, а именно была бы в родительском потоке. Ну вобщем любым способом избежать дублирования данных в физической памяти. Это возможно в pthreads или нет?
Большое спасибо за замечание. Долго думал, что с этим делать. На собственном хостинге увеличивать количество файловых дейскрипторов для любого приложения можно без проблем. Поэтому видимо надо просто следить за этой проблемой и должный report об этой ошибке сделать. И под нагрузкой socket_select использовать можно будет с таким подходом. Да?
Игра была запущена по схеме «тихий старт». То есть нигде не постится новость о новой игре, а в недрах раздела «все игры» — собственно и появляется игра. На данный момент посмотрело игру ~1750 человек. Google analytic пишет, что safary с версией 5 было аж 1 пользователь за всё время (ниже пятой версии пользователей не было). Очевидно это был мой браузер. И проблемы со старым протоколом на самом деле неактуальны.
22 янв 2016 я подал очередную заявку на публикацию игры в vk. По логам вижу: зашёл админ и сразу закрыл игру. Вот его useragent «Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36». У меня windows. Скачал последнюю версию safari, что нашёл в инете на тот момент: 5.1.7 версии.
Как теперь видно — админ vk не поймал ошибку соединения, но ему что-то другое не правилось, а я поймал такую ошибку. В итоге сделал неправильный вывод (что websocket под safari не сработал) и неправильные дальнейшие мои действия.
Должен признать, что это мой огромный провал.
В статье вы используете stream_select, а в коде на github используете EventBuffer. Но впрочем я уже созрел написать на тему других проблем, с которыми я столкнулся (правильное использование cocos js и вынесение задач в отдельный процесс). Но… а что за ограничения socket_select/stream_select на количество соединений?
В этой ветке вы об этом прямо не говорите, но похоже вы поймали ту же проблему из-за которой была написана эта статья.
Я постоянно ссылаюсь на https://habrahabr.ru/post/209864/ что он хорош и прост, но safary с ним не работает. То есть IOS не работает. А чтобы работало под IOS — надо посложнее код написать. Собственно об этом «посложнее» и идёт вся эта статья.
Да «event loop» наверное можно назвать. Разумеется это всё надо, чтобы обработка запросов не стопорила общение клиентов с сервером. Темболее в данном проекте некоторые рядовые запросы могут обрабатываться несколько секунд.
reactphp websocket — он же http://socketo.me/. Уже советовали посмотреть, посмотрю чем он лучше. Кроме того, что они поймали и пофиксили кучу багов — есть инфа чем он лучше, чем самодельный event loop?
pthreads к сожалению не рассматривал. Отстал от жизни, изучу это. Бегло осмотрел — похоже это то, что мне было надо, а я по сути написал и оттестировал самодельный pthreads на чистом php.
«напоминаю, что это делается только созданием новых потоков» очень извиняюсь, опечатался.
«напоминаю, что это делается только созданием новых процессов» я хотел сказать.
В приведенном коде да, stream_select работает только с WebSocket. Можно наверное больше кода игрового сервера привести «аналог pthreads» — оно кому-то интересно?
Игровой сервер используется как сервис linux. То есть "/etc/init.d/game restart". Соответственно игровой сервер корректно реагирует на события из ОСи (выключение, перезагрузка, перезапуск сервиса). Работает мастер-процесс и несколько дочерних процессов (по количеству процессоров + 1 для очень долгих и низко-приоритетных задач). Приведенный в статье код — это кусок мастера.
Я вроде подчеркнул не раз в тексте, что код максимально упрощён с ООП до процедурного стиля. Соответственно в тупую использовать этот код целиком нельзя. Но в процедурном стиле — проще всего изложить что происходит в коде и читателю проще выдернуть нужные куски и вставить себе в код.
Для стиля «давайте сделаем побыстрее» — вся статья неприемлема, то есть надо использовать PhpDeamon, socketo.me или ещё какое-то готовое решение. А для стиля «давайте сделаем хорошо» — статья то, что надо. То есть читатель пишет свой сервис, у него свой основной цикл stream_select и ему в таком виде намного проще надёргать куски кода себе, чем дёргать куски кода из готового решения.
Если речь идёт про «а шо такое websocket» — то это статья не про это.
Хотя я собираюсь опубликовать на github и запостить тут ссылку туда. И есть небольшая ошибка в методе «read_line» и небольшая опечатка в указателях основного цикла — разумеется надо как-то опубликовать исправленный код.
Я рассматривал только PhpDeamon на этапе проектирования.
https://habrahabr.ru/post/301822/#comment_9627298 тут написал почему отказался от его использования.
socketo.me обязательно просмотрю — может ещё чего-то важное найду.
Пока предварительная информация — но похоже в коде PhpDeamon / WebSocket / V13 есть ошибка, код некорректно реагирует на ping frame. Из-за чего FF@Linux разрывает websocket соединение. Подчёркиваю, что это требует перепроверки.
Обращаю внимание, что поиск и исправление подобной ошибки в сторонней библиотеке существенно сложнее чем в своей.
Потому что в этой задаче необходимо выполнять очень много математики под нагрузкой. Соответственно необходимо было сделать многопоточность в рамках php (напоминаю, что это делается только созданием новых потоков). А это в свою очередь требует использовать stream_select общий для дочерних процессов, и для websocket, и для админки. И этот подход мне хорошо знаком.
С другой стороны PhpDeamon в основе использует EventBuffer — с которым я совсем незнаком и грамотное использование этого дела под нагрузкой — это неизвестно сколько времени и нервов и потерянных пользователей.
Ещё надо будет с двумя вопросами разобраться — это socket_select под нагрузкой и причина закрытия сокета. Ещё расставить кучу комментариев по коду и вопрос можно будет закрывать.
На первый взгляд libevent больше нравится — потому что PhpStorm о нём знает, а про event — нет. :)
Во втором примере данные хранятся в экземплярах класса Connect и конкретная задача их успешно читает, но это чтение данных внутри одного потока. А запись осуществляется дёрганьем метода, а не напрямую.
Но я собственно на такое чтение данных и не жаловался. Как мне в дочернем потоке почитать данные из родительского потока не дублируя при этом данные в физической памяти? Как мне из дочернего потока изменить данные в родительском потоке? Без этого для моей задачи использовать pthreads бессмысленно. И если погуглить «c++ многопоточность память» то видно, что в C++ таких проблем нет.
К чему этот разговор вообще веду. Вот у меня есть реализованый механизм распределения задач в несколько процессов, которые общаются через сокеты. Общаются посылая сигналы друг-другу. Это собственно первая проблема — что общаться они должны только сигналами. Это очень неудобно и громоздко. Большие куски данных хранятся там где собственно используются (например данные по конкретному игроку, а они довольно объёмные), если другому процессу нужны данные из этого процесса — то он их тянет из БД или процесса, поработает с ними и удаляет из своей памяти. Это вторая проблема — хотелось жеж бы просто по id пользователя обратится куда-то в памяти, получить нужные данные и всё, не дублируя объёмные данные, не реализуя сигналы. Кроме того, есть большой массив констант типа тексты заданий, куча настроек игрового процесса и т.д. — это тоже желательно не дублировать, один экземпляр на процесс.
Так вот я надеялся, что pthread решат эти две проблемы. Но похоже он не решает ни того ни другого и добавляют ещё одну. Я надеялся, что у меня будет одна большая переменная с текстами-настройками игры, будет один большой массив с игроками. И потоки будут просто получать задачи и указатели на память и работать над ними. Не дублируя данные. Код был бы маленький и очень простой. Память использовалась бы максимально эффективно. Процессоры при необходимости загружались бы на максимум. Новые фичи писать в такой системе было бы легко.
Но что же это получается с pthreads — поток должен к себе полностью скопировать всё, обработать, изменить и отправить обратно? Опять сигналы вместо прямого обращения к данным? Далее по конкретному игроку — пока он онлайн, для него формируется большой кэш данных, который ещё и меняется постоянно, его тоже надо весь скопировать в поток, а потом записать обратно? И бонусная проблема — тексты-настройки игры тоже надо копировать целиком в поток?
Я надеюсь что ошибаюсь. Или нет?
Но кстати я изучил код Ratchet и он не поддерживает не шифрованные (masked) данные. А в протоколе указано как поддерживать это (и PhpDeamon и код приведенный тут умеет это). Может ещё чего всплывёт по ходу ковыряния его исходника.
Ну вобщем я статью обновлю — как все вылезшие подробности прояснятся.
Я взял этот пример и чуть подправил его. И попробовал в разном месте по разному расставить memory_get_usage.
И выяснилось, что если мы записываем ответ getStorage в локальную переменную, например $data — то расхода памяти нет. Как будто ссылка передалась.
Если записать в $this->data — то данные полностью копируются.
Попытка использовать "&" приводит к крашу CLI.
Возвращаясь к $data я попробовал перезаписать данные пустым массивом — не получается. То есть локальная переменная становится пустым массивом, а в Connect::$storage продолжает лежать большой кусок данных.
Вывод какой — если используется Pool, то конкретные задачи могут получить данные только для чтения из Worker без потери памяти. Запись в Worker невозможно.
Вывод правильный?
Вот рассмотрел повнимательнее. Попробовал. Воодушевился, собрался переписывать всё с использованием потоков. Но…
В документации и примерах нет информации как ворочать большие объёмы данных между потоками. Или я не нашёл?
Приведенный код не работает — то есть выводит «true», хотя я ожидаю «false».
Мне очень хотелось бы, чтобы переменная $var не дублировалась, не синхронизировалась, а именно была бы в родительском потоке. Ну вобщем любым способом избежать дублирования данных в физической памяти. Это возможно в pthreads или нет?
Игра была запущена по схеме «тихий старт». То есть нигде не постится новость о новой игре, а в недрах раздела «все игры» — собственно и появляется игра. На данный момент посмотрело игру ~1750 человек. Google analytic пишет, что safary с версией 5 было аж 1 пользователь за всё время (ниже пятой версии пользователей не было). Очевидно это был мой браузер. И проблемы со старым протоколом на самом деле неактуальны.
22 янв 2016 я подал очередную заявку на публикацию игры в vk. По логам вижу: зашёл админ и сразу закрыл игру. Вот его useragent «Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36». У меня windows. Скачал последнюю версию safari, что нашёл в инете на тот момент: 5.1.7 версии.
Как теперь видно — админ vk не поймал ошибку соединения, но ему что-то другое не правилось, а я поймал такую ошибку. В итоге сделал неправильный вывод (что websocket под safari не сработал) и неправильные дальнейшие мои действия.
Должен признать, что это мой огромный провал.
В статье вы используете stream_select, а в коде на github используете EventBuffer. Но впрочем я уже созрел написать на тему других проблем, с которыми я столкнулся (правильное использование cocos js и вынесение задач в отдельный процесс). Но… а что за ограничения socket_select/stream_select на количество соединений?
Вот камменты в протоколе для Сафари:
Ща внимательно всё просмотрю, может тут реализован последний протокол так, что нет ошибки о которой я писал «firefox@linux ping frame» у PhpDeamon.
Кроме того меня очень радует, что названия протоколов нормальные:
Я постоянно ссылаюсь на https://habrahabr.ru/post/209864/ что он хорош и прост, но safary с ним не работает. То есть IOS не работает. А чтобы работало под IOS — надо посложнее код написать. Собственно об этом «посложнее» и идёт вся эта статья.
reactphp websocket — он же http://socketo.me/. Уже советовали посмотреть, посмотрю чем он лучше. Кроме того, что они поймали и пофиксили кучу багов — есть инфа чем он лучше, чем самодельный event loop?
«напоминаю, что это делается только созданием новых потоков» очень извиняюсь, опечатался.
«напоминаю, что это делается только созданием новых процессов» я хотел сказать.
В приведенном коде да, stream_select работает только с WebSocket. Можно наверное больше кода игрового сервера привести «аналог pthreads» — оно кому-то интересно?
Игровой сервер используется как сервис linux. То есть "/etc/init.d/game restart". Соответственно игровой сервер корректно реагирует на события из ОСи (выключение, перезагрузка, перезапуск сервиса). Работает мастер-процесс и несколько дочерних процессов (по количеству процессоров + 1 для очень долгих и низко-приоритетных задач). Приведенный в статье код — это кусок мастера.
Для стиля «давайте сделаем побыстрее» — вся статья неприемлема, то есть надо использовать PhpDeamon, socketo.me или ещё какое-то готовое решение. А для стиля «давайте сделаем хорошо» — статья то, что надо. То есть читатель пишет свой сервис, у него свой основной цикл stream_select и ему в таком виде намного проще надёргать куски кода себе, чем дёргать куски кода из готового решения.
Если речь идёт про «а шо такое websocket» — то это статья не про это.
Хотя я собираюсь опубликовать на github и запостить тут ссылку туда. И есть небольшая ошибка в методе «read_line» и небольшая опечатка в указателях основного цикла — разумеется надо как-то опубликовать исправленный код.
https://habrahabr.ru/post/301822/#comment_9627298 тут написал почему отказался от его использования.
socketo.me обязательно просмотрю — может ещё чего-то важное найду.
Пока предварительная информация — но похоже в коде PhpDeamon / WebSocket / V13 есть ошибка, код некорректно реагирует на ping frame. Из-за чего FF@Linux разрывает websocket соединение. Подчёркиваю, что это требует перепроверки.
Обращаю внимание, что поиск и исправление подобной ошибки в сторонней библиотеке существенно сложнее чем в своей.
С другой стороны PhpDeamon в основе использует EventBuffer — с которым я совсем незнаком и грамотное использование этого дела под нагрузкой — это неизвестно сколько времени и нервов и потерянных пользователей.