Форум Flasher.ru
Ближайшие курсы в Школе RealTime
Список интенсивных курсов: [см.]  
  
Специальные предложения: [см.]  
  
 
Блоги Правила Справка Пользователи Календарь Сообщения за день
 

Вернуться   Форум Flasher.ru > Flash > Серверные технологии и Flash

Версия для печати  Отправить по электронной почте    « Предыдущая тема | Следующая тема »  
Опции темы Опции просмотра
 
Создать новую тему Ответ
Старый 09.04.2013, 16:51
wolfenstein вне форума Посмотреть профиль Отправить личное сообщение для wolfenstein Найти все сообщения от wolfenstein
  № 1  
Ответить с цитированием
wolfenstein
 
Аватар для wolfenstein

Регистрация: Apr 2013
Сообщений: 8
По умолчанию Сокет-сервер php: как удалить из списка отключившихся клиентов

Здравствуйте,

Подскажите как можно удалить из массива отключившихся клиентов от сокет-сервера.
А то почему-то список удаляемых клиентов совсем не формируется, а почему, я никак понять не могу. Подскажите, что нетак?

Вот мой сокет-сервер:
PHP код:
<?php
require_once('SocketInstructName.php');
require_once(
'ConfigureVars.php');
// РћС‚образить РІСЃРµ РѕС€РёР±РєРё РЅР° РєРѕРЅСЃРѕР»СЊ
error_reporting(E_ALL|E_STRICT);
// Р Р°Р·СЂРµС€РёС‚СЊ СЃС†РµРЅР°СЂРёСЋ Р¶РґР°С‚СЊ СЃРѕРµРґРёРЅРµРЅРёСЏ Р±РµСЃРєРѕРЅРµС‡РЅРѕ
set_time_limit(0);
//Включение РЅРµСЏРІРЅРѕР№ РѕС‡РёСЃС‚РєРё РѕС‚ключает Р±СѓС„еризацию РІС‹РІРѕРґР°, С‚екущее СЃРѕРґРµСЂР¶РёРјРѕРµ Р±СѓС„ера РІС‹РІРѕРґР° Р±СѓРґРµС‚ РѕС‚правлено, РєР°Рє РїСЂРё РІС‹Р·РѕРІРµ ob_end_flush().
ob_implicit_flush();


/*///////////////////////////////////////

    Сокет-сервер

//////////////////////////////////////*/
class SocketServer
{
    private 
$address;
    private 
$port;
    private 
$sock;
    
    
// Объект выборки из сокета
    
private $objSelect// = (object) array();
    /*/
        read - сокеты, доступные для чтения
        write - сокеты, доступные для записи
        except - сокеты, вернувшие исключения (ошибку)
    /*/
    
    // Количество читаемых байт за раз
    
private $lenRead 4095;
    
// Флаг чтения данных
    
private $flagRead 0;
    
    
// Передаваемый метод обработки данных
    
private $classExecute;
    
// Метод, обработки данных
    
private $executeFunction 'ExecuteFunction';
    
    
//////////////////////////////////////////
    
    // Клиенты: одномерный массив, содержащий в себе object
    
private $clients;    
    
/*/
        sock:socket - сокетное соединение
        instruct: SocketInstructName - флаг разрешения отправки данных от других сокетов
        address:string - ип-адрес
        port:int - порт
        lang:string - язык
        bufRead:String - Буфер данных, прочитанных из сокета
        bufWrite:Array - Буфер данных, готовых к записи в сокет
        
        chr(0) - нулевой байт
    /*/ 
    // Массив указателей на сокет
    
private $sockets_ln;
    
    
//////////////////////////////////////////
    
    // Конструктор
    
public function __construct()
    {
        
$this->InitSocketServer();
        
$this->BuilderSocket();
    }
    
    
// Устанавливает функцию обработки данных
    
public function ClassExecute($classExecute)
    {
        
$this->classExecute = new $classExecute();
    }
        
    
// Запуск цикла прослушивающего и обрабатывающего входые и выходные данные сокета
    
public function RunInLoop()
    {
        while (
true)
        {
            
//Проверяем есть ли новые подключения
            
if ($this->IsNewSocketConnect())
            {
                
// Добавляем в массив подключившегося собеседника
                
$this->NewClientConnect();
            }
            
            if (
count($this->clients))
            {
                
//Делаем выборку из сокета
                
if ($this->IsSelect())
                {
                    
// Исключения скокетов
                    
if (is_array($this->objSelect->{ 'except' } ))
                    {
                        
$this->CloseClientsSocket($this->objSelect->{ 'except' } );
                    }
                    
// Чтение из сокетов и отключаем отключившихся
                    
if (is_array($this->objSelect->{ 'read' } ))
                    {
                        
$this->CloseClientsSocket($this->ReadClientsSocket($this->objSelect->{ 'read' }));
                    }
                    
// Запись в сокеты и отключаем отключившихся
                    
if (is_array($this->objSelect->{ 'write' } ))
                    {
                        
$this->CloseClientsSocket($this->WriteClietnsSocket($this->objSelect->{ 'write' }));
                    }
                    
// Обработка данных
                    
$this->DataExecute();
                    
// Удаление клиентов из массива
                    
$this->DeleteClientsArray();
                }
            }
            
            
        } 
// конец цикла
    
// конец метода
    
    // Инициализируем сокет
    
private function InitSocketServer()
    {
        
$this->address gethostbyname(ConfigureVars::s_host);
        
$this->port ConfigureVars::s_port;
        
$this->clients = array();
        
$this->sockets_ln = array();
        
$this->objSelect = (object) array();
        
$this->objSelect->{ 'read' } = null;
        
$this->objSelect->{ 'write' } = null;
        
$this->objSelect->{ 'except' } = null;
    }
    
    
// Создаём сокет
    
private function BuilderSocket()
    {
        do
        {
            
$flagNoNext false;
            
// Создаём сокет
            
if (($this->sock socket_create(AF_INETSOCK_STREAMSOL_TCP)) === false
            {
                echo 
'socket_create()'.PHP_EOL;
                echo 
'Не удалось создать сокет:'.PHP_EOL;
                echo 
socket_last_error().'# '.socket_strerror(socket_last_error()) . PHP_EOL;
            }

            
// Устанавливаем опцию на сокет
            
if (!socket_set_option($this->sockSOL_SOCKETSO_REUSEADDR1)) 
            {
                echo 
'socket_set_option'.PHP_EOL;
                echo 
'Не удалось установить опцию на сокете:'.PHP_EOL;
                echo 
socket_last_error().'# '.socket_strerror(socket_last_error()) . PHP_EOL;
            }

            
// Переводим сокет в неблокирующий режим
            
if (!socket_set_nonblock($this->sock)) 
            {
                echo 
'socket_set_nonblock()'.PHP_EOL;
                echo 
'Не удалось перевести сокет в неблокирующий режим'.PHP_EOL;
                echo 
socket_last_error().'# '.socket_strerror(socket_last_error()).PHP_EOL;
            }

            
// Привязка сокета к адресу и порту
            
if (@socket_bind($this->sock$this->address$this->port) === false
            {
                echo 
'socket_bind()'.PHP_EOL;
                echo 
'Не удалось прявязать сокет к адресу:порту'.PHP_EOL;
                echo 
$this->address.':'.$this->port.PHP_EOL;
                echo 
socket_last_error($this->sock).'# '.socket_strerror(socket_last_error($this->sock)) . PHP_EOL;
                
                
// Такой адрес:порт сокета уже используется
                
if (socket_last_error($this->sock) === EADDRINUSE)
                {
                    echo 
'Такой адрес:порт сокета уже используется'.PHP_EOL;
                    
socket_close($this->sock);
                    echo 
'Уничтожаем сокет и создаём его снова'.PHP_EOL;
                    
sleep(ini_get('default_socket_timeout'));
                    
$flagNoNext true;
                }
            }

        } while (
$flagNoNext);

        
// Слшушаем сокет для максимально возможного количества подключений
        
if (socket_listen($this->sockSOMAXCONN) === false
        {
            echo 
'socket_listen()'.PHP_EOL;
            echo 
'Не удалось установить слушатель сокета на максимально возможное количество подключений'.PHP_EOL;
            echo 
socket_last_error($this->sock).'# '.socket_strerror(socket_last_error($this->sock)) . PHP_EOL;
        }
    }
    
    
/*///////////////////////////////////////
        Далее методы в цикле
    //////////////////////////////////////*/
    
    // Обработка данных сокета
    
private function DataExecute()
    {
        if (isset(
$this->classExecute) && method_exists($this->classExecute$this->executeFunction))
        {
            if (
is_array($this->clients))
            {
                
$clients $this->classExecute->{$this->executeFunction}($this->clients);
                if (
count($this->clients) === count($clients))
                    
$this->clients $clients;
            }
        }
        else
        {
            
$i count($this->clients);
            while (--
$i > -1)
            {
                
$this->clients[$i]->{ 'bufRead' } = '';
            }
        }
    }
    
    
// Преобразование массива объектов-сокетов в массив сокетов
    
private function ObjToArr()
    {
        
$arrSockets array_merge($this->sockets_ln);
        return 
$arrSockets;
    }
    
    
// Преобразование сокетов в ключи
    
private function ArrToKey($sockets)
    {
        if (
is_array($sockets))
        {
            
$keySocket array_keys(array_intersect$this->sockets_ln $sockets ));
            return 
$keySocket;
        }
        else return 
null;
    }
    
    
// Проверяет есть ли новые подключения к сокету
    
private function IsNewSocketConnect()
    {
        
$readS = array();
        
$readS[] = $this->sock;
        
$writeS = array();
        
$exceptS = array();
        
        if ((
$status socket_select($readS$writeS$exceptS5)) === false)
        {
            echo 
'socket_select()'.PHP_EOL;
            echo 
'Не удалось выполнить выборку нового подключения'.PHP_EOL;
            echo 
socket_last_error().'# '.socket_strerror(socket_last_error()) . PHP_EOL;            
            return 
false;
        }
        elseif(
$status 0)
        {
            return 
true;
        }
        else return 
false;
    }
    
    
// Подключение нового клиента на сокет
    
private function NewClientConnect()
    {
        if ((
$msgsock socket_accept($this->sock)) === false)
        {
            echo 
'socket_accept()'.PHP_EOL;
            echo 
'Не удалось добавить нового клиента на сокет'.PHP_EOL;
            echo 
socket_last_error().'# '.socket_strerror(socket_last_error()) . PHP_EOL;            
        }
        else
        {
            if(
socket_getpeername$msgsock $address $port))
            {
                
$objClient = (object) array();
                
$objClient->{'sock'} = $msgsock;
                
$objClient->{'instruct'} = SocketInstructName::_new;
                
$objClient->{'address'} = $address;
                
$objClient->{'port'} = $port;
                
$objClient->{'bufRead'} = '';
                
$objClient->{'bufWrite'} = array();
                
array_push($this->clients$objClient);
                
$this->sockets_ln[] = &$objClient->{'sock'};
            }
        }
    }
    
    
// Выборка сокетов селектом: доступных для чтения, записи, исключения (ошибка)
    
private function IsSelect()
    {
        
$readSock $this->ObjToArr();
        
$writeSock $this->ObjToArr();
        
$exceptSock $this->ObjToArr();
        if((
$status socket_select($readSock$writeSock$exceptSock5)) === false)
        {
            echo 
'socket_select()'.PHP_EOL;
            echo 
'Не удалось выполнить выборку клиентов сокета'.PHP_EOL;
            echo 
socket_last_error().'# '.socket_strerror(socket_last_error()) . PHP_EOL;                        
        }
        elseif(
$status >0)
        {
            
$this->objSelect->{ 'read' } = ($readSock) ? $this->ArrToKey($readSock):null;
            
$this->objSelect->{ 'write' } = ($writeSock) ? $this->ArrToKey($writeSock):null;
            
$this->objSelect->{ 'except' } = ($exceptSock) ? $this->ArrToKey($exceptSock):null;
            return 
true;
        }
        else return 
false;
    }

    
// Читаем сокет
    
private function ReadClientsSocket($keySocket)
    {
        
        if(
is_array($keySocket) && ($i count($keySocket)))
        {
            
// Ключи клиентов на отключение сокетов
            
$keyClietnDel = array();
            
// Флаг отключения текущего сокета в цикле
            
$flagDel;
            
            while(--
$i > -1)
            {
                
$flagDel false;
                if (
is_resource($this->sockets_ln[$keySocket[$i]]))
                {
                    if((
$this->clients[$i]->{ 'instruct' } === SocketInstructName::_new) ||
                        (
$this->clients[$i]->{ 'instruct' } === SocketInstructName::_established)
                    )
                    {
                        
$countByte socket_recv $this->sockets_ln[$keySocket[$i]] , $buf $this->lenRead $this->flagRead );
                        if(
$countByte === false)
                        {
                            
$flagDel true;
                        }
                        elseif(
$countByte 0)
                        {
                            
// Читаем данные
                            
$this->clients[$keySocket[$i]]->{ 'bufRead' } .= $buf;
                            
// для тестирования
                            
echo $keySocket[$i].' Read: '.$this->clients[$keySocket[$i]]->{ 'bufRead' }.PHP_EOL;
                        }
                    }
                }
                else
                {
                    
$flagDel  true;
                }
                
                if (
$flagDel)
                {
                    
// Отключаем собеседника
                    
array_push($keyClietnDel$keySocket[$i]);
                    
$this->InstructionClientDelete($keySocket[$i]);
                    
// для тестирования
                    
echo $keySocket[$i] .' Read Close'.PHP_EOL;
                }
            } 
// цикл закончился
            
            
return (count($keyClietnDel)) ? $keyClietnDel:null;
            
        }
        else return 
null;
            
    }
    
    
// Пишем в сокет
    
private function WriteClietnsSocket($keySocket)
    {
        if(
is_array($keySocket) && ($i count($keySocket)))
        {
            
$keyClietnDel = array();
            
$flagDel;
        
            while(--
$i > -1)
            {
                
$flagDel false;
                if (
is_resource($this->clients[$keySocket[$i]]->{ 'sock' }))
                {
                    if ( (
$this->clients[$keySocket[$i]]->{ 'instruct' } === SocketInstructName::_policy) ||
                        (
$this->clients[$keySocket[$i]]->{ 'instruct' } === SocketInstructName::_established)
                    )
                    {
                        
$bufWrite array_shift($this->clients[$keySocket[$i]]->{ 'bufWrite' } );
            
                        if(isset(
$bufWrite) )
                        {
                            
$countByte socket_send($this->clients[$keySocket[$i]]->{'sock'}, $bufWritestrlen($bufWrite), 0);
                            if(
$countByte === false)
                            {
                                
$flagDel true;
                            }
                            elseif(
$countByte 0)
                            {
                                
// Узнаём сколько записано данных и вычитаем это значение
                                
if($countByte strlen($bufWrite) )
                                {
                                    
$bufWrite substr($bufWrite$countByte);
                                    
array_unshift($this->clients[$keySocket[$i]]->{'bufWrite'} , $bufWrite);
                                }
                        
                                
// Передача файла политик выполнена, выполняем отключение
                                
if( (count($this->clients[$keySocket[$i]]->{'bufWrite'}) === 0) && 
                                    (
$this->clients[$keySocket[$i]]->{ 'instruct' } === SocketInstructName::_policy)
                                )
                                {
                                    
$flagDel true;
                                }
                        
                                
// для тестирования
                                
echo $keySocket[$i].' Write: '.$bufWrite.PHP_EOL;
                            }
                        }
                    }
                
                }
                else
                {
                    
$flagDel true;
                }
            
                if(
$flagDel)
                {
                    
// Добавляем собеседника в список отключаемых
                    
array_push($keyClietnDel$keySocket[$i]);
                    
$this->InstructionClientDelete($keySocket[$i]);
                    
// для тестирования
                    
echo $keySocket[$i] .' Write Close'.PHP_EOL;
                }
            } 
// цикл закончился
            
            
return (count($keyClietnDel)) ? $keyClietnDel:null;
        }
        else return 
null;
    }
    
    
// Изменение инструкции действия при закрытии сокета
    
private function InstructionClose($i)
    {
        switch(
$this->clients[$i]->{ 'instruct' })
        {
            case 
SocketInstructName::_established :
                
$this->clients[$i]->{ 'instruct' } = SocketInstructName::_closeMess;
                break;
            default :
                
$this->clients[$i]->{ 'instruct' } = SocketInstructName::_close;
        }
    }
    
    
// Закрываем сокет клиента
    
private function CloseClientsSocket($keyClients)
    {
        
// для тестирования
        
echo 'CloseClientsSocket массив ли: '.(int) is_array($keyClients).PHP_EOL;
        
        if(
is_array($keyClients) && ($i count($keyClients))  )
        {
            while(--
$i > -1)
            {
                if(    (
$this->clients[$keyClients[$i]]->{'instruct'} != SocketInstructName::_close) &&
                    (
$this->clients[$keyClients[$i]]->{'instruct'} != SocketInstructName::_closeMess)
                )
                {
                    if(!
socket_shutdown($this->sockets_ln[$keyClients[$i]], 2))
                    {
                        echo 
'socket_shutdown()'.PHP_EOL;
                        echo 
'Не удалось завершить работу сокета на получение и отправку данных'.PHP_EOL;
                        echo 
socket_last_error().'# '.socket_strerror(socket_last_error()) . PHP_EOL;            
                    }
                    
$this->InstructionClose($keyClients[$i]);
                    
socket_close($this->clients[$keyClients[$i]]->{'sock'});
                    
// для тестирования
                    
echo $keyClients[$i] .' Close'.PHP_EOL;
                }
                
//array_splice($this->clients, $keyClients[$i], 1 );
                
            
}
            
            

        }
    }
    
    
// Очистка массива от отключившихся клиентов
    
private function DeleteClientsArray()
    {
        
$flagDel false;
        if(
$i count($this->clients)) 
        while(--
$i > -1)
        {
            if(
$this->clients[$i]->{'instruct'} === SocketInstructName::_close)
            {
                unset(
$this->sockets_ln[$i] , $this->clients[$i]); // дырявит массив
                
$flagDel true;
            }
        }
        
        if(
$flagDel)
        {
            
$this->sockets_ln array_values($this->sockets_ln);
            
$this->clients array_values($this->clients);
            
// для тестирования
            
echo $i .' Delete'.PHP_EOL;
        }
    }
    
    
// Деструктор
    
private function __destruct()
    {
        
socket_shutdown($this->sock,2);
        
socket_close($this->sock);
        
$i count($this->clients);
        while(--
$i > -1)
        {
            unset(
$this->sockets_ln[$i] , $this->clients[$i]);
        }
    }
    
// Конец класса

// Реализация класса

$s = new SocketServer();
$s->RunInLoop();
?>
Список статусов сокета:
PHP код:
<?php
class SocketInstructName
{
    
// Сокет, доступный для нового подключения
    
const _empty 'empty'
    
// Новый клиент: не принимает данные с других сокетов, 
    // ожидает файла политик, либо данных идентификации 
    
const _new 'new'
    
// Выполняется передача файла политик
    
const _policy 'policy';
    
// Идентификация пройдена: принимает данные с других сокетов
    
const _established 'established';
    
// Клиент отключился, уведомления остальным не требуется
    
const _close 'close';    
    
// Клиент отключился, ожидается рассылка сообщения об этом остальным клиентам
    
const _closeMess 'closeMess';
    
// Удаление отключившегося клиента из массива
    
const _delete 'delete';
}
?>
Добавлено через 17 часов 6 минут
И ещё хотел спросить, почему у меня сокет получился долгодумающий? Между отправкой данных через telnet и появлением их в окне терминала проходит порядка 5 секунд, несмотря на то, что и клиент и сервер выполняется на одном компе. Почему так?

Создать новую тему Ответ Часовой пояс GMT +4, время: 05:46.
Быстрый переход
  « Предыдущая тема | Следующая тема »  

Ваши права в разделе
Вы не можете создавать новые темы
Вы не можете отвечать в темах
Вы не можете прикреплять вложения
Вы не можете редактировать свои сообщения

BB коды Вкл.
Смайлы Вкл.
[IMG] код Вкл.
HTML код Выкл.


 


Часовой пояс GMT +4, время: 05:46.


Copyright © 1999-2008 Flasher.ru. All rights reserved.
Работает на vBulletin®. Copyright ©2000 - 2026, Jelsoft Enterprises Ltd. Перевод: zCarot
Администрация сайта не несёт ответственности за любую предоставленную посетителями информацию. Подробнее см. Правила.