Имеется нагруженный демон на php работает на прием входящих TCP коннектов.
У демона есть свой буфер внутренний равный например 500 мегабайтам ОЗУ, клиенты активно посылают информацию, для быстрого снятия инфы с сокета используется этот буфер, чтобы освободить буфер сокетов, дальше инфа обрабатывается другим демоном который напрямую обращается к памяти.
Пока 500 метров достаточно, но, демон следит, чтобы за 500 метров не вылазила инфа, подумаю реализовать что-то вроде троттлинга для сокета. (На данный момент, там костыль который передает сообщение в управляющий демон об переполнении, и демон отваливается.)
Реализовать буду так: пока буфер внутренний не уменьшится, не читать данные с сокета.
Собственно вопрос, что будет делать сокет когда его буфер закончится? Я так понимаю он отправит клиенту флаг, о том, что не могу принять данные. И клиент при записи в сокет получит сообщение, о том, что запись не возможна. (клиент при этом настроен так, что он будет повторять попытку позже).
В целом мысли верные? Или есть подводные камни?
Спустя 11 минут, 42 секунды (28.11.2011 - 11:02) linker написал(а):
Думается мне, что демон просто упадёт, а клиент отвалится по таймауту.
Спустя 9 минут, 49 секунд (28.11.2011 - 11:12) I++ написал(а):
Т.е если TCP буфер сокета полностью заполнится, демон отвалится? А клиент отвалится по таймауту? Странно...
Спустя 1 час, 4 минуты, 25 секунд (28.11.2011 - 12:17) Zerstoren написал(а):
один раз у меня демон зациклился и сожрал 2.5 гб оперативы.
Другой демон, который следит за тем как другие апликухи жрут оперативу рубанул его с словами "Убито".
Linker, какому именно таймауту?
Другой демон, который следит за тем как другие апликухи жрут оперативу рубанул его с словами "Убито".
Linker, какому именно таймауту?
Спустя 29 минут, 38 секунд (28.11.2011 - 12:46) I++ написал(а):
Цитата (Zerstoren @ 28.11.2011 - 13:17) |
один раз у меня демон зациклился и сожрал 2.5 гб оперативы. Другой демон, который следит за тем как другие апликухи жрут оперативу рубанул его с словами "Убито". Linker, какому именно таймауту? |
Вопрос не в количестве памяти, а в реализация пропуска чтения сокета если буфер переполняется, для чего? Допустим приложение не успевает обрабатывать данные, которые приходят с сокета, и внутренний буфер (реализован в виде стека) не успевает уменьшатся (принцип: последний зашел, первый вышел.). Как я понимаю в теории, если буфер сокета будет полностью заполнен, то клиенту пишущему в сокет будет отправлена мессага, на уровне tcp/ip с просьбой не писать в сокет TCP. Вот за это время пока, клиент не пишет новую порцию данных, мы высвобождаем внутренний буфер приложения с уже поступившими данными, и когда буфер становится меньше предела, начинаем читать сокет, принимая данные и занося их в буфер приложения, как только освободится TCP буфер, удаленный клиент вновь начнет передачу следующих порций данных.
Вот, собственно, что было интересно, в PHP такое прокатит? Есть у кого опыт с подобным?
Спустя 30 минут, 30 секунд (28.11.2011 - 13:17) linker написал(а):
Само ничего не отсылается и само ничего не решает. Это делается либо на уровне драйверов или сетевой карты с проводами, либо архитектурно на прикладном уровне, собственно программистом.
Спустя 2 часа, 36 минут, 9 секунд (28.11.2011 - 15:53) I++ написал(а):
А подробнее можно? Конкретно касательно, поведения ответа сервера на попытки записи в сокет, где буфер чтения переполнен. У клиента функция socket_write должна вернуть либо 0 либо еррор. В теории, а на практике?
Спустя 11 минут, 29 секунд (28.11.2011 - 16:04) linker написал(а):
Да должна вернуть, но это не будет фактом переполнения буфера, причин может быть куча.
Спустя 34 минуты, 56 секунд (28.11.2011 - 16:39) I++ написал(а):
Ясно буду экспериментировать. Как говорится в ногах правды нет, она где то между. Напишу клиент и сервер, попробую у сервера читать медленно сокет, а на клиенте обильно слать, так, чтобы успеть заполнить полностью на сервере буфер приема, и посмотрим, что произойдет. Надеюсь мои догадки верны, будет чудненько.
Спустя 1 час, 43 минуты, 35 секунд (28.11.2011 - 18:23) I++ написал(а):
Собственно как и в сях в php так же.
Получаю ошибку в php: PHP Warning: socket_write(): unable to write to socket [10035]: Операция на незаблокированном сокете не может быть завершена немедленно.
Developer suggestions: Every application that uses non-blocking sockets must be prepared for this error on any call to the functions mentioned below. For instance, even if you request to send() a few bytes of data on a newly created TCP connection, send() could fail with WSAEWOULDBLOCK (if, say, the network system has a TCP slow-start algorithm implemented). The WSAAsyncSelect() FD_WRITE event is specifically designed to notify an application after a WSAEWOULDBLOCK error when buffer space is available again so send() or sendto() should succeed.
Вот код тестирования:
Получаю ошибку в php: PHP Warning: socket_write(): unable to write to socket [10035]: Операция на незаблокированном сокете не может быть завершена немедленно.
Developer suggestions: Every application that uses non-blocking sockets must be prepared for this error on any call to the functions mentioned below. For instance, even if you request to send() a few bytes of data on a newly created TCP connection, send() could fail with WSAEWOULDBLOCK (if, say, the network system has a TCP slow-start algorithm implemented). The WSAAsyncSelect() FD_WRITE event is specifically designed to notify an application after a WSAEWOULDBLOCK error when buffer space is available again so send() or sendto() should succeed.
Вот код тестирования:
Свернутый текст
$obj = new proc_interface('./test.php');
if(!$obj->create_node())
{
echo 'timeout';
exit;
}
while(1)
{
$response = $obj->send_command(mt_rand(100,1000));
if($response === 'socket_closed')
exit;
usleep(100);
}
$obj = new proc_interface();
$obj->attach();
while(1)
{
$response = $obj->get_command();
var_dump($response)."\n";
if($response === 'socket_closed')
exit;
sleep(1);
}
<?php
class proc_interface
{
private $handle_connection;
private $handle_client_connection;
private $handle_current_connection;
private $socket_port;
private $socket_tcp_recvspace = 1024;
private $socket_type = 0;
private $file_name = null;
function __construct($file_name = null)
{
$this->file_name = $file_name;
}
public function send_command($cmd)
{
if($this->socket_type === 1)
$this->handle_current_connection = $this->handle_client_connection;
else
$this->handle_current_connection = $this->handle_connection;
if($this->sock_error_trigger())
return 'socket_closed';
$this->write_socket($cmd);
}
public function get_command()
{
if($this->socket_type === 1)
$this->handle_current_connection = $this->handle_client_connection;
else
$this->handle_current_connection = $this->handle_connection;
if($this->sock_error_trigger())
return 'socket_closed';
$read = array($this->handle_current_connection);
$num_changed_sockets = socket_select($read, $write = NULL, $except = NULL, 0);
if($num_changed_sockets === false)
return false;
else if($num_changed_sockets === 0)
return false;
return $this->read_socket();
}
public function create_node()
{
$this->handle_connection = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_bind($this->handle_connection, 'localhost', 0);
$this->socket_tcp_recvspace = socket_get_option($this->handle_connection, SOL_SOCKET, SO_RCVBUF);
socket_getsockname($this->handle_connection, $socket_address, $this->socket_port);
socket_set_nonblock($this->handle_connection);
socket_listen($this->handle_connection);
$this->socket_type = 1; // Server
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN')
pclose(popen('start php.exe '.$this->file_name.' '.$this->socket_port, 'r'));
else
pclose(popen('php '.$this->file_name.' '.$this->socket_port.' > /dev/null &', 'r'));
$err_counter = 0;
while(1)
{
if(($tmp_client = @socket_accept($this->handle_connection)) !== false)
{
$this->handle_client_connection = $tmp_client;
break;
}
else
{
$err_counter++;
usleep(10000);
}
// TimeOut
if($err_counter === 500)
return false; // 10000 * 500 = 5 sec.
}
return true;
}
public function attach()
{
global $argv;
$this->handle_connection = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_bind($this->handle_connection, 'localhost', 0);
$this->socket_tcp_recvspace = socket_get_option($this->handle_connection, SOL_SOCKET, SO_RCVBUF);
socket_connect($this->handle_connection, 'localhost', $argv[1]);
socket_set_nonblock($this->handle_connection);
$this->socket_type = 2; // Client
}
private function read_socket()
{
$return_data = trim(socket_read($this->handle_current_connection, $this->socket_tcp_recvspace, PHP_NORMAL_READ));
if($return_data === false)
{
return false;
}
else if($return_data === '')
{
echo 'error read';
return false;
}
return $return_data;
}
private function write_socket($cmd)
{
socket_write($this->handle_current_connection, $cmd."\n");
}
function __destruct()
{
socket_close($this->handle_connection);
}
private function sock_error_trigger()
{
$err_num = socket_last_error($this->handle_current_connection);
if($err_num === 0) // *nix, Windows (In non-blocking mode when the connection is not established, but we're trying to read data from socket)
return false;
else if($err_num === 35) // *nix (tested on FreeBSD)
return false;
else if($err_num === 10035) // Windows (tested on Windows 7)
return false;
else
return true;
}
}
?>