<?PHP
/**
* патсервер
* Базовый класс сервера сокетов PHP
* События, которые можно обработать:
* * onStart
* * onConnect
* * onConnectionRefused
* * onЗакрыть
* * при выключении
* * onReceiveData
*
* @версия 1.1
* @author Стефан Шмидт < [email protected] >
* @package patServer
*/
класс patServer{
/**
* информация о проекте
* @var массив $systemVars
*/
var $systemVars = array(
"appName" => "patServer",
"appVersion" => "1.1",
"author" => array("Штефан Шмидт < [email protected] >", )
);
/**
* порт для прослушивания
* @var целое число $port
*/
вар $порт = 10000;
/**
* домен для привязки
* @var строка $домен
*/
вар $domain = "локальный хост";
/**
* максимальное количество клиентов
* @var целое число $maxClients
*/
вар $maxClients = -1;
/**
* размер буфера для сокета_read
* @var целое число $readBufferSize
*/
вар $readBufferSize = 128;
/**
* конечный символ для socket_read
* @var целое число $readEndCharacter
*/
вар $readEndCharacter = "n";
/**
* максимальное количество отставаний в очереди
* @var целое число $maxQueue
*/
вар $maxQueue = 500;
/**
* режим отладки
* @var boolean $debug
*/
вар $debug = правда;
/**
* режим отладки
* @var строка $debugMode
*/
вар $debugMode = "текст";
/**
* место отладки (имя файла или стандартный вывод)
* @var строка $debugDest
*/
вар $debugDest = "stdout";
/**
* пустой массив, используемый для socket_select
* @var массив $null
*/
вар $null = массив();
/**
* здесь хранятся все дескрипторы файлов
* @var массив $clientFD
*/
вар $clientFD = массив();
/**
*необходим для хранения информации о клиенте
* @var массив $clientInfo
*/
вар $clientInfo = массив();
/**
* необходимо для хранения информации о сервере
* @var массив $serverInfo
*/
вар $serverInfo = массив();
/**
* количество клиентов
* @var целое число $clients
*/
вар $клиенты = 0;
/**
* создать новый сервер сокетов
*
* @доступ к общедоступному
* @param string $domain домен для привязки
* @param целое число $port порт для прослушивания
*/
функция patServer($domain = "localhost", $port = 10000){
$this->домен = $домен;
$this->порт = $порт;
$this->serverInfo["домен"] = $domain;
$this->serverInfo["порт"] = $port;
$this->serverInfo["имя_сервера"] = $this->systemVars["имя_приложения"];
$this->serverInfo["serverversion"] = $this->systemVars["appVersion"];
set_time_limit(0);
}
/**
* установить максимальное количество одновременных подключений
*
* @доступ к общедоступному
* @param int $maxClients
*/
функция setMaxClients($maxClients){
$this->maxClients = $maxClients;
}
/**
* установить режим отладки
*
* @доступ к общедоступному
* @param смешанный $debug [text|htmlfalse]
* @param string $dest назначение отладочного сообщения (стандартный вывод для вывода или имя файла, если журнал должен быть записан)
*/
функция setDebugMode($debug, $dest = "stdout"){
если ($ отладка === ложь) {
$this->debug = ложь;
вернуть истину;
}
$this->debug = true;
$this->debugMode = $debug;
$this->debugDest = $dest;
}
/**
* запустить сервер
*
* @доступ к общедоступному
* @param int $maxClients
*/
функция старт(){
$this->initFD = @socket_create(AF_INET, SOCK_STREAM, 0);
если( !$this->initFD )
die("patServer: Не удалось создать сокет.");
// адрес можно использовать повторно
Socket_setopt($this->initFD, SOL_SOCKET, SO_REUSEADDR, 1);
// привязываем сокет
if( !@socket_bind ($this->initFD, $this->domain, $this->port )){
@socket_close($this->initFD);
die( "patServer: Не удалось привязать сокет к ".$this->domain." на порту ".$this->port." ( ".$this->getLastSocketError($this->initFd )." )." );
}
// прослушиваем выбранный порт
if( !@socket_listen ($this->initFD, $this->maxQueue))
die( "patServer: Не удалось прослушать ( ".$this->getLastSocketError($this->initFd)." )." );
$this->sendDebugMessage( "Прослушивание порта ".$this->port.". Сервер запущен в ".date( "H:i:s", time() ) );
// это позволяет функции завершения работы проверить, выключен ли сервер уже
$GLOBALS["_patServerStatus"] = "работает";
// это гарантирует правильное завершение работы сервера
Register_shutdown_function(array($this, "shutdown" ));
если (method_exists($this, "onStart" ))
$this->onStart();
$this->serverInfo["started"] = time();
$this->serverInfo["статус"] = "работает";
пока( правда ){
$readFDs = массив();
array_push($readFDs, $this->initFD);
// извлекаем всех клиентов, ожидающих подключения
for($i = 0; $i <count($this->clientFD); $i++)
if( isset($this->clientFD[$i]))
array_push($readFDs, $this->clientFD[$i]);
// блокируем и ждем данных или нового соединения
$ready = @socket_select($readFDs, $this->null, $this->null, NULL );
если ($ готово === ложь) {
$this->sendDebugMessage("Ошибка выбора сокета.");
$this->выключение();
}
// проверяем наличие нового соединения
if( in_array($this->initFD, $readFDs)){
$newClient = $this->acceptConnection($this->initFD);
//проверяем максимальное количество соединений
если ($this->maxClients > 0){
if($this->clients > $this->maxClients){
$this->sendDebugMessage("Слишком много подключений.");
если (method_exists($this, "onConnectionRefused"))
$this->onConnectionRefused($newClient);
$this->closeConnection($newClient);
}
}
if( --$ready <= 0 )
продолжать;
}
// проверяем всех клиентов на наличие входящих данных
for($i = 0; $i <count($this->clientFD); $i++){
if( !isset($this->clientFD[$i]))
продолжать;
if( in_array($this->clientFD[$i], $readFDs)){
$data = $this->readFromSocket($i);
// пустые данные => соединение закрыто
если( !$данные ){
$this->sendDebugMessage("Соединение закрыто узлом");
$this->closeConnection($i);
}еще{
$this->sendDebugMessage( "Получено ".trim($data)." из ".$i);
если (method_exists($this, "onReceiveData"))
$this->onReceiveData($i, $data);
}
}
}
}
}
/**
* читать из сокета
*
* @access приватный
* @param целое число $clientId внутренний идентификатор клиента для чтения
* @return string $данные, которые были прочитаны
*/
функция readFromSocket($clientId){
// начинаем с пустой строки
$данные = "";
// читаем данные из сокета
while( $buf =ocket_read($this->clientFD[$clientId], $this->readBufferSize)){
$данные .= $buf;
$endString = substr($buf, - strlen($this->readEndCharacter));
if($endString == $this->readEndCharacter)
перерыв;
если ($buf == NULL)
перерыв;
}
Если ($buf === ложь)
$this->sendDebugMessage( "Не удалось прочитать из клиента ".$clientId." ( ".$this->getLastSocketError( $this->clientFD[$clientId] )." )." );
вернуть $данные;
}
/**
* принять новое соединение
*
* @доступ к общедоступному
* Ресурс @param и сокет $socket, получивший новое соединение
* @return int $clientID внутренний идентификатор клиента
*/
функция AcceptConnection(&$socket){
for( $i = 0 ; $i <= count( $this->clientFD ); $i++ ){
if( !isset( $this->clientFD[$i]) || $this->clientFD[$i] == NULL ){
$this->clientFD[$i] =ocket_accept($socket);
сокет_setopt($this->clientFD[$i], SOL_SOCKET, SO_REUSEADDR, 1);
$peer_host = "";
$peer_port = "";
Socket_getpeername($this->clientFD[$i], $peer_host, $peer_port);
$this->clientInfo[$i] = массив(
"хост" => $peer_host,
"порт" => $peer_port,
"connectOn" => время()
);
$this->клиенты++;
$this->sendDebugMessage("Новое соединение (".$i." ) от ".$peer_host." по порту ".$peer_port );
если (method_exists($this, "onConnect" ))
$this->onConnect($i);
вернуть $я;
}
}
}
/**
* проверьте, подключен ли еще клиент
*
* @доступ к общедоступному
* @param целое число $id идентификатор клиента
* @return boolean $connected true, если клиент подключен, в противном случае — false
*/
функция isConnected($id){
if( !isset($this->clientFD[$id]))
вернуть ложь;
вернуть истину;
}
/**
* тесное соединение с клиентом
*
* @доступ к общедоступному
* @param int $clientID внутренний идентификатор клиента
*/
функция closeConnection($id){
if( !isset($this->clientFD[$id]))
вернуть ложь;
если (method_exists($this, "onClose"))
$this->onClose($id);
$this->sendDebugMessage( "Закрытое соединение (".$id." ) из ".$this->clientInfo[$id]["host"]." на порту ".$this->clientInfo[$id][ «порт»] );
@socket_close($this->clientFD[$id]);
$this->clientFD[$id] = NULL;
unset($this->clientInfo[$id]);
$this->клиенты--;
}
/**
* выключение сервера
*
* @доступ к общедоступному
*/
функция выключения(){
if( $GLOBALS["_patServerStatus"] != "работает")
Выход;
$GLOBALS["_patServerStatus"] = "остановлено";
если (method_exists($this, "onShutdown"))
$this->onShutdown();
$maxFD = count($this->clientFD);
for($i = 0; $i < $maxFD; $i++)
$this->closeConnection($i);
@socket_close($this->initFD);
$this->sendDebugMessage("Завершение работы сервера.");
Выход;
}
/**
* получить текущее количество клиентов
*
* @доступ к общедоступному
* @return int $clients количество клиентов
*/
функция getClients(){
вернуть $this->клиенты;
}
/**
* отправить данные клиенту
*
* @доступ к общедоступному
* @param int $clientId ID клиента
* @param string $data данные для отправки
* Флаг @param boolean $debugData, указывающий, следует ли также отправлять данные, записываемые в сокет, как отладочное сообщение.
*/
функция sendData($clientId, $data, $debugData = true){
if( !isset( $this->clientFD[$clientId] ) || $this->clientFD[$clientId] == NULL )
вернуть ложь;
если ($debugData)
$this->sendDebugMessage("отправка: "" . $data . "" в: $clientId" );
if( !@socket_write ( $this->clientFD[$clientId], $data ))
$this->sendDebugMessage( "Не удалось записать '.$data."' client ".$clientId." ( ".$this->getLastSocketError( $this->clientFD[$clientId] )." ).") ;
}
/**
* отправлять данные всем клиентам
*
* @доступ к общедоступному
* @param string $data данные для отправки
* @param array $exclude идентификаторы клиентов, которые нужно исключить
*/
функция BroadcastData($data, $exclude = array(), $debugData = true){
if( !empty($exclude) && !is_array($exclude) )
$исключить = массив($исключить);
for($i = 0; $i <count($this->clientFD); $i++){
if( isset( $this->clientFD[$i] ) && $this->clientFD[$i] != NULL && !in_array( $i, $exclude )){
если ($debugData)
$this->sendDebugMessage("отправка: "" . $data . "" в: $i" );
if( !@socket_write ( $this->clientFD[$i], $data ))
$this->sendDebugMessage( "Не удалось записать '".$data."' клиент ".$i." ( ".$this->getLastSocketError( $this->clientFD[$i] )." )." ) ;
}
}
}
/**
* получить актуальную информацию о клиенте
*
* @доступ к общедоступному
* @param int $clientId ID клиента
* @return array $info информация о клиенте
*/
функция getClientInfo($clientId){
if( !isset( $this->clientFD[$clientId] ) || $this->clientFD[$clientId] == NULL )
вернуть ложь;
вернуть $this->clientInfo[$clientId];
}
/**
* отправить отладочное сообщение
*
* @access приватный
* @param string $msg сообщение для отладки
*/
функция sendDebugMessage($msg){
если( !$this->отладка )
вернуть ложь;
$msg = date( "Ymd H:i:s", time() ). " " . $сообщение;
переключатель ($this->debugMode){
случай «текст»:
$msg = $msg."n";
перерыв;
случай «html»:
$msg = htmlspecialchars($msg) . "<br />n";
перерыв;
}
if( $this->debugDest == "stdout" || пусто( $this->debugDest ) ){
эхо $msg;
румянец();
вернуть истину;
}
error_log($msg, 3, $this->debugDest);
вернуть истину;
}
/**
* возвращаемая строка для последней ошибки сокета
*
* @доступ к общедоступному
* @return string $error последняя ошибка
*/
функция getLastSocketError(&$fd){
$lastError = socket_last_error($fd);
верните «сообщение:». сокет_strerror($lastError) . " / Код: ".$lastError;
}
функция onReceiveData($ip,$data){
$this->broadcastData($data,array(), true);
}
}
$patServer = новый patServer();
$patServer->start();
?>