<?PHP
/**
* 팻서버
* PHP 소켓 서버 기본 클래스
* 처리할 수 있는 이벤트:
* * 시작 시
* * 온커넥트
* * onConnectionRefused
* * 닫기
* * 종료 시
* * onReceiveData
*
* @버전 1.1
* @저자 스테판 슈미트 < [email protected] >
* @package patServer
*/
클래스 patServer{
/**
* 프로젝트에 대한 정보
* @var 배열 $systemVars
*/
var $systemVars = array(
"appName" => "patServer",
"appVersion" => "1.1",
"author" => array("스테판 슈미트 < [email protected] >", )
);
/**
* 청취할 포트
* @var 정수 $port
*/
var $포트 = 10000;
/**
* 바인딩할 도메인
* @var 문자열 $domain
*/
var $domain = "localhost";
/**
* 최대 클라이언트 수
* @var 정수 $maxClients
*/
var $maxClients = -1;
/**
* 소켓_읽기의 버퍼 크기
* @var 정수 $readBufferSize
*/
var $readBufferSize = 128;
/**
* 소켓_읽기의 끝 문자
* @var 정수 $readEndCharacter
*/
var $readEndCharacter = "n";
/**
* 대기열의 최대 백로그
* @var 정수 $maxQueue
*/
var $maxQueue = 500;
/**
* 디버그 모드
* @var 부울 $debug
*/
var $debug = true;
/**
* 디버그 모드
* @var 문자열 $debugMode
*/
var $debugMode = "텍스트";
/**
* 디버그 대상(파일 이름 또는 stdout)
* @var 문자열 $debugDest
*/
var $debugDest = "stdout";
/**
* 소켓_선택에 사용되는 빈 배열
* @var 배열 $null
*/
var $null = 배열();
/**
* 모든 파일 설명자는 여기에 저장됩니다.
* @var 배열 $clientFD
*/
var $clientFD = 배열();
/**
* 클라이언트 정보를 저장하는 데 필요
* @var 배열 $clientInfo
*/
var $clientInfo = 배열();
/**
* 서버 정보를 저장하는 데 필요
* @var 배열 $serverInfo
*/
var $serverInfo = 배열();
/**
* 클라이언트 수
* @var 정수 $clients
*/
var $clients = 0;
/**
* 새로운 소켓 서버 생성
*
* @접속 공개
* @param string $domain 바인드할 도메인
* @param 정수 $port 수신할 포트
*/
함수 patServer( $domain = "localhost", $port = 10000 ){
$this->도메인 = $도메인;
$this->포트 = $포트;
$this->serverInfo["도메인"] = $domain;
$this->serverInfo["port"] = $port;
$this->serverInfo["서버 이름"] = $this->systemVars["appName"];
$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 디버그 메시지 대상(로그를 작성해야 하는 경우 출력 또는 파일 이름으로 stdout)
*/
함수 setDebugMode( $debug, $dest = "stdout" ){
if( $debug === false ){
$this->디버그 = 거짓;
사실을 반환;
}
$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: 소켓을 생성할 수 없습니다." );
// 주소는 재사용 가능
소켓_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, "shutdown" ) );
if( method_exists( $this, "onStart" ) )
$this->onStart();
$this->serverInfo["시작됨"] = 시간();
$this->serverInfo["status"] = "실행 중";
동안(참){
$readFDs = 배열();
array_push( $readFDs, $this->initFD );
// 연결을 기다리고 있는 모든 클라이언트를 가져옵니다.
for( $i = 0; $i < 개수( $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->클라이언트 > $this->maxClients ){
$this->sendDebugMessage( "연결이 너무 많습니다." );
if( method_exists( $this, "onConnectionRefused" ) )
$this->onConnectionRefused( $newClient );
$this->closeConnection( $newClient );
}
}
if( --$ready <= 0 )
계속하다;
}
// 모든 클라이언트에서 들어오는 데이터를 확인합니다.
for( $i = 0; $i < 개수( $this->clientFD ); $i++ ){
if( !isset( $this->clientFD[$i] ) )
계속하다;
if( in_array( $this->clientFD[$i], $readFDs ) ){
$data = $this->readFromSocket( $i );
// 빈 데이터 => 연결이 닫혔습니다.
if( !$데이터 ){
$this->sendDebugMessage( "피어에 의해 연결이 종료되었습니다." );
$this->closeConnection( $i );
}또 다른{
$this->sendDebugMessage( "".$i )에서 ".trim( $data )."를 받았습니다.
if( 메소드_존재( $this, "onReceiveData" ) )
$this->onReceiveData( $i, $data );
}
}
}
}
}
/**
* 소켓에서 읽기
*
* @액세스 프라이빗
* @param 정수 $clientId 읽을 클라이언트의 내부 ID
* @return string $data 읽은 데이터
*/
함수 readFromSocket( $clientId ){
// 빈 문자열로 시작
$data = "";
//소켓에서 데이터 읽기
while( $buf = 소켓_read( $this->clientFD[$clientId], $this->readBufferSize ) ){
$데이터 .= $buf;
$endString = substr( $buf, - strlen( $this->readEndCharacter ) );
if( $endString == $this->readEndCharacter )
부서지다;
if( $buf == NULL )
부서지다;
}
if( $buf === 거짓 )
$this->sendDebugMessage( "".$clientId 클라이언트에서 읽을 수 없습니다." ( ".$this->getLastSocketError( $this->clientFD[$clientId] )." )." );
$data를 반환합니다.
}
/**
* 새로운 연결을 수락
*
* @접속 공개
* 새로운 연결을 받은 @param 리소스 &$socket 소켓
* @return int $clientID 클라이언트의 내부 ID
*/
함수 acceptConnection( &$socket ){
for( $i = 0 ; $i <= 개수( $this->clientFD ); $i++ ){
if( !isset( $this->clientFD[$i] ) || $this->clientFD[$i] == NULL ){
$this->clientFD[$i] = 소켓_accept( $socket );
소켓_setopt( $this->clientFD[$i], SOL_SOCKET, SO_REUSEADDR, 1 );
$peer_host = "";
$peer_port = "";
소켓_getpeername( $this->clientFD[$i], $peer_host, $peer_port );
$this->clientInfo[$i] = 배열(
"호스트" => $peer_host,
"포트" => $peer_port,
"connectOn" => 시간()
);
$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( 메소드_존재( $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 = 개수( $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 )
거짓을 반환;
if($디버그데이터)
$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 ) )
$exclude = 배열( $exclude );
for( $i = 0; $i < 개수( $this->clientFD ); $i++ ){
if( isset( $this->clientFD[$i] ) && $this->clientFD[$i] != NULL && !in_array( $i, $exclude ) ){
if($디버그데이터)
$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 배열 $info 클라이언트에 대한 정보
*/
함수 getClientInfo( $clientId ){
if( !isset( $this->clientFD[$clientId] ) || $this->clientFD[$clientId] == NULL )
거짓을 반환;
$this->clientInfo[$clientId]를 반환합니다.
}
/**
* 디버그 메시지 보내기
*
* @액세스 프라이빗
* @param string $msg 디버깅할 메시지
*/
함수 sendDebugMessage( $msg ){
if( !$this->디버그)
거짓을 반환;
$msg = 날짜( "Ymd H:i:s", time() ) . " " . $msg;
스위치( $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 = 소켓_마지막_오류( $fd );
"메시지: "를 반환합니다. 소켓_strerror( $lastError ) . " / 코드: ".$lastError;
}
함수 onReceiveData($ip,$data){
$this->broadcastData( $data,array(), true );
}
}
$patServer = 새로운 patServer();
$patServer->시작();
?>