Как стать автором
Обновить

Комментарии 70

А почему отказались от использования PhpDeamon?
Потому что в этой задаче необходимо выполнять очень много математики под нагрузкой. Соответственно необходимо было сделать многопоточность в рамках php (напоминаю, что это делается только созданием новых потоков). А это в свою очередь требует использовать stream_select общий для дочерних процессов, и для websocket, и для админки. И этот подход мне хорошо знаком.
С другой стороны PhpDeamon в основе использует EventBuffer — с которым я совсем незнаком и грамотное использование этого дела под нагрузкой — это неизвестно сколько времени и нервов и потерянных пользователей.
Соответственно необходимо было сделать многопоточность в рамках php

pthreads не рассматривали? Ну мол что бы вынести вычисления в отдельный пул тредов и разгрузить воркер обработки запросов. Внутри потоков завести очереди, и разруливать кто что делает. Можно обойтись без каких-либо локов и т.д. и получить максимальную утилизацию CPU.


напоминаю, что это делается только созданием новых потоков

тут вы подразумеваете именно стримы, потоки ввода/вывода. Я раза 3 перечитал ваш комментарий что бы понять к чему тут потоки, дочерние и stream_select.


Ну то есть в приведенном вами коде у нас один процесс, один поток, и неблокируемые потоки ввода/вывода. Никакой многопоточности. Как и дочерних процессов собственно.

pthreads к сожалению не рассматривал. Отстал от жизни, изучу это. Бегло осмотрел — похоже это то, что мне было надо, а я по сути написал и оттестировал самодельный pthreads на чистом php.

«напоминаю, что это делается только созданием новых потоков» очень извиняюсь, опечатался.
«напоминаю, что это делается только созданием новых процессов» я хотел сказать.

В приведенном коде да, stream_select работает только с WebSocket. Можно наверное больше кода игрового сервера привести «аналог pthreads» — оно кому-то интересно?

Игровой сервер используется как сервис linux. То есть "/etc/init.d/game restart". Соответственно игровой сервер корректно реагирует на события из ОСи (выключение, перезагрузка, перезапуск сервиса). Работает мастер-процесс и несколько дочерних процессов (по количеству процессоров + 1 для очень долгих и низко-приоритетных задач). Приведенный в статье код — это кусок мастера.
а я по сути написал и оттестировал самодельный pthreads на чистом php.

Так, давайте все же проясним. Вот способы работы с вводом/выводом в порядке эффективности (последний — самый эффективный):


  • процессы
  • потоки
  • неблокируемые вызовы + stream_select или если проще — event loop

По сути вы последнее и реализовали, правда есть более эффективные реализации (reactphp + libev/libuv) но это уже мелочи.


То есть для web-sockets как раз таки ваш подход очень хорошо подходит. Просто что бы вычисления не стопорили обработку запросов — можно было это дело мувнуть в отдельный трэд. Тогда и накладных расходов на взаимодейтсвие было бы меньше и работало оно независимо.

Да «event loop» наверное можно назвать. Разумеется это всё надо, чтобы обработка запросов не стопорила общение клиентов с сервером. Темболее в данном проекте некоторые рядовые запросы могут обрабатываться несколько секунд.

reactphp websocket — он же http://socketo.me/. Уже советовали посмотреть, посмотрю чем он лучше. Кроме того, что они поймали и пофиксили кучу багов — есть инфа чем он лучше, чем самодельный event loop?
есть инфа чем он лучше, чем самодельный event loop?

1) проверенный в бою
2) не нужно поддерживать самому
3) есть готовый адаптер для libev/libuv. К сожалению тот же libuv имел бы для вас смысл если бы помимо парсинга http он предоставлял так же обработку websockets. Но для ребят, которые пишут на неумираемом PHP он неплохо облегчает жизнь.

Читаю вот код из «socketo.me» — там ребята тоже очень не любят Сафари :)

Вот камменты в протоколе для Сафари:
FOR THE LOVE OF BEER, PLEASE PLEASE PLEASE DON'T allow the use of this in your application!

The Hixie76 is currently implemented by Safari

Ща внимательно всё просмотрю, может тут реализован последний протокол так, что нет ошибки о которой я писал «firefox@linux ping frame» у PhpDeamon.

Кроме того меня очень радует, что названия протоколов нормальные:
  • Hixie76: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76
  • RFC6455: http://tools.ietf.org/html/rfc6455
  • HyBi10 extends RFC6455
pthreads не рассматривали?

Вот рассмотрел повнимательнее. Попробовал. Воодушевился, собрался переписывать всё с использованием потоков. Но…
В документации и примерах нет информации как ворочать большие объёмы данных между потоками. Или я не нашёл?
$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 или нет?
<?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 невозможно.

Вывод правильный?
Немного не понял.
Запоминаем в треды, потом используем в основном потоке:
<?php
class TestWork extends \Threaded implements \Collectable
{
    public $result;
    public function run()
    {
        sleep(1);
        $this->result = random_int(0, PHP_INT_MAX);
    }
}

echo 'Start', PHP_EOL;
$pool = new \Pool(4, \Worker::class);
$time = microtime(true);
$works = [];
for ($i = 0; $i < 10; $i++) {
    $work = new TestWork();
    $pool->submit($work);
    $works[] = $work;
}
$pool->shutdown();
$time = microtime(true) - $time;

foreach ($works as $work) {
    echo 'Work result: ', $work->result, PHP_EOL;
}
echo 'End at ' . $time, PHP_EOL;

Выдает:
Start
Work result: 7905944565329289890
Work result: 5108499292478502335
Work result: 5714338237394927753
Work result: 7690405846878811114
Work result: 8972815893492806475
Work result: 7717729732644902772
Work result: 3482825673580680883
Work result: 5913335648957882133
Work result: 7176745494439780246
Work result: 3082331963992595859
End at 3.005893945694


Попробовал изменить ваш код. В каждый воркер можно писать:
<?php
class Connect extends Worker
{
    public function getStorage()
    {
        if (!self::$storage) {
            self::$storage = [];
        }
        self::$storage[] = random_int(0, PHP_INT_MAX);
        return self::$storage;
    }

    protected static $storage;
}

class Query extends Threaded
{
    public function run()
    {
        $this->data = $this->worker->getStorage();
        var_dump(count($this->data));
        sleep(5);
    }

    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();

Вывод:
int(1)
int(1)
int(1)
int(1)
int(2)
int(2)
В первом примере данные хранятся в самих потоках, там же запись в них. Далее чтение этих данных из основного потока, я так понимаю без физического копирования данных.
Во втором примере данные хранятся в экземплярах класса Connect и конкретная задача их успешно читает, но это чтение данных внутри одного потока. А запись осуществляется дёрганьем метода, а не напрямую.

Но я собственно на такое чтение данных и не жаловался. Как мне в дочернем потоке почитать данные из родительского потока не дублируя при этом данные в физической памяти? Как мне из дочернего потока изменить данные в родительском потоке? Без этого для моей задачи использовать pthreads бессмысленно. И если погуглить «c++ многопоточность память» то видно, что в C++ таких проблем нет.

К чему этот разговор вообще веду. Вот у меня есть реализованый механизм распределения задач в несколько процессов, которые общаются через сокеты. Общаются посылая сигналы друг-другу. Это собственно первая проблема — что общаться они должны только сигналами. Это очень неудобно и громоздко. Большие куски данных хранятся там где собственно используются (например данные по конкретному игроку, а они довольно объёмные), если другому процессу нужны данные из этого процесса — то он их тянет из БД или процесса, поработает с ними и удаляет из своей памяти. Это вторая проблема — хотелось жеж бы просто по id пользователя обратится куда-то в памяти, получить нужные данные и всё, не дублируя объёмные данные, не реализуя сигналы. Кроме того, есть большой массив констант типа тексты заданий, куча настроек игрового процесса и т.д. — это тоже желательно не дублировать, один экземпляр на процесс.

Так вот я надеялся, что pthread решат эти две проблемы. Но похоже он не решает ни того ни другого и добавляют ещё одну. Я надеялся, что у меня будет одна большая переменная с текстами-настройками игры, будет один большой массив с игроками. И потоки будут просто получать задачи и указатели на память и работать над ними. Не дублируя данные. Код был бы маленький и очень простой. Память использовалась бы максимально эффективно. Процессоры при необходимости загружались бы на максимум. Новые фичи писать в такой системе было бы легко.

Но что же это получается с pthreads — поток должен к себе полностью скопировать всё, обработать, изменить и отправить обратно? Опять сигналы вместо прямого обращения к данным? Далее по конкретному игроку — пока он онлайн, для него формируется большой кэш данных, который ещё и меняется постоянно, его тоже надо весь скопировать в поток, а потом записать обратно? И бонусная проблема — тексты-настройки игры тоже надо копировать целиком в поток?

Я надеюсь что ошибаюсь. Или нет?
Понял. Тогда может быть пример с каналом вам подойдет? Вообще в примерах много интересного можно найти.

Разработчики pthreads специально сделали это ограничение, каждый поток со своим стораджем для переменных. Раньше можно было "расшаривать" данные делая их глобальными, но эту возможность убрали. Теперь можно только пересылать данные между потоками. И это правильно и сильно уменьшает риск выстрела в ногу.

Кстати, раз уж заговорили о pthreads — не сталкивался никогда с тем, что оно не позволяет передавать между потоками сложные структуры данных, вроде объектов (включая инстансы \Closure)?


У меня такое ощущение сложилось, что оно при передаче данных между потоками — сериализует их, а потом распаковывает, отсюда и косяк с замыканиями.

не сталкивался никогда с тем, что оно не позволяет передавать между потоками сложные структуры данных

Можно передавать только то что можно сериализовать. Сталкивался с этим пару лет назад когда пытался вынести доктрину в отдельный пул потоков для организации асинхронной работы… и проиграл поскольку сущности с проксями передать не выходило (там entity manager у каждой прокси есть).


что оно при передаче данных между потоками — сериализует их

именно так внутри и происходит и об этом есть в документации)

PhpDeamon на сегодняшний день морально устаревшее решение.

Я, видимо, еще из тех стариков извращенцев у кого очень сложный протокол WebSocket'а реализован на C/C++. Спасибо работодателю за терпение!
НЛО прилетело и опубликовало эту надпись здесь

Вот, кстати, хорошая C++-библиотечка для реализации WebSockets: WebSocket++ Основана на boost.asio, умеет многое, и очень расширяемая.
Ещё в Qt есть QWebSocket. Для простеньких приложений самое то, но не полностью поддерживает RFC6455 (например, не умеет Subprotocol).

Ха, только недавно делал бэкпорт WS под Delphi 7.
Да, нет. Мы вот тоже реализовали свой велосипед на С++. Правда, даже не поняли что он сложный. Гоняем по нему видео для MSE. Отлично работает на всех клиентах, кроме IOS-based.
Отлично работает на всех клиентах, кроме IOS-based.

что ж вы так отрубаете платежеспособных кастомеров?)


Справедливости ради речь у вас скорее идет не о web-сокетах а о старых добрых tcp сокетах.

Ну, к счастью, клиентов на IOS у нас пока нет. Мы используем Web клиент как дополнительный тонкий клиент к основному, без процедуры установки. Все работает только во внутри-корпоративных сетях, где очень мало мобильных клиентов. Не совсем понял про «старые» советы. Веб-сокеты, действительно, реализованы поверх обычных tcp, но сверху там еще есть: handshake, отдельные сообщения с заголовками и т.д. Что делать с IOS пока не понятно, так как MSE там выпилено, даже в Хроме.
В этой ветке вы об этом прямо не говорите, но похоже вы поймали ту же проблему из-за которой была написана эта статья.

Я постоянно ссылаюсь на https://habrahabr.ru/post/209864/ что он хорош и прост, но safary с ним не работает. То есть IOS не работает. А чтобы работало под IOS — надо посложнее код написать. Собственно об этом «посложнее» и идёт вся эта статья.
Не, я немного не об этом. Веб-сокеты, как раз, работают на всех, более или менее, современных платформах. Не работает MSE (Media Source Extensions). В моем случае, к счастью, нет необходимости поддерживать старые браузеры (до принятия WebSockets, как стандарта).
Спасибо за внимание, используйте на здоровье весь приведенный выше код (разумеется, я советую завернуть его в нормальные объекты, тут всё упрощено исключительно для понимания. Особенно callback на пришедший от пользователя frame советую сделать по-нормальному). Если вы будете использовать этот код, напишите пожалуйста где-то в коде «Спасибо Anlide и PhpDeamon».

Ну так оформите нормально, опубликуйте код и документацию, поддерживайте, тогда вам скажут спасибо.
А так — мыши и кактус
Я вроде подчеркнул не раз в тексте, что код максимально упрощён с ООП до процедурного стиля. Соответственно в тупую использовать этот код целиком нельзя. Но в процедурном стиле — проще всего изложить что происходит в коде и читателю проще выдернуть нужные куски и вставить себе в код.
Для стиля «давайте сделаем побыстрее» — вся статья неприемлема, то есть надо использовать PhpDeamon, socketo.me или ещё какое-то готовое решение. А для стиля «давайте сделаем хорошо» — статья то, что надо. То есть читатель пишет свой сервис, у него свой основной цикл stream_select и ему в таком виде намного проще надёргать куски кода себе, чем дёргать куски кода из готового решения.
Если речь идёт про «а шо такое websocket» — то это статья не про это.

Хотя я собираюсь опубликовать на github и запостить тут ссылку туда. И есть небольшая ошибка в методе «read_line» и небольшая опечатка в указателях основного цикла — разумеется надо как-то опубликовать исправленный код.
Про оформление и поддержку был скорее сарказм…

Зачем придумывать и героически преодолевать трудности? Да на php можно написать http-сервер, websocket-сервер. Но зачем? Когда есть готовые решения? пусть и на других платформах. Например, nodejs — event loop и http из коробки, реализации ws на выбор, и все это отлично работает + язык знакомый для вебразработчика. Или, например, go — тоже все есть, да, может язык и незнакомый, но эффективнее по по процессору и памяти.
Не рассматривали уже готовые решения?
К примеру очень хороший вариант socketo.me/docs
Я рассматривал только PhpDeamon на этапе проектирования.
https://habrahabr.ru/post/301822/#comment_9627298 тут написал почему отказался от его использования.
socketo.me обязательно просмотрю — может ещё чего-то важное найду.

Пока предварительная информация — но похоже в коде PhpDeamon / WebSocket / V13 есть ошибка, код некорректно реагирует на ping frame. Из-за чего FF@Linux разрывает websocket соединение. Подчёркиваю, что это требует перепроверки.
Обращаю внимание, что поиск и исправление подобной ошибки в сторонней библиотеке существенно сложнее чем в своей.

Я чего-то не понимаю. Но в чём причина писать такое, вместо того, что бы написать одну строчку composer require cboden/ratchet? Это всё, что надо сделать, чтобы включить полную (т.е. включая устаревшие протоколы) поддержку сокетов к себе в проект.


P.S. По-моему с приходом Ratchet — PhpDaemon ну не то чтобы умер, но в предсмертном состоянии. Код первого на порядок качественнее, имхо.

Поправка, с появлением решений вроде reactphp phpdaemon в принципе стал ненужным. Да и не поддерживается он уже довольно давно.

Почему PHP? Если вам нужно неумирающее приложение с сохранением состояния (сервер игры), многопоточная математика и вебсокеты — то PHP — это худшее решение для вашей задачи.

Не хочу показаться излишне грубым, но вы немного отстали от жизни =)


UPD: А вообще хочется аргументации почему вы так считаете? PHP7 кушает раза в 4-5 меньше оперативки, нежели nodejs, многопоточность и асинхроные плюшки есть, GC (по крайней мере в той же 7ке) наверное один из лучших, что есть на рынке. И т.д. А с приходом JIT, который сейчас в девел веточке...

Ну, многопоточность там в зачаточном состоянии. Я так понимаю, почти всё в PHP не thread-safe, то есть локи и сериализация доступа? Как с дсотупом к коллекциям? Лочить всю коллекцию? Могу ошибаться, поправьте. Какая модель памяти? О lock-free я так понимаю тоже можно только мечтать? А какой GC в PHP7? RC? Stop-the-world?
Я сторонник производительности, и для меня писать реалтаймовый сервер игры на скриптовом языке — уже сомнительная затея.

О, вот тут вы меня в тупик поставили. Я просто не представляю как устроены pthreads, т.к. работал с ними исключительно сквозь вышеупомянутый react.


На счёт GC — схема простая, у пыха нет явного подсчёта (т.е. вызов gc_collect_cycles только руками) ссылок, как вследствие — фризов освобождения оной — тоже, просто при создании нового объекта под него выделяется уже неиспользуемая память. В результате получаем да, небольшие задержки при создании объектов и постоянно растущую память, когда объекты не высвобождаются, с другой стороны — вполне эффективный рантайм без явных проседаний от очистки памяти. Лично мне не удавалось вообще заспамить оперативку в рантайме, при явном отсутсвии "заботы об удалении ненужного", скажем так. Думаю, что более подробную информацию по этому вопросу может предоставить Дмитрий Стогов, я просто не компетентен во внутренностях, но на практике — оно быстро работает, без фризов и "левых" утечек, на которые я напарывался при работе с джавой.


Ну когда действительно важна производительность — да, плюсы вне конкуренции. Это вообще другой уровень и другие требования. Но сабжевая игра написана для ВК, так что и требования соответсвующие.

да, плюсы вне конкуренции

А как же Си или Rust? По производительности они выигрывают. Последний в принципе неплох и в плане скорости разработки.

В моём случае название языка было использовано в качестве нарицательного. Имел ввиду любой язык, который предоставляет прямой доступ к памяти и стеку, и не делает львиную долю работы по её управлению за того, кто использует какой-либо язык. Короче любой +- низкоуровневый. Да даже Go можно, тоже вполне себе быстрая штука.

У меня вопрос, может кто знает какие крупные проекты используют WebSocket и long-polling? Знаю, что ВК использует и то и другое. WS хорош, но сложен в реализации. Хотелось бы узнать как long-polling работает с высокими нагрузками? (при хорошо настроенном сервере)

ВК использует сокеты? Где? о_0 Для сообщенек и новостей long-polling only

спасибо, long-polling видел в ВК в живую, про WS нашел на просторах гугла. Возможно знаете про другие крупные проекты? Одноклассники тоже сидят на long-polling.

Из того что знаю: Slack, Gitter, GoodGame (чатик), Twitch (чатик) на ws. Возможно и чатик Youtube тоже (это вполне было бы логично), но не уверен.

Спасибо! Может посоветуете что использовать при больших нагрузках (например одновременно 5-10к). Как относитесь к Server-Sent Events?

Опыта с реально высокой нагрузкой + реалтаймом не было. Так что тут я не советчик.

Нагрузка определяется не столько количеством соединений, сколько количеством сообщений. При асинхронной работе с соединениями держать большое кол-во открытых неактивных соединений не затратно. Все зависит от приложения, периодические сообщения по некоторым соединениям это одно, постоянный поток сообщений по всем или значительной части соединений это другое.

WS хорош, но сложен в реализации.

используйте готовое решение, вся сложность спрятана внутри.

По поводу long-polling vs websocket: если только https — можно смело использовать websocket, иначе long-polling либо гибридные решения типа socket.io. В ws есть бинарные сообщения, в некоторых ситуациях это очень полезно.

Есть опыт по транспорту котировок в браузер и моб.приложение. Каждому клиенту раз в секунду отправляется информация по изменившимся котировкам (до 50 штук). Первый прототип на node.js+socket.io на 2 тыс соединений на 100% грузил ядро. Были подозрения на избыточность протокола socket.io, раскопки показали что избыточность есть, но не такая значительная. Основной затык был в сжатии ws-фреймов, которое включено по умолчанию и через параметры socket.io не конфигурируется. В принципе хорошее решение для не интенсивного трафика.
Второй и рабочий вариант сделали на чистых вебсокетах, сжатие отключено, данные пересылаются в бинарном виде (protobuf). За счет использования бинарного формата даже при отключенном сжатии удалось выиграть в объеме. Так же бинарный протокол позволил избежать повторного кодирования одних и тех же данных для разных клиентов, пакет собирается из готовых бинарных кусков. Результаты тестов: 5 тыс активных соединений при 25% загрузке ядра, трафик 75Mbps.

Насколько я помню вконтактики да фэйсбуки используют long polling просто потому что многие сидят через прокси (с работы например) которые режут весь не http траффик. Так же long polling можно считать stateless и это намного удобнее в плане распределения нагрузок.

<irony>После беглого просмотра фконтактовских исходников kphp (https://github.com/vk-com/kphp-kdb) — я предполагаю, что у них, в частности, немного иная причина использования лонгполлинга.</irony>

anlide, в этой статье вы неоднократно даёте ссылку на мою статью Делаем вебсокеты на PHP с нуля, но это только первая статья из трёх на эту тему.
Вторая часть статьи: IPC. Межпроцессное взаимодействие
Третья часть статьи: От чата до игры: Battle City
Все они были написаны в начале 2014 года, а по их результатам создана библиотека и опубликована на гитхабе, в которой уже было учтено всё, что вы описываете ещё в начале 2014 года.
На всякий случай я попросил друга проверить работу моей библиотеки в Safari и она работает. В той же википедии пишут, что вебсокеты версии 13 (теперь эта версия закреплена в RFC 6455) поддерживаются в Apple Safari начиная с версии 6, которая вышла 2012 году, за два года до написания моих статей.

Ссылка на библиотеку также есть в каждой статье. Библиотека поддерживает только протокол вебсокетов и не поддерживает long polling, т.к. подавляющее большинство браузеров поддерживает вебсокеты, о чём я писал в третьей статье и приводил цифры (а это было напомню ещё в начале 2014 года, а сейчас 2016 год на дворе).

Также на странице библиотеки приведены ссылки на демки игр, которые физически не могли бы работать, если бы обладали описанными вами изъянами.

В вашей реализации вы используете socket_select, что не даёт возможности обслуживать более тысячи одновременных соединений без перекомпиляции php. В моей библиотеке также поддерживаются расширения pecl/event и pecl/libevent, что устраняет эти ограничения.
И да, моя библиотека протестирована на php седьмой версии и используется в продакшене уже несколько лет и не только мной, а демки висели без перезапуска больше года (до момента смены мною хостинга) и утечек памяти за этот период зафиксировано не было.

Сложилось впечатление, что статья была написана 2 года назад, а опубликована сейчас. Прошу добавить мой комментарий в конец статьи в качестве опровержения, чтобы не вводить читателей в заблуждение.
Спасибо за разъяснения, мне много стало понятнее.

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 на количество соединений?
Если в socket_select передавать дескрипторы с номером больше 1024 (т.е. открыто более 1024 соединений), то выдаётся такая ошибка:
PHP Warning: socket_select(): You MUST recompile PHP with a larger value of FD_SETSIZE. It is set to 1024, but you have descriptors numbered at least as high as 1024.

И судя по всему указанный вами user-agent принадлежит не сафари как могло показаться на первый взгляд, а хрому, что вполне логично — тестировать работоспособность приложения в самом используемом браузере.
http://www.useragentstring.com/Chrome35.0.1916.47_id_19788.php
https://github.com/gorhill/uMatrix/wiki/Latest-user-agent-strings
http://www.useragentstring.com/pages/Chrome/
http://www.webapps-online.com/online-tools/user-agent-strings/dv/operatingsystem51847/os-x
Большое спасибо за замечание. Долго думал, что с этим делать. На собственном хостинге увеличивать количество файловых дейскрипторов для любого приложения можно без проблем. Поэтому видимо надо просто следить за этой проблемой и должный report об этой ошибке сделать. И под нагрузкой socket_select использовать можно будет с таким подходом. Да?

Игра была запущена по схеме «тихий старт». То есть нигде не постится новость о новой игре, а в недрах раздела «все игры» — собственно и появляется игра. На данный момент посмотрело игру ~1750 человек. Google analytic пишет, что safary с версией 5 было аж 1 пользователь за всё время (ниже пятой версии пользователей не было). Очевидно это был мой браузер. И проблемы со старым протоколом на самом деле неактуальны.
Эта ошибка пользователя, а не баг в php (не нужно никуда писать репорты), именно поэтому генерируется PHP Warning, а не падает сам php. Эта ошибка сообщает пользователю, что socket_select нельзя использовать с дескрипторами с номером больше 1024. Это сделано потому что php-функция socket_select делает системный вызов select, а он работает используя (насколько помню) системный вызов poll, который не очень эффективный, особенно на большом количестве соединений. В системе есть более эффективные методы обработки — epoll, kqueue, kevent и т.д.
Конечно можно перекомпилировать php с увеличенной константой FD_SETSIZE, но тогда будет неэффективно использоваться процессор. Гораздо лучше использовать расширение pecl/event (которое уже обновили для его работы с php7).
pecl/event — это обёртка над системной библиотекой libevent (которую использует memcached, chrome, tor и т.д.)

В своей библиотеке я добавил поддержку pecl/event и возможность переключаться на неё с socket_select и обратно. Это помогает новичкам запустить свой вебсокет-сервер без установки дополнительных модулей, а когда нагрузка увеличится, то донастроить сервер и переключиться на более эффективный механизм.
В вашем коде я вижу 3 реализации WebSocket — socket_select, event, libevent. Чем отличаются event и libevent? Какой лучше выбрать? Почему?
На первый взгляд libevent больше нравится — потому что PhpStorm о нём знает, а про event — нет. :)
выбор очевиден в пользу event, потому что libevent в стадии beta, не обновлялся 3 года и не работает с php 7.
Собрался я наконец попробовать разобраться и применить event библиотеку.

Сначала я попытался разобраться — действительно ли нельзя использовать socket_select. Ведь для меня упереться в эту проблему — уже достижение. 1к юзверей онлайн это безусловная победа.
Понял, что пересобрать php будет не просто — в интернетах жалуются, что пересборка не помогает. Но зато есть клёвый обход — поднять несколько ws серверов, хоть 10к (или сколько там портов локальных может быть). На каждый пустить не более 1000 клиентов. А nginx через roundrobin выполняет балансировку — и вот новый лимит довольно быстро подымается до 100к юзверей онлайн. Далее можно спокойно применить event или ещё чего.

Теперь о самом event — много лет назад был создан такой плагин и не обновлялся. Далее вырос libevent он тоже перестал обновляться. Далее кто-то форкнул libevent в libevent2 и «недавно» торжественно переименовали libevent2 в event. Прошу подтвердить — правильно ли я понимаю происходящее?
Что мне очень не нравится в этом event — что его синтаксис не поддерживает PhpStorm. Я так понимаю из-за того, что переименование libevent2 -> event произошло недавно. И этот фактор склоняет меня к выбору использовать socket_select на кучу серверов, чем использовать event.

Далее, если смотреть на «новые» библиотеки (а event в данный момент я так понимаю является новой библиотекой) — то вот меньше месяца назад зарелизился плагин https://pecl.php.net/package/ev.
Понял, что пересобрать php будет не просто — в интернетах жалуются, что пересборка не помогает.


Как так? может просто ulimit не подняли?
Я в далёком 2007 пересобирал mysql, чтобы он мог работать с количеством открытых файлов более чем 65к. Под freebsd не получилось такое провернуть. А под debian получилось. Но всё-равно танцев с бубном с плясками вокруг сервера было очень много.

Поэтому разбираться что у кого не так пошло — желания нет.
Я так и не понял в чем проблема с Сафари.
Делал чатик с помощью Ratchet, замечательно работает под этим браузером.
Уже разобрались. Пол года назад, последняя версия safary под windows была пятёрка. Под ней нужен старый протокол. Кстати Ratchet его поддерживает.
Но кстати я изучил код Ratchet и он не поддерживает не шифрованные (masked) данные. А в протоколе указано как поддерживать это (и PhpDeamon и код приведенный тут умеет это). Может ещё чего всплывёт по ходу ковыряния его исходника.
Ну вобщем я статью обновлю — как все вылезшие подробности прояснятся.
На мой взгляд это не такая серьезная проблема, хотя реализация не помешала бы. Достаточно использовать WSS и тут, правда, без nginx никак, а также настроить фильтрацию origin domain.
Спасибо за статью, очень жду ссылку на github.
Метод read_lint() содержит ошибку — что мы читаем данные тела http запроса, хотя должны были читать только заголовки.
В основном теле цикла — не корректное использование указателей при переключении протокола.

Если кто-нить исправил эти ошибочки, скиньте кусочки кода пожалуйста.
Запостил на github https://github.com/anlide/websocket
Ещё надо будет с двумя вопросами разобраться — это socket_select под нагрузкой и причина закрытия сокета. Ещё расставить кучу комментариев по коду и вопрос можно будет закрывать.
socket_select под нагрузкой

А в чем именно вопрос? socket_select под нагрузкой нормально справляется. Не так как libev например (его можно заюзать) но норм. Заменять бы я рекомендовал просто на готовую библиотеку для реализации event loop. Более оптимального решения с точки зрения нагрузок для задач в духе web-сокетов как бы и нет.


Ещё расставить кучу комментариев по коду и вопрос можно будет закрывать.

Лучше пользуйтесь осмысленными названиями переменных и методов, а так же выносите части в приватные методы, используйте константы и все такое и комментарии будут просто не нужны.

https://habrahabr.ru/post/301822/#comment_9635078 вот жи человек говорит, что 1000 соединений и тю-тю, надо пересобирать php. И при этом он ещё и медленный. Так где же правда?

На тему комментариев — я с вами согласен. Наверное стоит поудалять все комментарии, написать свои только в сложных местах, в большей части кода сделать говорящие названия переменных.

Ну если у вас более 1000 соединений, тут проще использовать libev или libevent. Экстеншены ставить из пекла не особо то сложно, в обращении они не намного сложнее stream_select, так что...


А по поводу пересборки — это не такая большая проблема на самом деле) Особенно если использовать Docker — один раз собрал и хорошо.

libevent — не вариант, об этом я уже выше писал https://habrahabr.ru/post/301822/#comment_9641462
по поводу пересборки — это всё таки проблема. Совсем недавно вышла php 7.0.7, а до неё 7.0.6 и 7.0.5 и т.д. и все они — «security release», так что перекомпилировать каждый раз php вместо того чтобы обновить из пакета — это лишние действия, а если не обновлять то будет решето.
За libev спасибо, не знал, хотя его автор сделал также и event, который я использовал ранее.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории