Comments 77
Спасибо за код!
может пригодится) Но я бы добавил хотя бы strip_tags)
может пригодится) Но я бы добавил хотя бы strip_tags)
+2
Чат очень сильно глючит, это из-за нагрузок?
0
Не думаю. Скорее всего это связано с настройками окружения на сервере. Локально я тестировал, открывая 10к сокетов одновременно и проблем не было. Сейчас разбираюсь, в чём проблема на сервере.
0
то что вы открыли 10К соединений никак не покажет вам как сервер держит нагрузку.
0
Будет круто, если расскажите сколько памяти и процессора «съел» PHP и остальное при 10K подключиях.
0
около 90 мб на 10к соединений, т.е. 9кб на одно соединение
0
Пока что (на момент написания комментария!), увы, демонстрационный чат небезопасен — спокойно выполняет любой пришедший JS.
0
Через рандомные промежутки отключает с сообщением в консоль (Хром 28.0.1500.95 убунта):
WebSocket connection to 'ws://sharoid.ru:8000/' failed: Could not decode a text frame as UTF-8.
+1
Как приятно, что не перевелись еще Кулибины! ) Так держать
В данной реализации (если я ничего не пропустил) не обрабатывается вариант, когда одно сообщение содержит более 100000 байт. Или же по каким-то причинам одновременно прочиталось два сообщения. Я на этот случай обычно использую входящий буфер на каждый коннект. Сервер складывает все, что пришло от клиента, в этот буфер, а дальше происходит уже анализ содержимого этого буфера.
В данной реализации (если я ничего не пропустил) не обрабатывается вариант, когда одно сообщение содержит более 100000 байт. Или же по каким-то причинам одновременно прочиталось два сообщения. Я на этот случай обычно использую входящий буфер на каждый коннект. Сервер складывает все, что пришло от клиента, в этот буфер, а дальше происходит уже анализ содержимого этого буфера.
0
опять же в дополнение к такой обработке появляется потребность убирать блокировку сокета, чтобы (пока ничего не пришло) могли обрабатываться сообщения, уже имеющиеся в буффере, либо могли выполняться какие-то внутренние действия
0
Какой смысл в своем буфере, если такой буфер уже есть в операционной системе? Если вам не хватает дефолтного размера буфера чтения/записи, никто не помешает вам указать свой размер.
0
if (($connect = stream_socket_accept($socket, -1)) && $info = handshake($connect)) {
$connects[] = $connect;//добавляем его в список необходимых для обработки
onOpen($connect, $info);//вызываем пользовательский сценарий
}
$connects[] = $connect;//добавляем его в список необходимых для обработки
2 раза добавляете один и тот же сокет.
$data = fread($connect, 100000);
if (!$data) { //соединение было закрыто
А вот и нет. Клиент может прислать «0».
Используйте
strlen($data)
.Еще ваш код, как и 90% мануалов по сокетам, не учитывает 3 вещи:
1. при чтении данные могли прийти не полностью
2. блокирующая запись (сокет может быть не готов)
3. данные записались не полностью
Лечить так:
1. буфер чтения
2. разобраться с использованием параметра
$write
для stream_select
3. буфер записи
Хорошие исходники для медитации:
github.com/reactphp/event-loop/blob/master/StreamSelectLoop.php
github.com/igorw/webserver-zceu
+11
я для чтения из сокетов предпочитаю использовать более «низкоуровневые» вызовы *_recv
тогда отключение можно поймать, когда после селекта мы из сокета вычитываем 0 байт.
тогда отключение можно поймать, когда после селекта мы из сокета вычитываем 0 байт.
0
strlen($data) — это и есть количество байт, если там 0, значит клиент отключился.
Вы используете socket_recv? Оно работает с врапперами (ssl://)?
Вы используете socket_recv? Оно работает с врапперами (ssl://)?
+1
strlen — зачем нужен лишний вызов?
врапперы — очевидно, что нет. Тут уже все зависит от задачи, есть ли необходимость во врапперах или нет.
врапперы — очевидно, что нет. Тут уже все зависит от задачи, есть ли необходимость во врапперах или нет.
0
strlen — зачем нужен лишний вызов?
Вы правы (я и не спорил). В случае с
socket_recv
можно сэкономить на вызове strlen
.врапперы — очевидно, что нет
Мне было не очевидно, т.к. я
socket_recv
не использовал. Поэтому Вам и задал вопрос. 0
именно по этой причине (зависимость от использования сетевого протокола, ssl и т.д.) стоит использовать потоки, а не низкоуровневое api. Хотя все опять же зависит от задачи.
0
Всегда казалось что предпочтительнее использовать потоки…
+1
спасибо, код в статье подправил.
я сначала хотел в статье написать ещё пример с использованием libevent, но решил, что для статьи, в которой «делаем простой сервер вебсокетов» этого будет слишком много.
я сначала хотел в статье написать ещё пример с использованием libevent, но решил, что для статьи, в которой «делаем простой сервер вебсокетов» этого будет слишком много.
0
Советую взглянуть на pecl/event, он предоставляет более высокоуровневый и объектный интерфейс. Документации, правда, толком нет, но примеров из bitbucket-а вполне достаточно, если есть опыт работы с либевентом.
0
клиент не может прислать 0, данные кодируются по протоколу вебсокета — если клиент отправит 0, то придёт ������
по-этому можно не делать strlen($data), а достаточно if (!$data)
по-этому можно не делать strlen($data), а достаточно if (!$data)
0
Первое правило безопасности программ — данные, полученные из внешнего источника, могут быть какими угодно.
+2
UPD: второе правило безопасности — на любую, даже самую мелкую, ошибку всегда найдется хакер, который положит все нафиг с ее помощью.
+1
if (!$data) {@fclose($client);}
куда уже безопаснее? если пользователь в обход протокола запишет «0», то соединение будет разорвано.
если бы хакер прислал «0», то он не положил бы чат, а разорвал соединение.
чат зависал из-за того что использовалась
fgets($client);
, а она ожидает конец строки или таймаут. т.к. конца строки вы не приходило, то она «зависала».
0
клиент не может прислать 0, данные кодируются по протоколу вебсокета — если клиент отправит 0, то придёт ������
по-этому можно не делать strlen($data), а достаточно if (!$data)
В Вашей конкретной реализации — возможно.
В общем случае сервер состоит из:
1. цикл обработки событий (event loop) (абстрактный, не зависящий от протокола)
2. обработчик конкретного протокола
Цикл читает данные и передает в обработчик. Обработка закрытия/обрыва соединения находится в цикле (в общем случае).
Исходя из этих пунктов,
!$data
в цикле обработки использовать не стоит.Т.к. цикл обработки использует
stream_select
, то сервер может получать данные по кускам.Т.е. сообщение
Это стоит 1000$
Может прийти в виде
Эт
о с
тоит 1
0
00$
И на предпоследнем куске обработчик, проверяющий
!$data
, ошибочно закроет соединение. Каким бы способом сообщение не кодировалось, если там встречается «0» — соединение будет обработано неверно. +2
Про несколько процессов-воркеров будет крайне интересно увидеть статью.
0
Не забывайте, что Вам нужно следить за жизнью этого демона. Рестартовать при падении, перезагружать, собирать логи и т.д. Тут у Вас, опять же, 2 пути:
1. Написать обвязку самому.
2. Использовать готовые решения.
Я Вам рекомендую использовать supervisord.org/ — это Вас лишит лишней головной боли.
Пример конфига можно увидеть тут: webadvent.org/2009/daemonize-your-php-by-sean-coates
1. Написать обвязку самому.
2. Использовать готовые решения.
Я Вам рекомендую использовать supervisord.org/ — это Вас лишит лишней головной боли.
Пример конфига можно увидеть тут: webadvent.org/2009/daemonize-your-php-by-sean-coates
+5
Монстрообразность указанных в начале статьи библиотек обусловлена, как минимум наличием 4х протоколов вебсокетов (в вашем примере только один) и возможностью завести библиотеки вместе с libevent и pcntl (форки\многопроцессовость).
+3
Мы используем HttpPushStreamModule для работы с сокетами из пхп. Поддерживает, к слову, много чего, включая деградацию до long-pooling.
+3
А где можно поглядеть на пример взаимодействия php с этим модулем?
0
Где на пример взаимодействия с php посмотреть, не подскажу. Могу сказать, что взаимодействие сводится к http-запросам (например, записать что-то в канал = отправить запрос), но это, наверное, и так понятно.
0
Меня интересует вот что:
Publisher отправляет сообщение. Я его получаю из:
$_POST
php://input
STDIN
Какая-то библиотека
Откуда?
Дальше. Есть несколько subscribers. Я хочу отправить им это сообщение. Я пишу его в:
Сокет
Пайп
STDOUT
echo
Куда?
Publisher отправляет сообщение. Я его получаю из:
$_POST
php://input
STDIN
Какая-то библиотека
Откуда?
Дальше. Есть несколько subscribers. Я хочу отправить им это сообщение. Я пишу его в:
Сокет
Пайп
STDOUT
echo
Куда?
0
Хотите получить сообщение (сообщения) из канала — отправляете запрос.
Хотите отправить сообщение подписчикам — отправляете запрос.
Как именно отправить http-запрос из приложения — вам решать.
Хотите отправить сообщение подписчикам — отправляете запрос.
Как именно отправить http-запрос из приложения — вам решать.
0
Хотите получить сообщение (сообщения) из канала — отправляете запрос.
Это polling получается. Я имел ввиду использование WebSockets… Что-то я запутался. Надо пробовать на практике.
В любом случае, спасибо за наводку.
0
не будет ли проблемой то, что handshake выполняется сразу же после accept? Не будет ли fgets блокировать поток? Я имею в виду ситуацию, при которой любой сможет положить ваш comet-сервер просто установив соединение с сервером но не посылая никаких данных…
+1
Будет и блокирует. Я проверил.
+1
Чат замер. Уже весь Хабр проверяет.
+1
имеет смысл открыть для себя библиотеки libev/libevent/libevent2
0
$isMasked = ($secondByteBinary[0] == '1') ? true : false;
wat?
+13
от «Войны и Мира» упал. Upd: а нет, уже всё ок
0
Вешается чат довольно таки просто — telnet sharoid.ru 8000…
-2
добавил неблокирующую запись и неблокирующее рукопожатие.
итоговый код в статье обновил.
теперь ваша команда не сработает.
итоговый код в статье обновил.
теперь ваша команда не сработает.
0
Только я записываю в сокет нуль-символ — возвращаемся к началу.
0
while true; do nc sharoid.ru 8000 < /dev/zero ; done
phpdaemon и пр. не от хорошей жизни такие «монструозные», а из за того, что, в том числе, над безопасностью думают. +1
это статья про то как работают вебсокеты, вы слишком ответственно отнеслись к тестированию :)
+1
мне казалось, что часа вам будет достаточно, чтобы убедиться в своей правоте и перестать глушить мой сервер, который я использую не только для тестов, но видимо я ошибался.
0
Причем тут phpdaemon? На самом деле, вы успешно провели атаку на замеченную ранее ошибку с некорректной проверкой длины.
0
А вот Ratchet реализация
0
Если вы хотите довести этот пример до возможности использования в реальной жизни — то настоятельно рекомендую прочитать вот эту статью: www.kegel.com/c10k.html
0
Вот Вам еще пара ссылок по сокетам и
linux.die.net/man/3/select
beej.us/guide/bgnet/output/html/singlepage/bgnet.html
Там примеры на C под UNIX, но они здорово помогают глубже и полнее понять асинхронную работу с
P.S.
Путь «велосипеда» — это правильный путь. Правильный для обучения.
Но чем больше начинаешь понимать всяких нюансов, тем чаще тебя начинает посещать мысль, что не такие уже и монструозные, на самом деле, те готовые решения, использования которых ты решил избежать.
select
:linux.die.net/man/3/select
beej.us/guide/bgnet/output/html/singlepage/bgnet.html
Там примеры на C под UNIX, но они здорово помогают глубже и полнее понять асинхронную работу с
select
.P.S.
Путь «велосипеда» — это правильный путь. Правильный для обучения.
Но чем больше начинаешь понимать всяких нюансов, тем чаще тебя начинает посещать мысль, что не такие уже и монструозные, на самом деле, те готовые решения, использования которых ты решил избежать.
0
Рекоммендовал бы еще рассмотреть случай, когда в сетевой поток могла бы прекратиться запись до того, как будет записано все.
Пример расписан в оффициальной документации us2.php.net/manual/ru/function.fwrite.php
Пример расписан в оффициальной документации us2.php.net/manual/ru/function.fwrite.php
0
Я с PHP года 4 уже не работал, так что могу не знать нюансов, сужу по косвенным признакам… Вы написали, что переделали работу с сокетами на неблокирующую, но я нигде в коде не вижу вызовов
Пример — вызов fgets (его вы уже выпилили) возвращает в 3-х случаях: сокет закрыт, достигнут лимит или найден "\n". Как она работает с неблокирующими сокетами вообще не представляю, но совершенно точно что нужно удостовериться, что последний символ в считанных данных это "\n".
Дальше fwrite: на неблокирующих сокетах он не обязан записывать всё, что ему сказали записать — сколько он запишет зависит от размера буферов, так что нужно делать что-то вроде
Насчёт read та же фигня — в неблокирующем режиме он может вернуть хоть один байт (привет
Тот факт, что сокеты у вас всё-же блокирующие, позволяет, по идее, заблокировать воркера послав, например, всего один байт, в то время как сервер делает
stream_set_blocking
да и read
/write
/fgets
у вас используются так, как использовались бы с блокирующими сокетами. Мне кажется они у вас блокирующие… Пример — вызов fgets (его вы уже выпилили) возвращает в 3-х случаях: сокет закрыт, достигнут лимит или найден "\n". Как она работает с неблокирующими сокетами вообще не представляю, но совершенно точно что нужно удостовериться, что последний символ в считанных данных это "\n".
Дальше fwrite: на неблокирующих сокетах он не обязан записывать всё, что ему сказали записать — сколько он запишет зависит от размера буферов, так что нужно делать что-то вроде
if (length($data) != ($written = fwrite($data))) {
$data = substr($data, $written),
}
и дописывать остаток когда сокет снова станет записываемым.Насчёт read та же фигня — в неблокирующем режиме он может вернуть хоть один байт (привет
if(!$data)
) хоть всё вплоть до $length. Так что всегда нужно проверять достаточно ли данных считалось и если нет — сохранять в буфер и дожидаться, пока сокет снова станет readable.Тот факт, что сокеты у вас всё-же блокирующие, позволяет, по идее, заблокировать воркера послав, например, всего один байт, в то время как сервер делает
read($fd, 10000)
. Хотя насчёт этого не совсем уверен, возможно в PHP там промежуточные буферы какие то есть. 0
NodeJS + socket.io, нет?
-3
foreach($read as $connect) {//обрабатываем все соединения
...обрабатываем $connect
разве не foreach($read as $connects)?
0
Как запускать сторонние скрипты из функций onMessage?
Метод prt делает конкатенацию входящего сообщения с заданной строкой.
В результате корректно работает раз от раза:
В чем причина?
fwrite($connect, encode(Test::prt(decode($data)['payload'])));
Метод prt делает конкатенацию входящего сообщения с заданной строкой.
public function prt($m){
return $m . 'work!';
}
В результате корректно работает раз от раза:
work!
work!
fdwork!
work!
work!
fdwork!
В чем причина?
0
А в 2022 хром позволит создать небезопасное соединение?
Один пхп скрипт легко подключается к другому, но вот при попытке подключиться с хрома после получения им строки:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Version: 13
Sec-WebSocket-Accept: Hrk+hHg1R1HNWSsCbXcOU60HIm8=
Происходит онеррор
0
Sign up to leave a comment.
Делаем вебсокеты на PHP с нуля