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

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_INET, SOCK_STREAM, SOL_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->sock, SOL_SOCKET, SO_REUSEADDR, 1))
{
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->sock, SOMAXCONN) === 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, $exceptS, 5)) === 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, $exceptSock, 5)) === 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'}, $bufWrite, strlen($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 секунд, несмотря на то, что и клиент и сервер выполняется на одном компе. Почему так?