<?PHP
/**
* 帕特伺服器
* PHP套接字伺服器基底類
* 可處理的事件:
* * 開始時
** 連接時
* * onConnection拒絕
* * 關閉時
* * 關閉時
* * 接收數據
*
* @版本1.1
* @author Stephan Schmidt < [email protected] >
* @包patServer
*/
類別 patServer{
/**
* 有關項目的信息
* @var 數組 $systemVars
*/
var $systemVars = array(
"appName" => "patServer",
“應用程式版本”=>“1.1”,
「作者」 => 陣列(“史蒂芬·施密特 < [email protected] >”, )
);
/**
* 監聽的端口
* @var 整數 $port
*/
var $埠 = 10000;
/**
* 綁定的域名
* @var 字串 $domain
*/
var $domain = "本機";
/**
* 最大客戶數量
* @var 整數 $maxClients
*/
var $maxClients = -1;
/**
* socket_read的緩衝區大小
* @var 整數 $readBufferSize
*/
var $readBufferSize = 128;
/**
* socket_read 的結束符
* @var 整數 $readEndCharacter
*/
var $readEndCharacter = "n";
/**
* 隊列中積壓的最大數量
* @var 整數 $maxQueue
*/
var $maxQueue = 500;
/**
* 調試模式
* @var 布林值 $debug
*/
var $debug = true;
/**
* 調試模式
* @var 字串 $debugMode
*/
var $debugMode = "文字";
/**
* 調試目標(檔案名稱或標準輸出)
* @var 字串 $debugDest
*/
var $debugDest = "stdout";
/**
* 空數組,用於socket_select
* @var數組$null
*/
var $null = 數組();
/**
* 所有檔案描述符都儲存在這裡
* @var數組$clientFD
*/
var $clientFD = array();
/**
* 需要儲存客戶資訊
* @var數組$clientInfo
*/
var $clientInfo = array();
/**
* 需要儲存伺服器資訊
* @var數組$serverInfo
*/
var $serverInfo = array();
/**
* 客戶數量
* @var 整數 $clients
*/
var $客戶= 0;
/**
* 建立一個新的套接字伺服器
*
* @訪問公共
* @param string $domain 要綁定的域
* @param integer $port 要監聽的端口
*/
函數 patServer( $domain = "localhost", $port = 10000 ){
$這個->域= $域;
$這個->埠= $埠;
$this->serverInfo["domain"] = $domain;
$this->serverInfo["port"] = $port;
$this->serverInfo["伺服器名稱"] = $this->systemVars["appName"];
$this->serverInfo["serverversion"] = $this->systemVars["appVersion"];
設定時間限制(0);
}
/**
* 設定最大同時連線數
*
* @訪問公共
* @param int $maxClients
*/
函數 setMaxClients( $maxClients ){
$this->maxClients = $maxClients;
}
/**
* 設定調試模式
*
* @訪問公共
* @param mix $debug [text|htmlfalse]
* @param string $dest 偵錯訊息的目的地(如果應寫入日誌,則為輸出或檔案名稱的標準輸出)
*/
函數 setDebugMode( $debug, $dest = "stdout" ){
if( $debug === false ){
$this->debug = false;
返回真;
$
this->debug = true;
$this->debugMode = $debug;
$this->debugDest = $dest;
}
/**
* 啟動伺服器
*
* @訪問公共
* @param int $maxClients
*/
函數開始(){
$this->initFD = @socket_create( AF_INET, SOCK_STREAM, 0 );
if( !$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->port." 上的".$this->domain." ( ".$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, "關機" ) );
if( method_exists( $this, "onStart" ) )
$this->onStart();
$this->serverInfo["開始"] = time();
$this->serverInfo["status"] = "正在運行";
而(真){
$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 );
if( $ready === false ){
$this->sendDebugMessage( "socket_select 失敗。" );
$this->shutdown();
}
// 檢查新連接
if( in_array( $this->initFD, $readFDs ) ){
$newClient = $this->acceptConnection( $this->initFD );
// 檢查最大連線數
if( $this->maxClients > 0 ){
if( $this->clients > $this->maxClients ){
$this->sendDebugMessage( "連線數太多。" );
if( 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( "從 ".$i ) 接收到 ".trim( $data ).";
if( method_exists( $this, "onReceiveData" ) )
$this->onReceiveData( $i, $data );
}
}
}
}
}
/**
* 從套接字讀取
*
* @訪問私有
* @param integer $clientId 要讀取的客戶端的內部ID
* @return string $data 讀取到的數據
*/
函數 readFromSocket( $clientId ){
// 以空字串開頭
$數據=“”;
// 從套接字讀取數據
while( $buf = socket_read( $this->clientFD[$clientId], $this->readBufferSize ) ){
$data .= $buf;
$endString = substr( $buf, - strlen( $this->readEndCharacter ) );
if( $endString == $this->readEndCharacter )
休息;
如果( $buf == NULL )
休息;
}
if( $buf === false )
$this->sendDebugMessage( "無法從客戶端讀取 ".$clientId." ( ".$this->getLastSocketError( $this->clientFD[$clientId] )." )." );
返回$數據;
}
/**
* 接受一個新的連接
*
* @訪問公共
* @param resource &$socket 收到新連接的套接字
* @return int $clientID 用戶端的內部ID
*/
函數acceptConnection(&$socket){
for( $i = 0 ; $i <= count( $this->clientFD ); $i++ ){
if( !isset( $this->clientFD[$i] ) || $this->clientFD[$i] == NULL ){
$this->clientFD[$i] = socket_accept( $socket );
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,
「連線」 => 時間()
);
$this->客戶++;
$this->sendDebugMessage( "來自連接埠 ".$peer_port 上的 ".$peer_host." 的新連線 ( ".$i." );
if( method_exists( $this, "onConnect" ) )
$this->onConnect( $i );
返回$i;
}
}
}
/**
* 檢查客戶端是否仍連接
*
* @訪問公共
* @param 整數 $id 客戶端 ID
* @return boolean $connected 若客戶端已連線則為 true,否則為 false
*/
函數 isConnected( $id ){
if( !isset( $this->clientFD[$id] ) )
返回假;
返回真;
}
/**
* 與客戶端的緊密連接
*
* @訪問公共
* @param int $clientID 客戶端的內部ID
*/
函數 closeConnection( $id ){
if( !isset( $this->clientFD[$id] ) )
返回假;
if( method_exists( $this, "onClose" ) )
$this->onClose( $id );
$this->sendDebugMessage( "在連接埠".$this->clientInfo[$id][ 上來自".$this->clientInfo[$id]["host"]." 的已關閉連線( ".$id ." ) “港口”] );
@socket_close( $this->clientFD[$id] );
$this->clientFD[$id] = NULL;
取消設定( $this->clientInfo[$id] );
$this->客戶--;
}
/**
* 關閉伺服器
*
* @訪問公共
*/
函數關閉(){
if( $GLOBALS["_patServerStatus"] != "正在執行" )
出口;
$GLOBALS["_patServerStatus"] = "已停止";
if( 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."' 客戶端".$clientId." ( ".$this->getLastSocketError( $this->clientFD[$clientId] )." )." ) ;
}
/**
* 向所有客戶端發送數據
*
* @訪問公共
* @param string $data 要傳送的數據
* @param array $exclude 要排除的客戶端 ID
*/
函數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];
}
/**
* 發送偵錯訊息
*
* @訪問私有
* @param string $msg 要偵錯的訊息
*/
函數 sendDebugMessage( $msg ){
if(!$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 );
返回“訊息:”。 socket_strerror( $lastError ) 。 ” / 代碼:“.$lastError;
}
函數 onReceiveData($ip,$data){
$this->broadcastData( $data,array(), true );
}
}
$patServer = new patServer();
$patServer->start();
?>