<?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();
?>