php socket实现的聊天室代码分享

5年以前  |  阅读数:269 次  |  编程语言: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();
 相关文章:
PHP分页显示制作详细讲解
SSH 登录失败:Host key verification failed
将二进制数据转为16进制以便显示
获取IMSI
获取IMEI
Java生成UUID
PHP自定义函数获取搜索引擎来源关键字的方法
让你成为最历害的git提交人
在Zeus Web Server中安装PHP语言支持
再谈PHP中单双引号的区别详解
指定应用ID以获取对应的应用名称
Yii2汉字转拼音类的实例代码
Python 2与Python 3版本和编码的对比
php+ajax+json 详解及实例代码
php封装的page分页类完整实例
php数组合并array_merge()函数使用注意事项
PHP设计模式之工厂模式与单例模式
PHP实现简单爬虫的方法
php实现数组中索引关联数据转换成json对象的方法
wget使用技巧