PHP-демон с дочерними процессами

Vict0r

Новичок
Если есть более изящные решения, с удовольствием рассмотрю.

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

Особенности: некоторые соединения закрываются не сразу после вывода в них малых объемов данных, а какое-то время продолжают быть активными и получают данные от демона.

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

-~{}~ 07.12.08 22:42:

Проблема с "Interrupted system call" у socket_select() решилась следующим образом:

PHP:
while( $server_runs )
{
	$test_sockets = array();
	array_push( $test_sockets, $main_socket );

	for( $i = 0; $i < sizeof( $client_sockets ); $i++ )
	{
		if( isset( $client_sockets[$i] ))
		{
			array_push( $test_sockets, $client_sockets[$i] );
		}
	}

	$ready = @socket_select( $test_sockets, $_null, $_null, 0 );

	if( $ready === false )
	{
		make_log( "[*ERROR*] Socket_select failed! (" . socket_strerror( socket_last_error()) . ")" );
	}
	elseif( $ready > 0 )
	{
		if( in_array( $main_socket, $test_sockets ))
		{
			$temp = accept_connection( $main_socket );

			if( --$ready <= 0 )
				continue;
		}

		for( $i = 0; $i < sizeof( $client_sockets ); $i++ )
		{
			if( !isset( $client_sockets[$i] )) continue;

			if( in_array( $client_sockets[$i], $test_sockets ))
			{
				$output_data = read_from_socket( $i );

				if( !$output_data )
				{
					@socket_close( $client_sockets[$i] );
					$client_sockets[$i] = null;
				}
				else
				{
					eval_data( $i, $output_data );
				}
			}
		}
	}

	usleep( 5000 );
}
Но возникла новая проблема - если на демон приходит сразу несколько новых соединений, среди которых есть запрос на вывод большого объема данных (что заставляет родительский процесс форкаться), то некоторые из новых соединений не обрабатываются демоном и остаются "зависшими".

-~{}~ 07.12.08 22:55:

Кстати, версия PHP 4.4.9.
 

Wicked

Новичок
один из немногих случаев, когда мне не лень написать готовый код мультиплексирующего сервера (на основе phpsocketdaemon'а) .-)

PHP:
#!/usr/bin/php -Cq 
<?php

require("socket.php");

class myServer extends socketServer {
}

class myServerClient extends socketServerClient {
        public function on_read() {
                $this->last_action = time();
                switch (trim($this->read_buffer)) {
                    case "small":
                        echo "[httpServerClient] accepted 'small' request\n";
                        $data = "Here is your SMALL data ))) Stay online ;-)";
                        break;
                    case "big":
                        echo "[httpServerClient] accepted 'big' request\n";
                        $data = "Here is your BIG data ))) Good bye.";
                        break;
                    default:
                        echo "[httpServerClient] accepted unrecognized request\n";
                        $data = "";
                }
                $this->write($data);
        }

        public function on_connect() {
                echo "[httpServerClient] accepted connection from {$this->remote_address}\n";
        }

        public function on_write() {
                echo "[httpServerClient] writing to {$this->remote_address} completed\n";
                $this->disconnected = true;
                $this->on_disconnect();
                $this->close();
        }

        public function on_disconnect() {
                echo "[httpServerClient] {$this->remote_address} disconnected\n";
        }
}

ini_set('mbstring.func_overload', '0');
ini_set('output_handler', '');
error_reporting(E_ALL | E_STRICT);
@ob_end_flush();
set_time_limit(0);

$daemon = new socketDaemon();
$server = $daemon->create_server('myServer', 'myServerClient', 0, 8888);
$daemon->process();
Код:
wicked@w:~$ telnet localhost 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
small
Here is your SMALL data ))) Stay online ;-)Connection closed by foreign host.
wicked@w:~$ telnet localhost 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
big
Here is your BIG data ))) Good bye.Connection closed by foreign host.
-~{}~ 08.12.08 19:46:

не учел только то, что после small не надо закрывать сокет
 

MiksIr

miksir@home:~$
Не забудьте только сказать, что, блин, если большая дата не захардкорена в коде, а берется из файла или базы - то на время этих операций по получению данных все остальные клиенты сосут. Так что форк все же тут оправдан... это я тупил немного.
А уж о всяких там утечках памяти и думать нехотца... думаю, что вылезет эта проблема.
Так что первое что стоило бы спросить - какие ваще нагрузки то? Мож там один малый запрос в несколько секунд, а большие и того реже...
 

Vict0r

Новичок
Всем спасибо за помощь! Разобрался я со своим демоном. Обошелся без форканья. Мультиплексирующий сервер - это то, что мне было нужно. Отдельная благодарность Wicked за подсказку ) Покопавшись в исходниках phpsocketdaemon'а, выяснилось, что алгоритм его работы очень прост, и не стоит заморачиваться с форканьем и мертворожденными детьми ;-) Еще раз спасибо всем за участие!
 

Wicked

Новичок
Vict0r
пожалуйста.

но хотелось бы в ответ услышать некоторое резюме насчет:
1) для чего это используется?
2) какие нагрузки?
3) сделал ли ты все сам, или использовал phpsocketdaemon?
 

Vict0r

Новичок
1. Пишу свой HTTP-чат. Задумка серьезная и интересная. Для кросплатформенной совместимости необходимо, чтобы демон чата не только обрабатывал команды, но и выводил содержимое фреймов, иначе в Опере, FireFox'е и других броузерах в JavaScript'е не работает обращение между функциям и переменным, находящимся в разных фреймах.

2. Ожидаемые нагрузки - хотелось бы, чтобы как минимум 100 человек в онлайн общались комфортно. А это десятки запросов "малых" данных в секунду. При этом параллельно должно выводиться содержимое фреймов для только что вошедших в чат пользователей. Пресловутые "большие" запросы )))

3. Сделал все сам. Разобрался в алгоритме работы phpsocketdaemon'а и переписал свой демон подобным образом. Еще раз спасибо за наводку ;-)
 

dimagolov

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

Vict0r

Новичок
dimagolov, поверьте мне, я знаю, о чем говорю ))) Уже определенное время занимаюсь поиском решения этой проблемы.

Прошу прощения за оффтоп, но, если фреймсет выводится с 80 порта, а любой фрейм внутри него (к примеру, фрейм с командами чат-сервера) загружается с порта 8888, то обращаться из этого фрейма к переменным и функциям родительского фреймсета у вас не получится )) В броузере получите нечто вроде "Trying to read protected variable" или "В доступе отказано". Это т.н. проблема "не кликабельных ников" в чате. Некоторые решают ее при помощи специального модуля под Apache, когда все данные грузятся через 80 порт, а запросы к чат-севреру перебрасываются самим Апачем на порт чат-сервера. Но зачем так сложно-то? Когда можно весь фреймсет вывести чат-севрером ;-)
 

dimagolov

Новичок
Ну если порты разные то да, security броузера не пустит.
Вообще-то советую Вам отказаться от frameset и пользовать исключительно iframe.
Во-вторых отдавать все, что надо чату через чат-сервер, то есть к примеру сайт отдаете apache, в нем один iframe чата, причем с порта уже чата, а уже в нем все детские iframe чата. Не такая уж проблема отдать html на пару десятков строчек из файла с дизайном окна чата из чат-сервера и потом свободно через parent.farmes['ohterClindName'] общаться между фреймами чата в JS, чем гонять все через сервер.
 

Vict0r

Новичок
Через iframe не подходит для моего проекта. Вообще, можете глянуть наполовину рабочий движок моего чата. Основные функции работают, но многое еще не дописано. Дизайна как такового нет вообще. Регистрации пока тоже нет. Поэтому, если есть желание, можете зайти как гость. Адрес моего чата - http://phpchat.net.ua/demo/. Буду рад любым отзывам )

P.S.: Уважаемые модераторы, не сочтите за рекламу. Простая демонстрация работы PHP-скрипта.
 

serglt

Анус, ой, Ахтунг
Школьники :)) Чтобы обойти безопасность браузеров во всех фреймах с яваскриптом нуна установить переменную document.domain = "mydomain.com", тоесть сказать яваскрипту что все фреймы с одного домена.Все, проблема решена :) Не надо отдавать все демоном. Скажу еще на заметку, делал чат такой, все же лучше демон не на PHP. Мой висел долго без остановки (около года), тож селект использовался, без форков, но тем не менее на сервере со временем то появлялись то исчезали зомби :). Очень странное поведение. Вот на заметку сцылка первая найденная http://www.devguru.com/Technologies/ecmascript/quickref/doc_domain.html
 

serglt

Анус, ой, Ахтунг
Очень даже поможет, у меня же как то работало, жалко что на готовом примере не могу показать, не выстрелил чат, его и снесли потом, дома где то валяются сырцы, так бы показал. Просто браузер домен:порт понимает как домен, и по этому получается что свойства document.domain разные из за портов, появляются ошибки в безопасности, но этой одной строки достаточно чтоб браузер переубедить.
 

MiksIr

miksir@home:~$
т.е. если я хочу написать кросс-доменный скрипт, мне достаточно в своем скрипте сказать document.domain на чужой домен - и все заработает?
 

serglt

Анус, ой, Ахтунг
Нет, не заработает, это касается только поддоменов и портов

-~{}~ 11.12.08 17:15:

Для тех кто в танчике
http://xmlhttprequest.ru/book/export/html/4 читать с заголовка "Использование наддомена"
А вообще гугл рулит :) Сделал за вас так сказать

-~{}~ 11.12.08 17:50:

Запустили паделку мою http://chat.tusovka.lt/ если кто не верит можете глянуть все работает.
 

MiksIr

miksir@home:~$
Я правильно понял, что
Permission denied to get property Window.loginUser
Permission denied to get property Window.privateOnly
это и есть твои "наддомены"?

-~{}~ 11.12.08 18:12:

PS: тестирование работы "наддоменов" на стенде успешными не были. С удовольствием посмотрел бы на рабочий пример для FF3.
 

serglt

Анус, ой, Ахтунг
http://chat.tusovka.lt/ - сижу в ФФ3 все там работает, черным по русски же написано

Приятным бонусом свойства document.domain является возможность коммуникации между фреймами/ифреймами на одном домене.

То есть, например, если

* во фрейме с адреса http://a.site.com установлен document.domain='site.com',
* на фрейме с адреса http://b.site.com установлен домен document.domain='site.com'
* на фрейме с адреса http://site.com установлен (обязательно!) домен document.domain='site.com'

То эти три фрейма могут свободно общаться посредством javascript и XmlHttpRequest.
Обычно такая коммуникация используется при создании чатов/событий с сервера, когда на site.com находится основной веб-сервер, а на chat.site.com висит чат-демон.
Причем это касается и портов!
 

grigori

( ͡° ͜ʖ ͡°)
Команда форума
демон для 100 пользователей ... из пушки по воробьям
 

dimagolov

Новичок
Таки да, и с портами работает.
port_domain.html:
Код:
<html>
<head>
<title>Top</title>
<script>
document.domain='10.0.0.113';
my_name='TOP';
</script>
</head>
<body>
Hello, it is top<br />
<iframe name= 'First' src='http://10.0.0.113/test/port_frame.html'></iframe>
<iframe name= 'Second' src='http://10.0.0.113:8080/test/port_frame.html'></iframe>
</body>
</html>
port_frame.html
Код:
<html>
<head>
<title>Top</title>
<script>
document.domain='10.0.0.113';
my_name='Frame';
function onloadFunc () {
   alert ("url=" + document.location.href);
   alert ("top.my_name=" + top.my_name);
}
</script>
</head>
<body onload='onloadFunc ();'>
Hello, it is frame<br />
</body>
</html>
была ошибка (domain вместо document.domain) и работало только в ИЕ
 
Сверху