王朝网络
分享
 
 
 

用PHP的Socket建立自己的聊天室服务器

王朝php·作者佚名  2008-12-19
宽屏版  字体: |||超大  

<?PHP

/**

* patServer

* PHP socket server base class

* Events that can be handled:

* * onStart

* * onConnect

* * onConnectionRefused

* * onClose

* * onShutdown

* * onReceiveData

*

* @version 1.1

* @author Stephan Schmidt <schst@php-tools.de>

* @package patServer

*/

class patServer{

/**

* information about the project

* @var array $systemVars

*/

var $systemVars = array(

"appName" => "patServer",

"appVersion" => "1.1",

"author" => array("Stephan Schmidt <schst@php-tools.de>", )

);

/**

* port to listen

* @var integer $port

*/

var $port = 10000;

/**

* domain to bind to

* @var string $domain

*/

var $domain = "localhost";

/**

* maximum amount of clients

* @var integer $maxClients

*/

var $maxClients = -1;

/**

* buffer size for socket_read

* @var integer $readBufferSize

*/

var $readBufferSize = 128;

/**

* end character for socket_read

* @var integer $readEndCharacter

*/

var $readEndCharacter = "\n";

/**

* maximum of backlog in queue

* @var integer $maxQueue

*/

var $maxQueue = 500;

/**

* debug mode

* @var boolean $debug

*/

var $debug = true;

/**

* debug mode

* @var string $debugMode

*/

var $debugMode = "text";

/**

* debug destination (filename or stdout)

* @var string $debugDest

*/

var $debugDest = "stdout";

/**

* empty array, used for socket_select

* @var array $null

*/

var $null = array();

/**

* all file descriptors are stored here

* @var array $clientFD

*/

var $clientFD = array();

/**

* needed to store client information

* @var array $clientInfo

*/

var $clientInfo = array();

/**

* needed to store server information

* @var array $serverInfo

*/

var $serverInfo = array();

/**

* amount of clients

* @var integer $clients

*/

var $clients = 0;

/**

* create a new socket server

*

* @access public

* @param string $domain domain to bind to

* @param integer $port port to listen to

*/

function patServer( $domain = "localhost", $port = 10000 ){

$this->domain = $domain;

$this->port = $port;

$this->serverInfo["domain"] = $domain;

$this->serverInfo["port"] = $port;

$this->serverInfo["servername"] = $this->systemVars["appName"];

$this->serverInfo["serverversion"] = $this->systemVars["appVersion"];

set_time_limit( 0 );

}

/**

* set maximum amount of simultaneous connections

*

* @access public

* @param int $maxClients

*/

function setMaxClients( $maxClients ){

$this->maxClients = $maxClients;

}

/**

* set debug mode

*

* @access public

* @param mixed $debug [text|htmlfalse]

* @param string $dest destination of debug message (stdout to output or filename if log should be written)

*/

function setDebugMode( $debug, $dest = "stdout" ){

if( $debug === false ){

$this->debug = false;

return true;

}

$this->debug = true;

$this->debugMode = $debug;

$this->debugDest = $dest;

}

/**

* start the server

*

* @access public

* @param int $maxClients

*/

function start(){

$this->initFD = @socket_create( AF_INET, SOCK_STREAM, 0 );

if( !$this->initFD )

die( "patServer: Could not create socket." );

// adress may be reused

socket_setopt( $this->initFD, SOL_SOCKET, SO_REUSEADDR, 1 );

// bind the socket

if(!@socket_bind( $this->initFD, $this->domain, $this->port ) ){

@socket_close( $this->initFD );

die( "patServer: Could not bind socket to ".$this->domain." on port ".$this->port." ( ".$this->getLastSocketError( $this->initFd )." )." );

}

// listen on selected port

if(!@socket_listen( $this->initFD, $this->maxQueue ) )

die( "patServer: Could not listen ( ".$this->getLastSocketError( $this->initFd )." )." );

$this->sendDebugMessage( "Listening on port ".$this->port.". Server started at ".date( "H:i:s", time() ) );

// this allows the shutdown function to check whether the server is already shut down

$GLOBALS["_patServerStatus"] = "running";

// this ensures that the server will be sutdown correctly

register_shutdown_function( array( $this, "shutdown" ) );

if( method_exists( $this, "onStart" ) )

$this->onStart();

$this->serverInfo["started"] = time();

$this->serverInfo["status"] = "running";

while( true ){

$readFDs = array();

array_push( $readFDs, $this->initFD );

// fetch all clients that are awaiting connections

for( $i = 0; $i < count( $this->clientFD ); $i++ )

if( isset( $this->clientFD[$i] ) )

array_push( $readFDs, $this->clientFD[$i] );

// block and wait for data or new connection

$ready = @socket_select( $readFDs, $this->null, $this->null, NULL );

if( $ready === false ){

$this->sendDebugMessage( "socket_select failed." );

$this->shutdown();

}

// check for new connection

if( in_array( $this->initFD, $readFDs ) ){

$newClient = $this->acceptConnection( $this->initFD );

// check for maximum amount of connections

if( $this->maxClients > 0 ){

if( $this->clients > $this->maxClients ){

$this->sendDebugMessage( "Too many connections." );

if( method_exists( $this, "onConnectionRefused" ) )

$this->onConnectionRefused( $newClient );

$this->closeConnection( $newClient );

}

}

if( --$ready <= 0 )

continue;

}

// check all clients for incoming data

for( $i = 0; $i < count( $this->clientFD ); $i++ ){

if( !isset( $this->clientFD[$i] ) )

continue;

if( in_array( $this->clientFD[$i], $readFDs ) ){

$data = $this->readFromSocket( $i );

// empty data => connection was closed

if( !$data ){

$this->sendDebugMessage( "Connection closed by peer" );

$this->closeConnection( $i );

}else{

$this->sendDebugMessage( "Received ".trim( $data )." from ".$i );

if( method_exists( $this, "onReceiveData" ) )

$this->onReceiveData( $i, $data );

}

}

}

}

}

/**

* read from a socket

*

* @access private

* @param integer $clientId internal id of the client to read from

* @return string $data data that was read

*/

function readFromSocket( $clientId ){

// start with empty string

$data = "";

// read data from socket

while( $buf = socket_read( $this->clientFD[$clientId], $this->readBufferSize ) ){

$data .= $buf;

$endString = substr( $buf, - strlen( $this->readEndCharacter ) );

if( $endString == $this->readEndCharacter )

break;

if( $buf == NULL )

break;

}

if( $buf === false )

$this->sendDebugMessage( "Could not read from client ".$clientId." ( ".$this->getLastSocketError( $this->clientFD[$clientId] )." )." );

return $data;

}

/**

* accept a new connection

*

* @access public

* @param resource &$socket socket that received the new connection

* @return int $clientID internal ID of the client

*/

function 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] = array(

"host" => $peer_host,

"port" => $peer_port,

"connectOn" => time()

);

$this->clients++;

$this->sendDebugMessage( "New connection ( ".$i." ) from ".$peer_host." on port ".$peer_port );

if( method_exists( $this, "onConnect" ) )

$this->onConnect( $i );

return $i;

}

}

}

/**

* check, whether a client is still connected

*

* @access public

* @param integer $id client id

* @return boolean $connected true if client is connected, false otherwise

*/

function isConnected( $id ){

if( !isset( $this->clientFD[$id] ) )

return false;

return true;

}

/**

* close connection to a client

*

* @access public

* @param int $clientID internal ID of the client

*/

function closeConnection( $id ){

if( !isset( $this->clientFD[$id] ) )

return false;

if( method_exists( $this, "onClose" ) )

$this->onClose( $id );

$this->sendDebugMessage( "Closed connection ( ".$id." ) from ".$this->clientInfo[$id]["host"]." on port ".$this->clientInfo[$id]["port"] );

@socket_close( $this->clientFD[$id] );

$this->clientFD[$id] = NULL;

unset( $this->clientInfo[$id] );

$this->clients--;

}

/**

* shutdown server

*

* @access public

*/

function shutDown(){

if( $GLOBALS["_patServerStatus"] != "running" )

exit;

$GLOBALS["_patServerStatus"] = "stopped";

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( "Shutdown server." );

exit;

}

/**

* get current amount of clients

*

* @access public

* @return int $clients amount of clients

*/

function getClients(){

return $this->clients;

}

/**

* send data to a client

*

* @access public

* @param int $clientId ID of the client

* @param string $data data to send

* @param boolean $debugData flag to indicate whether data that is written to socket should also be sent as debug message

*/

function sendData( $clientId, $data, $debugData = true ){

if( !isset( $this->clientFD[$clientId] ) || $this->clientFD[$clientId] == NULL )

return false;

if( $debugData )

$this->sendDebugMessage( "sending: \"" . $data . "\" to: $clientId" );

if(!@socket_write( $this->clientFD[$clientId], $data ) )

$this->sendDebugMessage( "Could not write '".$data."' client ".$clientId." ( ".$this->getLastSocketError( $this->clientFD[$clientId] )." )." );

}

/**

* send data to all clients

*

* @access public

* @param string $data data to send

* @param array $exclude client ids to exclude

*/

function broadcastData( $data, $exclude = array(), $debugData = true ){

if( !empty( $exclude ) && !is_array( $exclude ) )

$exclude = array( $exclude );

for( $i = 0; $i < count( $this->clientFD ); $i++ ){

if( isset( $this->clientFD[$i] ) && $this->clientFD[$i] != NULL && !in_array( $i, $exclude ) ){

if( $debugData )

$this->sendDebugMessage( "sending: \"" . $data . "\" to: $i" );

if(!@socket_write( $this->clientFD[$i], $data ) )

$this->sendDebugMessage( "Could not write '".$data."' client ".$i." ( ".$this->getLastSocketError( $this->clientFD[$i] )." )." );

}

}

}

/**

* get current information about a client

*

* @access public

* @param int $clientId ID of the client

* @return array $info information about the client

*/

function getClientInfo( $clientId ){

if( !isset( $this->clientFD[$clientId] ) || $this->clientFD[$clientId] == NULL )

return false;

return $this->clientInfo[$clientId];

}

/**

* send a debug message

*

* @access private

* @param string $msg message to debug

*/

function sendDebugMessage( $msg ){

if( !$this->debug )

return false;

$msg = date( "Y-m-d H:i:s", time() ) . " " . $msg;

switch( $this->debugMode ){

case "text":

$msg = $msg."\n";

break;

case "html":

$msg = htmlspecialchars( $msg ) . "<br />\n";

break;

}

if( $this->debugDest == "stdout" || empty( $this->debugDest ) ){

echo $msg;

flush();

return true;

}

error_log( $msg, 3, $this->debugDest );

return true;

}

/**

* return string for last socket error

*

* @access public

* @return string $error last error

*/

function getLastSocketError( &$fd ){

$lastError = socket_last_error( $fd );

return "msg: " . socket_strerror( $lastError ) . " / Code: ".$lastError;

}

function onReceiveData($ip,$data){

$this->broadcastData( $data,array(), true );

}

}

$patServer = new patServer();

$patServer->start();

?>

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
>>返回首页<<
推荐阅读
 
 
频道精选
 
静静地坐在废墟上,四周的荒凉一望无际,忽然觉得,凄凉也很美
© 2005- 王朝网络 版权所有