контроль соединения на сокетах

nighthunter

Новичок
контроль соединения на сокетах

кусок кода
PHP:
if (($this->sock = @socket_create (AF_INET, SOCK_STREAM, 0)) < 0) {
    echo "socket_create() failed: reason: " . socket_strerror ($this->sock) . "\n"; exit(1);
}
socket_set_option($this->sock, SOL_SOCKET, SO_REUSEADDR, 1); 
if (($ret = @socket_bind ($this->sock, $this->host, $this->port)) < 0) {
    echo "socket_bind() failed: reason: " . socket_strerror ($ret) . "\n"; exit(1);
}
if (($ret = @socket_listen ($this->sock, 5)) < 0) {
    echo "socket_listen() failed: reason: " . socket_strerror ($ret) . "\n"; exit(1);
}
socket_set_nonblock($this->sock);

while ($this->run) {
    usleep($this->delay);
     if (($msgsock = @socket_accept($this->sock))) {
            socket_set_nonblock($msgsock);
            socket_set_option($msgsock, SOL_SOCKET, SO_KEEPALIVE, 1);

      }
      if ($tmp = @socket_read($msgsock, 1024)) {
            $buffer .= $tmp;
      }
}
вопрос: как определить что соединение оборвалось? socket_read и socket_last_error никак не реагируют на обрыв.
 

Dl

Новичок
Re: контроль соединения на сокетах

Socket_recv(), там в каментах есть пример с возвращаемым значением при дисконнекте. Но не проверял...
 

nighthunter

Новичок
если ничего нет в сокете, то эта функция возвращает false. то же самое при разрыве связи. т.е. никакой индикации о разрыве нет(((
 

Dl

Новичок
Ну, у меня работает до тех пор, пока прокси 502-ю ошибку не выдаст... Хотя в сокете тоже ничего нет...
Может, имеет смысл убрать "собаки"?
 

voodoo

Новичок
а socket_last_error не поможет?
вообще, для неблокирующего сокета я бы сначала делал socket_select, тогда если socket_read вернет false - это дисконнект.
вроде так.
 

nighthunter

Новичок
вот что я пробовал
PHP:
if ($tmp = @socket_read($msgsock, 1024)) {
    $buffer .= $tmp;
}
else{
    echo socket_last_error($msgsock)." ".socket_strerror(socket_last_error($msgsock));
}
если нечего читать из сокета, то возвращается

10035 Операция на незаблокированном сокете не может быть завершена немедленно.

то же самое и если оборвалась связь
 

voodoo

Новичок
А, сорри, пропустил про socket_last_error в первом сообщении.
Ну тогда socket_select
Причем т.к. один клиент, то без таймаута. Ну и можно проверять сразу read/except массивы

на php.net/socket_select в комментариях есть готовый код
 

nighthunter

Новичок
на самом деле это не весь код, я его упростил т.к. думал что этого куска достаточно.

PHP:
<?

error_reporting(E_ALL);

// Класс, занимающийся обработкой конкретного клиента
class phpServerThread {

    var $buffer;
    var $lastActivity;
    var $socket;

    function phpServerThread($socket) {
        $this->socket = $socket;
        $this->buffer = '';
        $this->lastActivity = time();
    }

    function response($str) {
        $mess = "Re: ".$str."\n";
        socket_write ($this->socket, $mess, strlen ($mess));
    }

    function destroy() {
        @socket_close($this->socket);
    }
}

class phpServerMultiTask {

    var $host;
    var $port;
    var $run;
    var $delay;
    var $sock;

    function phpServerMultiTask($host, $port, $delay = 100000) {
        $this->host = $host;
        $this->port = $port;
        $this->delay  = $delay;
        $this->run  = true;

        if (!in_array("sockets", get_loaded_extensions())) {
            echo "--enable--sockets REQUIRED \n";
        }
        error_reporting (E_ALL);
        @set_time_limit (0);

        if (($this->sock = @socket_create (AF_INET, SOCK_STREAM, 0)) < 0) {
            echo "socket_create() failed: reason: " . socket_strerror ($this->sock) . "\n"; exit(1);
        }
        if (($ret = @socket_bind ($this->sock, $this->host, $this->port)) < 0) {
            echo "socket_bind() failed: reason: " . socket_strerror ($ret) . "\n"; exit(1);
        }
        if (($ret = @socket_listen ($this->sock, 5)) < 0) {
            echo "socket_listen() failed: reason: " . socket_strerror ($ret) . "\n"; exit(1);
        }
        socket_set_nonblock($this->sock);
    }

    function run() {
        $pool = array();
        while ($this->run) {
            usleep($this->delay);

            // Принимаем клиентов, которые стоят в очереди
            $currentTime = time();
            if (($msgsock = @socket_accept($this->sock))) {
                socket_set_nonblock($msgsock);
                $pool[] =& new phpServerThread($msgsock);
            }

            // Обслуживаем клиентов
            foreach ($pool as $key => $client) {

                // Читаем все что они хотят сказать
                if ($tmp = @socket_read($pool[$key]->socket, 1024)) {
                    $pool[$key]->buffer .= $tmp;
                    $pool[$key]->lastActivity = $currentTime;
                }

                // Если в буфере есть законченая фраза - обрабатываем
                if ($pos = strrpos($pool[$key]->buffer, "\n")) {
                    $toSay = substr($pool[$key]->buffer, 0, $pos);
                    $pool[$key]->response($toSay);
                    // очищаем буфер
                    $pool[$key]->buffer = substr($pool[$key]->buffer, $pos + 1);
                }  
            }
        }
        echo "Server shutdown \n";
    }
}

$a = new phpServerMultiTask("localhost", "1128");
$a->run();
?>
за основу взят код отсюда и немного переделан
http://phpclub.ru/talk/showthread.php?s=&threadid=91100&highlight=phpServerThread

-~{}~ 08.02.07 14:44:

в примере на php.net/socket_select приведен пример для для броадкаста . а у меня скрипт просто с мультипоточностью (если это можно так назвать).
 

Dl

Новичок
Можно посоветовать убедиться, действительно ли в $pool записываются экземпляры phpServerThread...
 

nighthunter

Новичок
да, 100%. прошагал в зенд студио.

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

voodoo

Новичок
где тут потоки?? а пример, кстати, очень похожий, разница лишь в том что отправляется клиенту (эхо vs броадкаст).

в общем вместо foreach ($pool as $key => $client) { нужно брать список полученый из socket_select.

или зафигачивать в socket_select полный список клиентов, потом в этом цикле (foreach $pool )
1. перед чтением проверять, есть ли клиент-сокет в read-списке,
2. перед отправкой проверять, есть ли он в write-списке.

Да, еще такой момент:
// Принимаем клиентов, которые стоят в очереди
это неправда :) Правильно тут будет -- принимаем 1-го клиента, тут только один accept
Так что при большом кол-ве соединений очередь может вырасти большой. Имеет смысл делать аксепты пока не вернет false (ну и можно ограничить сверху, напр. макс 20 штук за цикл)
 

nighthunter

Новичок
сделал так
PHP:
function run() {
        $pool = array();

        $clients = array($this->sock);

        while ($this->run) {
            usleep($this->delay);
            
            $read = $clients;

            // Принимаем клиентов, которые стоят в очереди
            $currentTime = time();


            if (socket_select($read, $write = NULL, $except = NULL, 0) < 1)
            continue;

            if (in_array($this->sock, $read)) {

                $clients[] = $newsock = socket_accept($this->sock);
                
                socket_set_nonblock($newsock);
                socket_set_option($newsock, SOL_SOCKET, SO_KEEPALIVE, 1);
                $pool[] =& new phpServerThread($newsock);
                
                $key = array_search($this->sock, $read);
                unset($read[$key]);
            }

            // Обслуживаем клиентов
            foreach ($pool as $key => $client) {

                // Читаем все что они хотят сказать
                if ($tmp = @socket_read($pool[$key]->socket, 1024)) {
                    $pool[$key]->buffer .= $tmp;
                    $pool[$key]->lastActivity = $currentTime;
                }

                // Если в буфере есть законченая фраза - обрабатываем
                if ($pos = strrpos($pool[$key]->buffer, "\n")) {
                    $toSay = substr($pool[$key]->buffer, 0, $pos);
                    $pool[$key]->response($toSay);
                    // очищаем буфер
                    $pool[$key]->buffer = substr($pool[$key]->buffer, $pos + 1);
                }  
            }
        }
        echo "Server shutdown \n";
    }
}
если я правильно понял, то socket_select возвращает число > 0 если произошло изменение состояния сокета. так вот, при обрыве связи состояние не меняется.
 

voodoo

Новичок
блин. вроде по русски написано
1. перед чтением проверять, есть ли клиент-сокет в read-списке

Не надо читать из сокета, если ему нечего сказать. Это проверяется socket_select-ом. Если есть что-то, то читаем. И вот тут, если false, значит дисконнект.
 

nighthunter

Новичок
только не надо нервничать

сделал совсем как в примере

PHP:
function run() {
        
        $clients = array($this->sock);
        
        while (true) {
            
            $read = $clients;
            
            if (socket_select($read, $write = NULL, $except = NULL, 0) < 1)
            continue;
            
            if (in_array($this->sock, $read)) {

                $clients[] = $newsock = socket_accept($this->sock);

                $key = array_search($this->sock, $read);
                unset($read[$key]);
            }
                        
            foreach ($read as $read_sock) {
                $data = @socket_read($read_sock, 1024);
          
                if ($data === false) {

                    $key = array_search($read_sock, $clients);
                    unset($clients[$key]);
                    echo "client disconnected.\n";

                    continue;
               }
               echo $data."\n";
            }
        }
        echo "Server shutdown \r\n";
    }
}
если соединение оборвалось, то в $data - пустая строка, но никак не === false. пока сделал проверку if (!$data)
 

voodoo

Новичок
я просто в пхп с сокетами не работал, но вроде в доках пишут
socket_read() returns the data as a string on success, or FALSE on error (including if the remote host has closed the connection).
поэтому я про false и сказал. хотя ниже там и про пустую строчку.
Видимо, для надежности, надо проверять и то и другое.

Ну и еще теперь нет связи с $pool
поэтому я и предлагал
внутри foreach ($pool as $key => $client) делать сначала проверку in_array( pool[key]->socket , $ead), и если есть, то уже socket_read.

Но в целом, теперь оно делает что должно?
 

nighthunter

Новичок
если законектится телнетом, и потом просто закрыть окно, то дисконект отлавливается, а если просто оборвать связь(например выдернуть шнур) - то нет, socket_select не реагирует на изменение
 

voodoo

Новичок
ну это-то понятно, так tcp/ip устроен, в этой ситуации клиент нотификацию о разрыве (FIN) не шлет.
Ошибка в этом сокете проявится при попытке что-нибудь отправить (в частности, при отправке кипэлайв пакета)

И, кстати, если шнур воткнуть то коннектом снова можно пользоваться. Если ни клиент ни сервер не успели закрыть
 

nighthunter

Новичок
так я пробовал
PHP:
socket_set_option($newsock, SOL_SOCKET, SO_KEEPALIVE, 1);
по идее тогда должны контролироваться такие ситуации
 

voodoo

Новичок
по идее тогда жди 2 часа :)
SO_KEEPALIVE это просто флаг, слать-не слать.
на линуксе еще надо
Код:
       TCP_KEEPCNT
              The maximum number of keepalive probes TCP should send before dropping the connection.  This option should not be used in  code
              intended to be portable.

       TCP_KEEPIDLE
              The  time  (in  seconds)  the  connection needs to remain idle before TCP starts sending keepalive probes, if the socket option
              SO_KEEPALIVE has been set on this socket.  This option should not be used in code intended to be portable.

       TCP_KEEPINTVL
              The time (in seconds) between individual keepalive probes.  This option should not be used in code intended to be portable.
Только непонятно где это будет работать, а где нет. ставить на IPPROTO_TCP

Ну и кипэлайв-проверка SIGPIPE шлет если ответа нет, так что надо еще php.net/pcntl-signal попробовать (хотя и сокет должен быть доступен для чтения-посылки, так что в селект-е должен быть виден)
 
Сверху