LINUX.ORG.RU

websocket server

 ,


1

1

Сделал websocket сервер на Ratchet
Проблема в том, что в onMessage если будет долгая выборка из БД или ещё какая-нибудь длительная операция(например загрузка URL), то весь сервер ступорится и не обрабатывает сообщения в loop. После выполнения длительной операции все сообщения, которые накопились в очереди отрабатываются и проходят нормально.

Объясню на примере чата:
Пишет один в чате Hello, js отсылает через вебсокет это сообщение, в onMessage я пересылаю всем это сообщение и после скачиваю файл. Так вот, сообщения эти не уходят, пока файл не будет скачан до конца и другие когда пишут тоже не могут написать, потому что сервер ступорится и обрабатывает все сообщения после завершения операции.


Пример сервера можно посмотреть на
https://github.com/ratchetphp/Ratchet

★★★★

Неужели нужно на каждое сообщение websocket делать fork или дочерний процесс запускать?

gobot ★★★★ ()
Ответ на: комментарий от gobot

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

Есть еще вот такая хренотень https://github.com/kakserpom/phpdaemon, но это для совсем упоротых.

Vit ★★★★★ ()
Ответ на: комментарий от Vit

Получается сервер однозадачный? Пока одно сообщение не отработается, все клиенты будут ждать? Пляяя... Запускать процесс на каждое сообщение чата что-ли?

gobot ★★★★ ()
Ответ на: комментарий от gobot

Ничего удивительного - многозадачных серверов не так уж и много, даже в случае многозадачности, процесс как правило живет в своей ветке около 30-300 sec

anonymous ()
Ответ на: комментарий от gobot

Как сказал выше вит — у апача есть свой пул пыхов в памяти и он их сам рокирует и перезапускает если посчитает нужным. То же самое и в php fpm/fast-cgi — зигота держит пул процессов (а можт и тредов, я не вникал) и сама их переваривает. Но для тебя это все прозрачно, и по факту реально быстрее, чем подергивать лапками с одного пыходемона в одном треде одним сокетом.

deep-purple ★★★★★ ()
Ответ на: комментарий от gobot

Ну типа все вызовы к базе там блокирующие :) . Так что конкретно с вебсокетами имеешь все шансы получить, что процесс 90% времени простаивает.

1 пых = 1 запрос в каждый конкретный момент времени. Решается большим количеством процессов. Считай сколько во время запроса пых простаивает, и потом еще учти количество ядер.

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

Vit ★★★★★ ()

инструмент не под задачу просто, пхп и вебсокеты это вещи несовместимые, возьми хотя бы ноду

umren ★★★★★ ()

Специально для тебя придумали корутины, мультитрединг и нормальные языки программирования

JN ()

Чувак, там же event-loop, чего ты ещё ожидал? man корутины. Ну или очередь задач организуй, а приложение на ratchet используй как прослойку между воркерами и клиентом.

Kilte ★★★★★ ()
Последнее исправление: Kilte (всего исправлений: 1)
Ответ на: комментарий от gobot

зачем на сайте вебсокеты?

а так man architecture, micro services, etc

umren ★★★★★ ()

Как я помню в php асинхронный CURL есть. В PDO нет асинхронных запросов, но Mysqli это умеет (драйвер для pgsql тоже). Для многозадачности в PHP тоже всё есть:

http://nikic.github.io/2012/12/22/Cooperative-multitasking-using-coroutines-i...

Я ничего такого на PHP не писал, но не вижу, почему на PHP сейчас этого нельзя сделать и почему нужно обязательно блокировать процесс.

nguseff ()

будет долгая выборка из БД или ещё какая-нибудь длительная операция(например загрузка URL), то весь сервер ступорится и не обрабатывает сообщения в loop

Дык надо запросы делать асинхронно. Возьми какую-нибудь обёртку на reactphp (раз уж ты используешь ratchet), смотри тут https://github.com/reactphp/react/wiki/Users

no-such-file ★★★★★ ()
Последнее исправление: no-such-file (всего исправлений: 2)
Ответ на: комментарий от no-such-file

Ладно-ладно, застыдил :) . Я с него еще до композа свалил.

А там синтаксис-то для удобного вызова корутин добавили? Ну а ля генераторы или async/await. Потому что если ты говорил об асинхронном программировании «как в ноде на колбеках» - это не то чем можно гордиться. Промисы без генераторов тоже не предлагай :)

Vit ★★★★★ ()

Вебсокетов на пхп не бывает. Будь как все, юзай hxr polling

redixin ★★★★ ()
Ответ на: комментарий от Vit

Ну а ля генераторы или async/await

Генераторы есть уже давно, async/await есть пока только в hhvm, в стандарт я так понял их собираются запиливать в php8.

no-such-file ★★★★★ ()
Ответ на: комментарий от no-such-file

Я ничего в php не знаю, но вставлю свои пять коп. На lua вебсокеты делал так: каждый вебсокет запрос создавал свой тред и эти треды сохранялись в массив подписок к модели. Не сам тред, а немного поколдовать с семафором и что-то таки можно будет сохранить. А когда запись обновляется в базе, она смотрит, есть ли подписки на эту модель. Если есть, то изменение сохраняет в переменную т.е action: update, fields: {name: 'new name'} и т.д и потом проходится по массиву подписок и семафор переключает. Семафор - это такая штуковина в программировании потоков, на php вероятно есть такое что-то подобное. В общем, зачем создавать процессы, если можно создать потоки.

Romaboy ()
Ответ на: комментарий от Romaboy

каждый вебсокет запрос создавал свой тред

Зачем, если асинхронно всё в одном потоке успеет провернуться. А если нет, то велосипед не нужен, на пыхе обычно уже всё есть - вот, например https://github.com/php-pm/php-pm

всеобщее мнение гласит, что php говно

Всеобщее мнение гласит, что linux - говно. Десяточка круче, там танчики идут, азаза.

no-such-file ★★★★★ ()
Ответ на: комментарий от no-such-file

Зачем, если асинхронно всё в одном потоке успеет провернуться

В треде сохраняется состояние, по этому и тред. Человеку надо по вебсокету отправить сообщение - куда отправить? Как отправить? Сервер хз, а если состояние на момент приветствия вебсокета сохраняется в поток, то тогда внутри потока все серверу просто и понятно, ответит без проблем. Вопрос: а можно сделать умнее? Я долго бился, ничего лучше не смог придумать.

Romaboy ()
Ответ на: комментарий от Romaboy

ничего лучше не смог придумать

Зачем придумывать, если можно посмотреть как народ делает? Бери любой асинхронный фреймворк и изучай.

no-such-file ★★★★★ ()
Ответ на: комментарий от no-such-file

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

Romaboy ()
Ответ на: комментарий от no-such-file

Загрузка файла, да и любая операция блокирует скрипт полностью

gobot ★★★★ ()
Ответ на: комментарий от Kilte

Сделал я планировщик как по этой ссылке, но какой от него толк, если я в одной задаче ставлю sleep(100) и весь скрипт встает и другие задачи не выполняются

gobot ★★★★ ()
Ответ на: комментарий от gobot

Разумеется никакого, потому так никто и не делает. Всё же в одном потоке происходит. Никто не должен ничего блокировать. Для этого даже специально пишутся асинхронные версии библиотек. Тебе выше уже накидали ссылок. Кстати статья, что я тебе дал — это перевод статьи которой с тобой уже поделились в этом треде. Не знаю, ты или не тем местом читаешь, что тебе пишут, или не хочешь читать. Всё объяснили же.

Kilte ★★★★★ ()
Ответ на: комментарий от Kilte

Так в этом вся и проблема, я не могу избавится от блокирующих функций, в основном это обращение к БД. Быть может для каждого запроса отдельный процесс открывать, будет ли это быстро хз

gobot ★★★★ ()
Ответ на: комментарий от gobot

Вообще в PHP есть из асинхронного:

  • Какие-то функции стандартных библиотек, которые являются асинхронными, но их не так много. Например, в библиотеках mysqli и pgsql. Можно асинхронно послать запрос и дальше продолжить выполнять программу, взяв результат, когда он будет готов.
  • Про сокеты и Http запросы. Есть стимы и api достаточно низкоуровневый. Суть в том, чтобы создать неблокирующий стрим, руками послать в него запрос и ждать ответа, используя stream_select. Вот тут есть простой пример (самой библиотекой не пользовался):

    https://icicle.io/docs/manual/introduction/#non-blocking-operations

    Но лучше использовать уже готовое.

  • В библиотеке ReactPHP вроде как уже реализован асинхроный http клиент.

    https://github.com/reactphp/react/blob/master/examples/http-client.php

    Есть также популярный Http клиент Guzzle, который используется в Symfony и который умеет асинхронные запросы.

nguseff ()
Ответ на: комментарий от gobot

Ну тогда остается использовать потоки (pthreads) и/или отдельных воркеров. Но это скользкий путь, не думаю, что это будет сильно легче, чем переписать запросы на асинхронном API.

nguseff ()

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

php - уже давно устарел и непригоден для современного веба.

Если нужны вебсокеты - нужно использовать адекватные и современные технологии.

th3m3 ★★★★★ ()
Ответ на: комментарий от th3m3

Почему не предназначен? Какие костыли? У меня работает уже месяц сервер-вебсокет на пхп сутками без перезагрузки

gobot ★★★★ ()
Ответ на: комментарий от gobot

Внезапно открыл документацию Ratchet и у них есть то, что тебе нужно

http://socketo.me/docs/push

Лучше использовать этот путь, чем делать свой велосипед. Ты можешь запустить несколько воркеров в потоках, в которых сохранять сообщения, да даже можешь использовать асинхронные функции. Потом ты естесственно должен решить в какие моменты ты будешь отсылать сообщения и вообще как ты будешь их отсылать. Отсылать из другого потока у тебя не получится, а ворваться обратно в поток выполнения с каким-то произвольным кодом в php не так много возможностей и все они выглядят как костыли (например, register_tick_function). Чтобы это работало и не выглядело дикостью, в самом потоке выполнения должен работать планировщик, который будет обрабатывать очереди сообщений.

Но Ratchet тебе такого не позволяет, насколько я могу судить, а вместо этого пытается работать как нода, не имея асинхронного апи ноды (асинхронные функции php предполагают метод опроса, а не передачу callback'а). Хз, видимо на php это особо не нужно никому.

nguseff ()
Ответ на: комментарий от nguseff

Я начал через pthread, столкнулся с проблемой бд... У меня сервер открывает соединение с БД, а трид его не видит, говорит «Couldn't fetch mysqli». Нужно для каждого трида открывать новое соединение?

gobot ★★★★ ()
const WebSocketServer = require('ws').Server;
const wss = new WebSocketServer({ port: 8080 });

wss.on('connection', ws => {
  ws.on('message', message => {
    console.log('received: %s', message);
  });

  ws.send('something');
});

Только пхп выкинуть надо. Хотя был, вроде, http://reactphp.org/, но это помесь слона со сметаной.

ChALkeR ★★★★★ ()
Последнее исправление: ChALkeR (всего исправлений: 3)
Ответ на: комментарий от gobot

Да, для каждого трэда нужно свое соединение. Это не только для PHP, кстати, справедливо. Клиенту отдавать данные ты из другого трэда тоже не сможешь, нужно, чтобы планировщик в основном event loop'е ждал результатов от потоков. Также нужно думать про обработку ошибок. Не обрабатывать их не получится, так как от ошибки запроса упадет всё приложение, а не только один поток.

Поэтому вариант с ZeroMQ и обычным php-fpm, который будет обрабатывать синхронные операции в отдельных процессах, как ни крути, самый простой в твоем случае. Если нужен способ решить проблему, то я бы её решил так.

В этом случае, кстати решается и проблема взаимодействия с PHP. Websocket сервер можно будет написать на чем угодно, а взаимодействовать с PHP кодом через какой-нибудь MQ.

А вообще мне с виду кажется amphp более удобным. Там внутри болеее гибкий планировщик по сравнению с Ratchet.

nguseff ()
Ответ на: комментарий от nguseff

Проблему с базой я решил, в run() треда идет новое инициализация, в том числе и соединение с бд. Теперь не пойму как отправить из треда сообщение клиентам. Я как понял треду не передать ссылку на родительский объект, потому что он сериализирует объект и сыпятся ошибки «An error has occurred: Serialization of 'Closure' is not allowed». А мне нужно из треда вызвать метод send() конекта, который не передать в тред никак.

gobot ★★★★ ()
Ответ на: комментарий от gobot

ну сообщения допустим постятся через обычный PHP скрипт (сайт). В этом PHP скрипте ты сохраняешь сообщение и добавляешь в очередь сообщений (пусть будет ZeroMQ), WebSocket сервер тоже слушает этот ZeroMQ и уже передает сообщения всем клиентам.

В обратную сторону тоже работает, только нужен ещё PHP-демон (или несколько), который тоже будет слушать очередь. Но он не будет никак связан с ws сервером и в ws сервере будет все быстро, и ничего не будет блокироваться.

nguseff ()
Ответ на: комментарий от gobot

А мне нужно из треда вызвать метод send() конекта

Этого нельзя сделать. Я уже выше писал об этом.

Короче у тебя должно быть WS приложение и должны быть воркеры на PHP, которые будут делать запросы к БД и прочие долгие синхронные операции. Можешь играться с потоками, но внутри воркеров, а не основного приложения. Как взаймодействовать воркерам и ws приложению вопрос элементарный. Читай доки к ReactPHP и Ratchet, посмотри на тот же пример с ZeroMQ в доках Ratchet, если не хочешь разбираться с MQ - черт с ним, сделай чтобы просто через сокет общались.

nguseff ()
Ответ на: комментарий от nguseff

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

Kilte ★★★★★ ()
Ответ на: комментарий от nguseff

Трид никак не может передать данные в родительский объект? Например для обновления списка участников после присоединения

gobot ★★★★ ()
Ответ на: комментарий от nguseff

Как взаймодействовать воркерам и ws приложению вопрос элементарный

Не элементарный ) Что читать там в ReactPHP?

gobot ★★★★ ()
Ответ на: комментарий от nguseff

Все разобрался, установил zeromq, сейчас буду пробовать

gobot ★★★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.