diff --git a/Applications/PassWall/start.php b/Applications/PassWall/start.php index 47a90fa..b15d754 100644 --- a/Applications/PassWall/start.php +++ b/Applications/PassWall/start.php @@ -52,46 +52,16 @@ $worker->onMessage = function($connection, $buffer) $connection->close(); return; } - //$connection->send("\x05\x00\x00\x01\x00\x00\x00\x00\x10\x10"); - //$connection->stage = STAGE_DNS; $connection->stage = STAGE_CONNECTING; $remote_connection = new AsyncTcpConnection('tcp://'.$header_data[1].':'.$header_data[2]); $remote_connection->onConnect = function($remote_connection)use($connection) { $connection->state = STAGE_STREAM; $connection->send("\x05\x00\x00\x01\x00\x00\x00\x00\x10\x10"); + $connection->pipe($remote_connection); + $remote_connection->pipe($connection); }; - $remote_connection->onMessage = function($remote_connection, $buffer)use($connection) - { - $connection->send($buffer); - }; - $remote_connection->onClose = function($remote_connection)use($connection) - { - $connection->close(); - }; - $remote_connection->onError = function($remote_connection, $code, $type)use($connection) - { - if($connection->stage == STAGE_CONNECTING) - { - $connection->send("\x05\x03\x00\x01\x00\x00\x00\x00\x10\x10"); - } - $connection->close(); - }; - $connection->onMessage = function($connection, $data)use($remote_connection) - { - $remote_connection->send($data); - }; - $connection->onClose = function($connection)use($remote_connection) - { - $remote_connection->close(); - }; - $connection->onError = function($connection)use($remote_connection) - { - echo "connection err\n"; - $connection->close(); - $remote_connection->close(); - }; - + $remote_connection->connect(); } }; diff --git a/Workerman/Autoloader.php b/Workerman/Autoloader.php index 1111ba0..45773c9 100644 --- a/Workerman/Autoloader.php +++ b/Workerman/Autoloader.php @@ -1,61 +1,69 @@ + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ namespace Workerman; -// 定义Workerman根目录 -if(!defined('WORKERMAN_ROOT_DIR')) -{ - define('WORKERMAN_ROOT_DIR', realpath(__DIR__ . '/../')); -} -// 包含常量定义文件 -require_once WORKERMAN_ROOT_DIR.'/Workerman/Lib/Constants.php'; - /** - * 自动加载类 - * @author walkor + * Autoload. */ class Autoloader { - // 应用的初始化目录,作为加载类文件的参考目录 - protected static $_appInitPath = ''; + /** + * Autoload root path. + * + * @var string + */ + protected static $_autoloadRootPath = ''; /** - * 设置应用初始化目录 + * Set autoload root path. + * * @param string $root_path * @return void */ public static function setRootPath($root_path) { - self::$_appInitPath = $root_path; + self::$_autoloadRootPath = $root_path; } /** - * 根据命名空间加载文件 + * Load files by namespace. + * * @param string $name * @return boolean */ public static function loadByNamespace($name) { - // 相对路径 - $class_path = str_replace('\\', DIRECTORY_SEPARATOR ,$name); - // 先尝试在应用目录寻找文件 - $class_file = self::$_appInitPath . '/' . $class_path.'.php'; - // 文件不存在,则在workerman根目录中寻找 - if(!is_file($class_file)) - { - $class_file = WORKERMAN_ROOT_DIR . DIRECTORY_SEPARATOR . "$class_path.php"; + $class_path = str_replace('\\', DIRECTORY_SEPARATOR, $name); + if (strpos($name, 'Workerman\\') === 0) { + $class_file = __DIR__ . substr($class_path, strlen('Workerman')) . '.php'; + } else { + if (self::$_autoloadRootPath) { + $class_file = self::$_autoloadRootPath . DIRECTORY_SEPARATOR . $class_path . '.php'; + } + if (empty($class_file) || !is_file($class_file)) { + $class_file = __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . "$class_path.php"; + } } - // 找到文件 - if(is_file($class_file)) - { - // 加载 + + if (is_file($class_file)) { require_once($class_file); - if(class_exists($name, false)) - { + if (class_exists($name, false)) { return true; } } return false; } } -// 设置类自动加载回调函数 + spl_autoload_register('\Workerman\Autoloader::loadByNamespace'); \ No newline at end of file diff --git a/Workerman/Connection/AsyncTcpConnection.php b/Workerman/Connection/AsyncTcpConnection.php index 8466844..60ae851 100644 --- a/Workerman/Connection/AsyncTcpConnection.php +++ b/Workerman/Connection/AsyncTcpConnection.php @@ -1,128 +1,274 @@ + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ namespace Workerman\Connection; -use Workerman\Events\Libevent; -use Workerman\Events\Select; use Workerman\Events\EventInterface; use Workerman\Worker; -use \Exception; +use Exception; /** - * 异步tcp连接类 - * @author walkor + * AsyncTcpConnection. */ class AsyncTcpConnection extends TcpConnection { /** - * 连接状态 连接中 - * @var int - */ - protected $_status = self::STATUS_CONNECTING; - - /** - * 当连接成功时,如果设置了连接成功回调,则执行 + * Emitted when socket connection is successfully established. + * * @var callback */ public $onConnect = null; - + /** - * 构造函数,创建连接 - * @param resource $socket - * @param EventInterface $event + * Transport layer protocol. + * + * @var string + */ + public $transport = 'tcp'; + + /** + * Status. + * + * @var int + */ + protected $_status = self::STATUS_INITIAL; + + /** + * Remote host. + * + * @var string + */ + protected $_remoteHost = ''; + + /** + * Connect start time. + * + * @var string + */ + protected $_connectStartTime = 0; + + /** + * Remote URI. + * + * @var string + */ + protected $_remoteURI = ''; + + + /** + * PHP built-in protocols. + * + * @var array + */ + protected static $_builtinTransports = array( + 'tcp' => 'tcp', + 'udp' => 'udp', + 'unix' => 'unix', + 'ssl' => 'ssl', + 'sslv2' => 'sslv2', + 'sslv3' => 'sslv3', + 'tls' => 'tls' + ); + + /** + * Construct. + * + * @param string $remote_address + * @throws Exception */ public function __construct($remote_address) { - // 获得协议及远程地址 - list($scheme, $address) = explode(':', $remote_address, 2); - if($scheme != 'tcp') - { - // 判断协议类是否存在 - $scheme = ucfirst($scheme); - $this->protocol = '\\Protocols\\'.$scheme; - if(!class_exists($this->protocol)) - { - $this->protocol = '\\Workerman\\Protocols\\' . $scheme; - if(!class_exists($this->protocol)) - { + $address_info = parse_url($remote_address); + if (!$address_info) { + echo new \Exception('bad remote_address'); + $this->_remoteAddress = $remote_address; + } else { + if (!isset($address_info['port'])) { + $address_info['port'] = 80; + } + if (!isset($address_info['path'])) { + $address_info['path'] = '/'; + } + if (!isset($address_info['query'])) { + $address_info['query'] = ''; + } else { + $address_info['query'] = '?' . $address_info['query']; + } + $this->_remoteAddress = "{$address_info['host']}:{$address_info['port']}"; + $this->_remoteHost = $address_info['host']; + $this->_remoteURI = "{$address_info['path']}{$address_info['query']}"; + $scheme = isset($address_info['scheme']) ? $address_info['scheme'] : 'tcp'; + } + + $this->id = self::$_idRecorder++; + // Check application layer protocol class. + if (!isset(self::$_builtinTransports[$scheme])) { + $scheme = ucfirst($scheme); + $this->protocol = '\\Protocols\\' . $scheme; + if (!class_exists($this->protocol)) { + $this->protocol = "\\Workerman\\Protocols\\$scheme"; + if (!class_exists($this->protocol)) { throw new Exception("class \\Protocols\\$scheme not exist"); } } + } else { + $this->transport = self::$_builtinTransports[$scheme]; } - // 创建异步连接 - $this->_socket = stream_socket_client("tcp:$address", $errno, $errstr, 0, STREAM_CLIENT_ASYNC_CONNECT); - // 如果失败尝试触发失败回调(如果有回调的话) - if(!$this->_socket) - { - $this->emitError(WORKERMAN_CONNECT_FAIL, $errstr); + + // For statistics. + self::$statistics['connection_count']++; + $this->maxSendBufferSize = self::$defaultMaxSendBufferSize; + } + + /** + * Do connect. + * + * @return void + */ + public function connect() + { + if ($this->_status !== self::STATUS_INITIAL && $this->_status !== self::STATUS_CLOSING && $this->_status !== self::STATUS_CLOSED) { return; } - // 监听连接可写事件(可写意味着连接已经建立或者已经出错) + $this->_status = self::STATUS_CONNECTING; + $this->_connectStartTime = microtime(true); + // Open socket connection asynchronously. + $this->_socket = stream_socket_client("{$this->transport}://{$this->_remoteAddress}", $errno, $errstr, 0, + STREAM_CLIENT_ASYNC_CONNECT); + // If failed attempt to emit onError callback. + if (!$this->_socket) { + $this->emitError(WORKERMAN_CONNECT_FAIL, $errstr); + if ($this->_status === self::STATUS_CLOSING) { + $this->destroy(); + } + if ($this->_status === self::STATUS_CLOSED) { + $this->onConnect = null; + } + return; + } + // Add socket to global event loop waiting connection is successfully established or faild. Worker::$globalEvent->add($this->_socket, EventInterface::EV_WRITE, array($this, 'checkConnection')); } - + /** - * 尝试触发失败回调 - * @param int $code + * Get remote address. + * + * @return string + */ + public function getRemoteHost() + { + return $this->_remoteHost; + } + + /** + * Get remote URI. + * + * @return string + */ + public function getRemoteURI() + { + return $this->_remoteURI; + } + + /** + * Try to emit onError callback. + * + * @param int $code * @param string $msg * @return void */ protected function emitError($code, $msg) { - if($this->onError) - { - try{ + $this->_status = self::STATUS_CLOSING; + if ($this->onError) { + try { call_user_func($this->onError, $this, $code, $msg); - } - catch(Exception $e) - { - echo $e; + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); } } } - + /** - * 检查连接状态,连接成功还是失败 + * Check connection is successfully established or faild. + * * @param resource $socket * @return void */ public function checkConnection($socket) { - // 删除连接可写监听 - Worker::$globalEvent->del($this->_socket, EventInterface::EV_WRITE); - // 需要判断两次连接是否已经断开 - if(!feof($this->_socket) && !feof($this->_socket)) - { - // 设置非阻塞 - stream_set_blocking($this->_socket, 0); - // 监听可读事件 - Worker::$globalEvent->add($this->_socket, EventInterface::EV_READ, array($this, 'baseRead')); - // 如果发送缓冲区有数据则执行发送 - if($this->_sendBuffer) - { - Worker::$globalEvent->add($this->_socket, EventInterface::EV_WRITE, array($this, 'baseWrite')); + // Check socket state. + if ($address = stream_socket_get_name($socket, true)) { + // Remove write listener. + Worker::$globalEvent->del($socket, EventInterface::EV_WRITE); + // Nonblocking. + stream_set_blocking($socket, 0); + // Compatible with hhvm + if (function_exists('stream_set_read_buffer')) { + stream_set_read_buffer($socket, 0); } - // 标记状态为连接已经建立 - $this->_status = self::STATUS_ESTABLISH; - // 为status 命令统计数据 - ConnectionInterface::$statistics['connection_count']++; - // 如果有设置onConnect回调,则执行 - if($this->onConnect) - { - try - { + // Try to open keepalive for tcp and disable Nagle algorithm. + if (function_exists('socket_import_stream') && $this->transport === 'tcp') { + $raw_socket = socket_import_stream($socket); + socket_set_option($raw_socket, SOL_SOCKET, SO_KEEPALIVE, 1); + socket_set_option($raw_socket, SOL_TCP, TCP_NODELAY, 1); + } + // Register a listener waiting read event. + Worker::$globalEvent->add($socket, EventInterface::EV_READ, array($this, 'baseRead')); + // There are some data waiting to send. + if ($this->_sendBuffer) { + Worker::$globalEvent->add($socket, EventInterface::EV_WRITE, array($this, 'baseWrite')); + } + $this->_status = self::STATUS_ESTABLISH; + $this->_remoteAddress = $address; + + // Try to emit onConnect callback. + if ($this->onConnect) { + try { call_user_func($this->onConnect, $this); - } - catch(Exception $e) - { - self::$statistics['throw_exception']++; - echo $e; + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); } } - } - else - { - // 连接未建立成功 - $this->emitError(WORKERMAN_CONNECT_FAIL, 'connect fail'); + // Try to emit protocol::onConnect + if (method_exists($this->protocol, 'onConnect')) { + try { + call_user_func(array($this->protocol, 'onConnect'), $this); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + } else { + // Connection failed. + $this->emitError(WORKERMAN_CONNECT_FAIL, 'connect ' . $this->_remoteAddress . ' fail after ' . round(microtime(true) - $this->_connectStartTime, 4) . ' seconds'); + if ($this->_status === self::STATUS_CLOSING) { + $this->destroy(); + } + if ($this->_status === self::STATUS_CLOSED) { + $this->onConnect = null; + } } } } diff --git a/Workerman/Connection/ConnectionInterface.php b/Workerman/Connection/ConnectionInterface.php index fcce854..b39e5ae 100644 --- a/Workerman/Connection/ConnectionInterface.php +++ b/Workerman/Connection/ConnectionInterface.php @@ -1,68 +1,83 @@ + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ namespace Workerman\Connection; -use Workerman\Events\Libevent; -use Workerman\Events\Select; -use Workerman\Events\EventInterface; -use Workerman\Worker; -use \Exception; /** - * connection类的接口 - * @author walkor + * ConnectionInterface. */ abstract class ConnectionInterface { /** - * status命令的统计数据 + * Statistics for status command. + * * @var array */ public static $statistics = array( - 'connection_count'=>0, - 'total_request' => 0, - 'throw_exception' => 0, - 'send_fail' => 0, + 'connection_count' => 0, + 'total_request' => 0, + 'throw_exception' => 0, + 'send_fail' => 0, ); - + /** - * 当收到数据时,如果有设置$onMessage回调,则执行 + * Emitted when data is received. + * * @var callback */ public $onMessage = null; - + /** - * 当连接关闭时,如果设置了$onClose回调,则执行 + * Emitted when the other end of the socket sends a FIN packet. + * * @var callback */ public $onClose = null; - + /** - * 当出现错误时,如果设置了$onError回调,则执行 + * Emitted when an error occurs with connection. + * * @var callback */ public $onError = null; - + /** - * 发送数据给对端 + * Sends data on the connection. + * * @param string $send_buffer * @return void|boolean */ abstract public function send($send_buffer); - + /** - * 获得远端ip + * Get remote IP. + * * @return string */ abstract public function getRemoteIp(); - + /** - * 获得远端端口 + * Get remote port. + * * @return int */ abstract public function getRemotePort(); /** - * 关闭连接,为了保持接口一致,udp保留了此方法,当是udp时调用此方法无任何作用 - * @void + * Close connection. + * + * @param $data + * @return void */ abstract public function close($data = null); } diff --git a/Workerman/Connection/TcpConnection.php b/Workerman/Connection/TcpConnection.php index 6c5852b..d4ac761 100644 --- a/Workerman/Connection/TcpConnection.php +++ b/Workerman/Connection/TcpConnection.php @@ -1,317 +1,344 @@ + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ namespace Workerman\Connection; -use Workerman\Events\Libevent; -use Workerman\Events\Select; use Workerman\Events\EventInterface; use Workerman\Worker; -use \Exception; +use Exception; /** - * Tcp连接类 - * @author walkor + * TcpConnection. */ class TcpConnection extends ConnectionInterface { /** - * 当数据可读时,从socket缓冲区读取多少字节数据 + * Read buffer size. + * * @var int */ - const READ_BUFFER_SIZE = 8192; + const READ_BUFFER_SIZE = 65535; /** - * 连接状态 连接中 + * Status initial. + * + * @var int + */ + const STATUS_INITIAL = 0; + + /** + * Status connecting. + * * @var int */ const STATUS_CONNECTING = 1; - + /** - * 连接状态 已经建立连接 + * Status connection established. + * * @var int */ const STATUS_ESTABLISH = 2; /** - * 连接状态 连接关闭中,标识调用了close方法,但是发送缓冲区中任然有数据 - * 等待发送缓冲区的数据发送完毕(写入到socket写缓冲区)后执行关闭 + * Status closing. + * * @var int */ const STATUS_CLOSING = 4; - + /** - * 连接状态 已经关闭 + * Status closed. + * * @var int */ const STATUS_CLOSED = 8; - + /** - * 当对端发来数据时,如果设置了$onMessage回调,则执行 + * Emitted when data is received. + * * @var callback */ public $onMessage = null; - + /** - * 当连接关闭时,如果设置了$onClose回调,则执行 + * Emitted when the other end of the socket sends a FIN packet. + * * @var callback */ public $onClose = null; - + /** - * 当出现错误是,如果设置了$onError回调,则执行 + * Emitted when an error occurs with connection. + * * @var callback */ public $onError = null; - + /** - * 当发送缓冲区满时,如果设置了$onBufferFull回调,则执行 + * Emitted when the send buffer becomes full. + * * @var callback */ public $onBufferFull = null; - + /** - * 当发送缓冲区被清空时,如果设置了$onBufferDrain回调,则执行 + * Emitted when the send buffer becomes empty. + * * @var callback */ public $onBufferDrain = null; - + /** - * 使用的应用层协议,是协议类的名称 - * 值类似于 Workerman\\Protocols\\Http - * @var string + * Application layer protocol. + * The format is like this Workerman\\Protocols\\Http. + * + * @var \Workerman\Protocols\ProtocolInterface */ - public $protocol = ''; - + public $protocol = null; + /** - * 属于哪个worker + * Which worker belong to. + * * @var Worker */ public $worker = null; - + /** - * 发送缓冲区大小,当发送缓冲区满时,会尝试触发onBufferFull回调(如果有设置的话) - * 如果没设置onBufferFull回调,由于发送缓冲区满,则后续发送的数据将被丢弃, - * 直到发送缓冲区有空的位置 - * 注意 此值可以动态设置 - * 例如 Workerman\Connection\TcpConnection::$maxSendBufferSize=1024000; + * Connection->id. + * * @var int */ - public static $maxSendBufferSize = 1048576; - + public $id = 0; + /** - * 能接受的最大数据包,为了防止恶意攻击,当数据包的大小大于此值时执行断开 - * 注意 此值可以动态设置 - * 例如 Workerman\Connection\TcpConnection::$maxPackageSize=1024000; + * A copy of $worker->id which used to clean up the connection in worker->connections + * + * @var int + */ + protected $_id = 0; + + /** + * Sets the maximum send buffer size for the current connection. + * OnBufferFull callback will be emited When the send buffer is full. + * + * @var int + */ + public $maxSendBufferSize = 1048576; + + /** + * Default send buffer size. + * + * @var int + */ + public static $defaultMaxSendBufferSize = 1048576; + + /** + * Maximum acceptable packet size. + * * @var int */ public static $maxPackageSize = 10485760; - + /** - * 实际的socket资源 + * Id recorder. + * + * @var int + */ + protected static $_idRecorder = 1; + + /** + * Socket + * * @var resource */ protected $_socket = null; /** - * 发送缓冲区 + * Send buffer. + * * @var string */ protected $_sendBuffer = ''; - + /** - * 接收缓冲区 + * Receive buffer. + * * @var string */ protected $_recvBuffer = ''; - + /** - * 当前正在处理的数据包的包长(此值是协议的intput方法的返回值) + * Current package length. + * * @var int */ protected $_currentPackageLength = 0; /** - * 当前的连接状态 + * Connection status. + * * @var int */ protected $_status = self::STATUS_ESTABLISH; - + /** - * 对端ip - * @var string - */ - protected $_remoteIp = ''; - - /** - * 对端端口 - * @var int - */ - protected $_remotePort = 0; - - /** - * 对端的地址 ip+port - * 值类似于 192.168.1.100:3698 + * Remote address. + * * @var string */ protected $_remoteAddress = ''; - + /** - * 是否是停止接收数据 + * Is paused. + * * @var bool */ protected $_isPaused = false; - + /** - * 构造函数 + * Construct. + * * @param resource $socket - * @param EventInterface $event + * @param string $remote_address */ - public function __construct($socket) + public function __construct($socket, $remote_address = '') { + self::$statistics['connection_count']++; + $this->id = $this->_id = self::$_idRecorder++; $this->_socket = $socket; stream_set_blocking($this->_socket, 0); + // Compatible with hhvm + if (function_exists('stream_set_read_buffer')) { + stream_set_read_buffer($this->_socket, 0); + } Worker::$globalEvent->add($this->_socket, EventInterface::EV_READ, array($this, 'baseRead')); + $this->maxSendBufferSize = self::$defaultMaxSendBufferSize; + $this->_remoteAddress = $remote_address; } - + /** - * 发送数据给对端 + * Sends data on the connection. + * * @param string $send_buffer - * @param bool $raw - * @return void|boolean + * @param bool $raw + * @return void|bool|null */ public function send($send_buffer, $raw = false) { - // 如果当前状态是连接中,则把数据放入发送缓冲区 - if($this->_status === self::STATUS_CONNECTING) - { + // Try to call protocol::encode($send_buffer) before sending. + if (false === $raw && $this->protocol) { + $parser = $this->protocol; + $send_buffer = $parser::encode($send_buffer, $this); + if ($send_buffer === '') { + return null; + } + } + + if ($this->_status === self::STATUS_INITIAL || $this->_status === self::STATUS_CONNECTING) { $this->_sendBuffer .= $send_buffer; return null; - } - // 如果当前连接是关闭,则返回false - elseif($this->_status == self::STATUS_CLOSED) - { + } elseif ($this->_status === self::STATUS_CLOSING || $this->_status === self::STATUS_CLOSED) { return false; } - - // 如果没有设置以原始数据发送,并且有设置协议则按照协议编码 - if(false === $raw && $this->protocol) - { - $parser = $this->protocol; - $send_buffer = $parser::encode($send_buffer, $this); - } - // 如果发送缓冲区为空,尝试直接发送 - if($this->_sendBuffer === '') - { - // 直接发送 + + // Attempt to send data directly. + if ($this->_sendBuffer === '') { $len = @fwrite($this->_socket, $send_buffer); - // 所有数据都发送完毕 - if($len === strlen($send_buffer)) - { + // send successful. + if ($len === strlen($send_buffer)) { return true; } - // 只有部分数据发送成功 - if($len > 0) - { - // 未发送成功部分放入发送缓冲区 + // Send only part of the data. + if ($len > 0) { $this->_sendBuffer = substr($send_buffer, $len); - } - else - { - // 如果连接断开 - if(feof($this->_socket)) - { - // status统计发送失败次数 + } else { + // Connection closed? + if (!is_resource($this->_socket) || feof($this->_socket)) { self::$statistics['send_fail']++; - // 如果有设置失败回调,则执行 - if($this->onError) - { - try - { + if ($this->onError) { + try { call_user_func($this->onError, $this, WORKERMAN_SEND_FAIL, 'client closed'); - } - catch(Exception $e) - { - echo $e; + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); } } - // 销毁连接 $this->destroy(); return false; } - // 连接未断开,发送失败,则把所有数据放入发送缓冲区 $this->_sendBuffer = $send_buffer; } - // 监听对端可写事件 Worker::$globalEvent->add($this->_socket, EventInterface::EV_WRITE, array($this, 'baseWrite')); - // 检查发送缓冲区是否已满,如果满了尝试触发onBufferFull回调 + // Check if the send buffer is full. $this->checkBufferIsFull(); return null; - } - else - { - // 缓冲区已经标记为满,仍然然有数据发送,则丢弃数据包 - if(self::$maxSendBufferSize <= strlen($this->_sendBuffer)) - { - // 为status命令统计发送失败次数 + } else { + // Buffer has been marked as full but still has data to send the packet is discarded. + if ($this->maxSendBufferSize <= strlen($this->_sendBuffer)) { self::$statistics['send_fail']++; - // 如果有设置失败回调,则执行 - if($this->onError) - { - try - { + if ($this->onError) { + try { call_user_func($this->onError, $this, WORKERMAN_SEND_FAIL, 'send buffer full and drop package'); - } - catch(Exception $e) - { - echo $e; + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); } } return false; } - // 将数据放入放缓冲区 $this->_sendBuffer .= $send_buffer; - // 检查发送缓冲区是否已满,如果满了尝试触发onBufferFull回调 + // Check if the send buffer is full. $this->checkBufferIsFull(); } } - + /** - * 获得对端ip + * Get remote IP. + * * @return string */ public function getRemoteIp() { - if(!$this->_remoteIp) - { - $this->_remoteAddress = stream_socket_get_name($this->_socket, true); - if($this->_remoteAddress) - { - list($this->_remoteIp, $this->_remotePort) = explode(':', $this->_remoteAddress, 2); - $this->_remotePort = (int)$this->_remotePort; - } + $pos = strrpos($this->_remoteAddress, ':'); + if ($pos) { + return trim(substr($this->_remoteAddress, 0, $pos), '[]'); } - return $this->_remoteIp; + return ''; } - + /** - * 获得对端端口 + * Get remote port. + * * @return int */ public function getRemotePort() { - if(!$this->_remotePort) - { - $this->_remoteAddress = stream_socket_get_name($this->_socket, true); - if($this->_remoteAddress) - { - list($this->_remoteIp, $this->_remotePort) = explode(':', $this->_remoteAddress, 2); - $this->_remotePort = (int)$this->_remotePort; - } + if ($this->_remoteAddress) { + return (int)substr(strrchr($this->_remoteAddress, ':'), 1); } - return $this->_remotePort; + return 0; } - + /** - * 暂停接收数据,一般用于控制上传流量 + * Pauses the reading of data. That is onMessage will not be emitted. Useful to throttle back an upload. + * * @return void */ public function pauseRecv() @@ -319,178 +346,187 @@ class TcpConnection extends ConnectionInterface Worker::$globalEvent->del($this->_socket, EventInterface::EV_READ); $this->_isPaused = true; } - + /** - * 恢复接收数据,一般用户控制上传流量 + * Resumes reading after a call to pauseRecv. + * * @return void */ public function resumeRecv() { - if($this->_isPaused == true) - { + if ($this->_isPaused === true) { Worker::$globalEvent->add($this->_socket, EventInterface::EV_READ, array($this, 'baseRead')); $this->_isPaused = false; - $this->baseRead($this->_socket); + $this->baseRead($this->_socket, false); } } /** - * 当socket可读时的回调 + * Base read handler. + * * @param resource $socket + * @param bool $check_eof * @return void */ - public function baseRead($socket) + public function baseRead($socket, $check_eof = true) { - while($buffer = fread($socket, self::READ_BUFFER_SIZE)) - { - $this->_recvBuffer .= $buffer; - } - - if($this->_recvBuffer) - { - if(!$this->onMessage) - { - return ; - } - - // 如果设置了协议 - if($this->protocol) - { - $parser = $this->protocol; - while($this->_recvBuffer && !$this->_isPaused) - { - // 当前包的长度已知 - if($this->_currentPackageLength) - { - // 数据不够一个包 - if($this->_currentPackageLength > strlen($this->_recvBuffer)) - { - break; - } - } - else - { - // 获得当前包长 - $this->_currentPackageLength = $parser::input($this->_recvBuffer, $this); - // 数据不够,无法获得包长 - if($this->_currentPackageLength === 0) - { - break; - } - elseif($this->_currentPackageLength > 0 && $this->_currentPackageLength <= self::$maxPackageSize) - { - // 数据不够一个包 - if($this->_currentPackageLength > strlen($this->_recvBuffer)) - { - break; - } - } - // 包错误 - else - { - $this->close('error package. package_length='.var_export($this->_currentPackageLength, true)); - } - } - - // 数据足够一个包长 - self::$statistics['total_request']++; - // 从缓冲区中获取一个完整的包 - $one_request_buffer = substr($this->_recvBuffer, 0, $this->_currentPackageLength); - // 将当前包从接受缓冲区中去掉 - $this->_recvBuffer = substr($this->_recvBuffer, $this->_currentPackageLength); - // 重置当前包长为0 - $this->_currentPackageLength = 0; - // 处理数据包 - try - { - call_user_func($this->onMessage, $this, $parser::decode($one_request_buffer, $this)); - } - catch(Exception $e) - { - self::$statistics['throw_exception']++; - echo $e; - } - } - if($this->_status !== self::STATUS_CLOSED && feof($socket)) - { - $this->destroy(); - } - return; - } - // 没有设置协议,则直接把接收的数据当做一个包处理 - self::$statistics['total_request']++; - try - { - call_user_func($this->onMessage, $this, $this->_recvBuffer); - } - catch(Exception $e) - { - self::$statistics['throw_exception']++; - echo $e; - } - // 清空缓冲区 - $this->_recvBuffer = ''; - // 判断连接是否已经断开 - if($this->_status !== self::STATUS_CLOSED && feof($socket)) - { - $this->destroy(); - return; - } - } - // 没收到数据,判断连接是否已经断开 - else if(feof($socket)) - { - $this->destroy(); - return; - } + $buffer = fread($socket, self::READ_BUFFER_SIZE); + + // Check connection closed. + if ($buffer === '' || $buffer === false) { + if ($check_eof && (feof($socket) || !is_resource($socket) || $buffer === false)) { + $this->destroy(); + return; + } + } else { + $this->_recvBuffer .= $buffer; + } + + // If the application layer protocol has been set up. + if ($this->protocol) { + $parser = $this->protocol; + while ($this->_recvBuffer !== '' && !$this->_isPaused) { + // The current packet length is known. + if ($this->_currentPackageLength) { + // Data is not enough for a package. + if ($this->_currentPackageLength > strlen($this->_recvBuffer)) { + break; + } + } else { + // Get current package length. + $this->_currentPackageLength = $parser::input($this->_recvBuffer, $this); + // The packet length is unknown. + if ($this->_currentPackageLength === 0) { + break; + } elseif ($this->_currentPackageLength > 0 && $this->_currentPackageLength <= self::$maxPackageSize) { + // Data is not enough for a package. + if ($this->_currentPackageLength > strlen($this->_recvBuffer)) { + break; + } + } // Wrong package. + else { + echo 'error package. package_length=' . var_export($this->_currentPackageLength, true); + $this->destroy(); + return; + } + } + + // The data is enough for a packet. + self::$statistics['total_request']++; + // The current packet length is equal to the length of the buffer. + if (strlen($this->_recvBuffer) === $this->_currentPackageLength) { + $one_request_buffer = $this->_recvBuffer; + $this->_recvBuffer = ''; + } else { + // Get a full package from the buffer. + $one_request_buffer = substr($this->_recvBuffer, 0, $this->_currentPackageLength); + // Remove the current package from the receive buffer. + $this->_recvBuffer = substr($this->_recvBuffer, $this->_currentPackageLength); + } + // Reset the current packet length to 0. + $this->_currentPackageLength = 0; + if (!$this->onMessage) { + continue; + } + try { + // Decode request buffer before Emitting onMessage callback. + call_user_func($this->onMessage, $this, $parser::decode($one_request_buffer, $this)); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + return; + } + + if ($this->_recvBuffer === '' || $this->_isPaused) { + return; + } + + // Applications protocol is not set. + self::$statistics['total_request']++; + if (!$this->onMessage) { + $this->_recvBuffer = ''; + return; + } + try { + call_user_func($this->onMessage, $this, $this->_recvBuffer); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + // Clean receive buffer. + $this->_recvBuffer = ''; } /** - * socket可写时的回调 - * @return void + * Base write handler. + * + * @return void|bool */ public function baseWrite() { $len = @fwrite($this->_socket, $this->_sendBuffer); - if($len === strlen($this->_sendBuffer)) - { + if ($len === strlen($this->_sendBuffer)) { Worker::$globalEvent->del($this->_socket, EventInterface::EV_WRITE); $this->_sendBuffer = ''; - // 发送缓冲区的数据被发送完毕,尝试触发onBufferDrain回调 - if($this->onBufferDrain) - { - try - { + // Try to emit onBufferDrain callback when the send buffer becomes empty. + if ($this->onBufferDrain) { + try { call_user_func($this->onBufferDrain, $this); - } - catch(Exception $e) - { - echo $e; + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); } } - // 如果连接状态为关闭,则销毁连接 - if($this->_status == self::STATUS_CLOSING) - { + if ($this->_status === self::STATUS_CLOSING) { $this->destroy(); } return true; } - if($len > 0) - { - $this->_sendBuffer = substr($this->_sendBuffer, $len); - } - else - { - if(feof($this->_socket)) - { - self::$statistics['send_fail']++; - $this->destroy(); - } + if ($len > 0) { + $this->_sendBuffer = substr($this->_sendBuffer, $len); + } else { + self::$statistics['send_fail']++; + $this->destroy(); } } - + /** - * 从缓冲区中消费掉$length长度的数据 + * This method pulls all the data out of a readable stream, and writes it to the supplied destination. + * + * @param TcpConnection $dest + * @return void + */ + public function pipe($dest) + { + $source = $this; + $this->onMessage = function ($source, $data) use ($dest) { + $dest->send($data); + }; + $this->onClose = function ($source) use ($dest) { + $dest->destroy(); + }; + $dest->onBufferFull = function ($dest) use ($source) { + $source->pauseRecv(); + }; + $dest->onBufferDrain = function ($dest) use ($source) { + $source->resumeRecv(); + }; + } + + /** + * Remove $length of data from receive buffer. + * * @param int $length * @return void */ @@ -500,32 +536,30 @@ class TcpConnection extends ConnectionInterface } /** - * 关闭连接 + * Close connection. + * * @param mixed $data - * @void + * @param bool $raw + * @return void */ - public function close($data = null) + public function close($data = null, $raw = false) { - if($this->_status == self::STATUS_CLOSING) - { - return false; - } - else - { + if ($this->_status === self::STATUS_CLOSING || $this->_status === self::STATUS_CLOSED) { + return; + } else { + if ($data !== null) { + $this->send($data, $raw); + } $this->_status = self::STATUS_CLOSING; } - if($data !== null) - { - $this->send($data); - } - if($this->_sendBuffer === '') - { - $this->destroy(); + if ($this->_sendBuffer === '') { + $this->destroy(); } } - + /** - * 获得socket连接 + * Get the real socket. + * * @return resource */ public function getSocket() @@ -534,52 +568,85 @@ class TcpConnection extends ConnectionInterface } /** - * 检查发送缓冲区是否已满,如果满了尝试触发onBufferFull回调 + * Check whether the send buffer is full. + * * @return void */ protected function checkBufferIsFull() { - if(self::$maxSendBufferSize <= strlen($this->_sendBuffer)) - { - if($this->onBufferFull) - { - try - { + if ($this->maxSendBufferSize <= strlen($this->_sendBuffer)) { + if ($this->onBufferFull) { + try { call_user_func($this->onBufferFull, $this); - } - catch(Exception $e) - { - echo $e; + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); } } } } + /** - * 销毁连接 - * @void + * Destroy connection. + * + * @return void */ - protected function destroy() + public function destroy() { - self::$statistics['connection_count']--; - if($this->onClose) - { - try - { - call_user_func($this->onClose, $this); - } - catch (Exception $e) - { - self::$statistics['throw_exception']++; - echo $e; - } - } - if($this->worker) - { - unset($this->worker->connections[(int)$this->_socket]); - } - Worker::$globalEvent->del($this->_socket, EventInterface::EV_READ); - Worker::$globalEvent->del($this->_socket, EventInterface::EV_WRITE); - @fclose($this->_socket); - $this->_status = self::STATUS_CLOSED; + // Avoid repeated calls. + if ($this->_status === self::STATUS_CLOSED) { + return; + } + // Remove event listener. + Worker::$globalEvent->del($this->_socket, EventInterface::EV_READ); + Worker::$globalEvent->del($this->_socket, EventInterface::EV_WRITE); + // Close socket. + @fclose($this->_socket); + // Remove from worker->connections. + if ($this->worker) { + unset($this->worker->connections[$this->_id]); + } + $this->_status = self::STATUS_CLOSED; + // Try to emit onClose callback. + if ($this->onClose) { + try { + call_user_func($this->onClose, $this); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + // Try to emit protocol::onClose + if (method_exists($this->protocol, 'onClose')) { + try { + call_user_func(array($this->protocol, 'onClose'), $this); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + if ($this->_status === self::STATUS_CLOSED) { + // Cleaning up the callback to avoid memory leaks. + $this->onMessage = $this->onClose = $this->onError = $this->onBufferFull = $this->onBufferDrain = null; + } + } + + /** + * Destruct. + * + * @return void + */ + public function __destruct() + { + self::$statistics['connection_count']--; } } diff --git a/Workerman/Connection/UdpConnection.php b/Workerman/Connection/UdpConnection.php index 3fda857..e4cd2fe 100644 --- a/Workerman/Connection/UdpConnection.php +++ b/Workerman/Connection/UdpConnection.php @@ -1,105 +1,114 @@ + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ namespace Workerman\Connection; -use Workerman\Events\Libevent; -use Workerman\Events\Select; -use Workerman\Events\EventInterface; -use Workerman\Worker; -use \Exception; - /** - * udp连接类(udp实际上是无连接的,这里是为了保持与TCP接口一致) - * @author walkor + * UdpConnection. */ class UdpConnection extends ConnectionInterface { /** - * 应用层协议 - * 值类似于 Workerman\\Protocols\\Http - * @var string + * Application layer protocol. + * The format is like this Workerman\\Protocols\\Http. + * + * @var \Workerman\Protocols\ProtocolInterface */ - public $protocol = ''; - + public $protocol = null; + /** - * udp socket 资源 + * Udp socket. + * * @var resource */ protected $_socket = null; - + /** - * 对端 ip - * @var string - */ - protected $_remoteIp = ''; - - /** - * 对端 端口 - * @var int - */ - protected $_remotePort = 0; - - /** - * 对端 地址 - * 值类似于 192.168.10.100:3698 + * Remote address. + * * @var string */ protected $_remoteAddress = ''; /** - * 构造函数 + * Construct. + * * @param resource $socket - * @param string $remote_address + * @param string $remote_address */ public function __construct($socket, $remote_address) { - $this->_socket = $socket; + $this->_socket = $socket; $this->_remoteAddress = $remote_address; } - + /** - * 发送数据给对端 + * Sends data on the connection. + * * @param string $send_buffer + * @param bool $raw * @return void|boolean */ - public function send($send_buffer) + public function send($send_buffer, $raw = false) { + if (false === $raw && $this->protocol) { + $parser = $this->protocol; + $send_buffer = $parser::encode($send_buffer, $this); + if ($send_buffer === '') { + return null; + } + } return strlen($send_buffer) === stream_socket_sendto($this->_socket, $send_buffer, 0, $this->_remoteAddress); } - + /** - * 获得对端 ip + * Get remote IP. + * * @return string */ public function getRemoteIp() { - if(!$this->_remoteIp) - { - list($this->_remoteIp, $this->_remotePort) = explode(':', $this->_remoteAddress, 2); + $pos = strrpos($this->_remoteAddress, ':'); + if ($pos) { + return trim(substr($this->_remoteAddress, 0, $pos), '[]'); } - return $this->_remoteIp; - } - - /** - * 获得对端端口 - */ - public function getRemotePort() - { - if(!$this->_remotePort) - { - list($this->_remoteIp, $this->_remotePort) = explode(':', $this->_remoteAddress, 2); - } - return $this->_remotePort; + return ''; } /** - * 关闭连接(此处为了保持与TCP接口一致,提供了close方法) - * @void + * Get remote port. + * + * @return int */ - public function close($data = null) + public function getRemotePort() { - if($data !== null) - { - $this->send($data); + if ($this->_remoteAddress) { + return (int)substr(strrchr($this->_remoteAddress, ':'), 1); + } + return 0; + } + + /** + * Close connection. + * + * @param mixed $data + * @param bool $raw + * @return bool + */ + public function close($data = null, $raw = false) + { + if ($data !== null) { + $this->send($data, $raw); } return true; } diff --git a/Workerman/Events/Ev.php b/Workerman/Events/Ev.php new file mode 100644 index 0000000..acc1a36 --- /dev/null +++ b/Workerman/Events/Ev.php @@ -0,0 +1,173 @@ + + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events; + +use Workerman\Worker; + +/** + * ev eventloop + */ +class Ev implements EventInterface +{ + /** + * All listeners for read/write event. + * + * @var array + */ + protected $_allEvents = array(); + + /** + * Event listeners of signal. + * + * @var array + */ + protected $_eventSignal = array(); + + /** + * All timer event listeners. + * [func, args, event, flag, time_interval] + * + * @var array + */ + protected $_eventTimer = array(); + + /** + * Timer id. + * + * @var int + */ + protected static $_timerId = 1; + + /** + * Add a timer. + * {@inheritdoc} + */ + public function add($fd, $flag, $func, $args = null) + { + $callback = function ($event, $socket) use ($fd, $func) { + try { + call_user_func($func, $fd); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + }; + + switch ($flag) { + case self::EV_SIGNAL: + $event = new \EvSignal($fd, $callback); + $this->_eventSignal[$fd] = $event; + return true; + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + $repeat = $flag == self::EV_TIMER_ONCE ? 0 : $fd; + $param = array($func, (array)$args, $flag, $fd, self::$_timerId); + $event = new \EvTimer($fd, $repeat, array($this, 'timerCallback'), $param); + $this->_eventTimer[self::$_timerId] = $event; + return self::$_timerId++; + default : + $fd_key = (int)$fd; + $real_flag = $flag === self::EV_READ ? \Ev::READ : \Ev::WRITE; + $event = new \EvIo($fd, $real_flag, $callback); + $this->_allEvents[$fd_key][$flag] = $event; + return true; + } + + } + + /** + * Remove a timer. + * {@inheritdoc} + */ + public function del($fd, $flag) + { + switch ($flag) { + case self::EV_READ: + case self::EV_WRITE: + $fd_key = (int)$fd; + if (isset($this->_allEvents[$fd_key][$flag])) { + $this->_allEvents[$fd_key][$flag]->stop(); + unset($this->_allEvents[$fd_key][$flag]); + } + if (empty($this->_allEvents[$fd_key])) { + unset($this->_allEvents[$fd_key]); + } + break; + case self::EV_SIGNAL: + $fd_key = (int)$fd; + if (isset($this->_eventSignal[$fd_key])) { + $this->_allEvents[$fd_key][$flag]->stop(); + unset($this->_eventSignal[$fd_key]); + } + break; + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + if (isset($this->_eventTimer[$fd])) { + $this->_eventTimer[$fd]->stop(); + unset($this->_eventTimer[$fd]); + } + break; + } + return true; + } + + /** + * Timer callback. + * + * @param \EvWatcher $event + */ + public function timerCallback($event) + { + $param = $event->data; + $timer_id = $param[4]; + if ($param[2] === self::EV_TIMER_ONCE) { + $this->_eventTimer[$timer_id]->stop(); + unset($this->_eventTimer[$timer_id]); + } + try { + call_user_func_array($param[0], $param[1]); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + + /** + * Remove all timers. + * + * @return void + */ + public function clearAllTimer() + { + foreach ($this->_eventTimer as $event) { + $event->stop(); + } + $this->_eventTimer = array(); + } + + /** + * Main loop. + * + * @see EventInterface::loop() + */ + public function loop() + { + \Ev::run(); + } +} diff --git a/Workerman/Events/Event.php b/Workerman/Events/Event.php new file mode 100644 index 0000000..2d85aaf --- /dev/null +++ b/Workerman/Events/Event.php @@ -0,0 +1,188 @@ + + * @copyright 有个鬼<42765633@qq.com> + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events; + +use Workerman\Worker; + +/** + * libevent eventloop + */ +class Event implements EventInterface +{ + /** + * Event base. + * @var object + */ + protected $_eventBase = null; + + /** + * All listeners for read/write event. + * @var array + */ + protected $_allEvents = array(); + + /** + * Event listeners of signal. + * @var array + */ + protected $_eventSignal = array(); + + /** + * All timer event listeners. + * [func, args, event, flag, time_interval] + * @var array + */ + protected $_eventTimer = array(); + + /** + * Timer id. + * @var int + */ + protected static $_timerId = 1; + + /** + * construct + * @return void + */ + public function __construct() + { + $this->_eventBase = new \EventBase(); + } + + /** + * @see EventInterface::add() + */ + public function add($fd, $flag, $func, $args=array()) + { + switch ($flag) { + case self::EV_SIGNAL: + + $fd_key = (int)$fd; + $event = \Event::signal($this->_eventBase, $fd, $func); + if (!$event||!$event->add()) { + return false; + } + $this->_eventSignal[$fd_key] = $event; + return true; + + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + + $param = array($func, (array)$args, $flag, $fd, self::$_timerId); + $event = new \Event($this->_eventBase, -1, \Event::TIMEOUT|\Event::PERSIST, array($this, "timerCallback"), $param); + if (!$event||!$event->addTimer($fd)) { + return false; + } + $this->_eventTimer[self::$_timerId] = $event; + return self::$_timerId++; + + default : + $fd_key = (int)$fd; + $real_flag = $flag === self::EV_READ ? \Event::READ | \Event::PERSIST : \Event::WRITE | \Event::PERSIST; + $event = new \Event($this->_eventBase, $fd, $real_flag, $func, $fd); + if (!$event||!$event->add()) { + return false; + } + $this->_allEvents[$fd_key][$flag] = $event; + return true; + } + } + + /** + * @see Events\EventInterface::del() + */ + public function del($fd, $flag) + { + switch ($flag) { + + case self::EV_READ: + case self::EV_WRITE: + + $fd_key = (int)$fd; + if (isset($this->_allEvents[$fd_key][$flag])) { + $this->_allEvents[$fd_key][$flag]->del(); + unset($this->_allEvents[$fd_key][$flag]); + } + if (empty($this->_allEvents[$fd_key])) { + unset($this->_allEvents[$fd_key]); + } + break; + + case self::EV_SIGNAL: + + $fd_key = (int)$fd; + if (isset($this->_eventSignal[$fd_key])) { + $this->_allEvents[$fd_key][$flag]->del(); + unset($this->_eventSignal[$fd_key]); + } + break; + + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + if (isset($this->_eventTimer[$fd])) { + $this->_eventTimer[$fd]->del(); + unset($this->_eventTimer[$fd]); + } + break; + } + return true; + } + + /** + * Timer callback. + * @param null $fd + * @param int $what + * @param int $timer_id + */ + public function timerCallback($fd, $what, $param) + { + $timer_id = $param[4]; + + if ($param[2] === self::EV_TIMER_ONCE) { + $this->_eventTimer[$timer_id]->del(); + unset($this->_eventTimer[$timer_id]); + } + + try { + call_user_func_array($param[0], $param[1]); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + + /** + * @see Events\EventInterface::clearAllTimer() + * @return void + */ + public function clearAllTimer() + { + foreach ($this->_eventTimer as $event) { + $event->del(); + } + $this->_eventTimer = array(); + } + + + /** + * @see EventInterface::loop() + */ + public function loop() + { + $this->_eventBase->loop(); + } +} diff --git a/Workerman/Events/EventInterface.php b/Workerman/Events/EventInterface.php index 3643b9d..d71b5d4 100644 --- a/Workerman/Events/EventInterface.php +++ b/Workerman/Events/EventInterface.php @@ -1,63 +1,85 @@ + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ namespace Workerman\Events; interface EventInterface { /** - * 读事件 + * Read event. + * * @var int */ const EV_READ = 1; - + /** - * 写事件 + * Write event. + * * @var int */ const EV_WRITE = 2; - + /** - * 信号事件 + * Signal event. + * * @var int */ const EV_SIGNAL = 4; - + /** - * 连续的定时事件 + * Timer event. + * * @var int */ const EV_TIMER = 8; - + /** - * 定时一次 - * @var int + * Timer once event. + * + * @var int */ const EV_TIMER_ONCE = 16; - + /** - * 添加事件回调 - * @param resource $fd - * @param int $flag + * Add event listener to event loop. + * + * @param mixed $fd + * @param int $flag * @param callable $func + * @param mixed $args * @return bool */ public function add($fd, $flag, $func, $args = null); - + /** - * 删除事件回调 - * @param resource $fd - * @param int $flag + * Remove event listener from event loop. + * + * @param mixed $fd + * @param int $flag * @return bool */ public function del($fd, $flag); - + /** - * 清除所有定时器 + * Remove all timers. + * * @return void */ public function clearAllTimer(); - + /** - * 事件循环 + * Main loop. + * * @return void */ public function loop(); diff --git a/Workerman/Events/Libevent.php b/Workerman/Events/Libevent.php index 28f0b7e..5644bf1 100644 --- a/Workerman/Events/Libevent.php +++ b/Workerman/Events/Libevent.php @@ -1,145 +1,146 @@ - + * This file is part of workerman. + * + * Licensed under The MIT License + * For full copyright and license information, please see the MIT-LICENSE.txt + * Redistributions of files must retain the above copyright notice. + * + * @author walkor + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events; + +use Workerman\Worker; + +/** + * libevent eventloop */ class Libevent implements EventInterface { /** - * eventBase - * @var object + * Event base. + * + * @var resource */ protected $_eventBase = null; - + /** - * 所有的事件 + * All listeners for read/write event. + * * @var array */ protected $_allEvents = array(); - + /** - * 所有的信号事件 + * Event listeners of signal. + * * @var array */ protected $_eventSignal = array(); - + /** - * 所有的定时事件 + * All timer event listeners. * [func, args, event, flag, time_interval] + * * @var array */ protected $_eventTimer = array(); - + /** - * 构造函数 - * @return void + * construct */ public function __construct() { $this->_eventBase = event_base_new(); } - + /** - * 添加事件 - * @see EventInterface::add() + * {@inheritdoc} */ - public function add($fd, $flag, $func, $args=null) + public function add($fd, $flag, $func, $args = array()) { - switch($flag) - { + switch ($flag) { case self::EV_SIGNAL: - $fd_key = (int)$fd; - $real_flag = EV_SIGNAL | EV_PERSIST; + $fd_key = (int)$fd; + $real_flag = EV_SIGNAL | EV_PERSIST; $this->_eventSignal[$fd_key] = event_new(); - if(!event_set($this->_eventSignal[$fd_key], $fd, $real_flag, $func, null)) - { + if (!event_set($this->_eventSignal[$fd_key], $fd, $real_flag, $func, null)) { return false; } - if(!event_base_set($this->_eventSignal[$fd_key], $this->_eventBase)) - { + if (!event_base_set($this->_eventSignal[$fd_key], $this->_eventBase)) { return false; } - if(!event_add($this->_eventSignal[$fd_key])) - { + if (!event_add($this->_eventSignal[$fd_key])) { return false; } return true; case self::EV_TIMER: case self::EV_TIMER_ONCE: - $event = event_new(); + $event = event_new(); $timer_id = (int)$event; - if(!event_set($event, 0, EV_TIMEOUT, array($this, 'timerCallback'), $timer_id)) - { + if (!event_set($event, 0, EV_TIMEOUT, array($this, 'timerCallback'), $timer_id)) { return false; } - - if(!event_base_set($event, $this->_eventBase)) - { + + if (!event_base_set($event, $this->_eventBase)) { return false; } - - $time_interval = $fd*1000000; - if(!event_add($event, $time_interval)) - { + + $time_interval = $fd * 1000000; + if (!event_add($event, $time_interval)) { return false; } $this->_eventTimer[$timer_id] = array($func, (array)$args, $event, $flag, $time_interval); return $timer_id; - + default : - $fd_key = (int)$fd; - $real_flag = $flag == self::EV_READ ? EV_READ | EV_PERSIST : EV_WRITE | EV_PERSIST; - + $fd_key = (int)$fd; + $real_flag = $flag === self::EV_READ ? EV_READ | EV_PERSIST : EV_WRITE | EV_PERSIST; + $event = event_new(); - - if(!event_set($event, $fd, $real_flag, $func, null)) - { + + if (!event_set($event, $fd, $real_flag, $func, null)) { return false; } - - if(!event_base_set($event, $this->_eventBase)) - { + + if (!event_base_set($event, $this->_eventBase)) { return false; } - - if(!event_add($event)) - { + + if (!event_add($event)) { return false; } - + $this->_allEvents[$fd_key][$flag] = $event; - + return true; } - + } - + /** - * 删除事件 - * @see Events\EventInterface::del() + * {@inheritdoc} */ - public function del($fd ,$flag) + public function del($fd, $flag) { - switch($flag) - { + switch ($flag) { case self::EV_READ: case self::EV_WRITE: $fd_key = (int)$fd; - if(isset($this->_allEvents[$fd_key][$flag])) - { + if (isset($this->_allEvents[$fd_key][$flag])) { event_del($this->_allEvents[$fd_key][$flag]); unset($this->_allEvents[$fd_key][$flag]); } - if(empty($this->_allEvents[$fd_key])) - { + if (empty($this->_allEvents[$fd_key])) { unset($this->_allEvents[$fd_key]); } break; case self::EV_SIGNAL: $fd_key = (int)$fd; - if(isset($this->_eventSignal[$fd_key])) - { + if (isset($this->_eventSignal[$fd_key])) { event_del($this->_eventSignal[$fd_key]); unset($this->_eventSignal[$fd_key]); } @@ -147,8 +148,7 @@ class Libevent implements EventInterface case self::EV_TIMER: case self::EV_TIMER_ONCE: // 这里 fd 为timerid - if(isset($this->_eventTimer[$fd])) - { + if (isset($this->_eventTimer[$fd])) { event_del($this->_eventTimer[$fd][2]); unset($this->_eventTimer[$fd]); } @@ -156,48 +156,46 @@ class Libevent implements EventInterface } return true; } - + /** - * 定时器回调 - * @param null $_null - * @param null $_null - * @param int $timer_id + * Timer callback. + * + * @param mixed $_null1 + * @param int $_null2 + * @param mixed $timer_id */ - protected function timerCallback($_null, $_null, $timer_id) + protected function timerCallback($_null1, $_null2, $timer_id) { - // 如果是连续的定时任务,再把任务加进去 - if($this->_eventTimer[$timer_id][3] == self::EV_TIMER) - { + if ($this->_eventTimer[$timer_id][3] === self::EV_TIMER) { event_add($this->_eventTimer[$timer_id][2], $this->_eventTimer[$timer_id][4]); } - try - { - // 执行任务 + try { call_user_func_array($this->_eventTimer[$timer_id][0], $this->_eventTimer[$timer_id][1]); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); } - catch(\Exception $e) - { - echo $e; + if (isset($this->_eventTimer[$timer_id]) && $this->_eventTimer[$timer_id][3] === self::EV_TIMER_ONCE) { + $this->del($timer_id, self::EV_TIMER_ONCE); } } - + /** - * 删除所有定时器 - * @return void + * {@inheritdoc} */ public function clearAllTimer() { - foreach($this->_eventTimer as $task_data) - { + foreach ($this->_eventTimer as $task_data) { event_del($task_data[2]); } $this->_eventTimer = array(); } - /** - * 事件循环 - * @see EventInterface::loop() + * {@inheritdoc} */ public function loop() { diff --git a/Workerman/Events/Select.php b/Workerman/Events/Select.php index 3389c97..1b9d16f 100644 --- a/Workerman/Events/Select.php +++ b/Workerman/Events/Select.php @@ -1,141 +1,163 @@ + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ namespace Workerman\Events; +/** + * select eventloop + */ class Select implements EventInterface { /** - * 所有的事件 + * All listeners for read/write event. + * * @var array */ public $_allEvents = array(); - + /** - * 所有信号事件 + * Event listeners of signal. + * * @var array */ public $_signalEvents = array(); - + /** - * 监听这些描述符的读事件 + * Fds waiting for read event. + * * @var array */ protected $_readFds = array(); - + /** - * 监听这些描述符的写事件 + * Fds waiting for write event. + * * @var array */ protected $_writeFds = array(); - + /** - * 任务调度器,最大堆 + * Timer scheduler. * {['data':timer_id, 'priority':run_timestamp], ..} - * @var SplPriorityQueue + * + * @var \SplPriorityQueue */ protected $_scheduler = null; - + /** - * 定时任务 + * All timer event listeners. * [[func, args, flag, timer_interval], ..] + * * @var array */ protected $_task = array(); - + /** - * 定时器id + * Timer id. + * * @var int */ protected $_timerId = 1; - + /** - * select超时时间,单位:微妙 + * Select timeout. + * * @var int */ protected $_selectTimeout = 100000000; - + /** - * 构造函数 - * @return void + * Paired socket channels + * + * @var array + */ + protected $channel = array(); + + /** + * Construct. */ public function __construct() { - // 创建一个管道,放入监听读的描述符集合中,避免空轮询 + // Create a pipeline and put into the collection of the read to read the descriptor to avoid empty polling. $this->channel = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP); - if($this->channel) - { + if ($this->channel) { stream_set_blocking($this->channel[0], 0); $this->_readFds[0] = $this->channel[0]; } - // 初始化优先队列(最大堆) + // Init SplPriorityQueue. $this->_scheduler = new \SplPriorityQueue(); $this->_scheduler->setExtractFlags(\SplPriorityQueue::EXTR_BOTH); } - + /** - * 添加事件及处理函数 - * @see Events\EventInterface::add() + * {@inheritdoc} */ - public function add($fd, $flag, $func, $args = null) + public function add($fd, $flag, $func, $args = array()) { - switch ($flag) - { + switch ($flag) { case self::EV_READ: - $fd_key = (int)$fd; + $fd_key = (int)$fd; $this->_allEvents[$fd_key][$flag] = array($func, $fd); - $this->_readFds[$fd_key] = $fd; + $this->_readFds[$fd_key] = $fd; break; case self::EV_WRITE: - $fd_key = (int)$fd; + $fd_key = (int)$fd; $this->_allEvents[$fd_key][$flag] = array($func, $fd); - $this->_writeFds[$fd_key] = $fd; + $this->_writeFds[$fd_key] = $fd; break; case self::EV_SIGNAL: - $fd_key = (int)$fd; + $fd_key = (int)$fd; $this->_signalEvents[$fd_key][$flag] = array($func, $fd); pcntl_signal($fd, array($this, 'signalHandler')); break; case self::EV_TIMER: case self::EV_TIMER_ONCE: - // $fd 为 定时的时间间隔,单位为秒,支持小数,能精确到0.001秒 - $run_time = microtime(true)+$fd; + $run_time = microtime(true) + $fd; $this->_scheduler->insert($this->_timerId, -$run_time); - $this->_task[$this->_timerId] = array($func, $args, $flag, $fd); + $this->_task[$this->_timerId] = array($func, (array)$args, $flag, $fd); $this->tick(); return $this->_timerId++; } - + return true; } - + /** - * 信号处理函数 + * Signal handler. + * * @param int $signal */ public function signalHandler($signal) { call_user_func_array($this->_signalEvents[$signal][self::EV_SIGNAL][0], array($signal)); } - + /** - * 删除某个描述符的某类事件的监听 - * @see Events\EventInterface::del() + * {@inheritdoc} */ - public function del($fd ,$flag) + public function del($fd, $flag) { $fd_key = (int)$fd; - switch ($flag) - { + switch ($flag) { case self::EV_READ: unset($this->_allEvents[$fd_key][$flag], $this->_readFds[$fd_key]); - if(empty($this->_allEvents[$fd_key])) - { + if (empty($this->_allEvents[$fd_key])) { unset($this->_allEvents[$fd_key]); } return true; case self::EV_WRITE: unset($this->_allEvents[$fd_key][$flag], $this->_writeFds[$fd_key]); - if(empty($this->_allEvents[$fd_key])) - { + if (empty($this->_allEvents[$fd_key])) { unset($this->_allEvents[$fd_key]); } return true; @@ -145,67 +167,51 @@ class Select implements EventInterface break; case self::EV_TIMER: case self::EV_TIMER_ONCE; - // $fd_key为要删除的定时器id,即timerId unset($this->_task[$fd_key]); return true; } - return false;; + return false; } - + /** - * 检查是否有可执行的定时任务,有的话执行 + * Tick for timer. + * * @return void */ protected function tick() { - while(!$this->_scheduler->isEmpty()) - { - $scheduler_data = $this->_scheduler->top(); - $timer_id = $scheduler_data['data']; - $next_run_time = -$scheduler_data['priority']; - $time_now = microtime(true); - if($time_now >= $next_run_time) - { + while (!$this->_scheduler->isEmpty()) { + $scheduler_data = $this->_scheduler->top(); + $timer_id = $scheduler_data['data']; + $next_run_time = -$scheduler_data['priority']; + $time_now = microtime(true); + $this->_selectTimeout = ($next_run_time - $time_now) * 1000000; + if ($this->_selectTimeout <= 0) { $this->_scheduler->extract(); - - // 如果任务不存在,则是对应的定时器已经删除 - if(!isset($this->_task[$timer_id])) - { + + if (!isset($this->_task[$timer_id])) { continue; } - - // 任务数据[func, args, flag, timer_interval] + + // [func, args, flag, timer_interval] $task_data = $this->_task[$timer_id]; - // 如果是持续的定时任务,再把任务加到定时队列 - if($task_data[2] == self::EV_TIMER) - { - $next_run_time = $time_now+$task_data[3]; + if ($task_data[2] === self::EV_TIMER) { + $next_run_time = $time_now + $task_data[3]; $this->_scheduler->insert($timer_id, -$next_run_time); } - // 尝试执行任务 - try - { - call_user_func_array($task_data[0], $task_data[1]); - } - catch(\Exception $e) - { - echo $e; + call_user_func_array($task_data[0], $task_data[1]); + if (isset($this->_task[$timer_id]) && $task_data[2] === self::EV_TIMER_ONCE) { + $this->del($timer_id, self::EV_TIMER_ONCE); } continue; } - else - { - // 设定超时时间 - $this->_selectTimeout = ($next_run_time - $time_now)*1000000; - return; - } + return; } $this->_selectTimeout = 100000000; } - + /** - * 删除所有定时器 - * @return void + * {@inheritdoc} */ public function clearAllTimer() { @@ -213,55 +219,45 @@ class Select implements EventInterface $this->_scheduler->setExtractFlags(\SplPriorityQueue::EXTR_BOTH); $this->_task = array(); } - + /** - * 主循环 - * @see Events\EventInterface::loop() + * {@inheritdoc} */ public function loop() { $e = null; - while (1) - { - // 如果有信号,尝试执行信号处理函数 + while (1) { + // Calls signal handlers for pending signals pcntl_signal_dispatch(); - - $read = $this->_readFds; + + $read = $this->_readFds; $write = $this->_writeFds; - // 等待可读或者可写事件 - @stream_select($read, $write, $e, 0, $this->_selectTimeout); - - // 这些描述符可读,执行对应描述符的读回调函数 - if($read) - { - foreach($read as $fd) - { - $fd_key = (int) $fd; - if(isset($this->_allEvents[$fd_key][self::EV_READ])) - { - call_user_func_array($this->_allEvents[$fd_key][self::EV_READ][0], array($this->_allEvents[$fd_key][self::EV_READ][1])); - } - } - } - - // 这些描述符可写,执行对应描述符的写回调函数 - if($write) - { - foreach($write as $fd) - { - $fd_key = (int) $fd; - if(isset($this->_allEvents[$fd_key][self::EV_WRITE])) - { - call_user_func_array($this->_allEvents[$fd_key][self::EV_WRITE][0], array($this->_allEvents[$fd_key][self::EV_WRITE][1])); - } - } - } - - // 尝试执行定时任务 - if(!$this->_scheduler->isEmpty()) - { + // Waiting read/write/signal/timeout events. + $ret = @stream_select($read, $write, $e, 0, $this->_selectTimeout); + + if (!$this->_scheduler->isEmpty()) { $this->tick(); } + + if (!$ret) { + continue; + } + + foreach ($read as $fd) { + $fd_key = (int)$fd; + if (isset($this->_allEvents[$fd_key][self::EV_READ])) { + call_user_func_array($this->_allEvents[$fd_key][self::EV_READ][0], + array($this->_allEvents[$fd_key][self::EV_READ][1])); + } + } + + foreach ($write as $fd) { + $fd_key = (int)$fd; + if (isset($this->_allEvents[$fd_key][self::EV_WRITE])) { + call_user_func_array($this->_allEvents[$fd_key][self::EV_WRITE][0], + array($this->_allEvents[$fd_key][self::EV_WRITE][1])); + } + } } } } diff --git a/Workerman/Lib/Constants.php b/Workerman/Lib/Constants.php index 46e0b20..8144074 100644 --- a/Workerman/Lib/Constants.php +++ b/Workerman/Lib/Constants.php @@ -1,13 +1,35 @@ + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +// Date.timezone +if (!ini_get('date.timezone')) { date_default_timezone_set('Asia/Shanghai'); } -// 显示错误到终端 +// Display errors. ini_set('display_errors', 'on'); +// Reporting all. +error_reporting(E_ALL); -// 连接失败 +// For onError callback. define('WORKERMAN_CONNECT_FAIL', 1); -// 发送失败 -define('WORKERMAN_SEND_FAIL', 2); \ No newline at end of file +// For onError callback. +define('WORKERMAN_SEND_FAIL', 2); + +// Compatible with php7 +if(!class_exists('Error')) +{ + class Error extends Exception + { + } +} \ No newline at end of file diff --git a/Workerman/Lib/Timer.php b/Workerman/Lib/Timer.php index 9ce5f47..b382cbb 100644 --- a/Workerman/Lib/Timer.php +++ b/Workerman/Lib/Timer.php @@ -1,146 +1,142 @@ + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ namespace Workerman\Lib; -use \Workerman\Events\EventInterface; -use \Exception; + +use Workerman\Events\EventInterface; +use Exception; /** - * - * 定时器 - * - * example: - *
- * 
- * Workerman\Lib\Timer::init();
+ * Timer.
+ *
+ * example:
  * Workerman\Lib\Timer::add($time_interval, callback, array($arg1, $arg2..));
- * 
- * 
-* @author walkor */ -class Timer +class Timer { /** - * 基于ALARM信号的任务 + * Tasks that based on ALARM signal. * [ * run_time => [[$func, $args, $persistent, time_interval],[$func, $args, $persistent, time_interval],..]], * run_time => [[$func, $args, $persistent, time_interval],[$func, $args, $persistent, time_interval],..]], - * .. + * .. * ] + * * @var array */ protected static $_tasks = array(); - + /** * event - * @var event + * + * @var \Workerman\Events\EventInterface */ protected static $_event = null; - - + /** - * 初始化 + * Init. + * + * @param \Workerman\Events\EventInterface $event * @return void */ public static function init($event = null) { - if($event) - { + if ($event) { self::$_event = $event; - } - else - { + } else { pcntl_signal(SIGALRM, array('\Workerman\Lib\Timer', 'signalHandle'), false); } } - + /** - * 信号处理函数,只处理ALARM事件 + * ALARM signal handler. + * * @return void */ public static function signalHandle() { - if(!self::$_event) - { + if (!self::$_event) { pcntl_alarm(1); self::tick(); } } - - + /** - * 添加一个定时器 - * @param int $time_interval + * Add a timer. + * + * @param int $time_interval * @param callback $func - * @param mix $args - * @return void + * @param mixed $args + * @param bool $persistent + * @return bool */ public static function add($time_interval, $func, $args = array(), $persistent = true) { - if($time_interval <= 0) - { + if ($time_interval <= 0) { echo new Exception("bad time_interval"); return false; } - - if(self::$_event) - { - return self::$_event->add($time_interval, $persistent ? EventInterface::EV_TIMER : EventInterface::EV_TIMER_ONCE , $func, $args); + + if (self::$_event) { + return self::$_event->add($time_interval, + $persistent ? EventInterface::EV_TIMER : EventInterface::EV_TIMER_ONCE, $func, $args); } - - if(!is_callable($func)) - { + + if (!is_callable($func)) { echo new Exception("not callable"); return false; } - - if(empty(self::$_tasks)) - { + + if (empty(self::$_tasks)) { pcntl_alarm(1); } - + $time_now = time(); $run_time = $time_now + $time_interval; - if(!isset(self::$_tasks[$run_time])) - { + if (!isset(self::$_tasks[$run_time])) { self::$_tasks[$run_time] = array(); } - self::$_tasks[$run_time][] = array($func, $args, $persistent, $time_interval); + self::$_tasks[$run_time][] = array($func, (array)$args, $persistent, $time_interval); return true; } - - + + /** - * 尝试触发定时回调 + * Tick. + * * @return void */ public static function tick() { - if(empty(self::$_tasks)) - { + if (empty(self::$_tasks)) { pcntl_alarm(0); return; } - + $time_now = time(); - foreach (self::$_tasks as $run_time=>$task_data) - { - if($time_now >= $run_time) - { - foreach($task_data as $index=>$one_task) - { - $task_func = $one_task[0]; - $task_args = $one_task[1]; - $persistent = $one_task[2]; + foreach (self::$_tasks as $run_time => $task_data) { + if ($time_now >= $run_time) { + foreach ($task_data as $index => $one_task) { + $task_func = $one_task[0]; + $task_args = $one_task[1]; + $persistent = $one_task[2]; $time_interval = $one_task[3]; - try - { + try { call_user_func_array($task_func, $task_args); - } - catch(\Exception $e) - { + } catch (\Exception $e) { echo $e; } - if($persistent) - { + if ($persistent) { self::add($time_interval, $task_func, $task_args); } } @@ -148,28 +144,32 @@ class Timer } } } - + /** - * 删除定时器 - * @param $timer_id + * Remove a timer. + * + * @param mixed $timer_id + * @return bool */ public static function del($timer_id) { - if(self::$_event) - { + if (self::$_event) { return self::$_event->del($timer_id, EventInterface::EV_TIMER); } + + return false; } - + /** - * 删除所有定时 + * Remove all timers. + * + * @return void */ public static function delAll() { self::$_tasks = array(); pcntl_alarm(0); - if(self::$_event) - { + if (self::$_event) { self::$_event->clearAllTimer(); } } diff --git a/Workerman/MIT-LICENSE.txt b/Workerman/MIT-LICENSE.txt new file mode 100644 index 0000000..fd6b1c8 --- /dev/null +++ b/Workerman/MIT-LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2009-2015 walkor and contributors (see https://github.com/walkor/workerman/contributors) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/Workerman/Protocols/Frame.php b/Workerman/Protocols/Frame.php new file mode 100644 index 0000000..4a6b13e --- /dev/null +++ b/Workerman/Protocols/Frame.php @@ -0,0 +1,61 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Protocols; + +use Workerman\Connection\TcpConnection; + +/** + * Frame Protocol. + */ +class Frame +{ + /** + * Check the integrity of the package. + * + * @param string $buffer + * @param TcpConnection $connection + * @return int + */ + public static function input($buffer, TcpConnection $connection) + { + if (strlen($buffer) < 4) { + return 0; + } + $unpack_data = unpack('Ntotal_length', $buffer); + return $unpack_data['total_length']; + } + + /** + * Decode. + * + * @param string $buffer + * @return string + */ + public static function decode($buffer) + { + return substr($buffer, 4); + } + + /** + * Encode. + * + * @param string $buffer + * @return string + */ + public static function encode($buffer) + { + $total_length = 4 + strlen($buffer); + return pack('N', $total_length) . $buffer; + } +} diff --git a/Workerman/Protocols/GatewayProtocol.php b/Workerman/Protocols/GatewayProtocol.php deleted file mode 100644 index 107eefd..0000000 --- a/Workerman/Protocols/GatewayProtocol.php +++ /dev/null @@ -1,151 +0,0 @@ - - */ - -class GatewayProtocol -{ - // 发给worker,gateway有一个新的连接 - const CMD_ON_CONNECTION = 1; - - // 发给worker的,客户端有消息 - const CMD_ON_MESSAGE = 3; - - // 发给worker上的关闭链接事件 - const CMD_ON_CLOSE = 4; - - // 发给gateway的向单个用户发送数据 - const CMD_SEND_TO_ONE = 5; - - // 发给gateway的向所有用户发送数据 - const CMD_SEND_TO_ALL = 6; - - // 发给gateway的踢出用户 - const CMD_KICK = 7; - - // 发给gateway,通知用户session更改 - const CMD_UPDATE_SESSION = 9; - - // 获取在线状态 - const CMD_GET_ONLINE_STATUS = 10; - - // 判断是否在线 - const CMD_IS_ONLINE = 11; - - // 包体是标量 - const FLAG_BODY_IS_SCALAR = 0x01; - - /** - * 包头长度 - * @var integer - */ - const HEAD_LEN = 26; - - public static $empty = array( - 'cmd' => 0, - 'local_ip' => '0.0.0.0', - 'local_port' => 0, - 'client_ip' => '0.0.0.0', - 'client_port' => 0, - 'client_id' => 0, - 'flag' => 0, - 'ext_data' => '', - 'body' => '', - ); - - /** - * 返回包长度 - * @param string $buffer - * @return int return current package length - */ - public static function input($buffer) - { - if(strlen($buffer) < self::HEAD_LEN) - { - return 0; - } - - $data = unpack("Npack_len", $buffer); - return $data['pack_len']; - } - - /** - * 获取整个包的buffer - * @param array $data - * @return string - */ - public static function encode($data) - { - $flag = (int)is_scalar($data['body']); - if(!$flag) - { - $data['body'] = serialize($data['body']); - } - $ext_len = strlen($data['ext_data']); - $package_len = self::HEAD_LEN + $ext_len + strlen($data['body']); - return pack("NCNnNnNNC", $package_len, - $data['cmd'], ip2long($data['local_ip']), - $data['local_port'], ip2long($data['client_ip']), - $data['client_port'], $data['client_id'], - $ext_len, $flag) . $data['ext_data'] . $data['body']; - } - - /** - * 从二进制数据转换为数组 - * @param string $buffer - * @return array - */ - public static function decode($buffer) - { - $data = unpack("Npack_len/Ccmd/Nlocal_ip/nlocal_port/Nclient_ip/nclient_port/Nclient_id/Next_len/Cflag", $buffer); - $data['local_ip'] = long2ip($data['local_ip']); - $data['client_ip'] = long2ip($data['client_ip']); - if($data['ext_len'] > 0) - { - $data['ext_data'] = substr($buffer, self::HEAD_LEN, $data['ext_len']); - if($data['flag'] & self::FLAG_BODY_IS_SCALAR) - { - $data['body'] = substr($buffer, self::HEAD_LEN + $data['ext_len']); - } - else - { - $data['body'] = unserialize(substr($buffer, self::HEAD_LEN + $data['ext_len'])); - } - } - else - { - $data['ext_data'] = ''; - if($data['flag'] & self::FLAG_BODY_IS_SCALAR) - { - $data['body'] = substr($buffer, self::HEAD_LEN); - } - else - { - $data['body'] = unserialize(substr($buffer, self::HEAD_LEN)); - } - } - return $data; - } -} - - - diff --git a/Workerman/Protocols/Http.php b/Workerman/Protocols/Http.php index 98e7215..07d4df4 100644 --- a/Workerman/Protocols/Http.php +++ b/Workerman/Protocols/Http.php @@ -1,476 +1,430 @@ - + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Protocols; use Workerman\Connection\TcpConnection; +use Workerman\Worker; /** * http protocol - * @author walkor */ class Http { /** - * 判断包长 - * @param string $recv_buffer + * Check the integrity of the package. + * + * @param string $recv_buffer * @param TcpConnection $connection * @return int */ public static function input($recv_buffer, TcpConnection $connection) { - if(!strpos($recv_buffer, "\r\n\r\n")) - { - // 无法获得包长,避免客户端传递超大头部的数据包 - if(strlen($recv_buffer)>=TcpConnection::$maxPackageSize) - { + if (!strpos($recv_buffer, "\r\n\r\n")) { + // Judge whether the package length exceeds the limit. + if (strlen($recv_buffer) >= TcpConnection::$maxPackageSize) { $connection->close(); return 0; } return 0; } - - list($header, $body) = explode("\r\n\r\n", $recv_buffer, 2); - if(0 === strpos($recv_buffer, "POST")) - { + + list($header,) = explode("\r\n\r\n", $recv_buffer, 2); + if (0 === strpos($recv_buffer, "POST")) { // find Content-Length $match = array(); - if(preg_match("/\r\nContent-Length: ?(\d+)/", $header, $match)) - { - $content_lenght = $match[1]; - return $content_lenght + strlen($header) + 4; - } - else - { + if (preg_match("/\r\nContent-Length: ?(\d+)/i", $header, $match)) { + $content_length = $match[1]; + return $content_length + strlen($header) + 4; + } else { return 0; } - } - else - { - return strlen($header)+4; + } elseif (0 === strpos($recv_buffer, "GET")) { + return strlen($header) + 4; + } else { + $connection->send("HTTP/1.1 400 Bad Request\r\n\r\n", true); + return 0; } } - + /** - * 从http数据包中解析$_POST、$_GET、$_COOKIE等 - * @param string $recv_buffer + * Parse $_POST、$_GET、$_COOKIE. + * + * @param string $recv_buffer * @param TcpConnection $connection - * @return void + * @return array */ public static function decode($recv_buffer, TcpConnection $connection) { - // 初始化 - $_POST = $_GET = $_COOKIE = $_REQUEST = $_SESSION = $_FILES = array(); + // Init. + $_POST = $_GET = $_COOKIE = $_REQUEST = $_SESSION = $_FILES = array(); $GLOBALS['HTTP_RAW_POST_DATA'] = ''; - // 清空上次的数据 - HttpCache::$header = array(); + // Clear cache. + HttpCache::$header = array('Connection' => 'Connection: keep-alive'); HttpCache::$instance = new HttpCache(); - // 需要设置的变量名 - $_SERVER = array ( - 'QUERY_STRING' => '', - 'REQUEST_METHOD' => '', - 'REQUEST_URI' => '', - 'SERVER_PROTOCOL' => '', - 'SERVER_SOFTWARE' => 'workerman/3.0', - 'SERVER_NAME' => '', - 'HTTP_HOST' => '', - 'HTTP_USER_AGENT' => '', - 'HTTP_ACCEPT' => '', - 'HTTP_ACCEPT_LANGUAGE' => '', - 'HTTP_ACCEPT_ENCODING' => '', - 'HTTP_COOKIE' => '', - 'HTTP_CONNECTION' => '', - 'REMOTE_ADDR' => '', - 'REMOTE_PORT' => '0', - ); - - // 将header分割成数组 + // $_SERVER + $_SERVER = array( + 'QUERY_STRING' => '', + 'REQUEST_METHOD' => '', + 'REQUEST_URI' => '', + 'SERVER_PROTOCOL' => '', + 'SERVER_SOFTWARE' => 'workerman/'.Worker::VERSION, + 'SERVER_NAME' => '', + 'HTTP_HOST' => '', + 'HTTP_USER_AGENT' => '', + 'HTTP_ACCEPT' => '', + 'HTTP_ACCEPT_LANGUAGE' => '', + 'HTTP_ACCEPT_ENCODING' => '', + 'HTTP_COOKIE' => '', + 'HTTP_CONNECTION' => '', + 'REMOTE_ADDR' => '', + 'REMOTE_PORT' => '0', + ); + + // Parse headers. list($http_header, $http_body) = explode("\r\n\r\n", $recv_buffer, 2); $header_data = explode("\r\n", $http_header); - - list($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI'], $_SERVER['SERVER_PROTOCOL']) = explode(' ', $header_data[0]); - + + list($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI'], $_SERVER['SERVER_PROTOCOL']) = explode(' ', + $header_data[0]); + + $http_post_boundary = ''; unset($header_data[0]); - foreach($header_data as $content) - { + foreach ($header_data as $content) { // \r\n\r\n - if(empty($content)) - { + if (empty($content)) { continue; } - list($key, $value) = explode(':', $content, 2); - $key = strtolower($key); - $value = trim($value); - switch($key) - { + list($key, $value) = explode(':', $content, 2); + $key = str_replace('-', '_', strtoupper($key)); + $value = trim($value); + $_SERVER['HTTP_' . $key] = $value; + switch ($key) { // HTTP_HOST - case 'host': - $_SERVER['HTTP_HOST'] = $value; - $tmp = explode(':', $value); + case 'HOST': + $tmp = explode(':', $value); $_SERVER['SERVER_NAME'] = $tmp[0]; - if(isset($tmp[1])) - { + if (isset($tmp[1])) { $_SERVER['SERVER_PORT'] = $tmp[1]; } break; // cookie - case 'cookie': - $_SERVER['HTTP_COOKIE'] = $value; + case 'COOKIE': parse_str(str_replace('; ', '&', $_SERVER['HTTP_COOKIE']), $_COOKIE); break; - // user-agent - case 'user-agent': - $_SERVER['HTTP_USER_AGENT'] = $value; - break; - // accept - case 'accept': - $_SERVER['HTTP_ACCEPT'] = $value; - break; - // accept-language - case 'accept-language': - $_SERVER['HTTP_ACCEPT_LANGUAGE'] = $value; - break; - // accept-encoding - case 'accept-encoding': - $_SERVER['HTTP_ACCEPT_ENCODING'] = $value; - break; - // connection - case 'connection': - $_SERVER['HTTP_CONNECTION'] = $value; - break; - case 'referer': - $_SERVER['HTTP_REFERER'] = $value; - break; - case 'if-modified-since': - $_SERVER['HTTP_IF_MODIFIED_SINCE'] = $value; - break; - case 'if-none-match': - $_SERVER['HTTP_IF_NONE_MATCH'] = $value; - break; - case 'content-type': - if(!preg_match('/boundary="?(\S+)"?/', $value, $match)) - { + // content-type + case 'CONTENT_TYPE': + if (!preg_match('/boundary="?(\S+)"?/', $value, $match)) { $_SERVER['CONTENT_TYPE'] = $value; - } - else - { + } else { $_SERVER['CONTENT_TYPE'] = 'multipart/form-data'; - $http_post_boundary = '--'.$match[1]; + $http_post_boundary = '--' . $match[1]; } break; + case 'CONTENT_LENGTH': + $_SERVER['CONTENT_LENGTH'] = $value; + break; } } - - // 需要解析$_POST - if($_SERVER['REQUEST_METHOD'] == 'POST') - { - if(isset($_SERVER['CONTENT_TYPE']) && $_SERVER['CONTENT_TYPE'] == 'multipart/form-data') - { + + // Parse $_POST. + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + if (isset($_SERVER['CONTENT_TYPE']) && $_SERVER['CONTENT_TYPE'] === 'multipart/form-data') { self::parseUploadFiles($http_body, $http_post_boundary); - } - else - { + } else { parse_str($http_body, $_POST); // $GLOBALS['HTTP_RAW_POST_DATA'] $GLOBALS['HTTP_RAW_POST_DATA'] = $http_body; } } - + // QUERY_STRING $_SERVER['QUERY_STRING'] = parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY); - if($_SERVER['QUERY_STRING']) - { + if ($_SERVER['QUERY_STRING']) { // $GET parse_str($_SERVER['QUERY_STRING'], $_GET); - } - else - { + } else { $_SERVER['QUERY_STRING'] = ''; } - + // REQUEST $_REQUEST = array_merge($_GET, $_POST); - + // REMOTE_ADDR REMOTE_PORT $_SERVER['REMOTE_ADDR'] = $connection->getRemoteIp(); $_SERVER['REMOTE_PORT'] = $connection->getRemotePort(); - - return $recv_buffer; + + return array('get' => $_GET, 'post' => $_POST, 'cookie' => $_COOKIE, 'server' => $_SERVER, 'files' => $_FILES); } - + /** - * 编码,增加HTTP头 - * @param string $content + * Http encode. + * + * @param string $content * @param TcpConnection $connection * @return string */ public static function encode($content, TcpConnection $connection) { - // 没有http-code默认给个 - if(!isset(HttpCache::$header['Http-Code'])) - { + // Default http-code. + if (!isset(HttpCache::$header['Http-Code'])) { $header = "HTTP/1.1 200 OK\r\n"; - } - else - { - $header = HttpCache::$header['Http-Code']."\r\n"; + } else { + $header = HttpCache::$header['Http-Code'] . "\r\n"; unset(HttpCache::$header['Http-Code']); } - + // Content-Type - if(!isset(HttpCache::$header['Content-Type'])) - { + if (!isset(HttpCache::$header['Content-Type'])) { $header .= "Content-Type: text/html;charset=utf-8\r\n"; } - + // other headers - foreach(HttpCache::$header as $key=>$item) - { - if('Set-Cookie' == $key && is_array($item)) - { - foreach($item as $it) - { - $header .= $it."\r\n"; + foreach (HttpCache::$header as $key => $item) { + if ('Set-Cookie' === $key && is_array($item)) { + foreach ($item as $it) { + $header .= $it . "\r\n"; } - } - else - { - $header .= $item."\r\n"; + } else { + $header .= $item . "\r\n"; } } - + // header - $header .= "Server: WorkerMan/3.0\r\nContent-Length: ".strlen($content)."\r\n\r\n"; - + $header .= "Server: workerman/" . Worker::VERSION . "\r\nContent-Length: " . strlen($content) . "\r\n\r\n"; + // save session self::sessionWriteClose(); - + // the whole http package - return $header.$content; + return $header . $content; } - + /** * 设置http头 - * @return bool + * + * @return bool|void */ public static function header($content, $replace = true, $http_response_code = 0) { - if(PHP_SAPI != 'cli') - { + if (PHP_SAPI != 'cli') { return $http_response_code ? header($content, $replace, $http_response_code) : header($content, $replace); } - if(strpos($content, 'HTTP') === 0) - { + if (strpos($content, 'HTTP') === 0) { $key = 'Http-Code'; - } - else - { + } else { $key = strstr($content, ":", true); - if(empty($key)) - { + if (empty($key)) { return false; } } - - if('location' == strtolower($key) && !$http_response_code) - { + + if ('location' === strtolower($key) && !$http_response_code) { return self::header($content, true, 302); } - - if(isset(HttpCache::$codes[$http_response_code])) - { - HttpCache::$header['Http-Code'] = "HTTP/1.1 $http_response_code " . HttpCache::$codes[$http_response_code]; - if($key == 'Http-Code') - { + + if (isset(HttpCache::$codes[$http_response_code])) { + HttpCache::$header['Http-Code'] = "HTTP/1.1 $http_response_code " . HttpCache::$codes[$http_response_code]; + if ($key === 'Http-Code') { return true; } } - - if($key == 'Set-Cookie') - { + + if ($key === 'Set-Cookie') { HttpCache::$header[$key][] = $content; - } - else - { + } else { HttpCache::$header[$key] = $content; } - + return true; } - + /** - * 删除一个header + * Remove header. + * * @param string $name * @return void */ public static function headerRemove($name) { - if(PHP_SAPI != 'cli') - { - return header_remove($name); + if (PHP_SAPI != 'cli') { + header_remove($name); + return; } - unset( HttpCache::$header[$name]); + unset(HttpCache::$header[$name]); } - + /** - * 设置cookie - * @param string $name - * @param string $value + * Set cookie. + * + * @param string $name + * @param string $value * @param integer $maxage - * @param string $path - * @param string $domain - * @param bool $secure - * @param bool $HTTPOnly + * @param string $path + * @param string $domain + * @param bool $secure + * @param bool $HTTPOnly + * @return bool|void */ - public static function setcookie($name, $value = '', $maxage = 0, $path = '', $domain = '', $secure = false, $HTTPOnly = false) { - if(PHP_SAPI != 'cli') - { + public static function setcookie( + $name, + $value = '', + $maxage = 0, + $path = '', + $domain = '', + $secure = false, + $HTTPOnly = false + ) { + if (PHP_SAPI != 'cli') { return setcookie($name, $value, $maxage, $path, $domain, $secure, $HTTPOnly); } return self::header( - 'Set-Cookie: ' . $name . '=' . rawurlencode($value) - . (empty($domain) ? '' : '; Domain=' . $domain) - . (empty($maxage) ? '' : '; Max-Age=' . $maxage) - . (empty($path) ? '' : '; Path=' . $path) - . (!$secure ? '' : '; Secure') - . (!$HTTPOnly ? '' : '; HttpOnly'), false); + 'Set-Cookie: ' . $name . '=' . rawurlencode($value) + . (empty($domain) ? '' : '; Domain=' . $domain) + . (empty($maxage) ? '' : '; Max-Age=' . $maxage) + . (empty($path) ? '' : '; Path=' . $path) + . (!$secure ? '' : '; Secure') + . (!$HTTPOnly ? '' : '; HttpOnly'), false); } - + /** * sessionStart + * * @return bool */ public static function sessionStart() { - if(PHP_SAPI != 'cli') - { + if (PHP_SAPI != 'cli') { return session_start(); } - if(HttpCache::$instance->sessionStarted) - { + if (HttpCache::$instance->sessionStarted) { echo "already sessionStarted\nn"; return true; } HttpCache::$instance->sessionStarted = true; - // 没有sid,则创建一个session文件,生成一个sid - if(!isset($_COOKIE[HttpCache::$sessionName]) || !is_file(HttpCache::$sessionPath . '/sess_' . $_COOKIE[HttpCache::$sessionName])) - { - $file_name = tempnam(HttpCache::$sessionPath, 'sess_'); - if(!$file_name) - { + // Generate a SID. + if (!isset($_COOKIE[HttpCache::$sessionName]) || !is_file(HttpCache::$sessionPath . '/ses' . $_COOKIE[HttpCache::$sessionName])) { + $file_name = tempnam(HttpCache::$sessionPath, 'ses'); + if (!$file_name) { return false; } HttpCache::$instance->sessionFile = $file_name; - $session_id = substr(basename($file_name), strlen('sess_')); + $session_id = substr(basename($file_name), strlen('ses')); return self::setcookie( - HttpCache::$sessionName - , $session_id - , ini_get('session.cookie_lifetime') - , ini_get('session.cookie_path') - , ini_get('session.cookie_domain') - , ini_get('session.cookie_secure') - , ini_get('session.cookie_httponly') + HttpCache::$sessionName + , $session_id + , ini_get('session.cookie_lifetime') + , ini_get('session.cookie_path') + , ini_get('session.cookie_domain') + , ini_get('session.cookie_secure') + , ini_get('session.cookie_httponly') ); } - if(!HttpCache::$instance->sessionFile) - { - HttpCache::$instance->sessionFile = HttpCache::$sessionPath . '/sess_' . $_COOKIE[HttpCache::$sessionName]; + if (!HttpCache::$instance->sessionFile) { + HttpCache::$instance->sessionFile = HttpCache::$sessionPath . '/ses' . $_COOKIE[HttpCache::$sessionName]; } - // 有sid则打开文件,读取session值 - if(HttpCache::$instance->sessionFile) - { + // Read session from session file. + if (HttpCache::$instance->sessionFile) { $raw = file_get_contents(HttpCache::$instance->sessionFile); - if($raw) - { + if ($raw) { session_decode($raw); } } + return true; } - + /** - * 保存session + * Save session. + * * @return bool */ public static function sessionWriteClose() { - if(PHP_SAPI != 'cli') - { + if (PHP_SAPI != 'cli') { return session_write_close(); } - if(!empty(HttpCache::$instance->sessionStarted) && !empty($_SESSION)) - { + if (!empty(HttpCache::$instance->sessionStarted) && !empty($_SESSION)) { $session_str = session_encode(); - if($session_str && HttpCache::$instance->sessionFile) - { + if ($session_str && HttpCache::$instance->sessionFile) { return file_put_contents(HttpCache::$instance->sessionFile, $session_str); } } return empty($_SESSION); } - + /** - * 退出 + * End, like call exit in php-fpm. + * * @param string $msg * @throws \Exception */ public static function end($msg = '') { - if(PHP_SAPI != 'cli') - { + if (PHP_SAPI != 'cli') { exit($msg); } - if($msg) - { + if ($msg) { echo $msg; } throw new \Exception('jump_exit'); } - + /** - * get mime types + * Get mime types. + * + * @return string */ public static function getMimeTypesFile() { - return __DIR__.'/Http/mime.types'; + return __DIR__ . '/Http/mime.types'; } - - /** - * 解析$_FILES + + /** + * Parse $_FILES. + * + * @param string $http_body + * @param string $http_post_boundary + * @return void */ - protected function parseUploadFiles($http_body, $http_post_boundary) + protected static function parseUploadFiles($http_body, $http_post_boundary) { - $http_body = substr($http_body, 0, strlen($http_body) - (strlen($http_post_boundary) + 4)); - $boundary_data_array = explode($http_post_boundary."\r\n", $http_body); - if($boundary_data_array[0] === '') - { + $http_body = substr($http_body, 0, strlen($http_body) - (strlen($http_post_boundary) + 4)); + $boundary_data_array = explode($http_post_boundary . "\r\n", $http_body); + if ($boundary_data_array[0] === '') { unset($boundary_data_array[0]); } - foreach($boundary_data_array as $boundary_data_buffer) - { + foreach ($boundary_data_array as $boundary_data_buffer) { list($boundary_header_buffer, $boundary_value) = explode("\r\n\r\n", $boundary_data_buffer, 2); - // 去掉末尾\r\n + // Remove \r\n from the end of buffer. $boundary_value = substr($boundary_value, 0, -2); - foreach (explode("\r\n", $boundary_header_buffer) as $item) - { + foreach (explode("\r\n", $boundary_header_buffer) as $item) { list($header_key, $header_value) = explode(": ", $item); $header_key = strtolower($header_key); - switch ($header_key) - { + switch ($header_key) { case "content-disposition": - // 是文件 - if(preg_match('/name=".*?"; filename="(.*?)"$/', $header_value, $match)) - { + // Is file data. + if (preg_match('/name=".*?"; filename="(.*?)"$/', $header_value, $match)) { + // Parse $_FILES. $_FILES[] = array( 'file_name' => $match[1], 'file_data' => $boundary_value, 'file_size' => strlen($boundary_value), ); continue; - } - // 是post field - else - { - // 收集post - if(preg_match('/name="(.*?)"$/', $header_value, $match)) - { + } // Is post field. + else { + // Parse $_POST. + if (preg_match('/name="(.*?)"$/', $header_value, $match)) { $_POST[$match[1]] = $boundary_value; } } @@ -482,57 +436,61 @@ class Http } /** - * 解析http协议数据包 缓存先关 - * @author walkor + * Http cache for the current http response. */ class HttpCache { public static $codes = array( - 100 => 'Continue', - 101 => 'Switching Protocols', - 200 => 'OK', - 201 => 'Created', - 202 => 'Accepted', - 203 => 'Non-Authoritative Information', - 204 => 'No Content', - 205 => 'Reset Content', - 206 => 'Partial Content', - 300 => 'Multiple Choices', - 301 => 'Moved Permanently', - 302 => 'Found', - 303 => 'See Other', - 304 => 'Not Modified', - 305 => 'Use Proxy', - 306 => '(Unused)', - 307 => 'Temporary Redirect', - 400 => 'Bad Request', - 401 => 'Unauthorized', - 402 => 'Payment Required', - 403 => 'Forbidden', - 404 => 'Not Found', - 405 => 'Method Not Allowed', - 406 => 'Not Acceptable', - 407 => 'Proxy Authentication Required', - 408 => 'Request Timeout', - 409 => 'Conflict', - 410 => 'Gone', - 411 => 'Length Required', - 412 => 'Precondition Failed', - 413 => 'Request Entity Too Large', - 414 => 'Request-URI Too Long', - 415 => 'Unsupported Media Type', - 416 => 'Requested Range Not Satisfiable', - 417 => 'Expectation Failed', - 422 => 'Unprocessable Entity', - 423 => 'Locked', - 500 => 'Internal Server Error', - 501 => 'Not Implemented', - 502 => 'Bad Gateway', - 503 => 'Service Unavailable', - 504 => 'Gateway Timeout', - 505 => 'HTTP Version Not Supported', - ); + 100 => 'Continue', + 101 => 'Switching Protocols', + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 306 => '(Unused)', + 307 => 'Temporary Redirect', + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Timeout', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Long', + 415 => 'Unsupported Media Type', + 416 => 'Requested Range Not Satisfiable', + 417 => 'Expectation Failed', + 422 => 'Unprocessable Entity', + 423 => 'Locked', + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Timeout', + 505 => 'HTTP Version Not Supported', + ); + + /** + * @var HttpCache + */ public static $instance = null; + public static $header = array(); public static $sessionPath = ''; public static $sessionName = ''; @@ -543,8 +501,7 @@ class HttpCache { self::$sessionName = ini_get('session.name'); self::$sessionPath = session_save_path(); - if(!self::$sessionPath) - { + if (!self::$sessionPath) { self::$sessionPath = sys_get_temp_dir(); } @\session_start(); diff --git a/Workerman/Protocols/ProtocolInterface.php b/Workerman/Protocols/ProtocolInterface.php index b953633..9afe984 100644 --- a/Workerman/Protocols/ProtocolInterface.php +++ b/Workerman/Protocols/ProtocolInterface.php @@ -1,43 +1,51 @@ + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ namespace Workerman\Protocols; -use \Workerman\Connection\ConnectionInterface; +use Workerman\Connection\ConnectionInterface; /** * Protocol interface -* @author walkor */ interface ProtocolInterface { /** - * 用于分包,即在接收的buffer中返回当前请求的长度(字节) - * 如果可以在$recv_buffer中得到请求包的长度则返回长度 - * 否则返回0,表示需要更多的数据才能得到当前请求包的长度 - * 如果返回false或者负数,则代表请求不符合协议,则连接会断开 + * Check the integrity of the package. + * Please return the length of package. + * If length is unknow please return 0 that mean wating more data. + * If the package has something wrong please return false the connection will be closed. + * * @param ConnectionInterface $connection - * @param string $recv_buffer + * @param string $recv_buffer * @return int|false */ public static function input($recv_buffer, ConnectionInterface $connection); - + /** - * 用于请求解包 - * input返回值大于0,并且WorkerMan收到了足够的数据,则自动调用decode - * 然后触发onMessage回调,并将decode解码后的数据传递给onMessage回调的第二个参数 - * 也就是说当收到完整的客户端请求时,会自动调用decode解码,无需业务代码中手动调用 + * Decode package and emit onMessage($message) callback, $message is the result that decode returned. + * * @param ConnectionInterface $connection - * @param string $recv_buffer + * @param string $recv_buffer * @return mixed */ public static function decode($recv_buffer, ConnectionInterface $connection); - + /** - * 用于请求打包 - * 当需要向客户端发送数据即调用$connection->send($data);时 - * 会自动把$data用encode打包一次,变成符合协议的数据格式,然后再发送给客户端 - * 也就是说发送给客户端的数据会自动encode打包,无需业务代码中手动调用 + * Encode package brefore sending to client. + * * @param ConnectionInterface $connection - * @param mixed $data + * @param mixed $data * @return string */ public static function encode($data, ConnectionInterface $connection); diff --git a/Workerman/Protocols/Text.php b/Workerman/Protocols/Text.php index 68c6a72..189baf4 100644 --- a/Workerman/Protocols/Text.php +++ b/Workerman/Protocols/Text.php @@ -1,60 +1,70 @@ - + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ namespace Workerman\Protocols; -use \Workerman\Connection\TcpConnection; + +use Workerman\Connection\TcpConnection; /** - * Text协议 - * 以换行为请求结束标记 - * @author walkor + * Text Protocol. */ - class Text { /** - * 检查包的完整性 - * 如果能够得到包长,则返回包的长度,否则返回0继续等待数据 - * @param string $buffer + * Check the integrity of the package. + * + * @param string $buffer + * @param TcpConnection $connection + * @return int */ - public static function input($buffer ,TcpConnection $connection) + public static function input($buffer, TcpConnection $connection) { - // 由于没有包头,无法预先知道包长,不能无限制的接收数据, - // 所以需要判断当前接收的数据是否超过限定值 - if(strlen($buffer)>=TcpConnection::$maxPackageSize) - { + // Judge whether the package length exceeds the limit. + if (strlen($buffer) >= TcpConnection::$maxPackageSize) { $connection->close(); return 0; } - // 获得换行字符"\n"位置 + // Find the position of "\n". $pos = strpos($buffer, "\n"); - // 没有换行符,无法得知包长,返回0继续等待数据 - if($pos === false) - { + // No "\n", packet length is unknown, continue to wait for the data so return 0. + if ($pos === false) { return 0; } - // 有换行符,返回当前包长,包含换行符 - return $pos+1; + // Return the current package length. + return $pos + 1; } - + /** - * 打包,当向客户端发送数据的时候会自动调用 + * Encode. + * * @param string $buffer * @return string */ public static function encode($buffer) { - // 加上换行 - return $buffer."\n"; + // Add "\n" + return $buffer . "\n"; } - + /** - * 解包,当接收到的数据字节数等于input返回的值(大于0的值)自动调用 - * 并传递给onMessage回调函数的$data参数 + * Decode. + * * @param string $buffer * @return string */ public static function decode($buffer) { - // 去掉换行 + // Remove "\n" return trim($buffer); } } diff --git a/Workerman/Protocols/Websocket.php b/Workerman/Protocols/Websocket.php index 746b077..43a37e1 100644 --- a/Workerman/Protocols/Websocket.php +++ b/Workerman/Protocols/Websocket.php @@ -1,221 +1,258 @@ - + * This file is part of workerman. + * + * Licensed under The MIT License + * For full copyright and license information, please see the MIT-LICENSE.txt + * Redistributions of files must retain the above copyright notice. + * + * @author walkor + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License */ +namespace Workerman\Protocols; use Workerman\Connection\ConnectionInterface; +use Workerman\Worker; +/** + * WebSocket protocol. + */ class Websocket implements \Workerman\Protocols\ProtocolInterface { /** - * websocket头部最小长度 + * Minimum head length of websocket protocol. + * * @var int */ - const MIN_HEAD_LEN = 6; - + const MIN_HEAD_LEN = 2; + /** - * websocket blob类型 - * @var char + * Websocket blob type. + * + * @var string */ const BINARY_TYPE_BLOB = "\x81"; /** - * websocket arraybuffer类型 - * @var char + * Websocket arraybuffer type. + * + * @var string */ const BINARY_TYPE_ARRAYBUFFER = "\x82"; - + /** - * 检查包的完整性 - * @param string $buffer + * Check the integrity of the package. + * + * @param string $buffer + * @param ConnectionInterface $connection + * @return int */ public static function input($buffer, ConnectionInterface $connection) { - // 数据长度 + // Receive length. $recv_len = strlen($buffer); - // 长度不够 - if($recv_len < self::MIN_HEAD_LEN) - { + // We need more data. + if ($recv_len < self::MIN_HEAD_LEN) { return 0; } - - // 还没有握手 - if(empty($connection->websocketHandshake)) - { + + // Has not yet completed the handshake. + if (empty($connection->websocketHandshake)) { return self::dealHandshake($buffer, $connection); } - - // $connection->websocketCurrentFrameLength有值说明当前fin为0,则缓冲websocket帧数据 - if($connection->websocketCurrentFrameLength) - { - // 如果当前帧数据未收全,则继续收 - if($connection->websocketCurrentFrameLength > $recv_len) - { - // 返回0,因为不清楚完整的数据包长度,需要等待fin=1的帧 + + // Buffer websocket frame data. + if ($connection->websocketCurrentFrameLength) { + // We need more frame data. + if ($connection->websocketCurrentFrameLength > $recv_len) { + // Return 0, because it is not clear the full packet length, waiting for the frame of fin=1. return 0; } - } - else - { - $data_len = ord($buffer[1]) & 127; - $firstbyte = ord($buffer[0]); - $is_fin_frame = $firstbyte>>7; - $opcode = $firstbyte & 0xf; - switch($opcode) - { - // 附加数据帧 @todo 实现附加数据帧 + } else { + $data_len = ord($buffer[1]) & 127; + $firstbyte = ord($buffer[0]); + $is_fin_frame = $firstbyte >> 7; + $opcode = $firstbyte & 0xf; + switch ($opcode) { case 0x0: break; - // 文本数据帧 + // Blob type. case 0x1: break; - // 二进制数据帧 + // Arraybuffer type. case 0x2: break; - // 关闭的包 + // Close package. case 0x8: - // 如果有设置onWebSocketClose回调,尝试执行 - if(isset($connection->onWebSocketClose)) - { - call_user_func($connection->onWebSocketClose, $connection); - } - // 默认行为是关闭连接 - else - { + // Try to emit onWebSocketClose callback. + if (isset($connection->onWebSocketClose)) { + try { + call_user_func($connection->onWebSocketClose, $connection); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } // Close connection. + else { $connection->close(); } return 0; - // ping的包 + // Ping package. case 0x9: - // 如果有设置onWebSocketPing回调,尝试执行 - if(isset($connection->onWebSocketPing)) - { - call_user_func($connection->onWebSocketPing, $connection); - } - // 默认发送pong - else - { + // Try to emit onWebSocketPing callback. + if (isset($connection->onWebSocketPing)) { + try { + call_user_func($connection->onWebSocketPing, $connection); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } // Send pong package to client. + else { $connection->send(pack('H*', '8a00'), true); } - // 从接受缓冲区中消费掉该数据包 - if(!$data_len) - { + + // Consume data from receive buffer. + if (!$data_len) { $connection->consumeRecvBuffer(self::MIN_HEAD_LEN); + if ($recv_len > self::MIN_HEAD_LEN) { + return self::input(substr($buffer, self::MIN_HEAD_LEN), $connection); + } return 0; } break; - // pong的包 + // Pong package. case 0xa: - // 如果有设置onWebSocketPong回调,尝试执行 - if(isset($connection->onWebSocketPong)) - { - call_user_func($connection->onWebSocketPong, $connection); + // Try to emit onWebSocketPong callback. + if (isset($connection->onWebSocketPong)) { + try { + call_user_func($connection->onWebSocketPong, $connection); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } } - // 从接受缓冲区中消费掉该数据包 - if(!$data_len) - { + // Consume data from receive buffer. + if (!$data_len) { $connection->consumeRecvBuffer(self::MIN_HEAD_LEN); + if ($recv_len > self::MIN_HEAD_LEN) { + return self::input(substr($buffer, self::MIN_HEAD_LEN), $connection); + } return 0; } break; - // 错误的opcode + // Wrong opcode. default : - echo "error opcode $opcode and close websocket connection\n"; + echo "error opcode $opcode and close websocket connection. Buffer:" . bin2hex($buffer) . "\n"; $connection->close(); return 0; } - - // websocket二进制数据 - $head_len = self::MIN_HEAD_LEN; + + // Calculate packet length. + $head_len = 6; if ($data_len === 126) { $head_len = 8; - if($head_len > $recv_len) - { + if ($head_len > $recv_len) { return 0; } - $pack = unpack('ntotal_len', substr($buffer, 2, 2)); + $pack = unpack('nn/ntotal_len', $buffer); $data_len = $pack['total_len']; - } else if ($data_len === 127) { - $head_len = 14; - if($head_len > $recv_len) - { - return 0; + } else { + if ($data_len === 127) { + $head_len = 14; + if ($head_len > $recv_len) { + return 0; + } + $arr = unpack('n/N2c', $buffer); + $data_len = $arr['c1']*4294967296 + $arr['c2']; } - $arr = unpack('N2', substr($buffer, 2, 8)); - $data_len = $arr[1]*4294967296 + $arr[2]; } $current_frame_length = $head_len + $data_len; - if($is_fin_frame) - { + if ($is_fin_frame) { return $current_frame_length; - } - else - { + } else { $connection->websocketCurrentFrameLength = $current_frame_length; } } - - // 收到的数据刚好是一个frame - if($connection->websocketCurrentFrameLength == $recv_len) - { + + // Received just a frame length data. + if ($connection->websocketCurrentFrameLength === $recv_len) { self::decode($buffer, $connection); $connection->consumeRecvBuffer($connection->websocketCurrentFrameLength); $connection->websocketCurrentFrameLength = 0; return 0; - } - // 收到的数据大于一个frame - elseif($connection->websocketCurrentFrameLength < $recv_len) - { + } // The length of the received data is greater than the length of a frame. + elseif ($connection->websocketCurrentFrameLength < $recv_len) { self::decode(substr($buffer, 0, $connection->websocketCurrentFrameLength), $connection); $connection->consumeRecvBuffer($connection->websocketCurrentFrameLength); - $current_frame_length = $connection->websocketCurrentFrameLength; + $current_frame_length = $connection->websocketCurrentFrameLength; $connection->websocketCurrentFrameLength = 0; - // 继续读取下一个frame + // Continue to read next frame. return self::input(substr($buffer, $current_frame_length), $connection); - } - // 收到的数据不足一个frame - else - { + } // The length of the received data is less than the length of a frame. + else { return 0; } } - + /** - * 打包 - * @param string $buffer + * Websocket encode. + * + * @param string $buffer + * @param ConnectionInterface $connection * @return string */ public static function encode($buffer, ConnectionInterface $connection) { + if (!is_scalar($buffer)) { + throw new \Exception("You can't send(" . gettype($buffer) . ") to client, you need to convert it to a string. "); + } $len = strlen($buffer); - // 还没握手不能发数据 - if(empty($connection->websocketHandshake)) - { - $connection->send("HTTP/1.1 400 Bad Request\r\n\r\n400 Bad Request
Send data before handshake. ", true); - $connection->close(); - return false; + if (empty($connection->websocketType)) { + $connection->websocketType = self::BINARY_TYPE_BLOB; } + $first_byte = $connection->websocketType; - - if($len<=125) - { - return $first_byte.chr($len).$buffer; + + if ($len <= 125) { + $encode_buffer = $first_byte . chr($len) . $buffer; + } else { + if ($len <= 65535) { + $encode_buffer = $first_byte . chr(126) . pack("n", $len) . $buffer; + } else { + $encode_buffer = $first_byte . chr(127) . pack("xxxxN", $len) . $buffer; + } } - else if($len<=65535) - { - return $first_byte.chr(126).pack("n", $len).$buffer; - } - else - { - return $first_byte.chr(127).pack("xxxxN", $len).$buffer; + + // Handshake not completed so temporary buffer websocket data waiting for send. + if (empty($connection->websocketHandshake)) { + if (empty($connection->tmpWebsocketData)) { + $connection->tmpWebsocketData = ''; + } + $connection->tmpWebsocketData .= $encode_buffer; + // Return empty string. + return ''; } + + return $encode_buffer; } - + /** - * 解包 - * @param string $buffer + * Websocket decode. + * + * @param string $buffer + * @param ConnectionInterface $connection * @return string */ public static function decode($buffer, ConnectionInterface $connection) @@ -224,166 +261,176 @@ class Websocket implements \Workerman\Protocols\ProtocolInterface $len = ord($buffer[1]) & 127; if ($len === 126) { $masks = substr($buffer, 4, 4); - $data = substr($buffer, 8); - } else if ($len === 127) { - $masks = substr($buffer, 10, 4); - $data = substr($buffer, 14); + $data = substr($buffer, 8); } else { - $masks = substr($buffer, 2, 4); - $data = substr($buffer, 6); + if ($len === 127) { + $masks = substr($buffer, 10, 4); + $data = substr($buffer, 14); + } else { + $masks = substr($buffer, 2, 4); + $data = substr($buffer, 6); + } } for ($index = 0; $index < strlen($data); $index++) { $decoded .= $data[$index] ^ $masks[$index % 4]; } - if($connection->websocketCurrentFrameLength) - { + if ($connection->websocketCurrentFrameLength) { $connection->websocketDataBuffer .= $decoded; return $connection->websocketDataBuffer; - } - else - { - $decoded = $connection->websocketDataBuffer . $decoded; - $connection->websocketDataBuffer = ''; + } else { + if ($connection->websocketDataBuffer !== '') { + $decoded = $connection->websocketDataBuffer . $decoded; + $connection->websocketDataBuffer = ''; + } return $decoded; } } - + /** - * 处理websocket握手 - * @param string $buffer - * @param TcpConnection $connection + * Websocket handshake. + * + * @param string $buffer + * @param \Workerman\Connection\TcpConnection $connection * @return int */ protected static function dealHandshake($buffer, $connection) { - // 握手阶段客户端发送HTTP协议 - if(0 === strpos($buffer, 'GET')) - { - // 判断\r\n\r\n边界 + // HTTP protocol. + if (0 === strpos($buffer, 'GET')) { + // Find \r\n\r\n. $heder_end_pos = strpos($buffer, "\r\n\r\n"); - if(!$heder_end_pos) - { + if (!$heder_end_pos) { return 0; } - - // 解析Sec-WebSocket-Key + $header_length = $heder_end_pos + 4; + + // Get Sec-WebSocket-Key. $Sec_WebSocket_Key = ''; - if(preg_match("/Sec-WebSocket-Key: *(.*?)\r\n/", $buffer, $match)) - { + if (preg_match("/Sec-WebSocket-Key: *(.*?)\r\n/i", $buffer, $match)) { $Sec_WebSocket_Key = $match[1]; - } - else - { - $connection->send("HTTP/1.1 400 Bad Request\r\n\r\n400 Bad Request
Sec-WebSocket-Key not found", true); + } else { + $connection->send("HTTP/1.1 400 Bad Request\r\n\r\n400 Bad Request
Sec-WebSocket-Key not found.
This is a WebSocket service and can not be accessed via HTTP.
See http://wiki.workerman.net/Error1", + true); $connection->close(); return 0; } - $new_key = base64_encode(sha1($Sec_WebSocket_Key."258EAFA5-E914-47DA-95CA-C5AB0DC85B11",true)); - // 握手返回的数据 - $new_message = "HTTP/1.1 101 Switching Protocols\r\n"; - $new_message .= "Upgrade: websocket\r\n"; - $new_message .= "Sec-WebSocket-Version: 13\r\n"; - $new_message .= "Connection: Upgrade\r\n"; - $new_message .= "Sec-WebSocket-Accept: " . $new_key . "\r\n\r\n"; + // Calculation websocket key. + $new_key = base64_encode(sha1($Sec_WebSocket_Key . "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", true)); + // Handshake response data. + $handshake_message = "HTTP/1.1 101 Switching Protocols\r\n"; + $handshake_message .= "Upgrade: websocket\r\n"; + $handshake_message .= "Sec-WebSocket-Version: 13\r\n"; + $handshake_message .= "Connection: Upgrade\r\n"; + $handshake_message .= "Server: workerman/".Worker::VERSION."\r\n"; + $handshake_message .= "Sec-WebSocket-Accept: " . $new_key . "\r\n\r\n"; + // Mark handshake complete.. $connection->websocketHandshake = true; + // Websocket data buffer. $connection->websocketDataBuffer = ''; + // Current websocket frame length. $connection->websocketCurrentFrameLength = 0; + // Current websocket frame data. $connection->websocketCurrentFrameBuffer = ''; - $connection->consumeRecvBuffer(strlen($buffer)); - $connection->send($new_message, true); - // blob or arraybuffer - $connection->websocketType = self::BINARY_TYPE_BLOB; - // 如果有设置onWebSocketConnect回调,尝试执行 - if(isset($connection->onWebSocketConnect)) - { - self::parseHttpHeader($buffer); - try - { - call_user_func($connection->onWebSocketConnect, $connection, $buffer); - } - catch(\Exception $e) - { - echo $e; - } - $_GET = $_COOKIE = $_SERVER = array(); + // Consume handshake data. + $connection->consumeRecvBuffer($header_length); + // Send handshake response. + $connection->send($handshake_message, true); + + // There are data waiting to be sent. + if (!empty($connection->tmpWebsocketData)) { + $connection->send($connection->tmpWebsocketData, true); + $connection->tmpWebsocketData = ''; } + // blob or arraybuffer + if (empty($connection->websocketType)) { + $connection->websocketType = self::BINARY_TYPE_BLOB; + } + // Try to emit onWebSocketConnect callback. + if (isset($connection->onWebSocketConnect)) { + self::parseHttpHeader($buffer); + try { + call_user_func($connection->onWebSocketConnect, $connection, $buffer); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + if (!empty($_SESSION) && class_exists('\GatewayWorker\Lib\Context')) { + $connection->session = \GatewayWorker\Lib\Context::sessionEncode($_SESSION); + } + $_GET = $_SERVER = $_SESSION = $_COOKIE = array(); + } + if (strlen($buffer) > $header_length) { + return self::input(substr($buffer, $header_length), $connection); + } return 0; - } - // 如果是flash的policy-file-request - elseif(0 === strpos($buffer,'send($policy_xml, true); $connection->consumeRecvBuffer(strlen($buffer)); return 0; } - // 出错 - $connection->send("HTTP/1.1 400 Bad Request\r\n\r\n400 Bad Request
Invalid handshake data for websocket. ", true); + // Bad websocket handshake request. + $connection->send("HTTP/1.1 400 Bad Request\r\n\r\n400 Bad Request
Invalid handshake data for websocket. ", + true); $connection->close(); return 0; } - + /** - * 从header中获取 + * Parse http header. + * * @param string $buffer * @return void */ protected static function parseHttpHeader($buffer) { - $header_data = explode("\r\n", $buffer); - $_SERVER = array(); - list($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI'], $_SERVER['SERVER_PROTOCOL']) = explode(' ', $header_data[0]); + // Parse headers. + list($http_header, ) = explode("\r\n\r\n", $buffer, 2); + $header_data = explode("\r\n", $http_header); + + if ($_SERVER) { + $_SERVER = array(); + } + + list($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI'], $_SERVER['SERVER_PROTOCOL']) = explode(' ', + $header_data[0]); + unset($header_data[0]); - foreach($header_data as $content) - { + foreach ($header_data as $content) { // \r\n\r\n - if(empty($content)) - { + if (empty($content)) { continue; } - list($key, $value) = explode(':', $content, 2); - $key = strtolower($key); - $value = trim($value); - switch($key) - { + list($key, $value) = explode(':', $content, 2); + $key = str_replace('-', '_', strtoupper($key)); + $value = trim($value); + $_SERVER['HTTP_' . $key] = $value; + switch ($key) { // HTTP_HOST - case 'host': - $_SERVER['HTTP_HOST'] = $value; - $tmp = explode(':', $value); + case 'HOST': + $tmp = explode(':', $value); $_SERVER['SERVER_NAME'] = $tmp[0]; - if(isset($tmp[1])) - { + if (isset($tmp[1])) { $_SERVER['SERVER_PORT'] = $tmp[1]; } break; - // HTTP_COOKIE - case 'cookie': - $_SERVER['HTTP_COOKIE'] = $value; + // cookie + case 'COOKIE': parse_str(str_replace('; ', '&', $_SERVER['HTTP_COOKIE']), $_COOKIE); break; - // HTTP_USER_AGENT - case 'user-agent': - $_SERVER['HTTP_USER_AGENT'] = $value; - break; - // HTTP_REFERER - case 'referer': - $_SERVER['HTTP_REFERER'] = $value; - break; - case 'origin': - $_SERVER['HTTP_ORIGIN'] = $value; - break; } } - + // QUERY_STRING $_SERVER['QUERY_STRING'] = parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY); - if($_SERVER['QUERY_STRING']) - { + if ($_SERVER['QUERY_STRING']) { // $GET parse_str($_SERVER['QUERY_STRING'], $_GET); - } - else - { + } else { $_SERVER['QUERY_STRING'] = ''; } } diff --git a/Workerman/Protocols/Ws.php b/Workerman/Protocols/Ws.php new file mode 100644 index 0000000..20fdc0e --- /dev/null +++ b/Workerman/Protocols/Ws.php @@ -0,0 +1,383 @@ +handshakeStep)) { + echo "recv data before handshake. Buffer:" . bin2hex($buffer) . "\n"; + return false; + } + // Recv handshake response + if ($connection->handshakeStep === 1) { + return self::dealHandshake($buffer, $connection); + } + $recv_len = strlen($buffer); + if ($recv_len < self::MIN_HEAD_LEN) { + return 0; + } + // Buffer websocket frame data. + if ($connection->websocketCurrentFrameLength) { + // We need more frame data. + if ($connection->websocketCurrentFrameLength > $recv_len) { + // Return 0, because it is not clear the full packet length, waiting for the frame of fin=1. + return 0; + } + } else { + $data_len = ord($buffer[1]) & 127; + $firstbyte = ord($buffer[0]); + $is_fin_frame = $firstbyte >> 7; + $opcode = $firstbyte & 0xf; + switch ($opcode) { + case 0x0: + break; + // Blob type. + case 0x1: + break; + // Arraybuffer type. + case 0x2: + break; + // Close package. + case 0x8: + // Try to emit onWebSocketClose callback. + if (isset($connection->onWebSocketClose)) { + try { + call_user_func($connection->onWebSocketClose, $connection); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } // Close connection. + else { + $connection->close(); + } + return 0; + // Ping package. + case 0x9: + // Try to emit onWebSocketPing callback. + if (isset($connection->onWebSocketPing)) { + try { + call_user_func($connection->onWebSocketPing, $connection); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } // Send pong package to client. + else { + $connection->send(pack('H*', '8a00'), true); + } + // Consume data from receive buffer. + if (!$data_len) { + $connection->consumeRecvBuffer(self::MIN_HEAD_LEN); + if ($recv_len > self::MIN_HEAD_LEN) { + return self::input(substr($buffer, self::MIN_HEAD_LEN), $connection); + } + return 0; + } + break; + // Pong package. + case 0xa: + // Try to emit onWebSocketPong callback. + if (isset($connection->onWebSocketPong)) { + try { + call_user_func($connection->onWebSocketPong, $connection); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + // Consume data from receive buffer. + if (!$data_len) { + $connection->consumeRecvBuffer(self::MIN_HEAD_LEN); + if ($recv_len > self::MIN_HEAD_LEN) { + return self::input(substr($buffer, self::MIN_HEAD_LEN), $connection); + } + return 0; + } + break; + // Wrong opcode. + default : + echo "error opcode $opcode and close websocket connection. Buffer:" . $buffer . "\n"; + $connection->close(); + return 0; + } + // Calculate packet length. + if ($data_len === 126) { + if (strlen($buffer) < 6) { + return 0; + } + $pack = unpack('nn/ntotal_len', $buffer); + $current_frame_length = $pack['total_len'] + 4; + } else if ($data_len === 127) { + if (strlen($buffer) < 10) { + return 0; + } + $arr = unpack('n/N2c', $buffer); + $current_frame_length = $arr['c1']*4294967296 + $arr['c2'] + 10; + } else { + $current_frame_length = $data_len + 2; + } + if ($is_fin_frame) { + return $current_frame_length; + } else { + $connection->websocketCurrentFrameLength = $current_frame_length; + } + } + // Received just a frame length data. + if ($connection->websocketCurrentFrameLength === $recv_len) { + self::decode($buffer, $connection); + $connection->consumeRecvBuffer($connection->websocketCurrentFrameLength); + $connection->websocketCurrentFrameLength = 0; + return 0; + } // The length of the received data is greater than the length of a frame. + elseif ($connection->websocketCurrentFrameLength < $recv_len) { + self::decode(substr($buffer, 0, $connection->websocketCurrentFrameLength), $connection); + $connection->consumeRecvBuffer($connection->websocketCurrentFrameLength); + $current_frame_length = $connection->websocketCurrentFrameLength; + $connection->websocketCurrentFrameLength = 0; + // Continue to read next frame. + return self::input(substr($buffer, $current_frame_length), $connection); + } // The length of the received data is less than the length of a frame. + else { + return 0; + } + } + + /** + * Websocket encode. + * + * @param string $buffer + * @param ConnectionInterface $connection + * @return string + */ + public static function encode($payload, $connection) + { + if (empty($connection->websocketType)) { + $connection->websocketType = self::BINARY_TYPE_BLOB; + } + $payload = (string)$payload; + if (empty($connection->handshakeStep)) { + self::sendHandshake($connection); + } + $mask = 1; + $mask_key = "\x00\x00\x00\x00"; + + $pack = ''; + $length = $length_flag = strlen($payload); + if (65535 < $length) { + $pack = pack('NN', ($length & 0xFFFFFFFF00000000) >> 32, $length & 0x00000000FFFFFFFF); + $length_flag = 127; + } else if (125 < $length) { + $pack = pack('n*', $length); + $length_flag = 126; + } + + $head = ($mask << 7) | $length_flag; + $head = $connection->websocketType . chr($head) . $pack; + + $frame = $head . $mask_key; + // append payload to frame: + for ($i = 0; $i < $length; $i++) { + $frame .= $payload[$i] ^ $mask_key[$i % 4]; + } + if ($connection->handshakeStep === 1) { + $connection->tmpWebsocketData = isset($connection->tmpWebsocketData) ? $connection->tmpWebsocketData . $frame : $frame; + return ''; + } + return $frame; + } + + /** + * Websocket decode. + * + * @param string $buffer + * @param ConnectionInterface $connection + * @return string + */ + public static function decode($bytes, $connection) + { + $masked = $bytes[1] >> 7; + $data_length = $masked ? ord($bytes[1]) & 127 : ord($bytes[1]); + $decoded_data = ''; + if ($masked === true) { + if ($data_length === 126) { + $mask = substr($bytes, 4, 4); + $coded_data = substr($bytes, 8); + } else if ($data_length === 127) { + $mask = substr($bytes, 10, 4); + $coded_data = substr($bytes, 14); + } else { + $mask = substr($bytes, 2, 4); + $coded_data = substr($bytes, 6); + } + for ($i = 0; $i < strlen($coded_data); $i++) { + $decoded_data .= $coded_data[$i] ^ $mask[$i % 4]; + } + } else { + if ($data_length === 126) { + $decoded_data = substr($bytes, 4); + } else if ($data_length === 127) { + $decoded_data = substr($bytes, 10); + } else { + $decoded_data = substr($bytes, 2); + } + } + if ($connection->websocketCurrentFrameLength) { + $connection->websocketDataBuffer .= $decoded_data; + return $connection->websocketDataBuffer; + } else { + if ($connection->websocketDataBuffer !== '') { + $decoded_data = $connection->websocketDataBuffer . $decoded_data; + $connection->websocketDataBuffer = ''; + } + return $decoded_data; + } + } + + /** + * Send websocket handshake data. + * + * @return void + */ + public static function onConnect($connection) + { + self::sendHandshake($connection); + } + + /** + * Clean + * + * @param $connection + */ + public static function onClose($connection) + { + $connection->handshakeStep = null; + $connection->websocketCurrentFrameLength = 0; + $connection->tmpWebsocketData = ''; + $connection->websocketDataBuffer = ''; + if (!empty($connection->websocketPingTimer)) { + Timer::del($connection->websocketPingTimer); + $connection->websocketPingTimer = null; + } + } + + /** + * Send websocket handshake. + * + * @param \Workerman\Connection\TcpConnection $connection + * @return void + */ + public static function sendHandshake($connection) + { + if (!empty($connection->handshakeStep)) { + return; + } + // Get Host. + $port = $connection->getRemotePort(); + $host = $port === 80 ? $connection->getRemoteHost() : $connection->getRemoteHost() . ':' . $port; + // Handshake header. + $header = 'GET ' . $connection->getRemoteURI() . " HTTP/1.1\r\n". + "Host: $host\r\n". + "Connection: Upgrade\r\n". + "Upgrade: websocket\r\n". + "Origin: ". (isset($connection->websocketOrigin) ? $connection->websocketOrigin : '*') ."\r\n". + "Sec-WebSocket-Version: 13\r\n". + "Sec-WebSocket-Key: ".base64_encode(sha1(uniqid(mt_rand(), true), true))."\r\n\r\n"; + $connection->send($header, true); + $connection->handshakeStep = 1; + $connection->websocketCurrentFrameLength = 0; + $connection->websocketDataBuffer = ''; + } + + /** + * Websocket handshake. + * + * @param string $buffer + * @param \Workerman\Connection\TcpConnection $connection + * @return int + */ + public static function dealHandshake($buffer, $connection) + { + $pos = strpos($buffer, "\r\n\r\n"); + if ($pos) { + // handshake complete + $connection->handshakeStep = 2; + $handshake_response_length = $pos + 4; + // Try to emit onWebSocketConnect callback. + if (isset($connection->onWebSocketConnect)) { + try { + call_user_func($connection->onWebSocketConnect, $connection, substr($buffer, 0, $handshake_response_length)); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + // Headbeat. + if (!empty($connection->websocketPingInterval)) { + $connection->websocketPingTimer = Timer::add($connection->websocketPingInterval, function() use ($connection){ + if (false === $connection->send(pack('H*', '8900'), true)) { + Timer::del($connection->websocketPingTimer); + $connection->websocketPingTimer = null; + } + }); + } + + $connection->consumeRecvBuffer($handshake_response_length); + if (!empty($connection->tmpWebsocketData)) { + $connection->send($connection->tmpWebsocketData, true); + $connection->tmpWebsocketData = ''; + } + if (strlen($buffer) > $handshake_response_length) { + return self::input(substr($buffer, $handshake_response_length), $connection); + } + } + return 0; + } +} diff --git a/Workerman/README.md b/Workerman/README.md new file mode 100644 index 0000000..edc0efe --- /dev/null +++ b/Workerman/README.md @@ -0,0 +1,400 @@ +# Workerman +[![Gitter](https://badges.gitter.im/walkor/Workerman.svg)](https://gitter.im/walkor/Workerman?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=body_badge) + +## What is it +Workerman is a library for event-driven programming in PHP. It has a huge number of features. Each worker is able to handle thousands of connections. + +## Requires + +PHP 5.3 or Higher +A POSIX compatible operating system (Linux, OSX, BSD) +POSIX and PCNTL extensions for PHP + +## Installation + +``` +composer require workerman/workerman +``` + +## Basic Usage + +### A websocket server +test.php +```php +count = 4; + +// Emitted when new connection come +$ws_worker->onConnect = function($connection) +{ + echo "New connection\n"; + }; + +// Emitted when data received +$ws_worker->onMessage = function($connection, $data) +{ + // Send hello $data + $connection->send('hello ' . $data); +}; + +// Emitted when connection closed +$ws_worker->onClose = function($connection) +{ + echo "Connection closed\n"; +}; + +// Run worker +Worker::runAll(); +``` + +### An http server +test.php +```php +require_once './Workerman/Autoloader.php'; +use Workerman\Worker; + +// #### http worker #### +$http_worker = new Worker("http://0.0.0.0:2345"); + +// 4 processes +$http_worker->count = 4; + +// Emitted when data received +$http_worker->onMessage = function($connection, $data) +{ + // $_GET, $_POST, $_COOKIE, $_SESSION, $_SERVER, $_FILES are available + var_dump($_GET, $_POST, $_COOKIE, $_SESSION, $_SERVER, $_FILES); + // send data to client + $connection->send("hello world \n"); +}; + +// run all workers +Worker::runAll(); +``` + +### A WebServer +test.php +```php +require_once './Workerman/Autoloader.php'; +use Workerman\WebServer; +use Workerman\Worker; + +// WebServer +$web = new WebServer("http://0.0.0.0:80"); + +// 4 processes +$web->count = 4; + +// Set the root of domains +$web->addRoot('www.your_domain.com', '/your/path/Web'); +$web->addRoot('www.another_domain.com', '/another/path/Web'); +// run all workers +Worker::runAll(); +``` + +### A tcp server +test.php +```php +require_once './Workerman/Autoloader.php'; +use Workerman\Worker; + +// #### create socket and listen 1234 port #### +$tcp_worker = new Worker("tcp://0.0.0.0:1234"); + +// 4 processes +$tcp_worker->count = 4; + +// Emitted when new connection come +$tcp_worker->onConnect = function($connection) +{ + echo "New Connection\n"; +}; + +// Emitted when data received +$tcp_worker->onMessage = function($connection, $data) +{ + // send data to client + $connection->send("hello $data \n"); +}; + +// Emitted when new connection come +$tcp_worker->onClose = function($connection) +{ + echo "Connection closed\n"; +}; + +Worker::runAll(); +``` + +### Custom protocol +Protocols/MyTextProtocol.php +```php +namespace Protocols; +/** + * User defined protocol + * Format Text+"\n" + */ +class MyTextProtocol +{ + public static function input($recv_buffer) + { + // Find the position of the first occurrence of "\n" + $pos = strpos($recv_buffer, "\n"); + // Not a complete package. Return 0 because the length of package can not be calculated + if($pos === false) + { + return 0; + } + // Return length of the package + return $pos+1; + } + + public static function decode($recv_buffer) + { + return trim($recv_buffer); + } + + public static function encode($data) + { + return $data."\n"; + } +} +``` + +test.php +```php +require_once './Workerman/Autoloader.php'; +use Workerman\Worker; + +// #### MyTextProtocol worker #### +$text_worker = new Worker("MyTextProtocol://0.0.0.0:5678"); + +$text_worker->onConnect = function($connection) +{ + echo "New connection\n"; +}; + +$text_worker->onMessage = function($connection, $data) +{ + // send data to client + $connection->send("hello world \n"); +}; + +$text_worker->onClose = function($connection) +{ + echo "Connection closed\n"; +}; + +// run all workers +Worker::runAll(); +``` + +### Timer +test.php +```php +require_once './Workerman/Autoloader.php'; +use Workerman\Worker; +use Workerman\Lib\Timer; + +$task = new Worker(); +$task->onWorkerStart = function($task) +{ + // 2.5 seconds + $time_interval = 2.5; + $timer_id = Timer::add($time_interval, + function() + { + echo "Timer run\n"; + } + ); +}; + +// run all workers +Worker::runAll(); +``` + +run with: + +```php test.php start``` + +## Available commands +```php test.php start ``` +```php test.php start -d ``` +![workerman start](http://www.workerman.net/img/workerman-start.png) +```php test.php status ``` +![workerman satus](http://www.workerman.net/img/workerman-status.png?a=123) +```php test.php stop ``` +```php test.php restart ``` +```php test.php reload ``` + +## Documentation + +中文主页:[http://www.workerman.net](http://www.workerman.net) + +中文文档: [http://doc3.workerman.net](http://doc3.workerman.net) + +Documentation:[https://github.com/walkor/workerman-manual](https://github.com/walkor/workerman-manual/blob/master/english/src/SUMMARY.md) + +# Benchmarks +``` +CPU: Intel(R) Core(TM) i3-3220 CPU @ 3.30GHz and 4 processors totally +Memory: 8G +OS: Ubuntu 14.04 LTS +Software: ab +PHP: 5.5.9 +``` + +**Codes** +```php +count=3; +$worker->onMessage = function($connection, $data) +{ + $connection->send("HTTP/1.1 200 OK\r\nConnection: keep-alive\r\nServer: workerman\r\nContent-Length: 5\r\n\r\nhello"); +}; +Worker::runAll(); +``` +**Result** + +```shell +ab -n1000000 -c100 -k http://127.0.0.1:1234/ +This is ApacheBench, Version 2.3 <$Revision: 1528965 $> +Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ +Licensed to The Apache Software Foundation, http://www.apache.org/ + +Benchmarking 127.0.0.1 (be patient) +Completed 100000 requests +Completed 200000 requests +Completed 300000 requests +Completed 400000 requests +Completed 500000 requests +Completed 600000 requests +Completed 700000 requests +Completed 800000 requests +Completed 900000 requests +Completed 1000000 requests +Finished 1000000 requests + + +Server Software: workerman/3.1.4 +Server Hostname: 127.0.0.1 +Server Port: 1234 + +Document Path: / +Document Length: 5 bytes + +Concurrency Level: 100 +Time taken for tests: 7.240 seconds +Complete requests: 1000000 +Failed requests: 0 +Keep-Alive requests: 1000000 +Total transferred: 73000000 bytes +HTML transferred: 5000000 bytes +Requests per second: 138124.14 [#/sec] (mean) +Time per request: 0.724 [ms] (mean) +Time per request: 0.007 [ms] (mean, across all concurrent requests) +Transfer rate: 9846.74 [Kbytes/sec] received + +Connection Times (ms) + min mean[+/-sd] median max +Connect: 0 0 0.0 0 5 +Processing: 0 1 0.2 1 9 +Waiting: 0 1 0.2 1 9 +Total: 0 1 0.2 1 9 + +Percentage of the requests served within a certain time (ms) + 50% 1 + 66% 1 + 75% 1 + 80% 1 + 90% 1 + 95% 1 + 98% 1 + 99% 1 + 100% 9 (longest request) + +``` + + +# Demos + +## [tadpole](http://kedou.workerman.net/) +[Live demo](http://kedou.workerman.net/) +[Source code](https://github.com/walkor/workerman) +![workerman todpole](http://www.workerman.net/img/workerman-todpole.png) + +## [BrowserQuest](http://www.workerman.net/demos/browserquest/) +[Live demo](http://www.workerman.net/demos/browserquest/) +[Source code](https://github.com/walkor/BrowserQuest-PHP) +![BrowserQuest width workerman](http://www.workerman.net/img/browserquest.jpg) + +## [web vmstat](http://www.workerman.net/demos/vmstat/) +[Live demo](http://www.workerman.net/demos/vmstat/) +[Source code](https://github.com/walkor/workerman-vmstat) +![web vmstat](http://www.workerman.net/img/workerman-vmstat.png) + +## [live-ascii-camera](https://github.com/walkor/live-ascii-camera) +[Live demo camera page](http://www.workerman.net/demos/live-ascii-camera/camera.html) +[Live demo receive page](http://www.workerman.net/demos/live-ascii-camera/) +[Source code](https://github.com/walkor/live-ascii-camera) +![live-ascii-camera](http://www.workerman.net/img/live-ascii-camera.png) + +## [live-camera](https://github.com/walkor/live-camera) +[Live demo camera page](http://www.workerman.net/demos/live-camera/camera.html) +[Live demo receive page](http://www.workerman.net/demos/live-camera/) +[Source code](https://github.com/walkor/live-camera) +![live-camera](http://www.workerman.net/img/live-camera.jpg) + +## [chat room](http://chat.workerman.net/) +[Live demo](http://chat.workerman.net/) +[Source code](https://github.com/walkor/workerman-chat) +![workerman-chat](http://www.workerman.net/img/workerman-chat.png) + +## [PHPSocket.IO](https://github.com/walkor/phpsocket.io) +[Live demo](http://www.workerman.net/demos/phpsocketio-chat/) +[Source code](https://github.com/walkor/phpsocket.io) +![phpsocket.io](http://www.workerman.net/img/socket.io.png) + +## [statistics](http://www.workerman.net:55757/) +[Live demo](http://www.workerman.net:55757/) +[Source code](https://github.com/walkor/workerman-statistics) +![workerman-statistics](http://www.workerman.net/img/workerman-statistics.png) + +## [flappybird](http://workerman.net/demos/flappy-bird/) +[Live demo](http://workerman.net/demos/flappy-bird/) +[Source code](https://github.com/walkor/workerman-flappy-bird) +![workerman-statistics](http://www.workerman.net/img/workerman-flappy-bird.png) + +## [jsonRpc](https://github.com/walkor/workerman-JsonRpc) +[Source code](https://github.com/walkor/workerman-JsonRpc) +![workerman-jsonRpc](http://www.workerman.net/img/workerman-json-rpc.png) + +## [thriftRpc](https://github.com/walkor/workerman-thrift) +[Source code](https://github.com/walkor/workerman-thrift) +![workerman-thriftRpc](http://www.workerman.net/img/workerman-thrift.png) + +## [web-msg-sender](https://github.com/walkor/web-msg-sender) +[Live demo send page](http://workerman.net:3333/) +[Live demo receive page](http://workerman.net/web-msg-sender.html) +[Source code](https://github.com/walkor/web-msg-sender) +![web-msg-sender](http://www.workerman.net/img/web-msg-sender.png) + +## [shadowsocks-php](https://github.com/walkor/shadowsocks-php) +[Source code](https://github.com/walkor/shadowsocks-php) +![shadowsocks-php](http://www.workerman.net/img/shadowsocks-php.png) + +## [queue](https://github.com/walkor/workerman-queue) +[Source code](https://github.com/walkor/workerman-queue) + +## LICENSE + +Workerman is released under the [MIT license](https://github.com/walkor/workerman/blob/master/MIT-LICENSE.txt). diff --git a/Workerman/WebServer.php b/Workerman/WebServer.php index f715cc0..a243a09 100644 --- a/Workerman/WebServer.php +++ b/Workerman/WebServer.php @@ -1,258 +1,301 @@ + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ namespace Workerman; -use \Workerman\Worker; -use \Workerman\Protocols\Http; -use \Workerman\Protocols\HttpCache; +use Workerman\Protocols\Http; +use Workerman\Protocols\HttpCache; /** - * - * 基于Worker实现的一个简单的WebServer - * 支持静态文件、支持文件上传、支持POST - * HTTP协议 - * - * @author walkor + * WebServer. */ class WebServer extends Worker { /** - * 默认mime类型 - * @var string - */ - protected static $defaultMimeType = 'text/html; charset=utf-8'; - - /** - * 服务器名到文件路径的转换 + * Virtual host to path mapping. + * * @var array ['workerman.net'=>'/home', 'www.workerman.net'=>'home/www'] */ protected $serverRoot = array(); - + /** - * mime类型映射关系 + * Mime mapping. + * * @var array */ protected static $mimeTypeMap = array(); - - + + /** - * 用来保存用户设置的onWorkerStart回调 + * Used to save user OnWorkerStart callback settings. + * * @var callback */ protected $_onWorkerStart = null; - + /** - * 添加站点域名与站点目录的对应关系,类似nginx的 + * Add virtual host. + * * @param string $domain * @param string $root_path * @return void */ - public function addRoot($domain, $root_path) + public function addRoot($domain, $root_path) { $this->serverRoot[$domain] = $root_path; } - + /** - * 构造函数 + * Construct. + * * @param string $socket_name - * @param array $context_option + * @param array $context_option */ public function __construct($socket_name, $context_option = array()) { - list($scheme, $address) = explode(':', $socket_name, 2); - parent::__construct('http:'.$address, $context_option); + list(, $address) = explode(':', $socket_name, 2); + parent::__construct('http:' . $address, $context_option); $this->name = 'WebServer'; } - + /** - * 运行 + * Run webserver instance. + * * @see Workerman.Worker::run() */ public function run() { $this->_onWorkerStart = $this->onWorkerStart; - $this->onWorkerStart = array($this, 'onWorkerStart'); - $this->onMessage = array($this, 'onMessage'); + $this->onWorkerStart = array($this, 'onWorkerStart'); + $this->onMessage = array($this, 'onMessage'); parent::run(); } - + /** - * 进程启动的时候一些初始化工作 + * Emit when process start. + * * @throws \Exception */ public function onWorkerStart() { - if(empty($this->serverRoot)) - { + if (empty($this->serverRoot)) { throw new \Exception('server root not set, please use WebServer::addRoot($domain, $root_path) to set server root path'); } - // 初始化HttpCache + // Init HttpCache. HttpCache::init(); - // 初始化mimeMap + // Init mimeMap. $this->initMimeTypeMap(); - - // 尝试执行开发者设定的onWorkerStart回调 - if($this->_onWorkerStart) - { - call_user_func($this->_onWorkerStart, $this); + + // Try to emit onWorkerStart callback. + if ($this->_onWorkerStart) { + try { + call_user_func($this->_onWorkerStart, $this); + } catch (\Exception $e) { + self::log($e); + exit(250); + } catch (\Error $e) { + self::log($e); + exit(250); + } } } - + /** - * 初始化mimeType + * Init mime map. + * * @return void */ public function initMimeTypeMap() { $mime_file = Http::getMimeTypesFile(); - if(!is_file($mime_file)) - { - $this->notice("$mime_file mime.type file not fond"); + if (!is_file($mime_file)) { + $this->log("$mime_file mime.type file not fond"); return; } $items = file($mime_file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); - if(!is_array($items)) - { + if (!is_array($items)) { $this->log("get $mime_file mime.type content fail"); return; } - foreach($items as $content) - { - if(preg_match("/\s*(\S+)\s+(\S.+)/", $content, $match)) - { - $mime_type = $match[1]; - $extension_var = $match[2]; - $extension_array = explode(' ', substr($extension_var, 0, -1)); - foreach($extension_array as $extension) - { - self::$mimeTypeMap[$extension] = $mime_type; - } + foreach ($items as $content) { + if (preg_match("/\s*(\S+)\s+(\S.+)/", $content, $match)) { + $mime_type = $match[1]; + $workerman_file_extension_var = $match[2]; + $workerman_file_extension_array = explode(' ', substr($workerman_file_extension_var, 0, -1)); + foreach ($workerman_file_extension_array as $workerman_file_extension) { + self::$mimeTypeMap[$workerman_file_extension] = $mime_type; + } } } } - + /** - * 当接收到完整的http请求后的处理逻辑 - * 1、如果请求的是以php为后缀的文件,则尝试加载 - * 2、如果请求的url没有后缀,则尝试加载对应目录的index.php - * 3、如果请求的是非php为后缀的文件,尝试读取原始数据并发送 - * 4、如果请求的文件不存在,则返回404 - * @param TcpConnection $connection - * @param mixed $data + * Emit when http message coming. + * + * @param Connection\TcpConnection $connection * @return void */ - public function onMessage($connection, $data) + public function onMessage($connection) { - // 请求的文件 - $url_info = parse_url($_SERVER['REQUEST_URI']); - if(!$url_info) - { + // REQUEST_URI. + $workerman_url_info = parse_url($_SERVER['REQUEST_URI']); + if (!$workerman_url_info) { Http::header('HTTP/1.1 400 Bad Request'); - return $connection->close('

400 Bad Request

'); + $connection->close('

400 Bad Request

'); + return; } - - $path = $url_info['path']; - - $path_info = pathinfo($path); - $extension = isset($path_info['extension']) ? $path_info['extension'] : '' ; - if($extension == '') - { - $path = ($len = strlen($path)) && $path[$len -1] == '/' ? $path.'index.php' : $path . '/index.php'; - $extension = 'php'; + + $workerman_path = isset($workerman_url_info['path']) ? $workerman_url_info['path'] : '/'; + + $workerman_path_info = pathinfo($workerman_path); + $workerman_file_extension = isset($workerman_path_info['extension']) ? $workerman_path_info['extension'] : ''; + if ($workerman_file_extension === '') { + $workerman_path = ($len = strlen($workerman_path)) && $workerman_path[$len - 1] === '/' ? $workerman_path . 'index.php' : $workerman_path . '/index.php'; + $workerman_file_extension = 'php'; } - - $root_dir = isset($this->serverRoot[$_SERVER['HTTP_HOST']]) ? $this->serverRoot[$_SERVER['HTTP_HOST']] : current($this->serverRoot); - - $file = "$root_dir/$path"; - - // 对应的php文件不存在则直接使用根目录的index.php - if($extension == 'php' && !is_file($file)) - { - $file = "$root_dir/index.php"; - } - - // 请求的文件存在 - if(is_file($file)) - { - // 判断是否是站点目录里的文件 - if((!($request_realpath = realpath($file)) || !($root_dir_realpath = realpath($root_dir))) || 0 !== strpos($request_realpath, $root_dir_realpath)) - { - Http::header('HTTP/1.1 400 Bad Request'); - return $connection->close('

400 Bad Request

'); + + $workerman_root_dir = isset($this->serverRoot[$_SERVER['SERVER_NAME']]) ? $this->serverRoot[$_SERVER['SERVER_NAME']] : current($this->serverRoot); + + $workerman_file = "$workerman_root_dir/$workerman_path"; + + if ($workerman_file_extension === 'php' && !is_file($workerman_file)) { + $workerman_file = "$workerman_root_dir/index.php"; + if (!is_file($workerman_file)) { + $workerman_file = "$workerman_root_dir/index.html"; + $workerman_file_extension = 'html'; } - - $file = realpath($file); - - // 如果请求的是php文件 - if($extension == 'php') - { - $cwd = getcwd(); - chdir($root_dir); + } + + // File exsits. + if (is_file($workerman_file)) { + // Security check. + if ((!($workerman_request_realpath = realpath($workerman_file)) || !($workerman_root_dir_realpath = realpath($workerman_root_dir))) || 0 !== strpos($workerman_request_realpath, + $workerman_root_dir_realpath) + ) { + Http::header('HTTP/1.1 400 Bad Request'); + $connection->close('

400 Bad Request

'); + return; + } + + $workerman_file = realpath($workerman_file); + + // Request php file. + if ($workerman_file_extension === 'php') { + $workerman_cwd = getcwd(); + chdir($workerman_root_dir); ini_set('display_errors', 'off'); - // 缓冲输出 ob_start(); - // 载入php文件 - try - { - // $_SERVER变量 + // Try to include php file. + try { + // $_SERVER. $_SERVER['REMOTE_ADDR'] = $connection->getRemoteIp(); $_SERVER['REMOTE_PORT'] = $connection->getRemotePort(); - include $file; - } - catch(\Exception $e) - { - // 如果不是exit - if($e->getMessage() != 'jump_exit') - { + include $workerman_file; + } catch (\Exception $e) { + // Jump_exit? + if ($e->getMessage() != 'jump_exit') { echo $e; } } $content = ob_get_clean(); ini_set('display_errors', 'on'); - $connection->close($content); - chdir($cwd); - return ; - } - - // 请求的是静态资源文件 - if(isset(self::$mimeTypeMap[$extension])) - { - Http::header('Content-Type: '. self::$mimeTypeMap[$extension]); - } - else - { - Http::header('Content-Type: '. self::$defaultMimeType); - } - - // 获取文件信息 - $info = stat($file); - - $modified_time = $info ? date('D, d M Y H:i:s', $info['mtime']) . ' GMT' : ''; - - // 如果有$_SERVER['HTTP_IF_MODIFIED_SINCE'] - if(!empty($_SERVER['HTTP_IF_MODIFIED_SINCE']) && $info) - { - // 文件没有更改则直接304 - if($modified_time === $_SERVER['HTTP_IF_MODIFIED_SINCE']) - { - // 304 - Http::header('HTTP/1.1 304 Not Modified'); - // 发送给客户端 - return $connection->close(''); + if (strtolower($_SERVER['HTTP_CONNECTION']) === "keep-alive") { + $connection->send($content); + } else { + $connection->close($content); } + chdir($workerman_cwd); + return; } - - if($modified_time) - { - Http::header("Last-Modified: $modified_time"); - } - // 发送给客户端 - return $connection->close(file_get_contents($file)); - } - else - { + + // Send file to client. + return self::sendFile($connection, $workerman_file); + } else { // 404 Http::header("HTTP/1.1 404 Not Found"); - return $connection->close('404 页面不存在

404 Not Found

'); + $connection->close('404 File not found

404 Not Found

'); + return; } } + + public static function sendFile($connection, $file_path) + { + // Check 304. + $info = stat($file_path); + $modified_time = $info ? date('D, d M Y H:i:s', $info['mtime']) . ' GMT' : ''; + if (!empty($_SERVER['HTTP_IF_MODIFIED_SINCE']) && $info) { + // Http 304. + if ($modified_time === $_SERVER['HTTP_IF_MODIFIED_SINCE']) { + // 304 + Http::header('HTTP/1.1 304 Not Modified'); + // Send nothing but http headers.. + $connection->close(''); + return; + } + } + + // Http header. + if ($modified_time) { + $modified_time = "Last-Modified: $modified_time\r\n"; + } + $file_size = filesize($file_path); + $file_info = pathinfo($file_path); + $extension = isset($file_info['extension']) ? $file_info['extension'] : ''; + $file_name = isset($file_info['filename']) ? $file_info['filename'] : ''; + $header = "HTTP/1.1 200 OK\r\n"; + if (isset(self::$mimeTypeMap[$extension])) { + $header .= "Content-Type: " . self::$mimeTypeMap[$extension] . "\r\n"; + } else { + $header .= "Content-Type: application/octet-stream\r\n"; + $header .= "Content-Disposition: attachment; filename=\"$file_name\"\r\n"; + } + $header .= "Connection: keep-alive\r\n"; + $header .= $modified_time; + $header .= "Content-Length: $file_size\r\n\r\n"; + $trunk_limit_size = 1024*1024; + if ($file_size < $trunk_limit_size) { + return $connection->send($header.file_get_contents($file_path), true); + } + $connection->send($header, true); + + // Read file content from disk piece by piece and send to client. + $connection->fileHandler = fopen($file_path, 'r'); + $do_write = function()use($connection) + { + // Send buffer not full. + while(empty($connection->bufferFull)) + { + // Read from disk. + $buffer = fread($connection->fileHandler, 8192); + // Read eof. + if($buffer === '' || $buffer === false) + { + return; + } + $connection->send($buffer, true); + } + }; + // Send buffer full. + $connection->onBufferFull = function($connection) + { + $connection->bufferFull = true; + }; + // Send buffer drain. + $connection->onBufferDrain = function($connection)use($do_write) + { + $connection->bufferFull = false; + $do_write(); + }; + $do_write(); + } } diff --git a/Workerman/Worker.php b/Workerman/Worker.php index fb05b05..7251afc 100644 --- a/Workerman/Worker.php +++ b/Workerman/Worker.php @@ -1,400 +1,542 @@ + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ namespace Workerman; -use \Workerman\Events\Libevent; -use \Workerman\Events\Select; -use \Workerman\Events\EventInterface; -use \Workerman\Connection\ConnectionInterface; -use \Workerman\Connection\TcpConnection; -use \Workerman\Connection\UdpConnection; -use \Workerman\Lib\Timer; -use \Workerman\Autoloader; -use \Exception; +require_once __DIR__ . '/Lib/Constants.php'; + +use Workerman\Events\EventInterface; +use Workerman\Connection\ConnectionInterface; +use Workerman\Connection\TcpConnection; +use Workerman\Connection\UdpConnection; +use Workerman\Lib\Timer; +use Exception; /** - * - * @author walkor + * Worker class + * A container for listening ports */ class Worker { /** - * 版本号 + * Version. + * * @var string */ - const VERSION = '3.0.9'; - + const VERSION = '3.3.4'; + /** - * 状态 启动中 + * Status starting. + * * @var int */ const STATUS_STARTING = 1; - + /** - * 状态 运行中 + * Status running. + * * @var int */ const STATUS_RUNNING = 2; - + /** - * 状态 停止 + * Status shutdown. + * * @var int */ const STATUS_SHUTDOWN = 4; - + /** - * 状态 平滑重启中 + * Status reloading. + * * @var int */ const STATUS_RELOADING = 8; - + /** - * 给子进程发送重启命令 KILL_WORKER_TIMER_TIME 秒后 - * 如果对应进程仍然未重启则强行杀死 + * After sending the restart command to the child process KILL_WORKER_TIMER_TIME seconds, + * if the process is still living then forced to kill. + * * @var int */ - const KILL_WORKER_TIMER_TIME = 1; - + const KILL_WORKER_TIMER_TIME = 2; + /** - * 默认的backlog,即内核中用于存放未被进程认领(accept)的连接队列长度 + * Default backlog. Backlog is the maximum length of the queue of pending connections. + * * @var int */ - const DEFAUL_BACKLOG = 1024; - + const DEFAULT_BACKLOG = 1024; /** - * udp最大包长 + * Max udp package size. + * * @var int */ - const MAX_UDP_PACKEG_SIZE = 65535; - + const MAX_UDP_PACKAGE_SIZE = 65535; + /** - * worker的名称,用于在运行status命令时标记进程 + * Worker id. + * + * @var int + */ + public $id = 0; + + /** + * Name of the worker processes. + * * @var string */ public $name = 'none'; - + /** - * 设置当前worker实例的进程数 + * Number of worker processes. + * * @var int */ public $count = 1; - + /** - * 设置当前worker进程的运行用户,启动时需要root超级权限 + * Unix user of processes, needs appropriate privileges (usually root). + * * @var string */ public $user = ''; - + /** - * 当前worker进程是否可以平滑重启 + * Unix group of processes, needs appropriate privileges (usually root). + * + * @var string + */ + public $group = ''; + + /** + * reloadable. + * * @var bool */ public $reloadable = true; - + /** - * 当worker进程启动时,如果设置了$onWorkerStart回调函数,则运行 - * 此钩子函数一般用于进程启动后初始化工作 + * reuse port. + * + * @var bool + */ + public $reusePort = false; + + /** + * Emitted when worker processes start. + * * @var callback */ public $onWorkerStart = null; - + /** - * 当有客户端连接时,如果设置了$onConnect回调函数,则运行 + * Emitted when a socket connection is successfully established. + * * @var callback */ public $onConnect = null; - + /** - * 当客户端连接上发来数据时,如果设置了$onMessage回调,则运行 + * Emitted when data is received. + * * @var callback */ public $onMessage = null; - + /** - * 当客户端的连接关闭时,如果设置了$onClose回调,则运行 + * Emitted when the other end of the socket sends a FIN packet. + * * @var callback */ public $onClose = null; - + /** - * 当客户端的连接发生错误时,如果设置了$onError回调,则运行 - * 错误一般为客户端断开连接导致数据发送失败 - * 具体错误码及错误详情会以参数的形式传递给回调,参见手册 + * Emitted when an error occurs with connection. + * * @var callback */ public $onError = null; - + /** - * 当连接的发送缓冲区满时,如果设置了$onBufferFull回调,则执行 + * Emitted when the send buffer becomes full. + * * @var callback */ public $onBufferFull = null; - + /** - * 当链接的发送缓冲区被清空时,如果设置了$onBufferDrain回调,则执行 + * Emitted when the send buffer becomes empty. + * * @var callback */ public $onBufferDrain = null; - + /** - * 当前进程退出时(由于平滑重启或者服务停止导致),如果设置了此回调,则运行 + * Emitted when worker processes stoped. + * * @var callback */ public $onWorkerStop = null; - + /** - * 传输层协议 + * Emitted when worker processes get reload command. + * + * @var callback + */ + public $onWorkerReload = null; + + /** + * Transport layer protocol. + * * @var string */ public $transport = 'tcp'; - + /** - * 所有的客户端连接 + * Store all connections of clients. + * * @var array */ public $connections = array(); - + /** - * 应用层协议,由初始化worker时指定 - * 例如 new worker('http://0.0.0.0:8080');指定使用http协议 + * Application layer protocol. + * + * @var Protocols\ProtocolInterface + */ + public $protocol = ''; + + /** + * Root path for autoload. + * * @var string */ - protected $_protocol = ''; - + protected $_autoloadRootPath = ''; + /** - * 当前worker实例初始化目录位置,用于设置应用自动加载的根目录 - * @var string - */ - protected $_appInitPath = ''; - - /** - * 是否以守护进程的方式运行。运行start时加上-d参数会自动以守护进程方式运行 - * 例如 php start.php start -d + * Daemonize. + * * @var bool */ public static $daemonize = false; - + /** - * 重定向标准输出,即将所有echo、var_dump等终端输出写到对应文件中 - * 注意 此参数只有在以守护进程方式运行时有效 + * Stdout file. + * * @var string */ public static $stdoutFile = '/dev/null'; - + /** - * pid文件的路径及名称 - * 例如 Worker::$pidFile = '/tmp/workerman.pid'; - * 注意 此属性一般不必手动设置,默认会放到php临时目录中 + * The file to store master process PID. + * * @var string */ public static $pidFile = ''; - + /** - * 日志目录,默认在workerman根目录下,与Applications同级 - * 可以手动设置 - * 例如 Worker::$logFile = '/tmp/workerman.log'; - * @var unknown_type + * Log file. + * + * @var mixed */ public static $logFile = ''; - + /** - * 全局事件轮询库,用于监听所有资源的可读可写事件 - * @var Select/Libevent + * Global event loop. + * + * @var Events\EventInterface */ public static $globalEvent = null; - + /** - * 主进程pid + * The PID of master process. + * * @var int */ protected static $_masterPid = 0; - + /** - * 监听的socket - * @var stream + * Listening socket. + * + * @var resource */ protected $_mainSocket = null; - + /** - * socket名称,包括应用层协议+ip+端口号,在初始化worker时设置 - * 值类似 http://0.0.0.0:80 + * Socket name. The format is like this http://0.0.0.0:80 . + * * @var string */ protected $_socketName = ''; - + /** - * socket的上下文,具体选项设置可以在初始化worker时传递 - * @var context + * Context of socket. + * + * @var resource */ protected $_context = null; - + /** - * 所有的worker实例 + * All worker instances. + * * @var array */ protected static $_workers = array(); - + /** - * 所有worker进程的pid - * 格式为 [worker_id=>[pid=>pid, pid=>pid, ..], ..] + * All worker porcesses pid. + * The format is like this [worker_id=>[pid=>pid, pid=>pid, ..], ..] + * * @var array */ protected static $_pidMap = array(); - + /** - * 所有需要重启的进程pid - * 格式为 [pid=>pid, pid=>pid] + * All worker processes waiting for restart. + * The format is like this [pid=>pid, pid=>pid]. + * * @var array */ protected static $_pidsToRestart = array(); - + /** - * 当前worker状态 + * Mapping from PID to worker process ID. + * The format is like this [worker_id=>[0=>$pid, 1=>$pid, ..], ..]. + * + * @var array + */ + protected static $_idMap = array(); + + /** + * Current status. + * * @var int */ protected static $_status = self::STATUS_STARTING; - + /** - * 所有worke名称(name属性)中的最大长度,用于在运行 status 命令时格式化输出 + * Maximum length of the worker names. + * * @var int */ protected static $_maxWorkerNameLength = 12; - + /** - * 所有socket名称(_socketName属性)中的最大长度,用于在运行 status 命令时格式化输出 + * Maximum length of the socket names. + * * @var int */ protected static $_maxSocketNameLength = 12; - + /** - * 所有user名称(user属性)中的最大长度,用于在运行 status 命令时格式化输出 + * Maximum length of the process user names. + * * @var int */ protected static $_maxUserNameLength = 12; - + /** - * 运行 status 命令时用于保存结果的文件名 + * The file to store status info of current worker process. + * * @var string */ protected static $_statisticsFile = ''; - + /** - * 启动的全局入口文件 - * 例如 php start.php start ,则入口文件为start.php + * Start file. + * * @var string */ protected static $_startFile = ''; - + /** - * 全局统计数据,用于在运行 status 命令时展示 - * 统计的内容包括 workerman启动的时间戳及每组worker进程的退出次数及退出状态码 + * Status info of current worker process. + * * @var array */ protected static $_globalStatistics = array( - 'start_timestamp' => 0, + 'start_timestamp' => 0, 'worker_exit_info' => array() ); - + /** - * 运行所有worker实例 + * Available event loops. + * + * @var array + */ + protected static $_availableEventLoops = array( + 'libevent', + 'event', + 'ev' + ); + + /** + * Current eventLoop name. + * + * @var string + */ + protected static $_eventLoopName = 'select'; + + /** + * PHP built-in protocols. + * + * @var array + */ + protected static $_builtinTransports = array( + 'tcp' => 'tcp', + 'udp' => 'udp', + 'unix' => 'unix', + 'ssl' => 'tcp', + 'sslv2' => 'tcp', + 'sslv3' => 'tcp', + 'tls' => 'tcp' + ); + + /** + * Run all worker instances. + * * @return void */ public static function runAll() { - // 初始化环境变量 + self::checkSapiEnv(); self::init(); - // 解析命令 self::parseCommand(); - // 尝试以守护进程模式运行 self::daemonize(); - // 初始化所有worker实例,主要是监听端口 self::initWorkers(); - // 初始化所有信号处理函数 self::installSignal(); - // 展示启动界面 - self::displayUI(); - // 尝试重定向标准输入输出 - self::resetStd(); - // 保存主进程pid self::saveMasterPid(); - // 创建子进程(worker进程)并运行 self::forkWorkers(); - // 监控所有子进程(worker进程) + self::displayUI(); + self::resetStd(); self::monitorWorkers(); } - + /** - * 初始化一些环境变量 + * Check sapi. + * * @return void */ - public static function init() + protected static function checkSapiEnv() { - // 如果没设置$pidFile,则生成默认值 - if(empty(self::$pidFile)) - { - $backtrace = debug_backtrace(); - self::$_startFile = $backtrace[count($backtrace)-1]['file']; - self::$pidFile = sys_get_temp_dir()."/workerman.".str_replace('/', '_', self::$_startFile).".pid"; + // Only for cli. + if (php_sapi_name() != "cli") { + exit("only run in command line mode \n"); } - // 没有设置日志文件,则生成一个默认值 - if(empty(self::$logFile)) - { + } + + /** + * Init. + * + * @return void + */ + protected static function init() + { + // Start file. + $backtrace = debug_backtrace(); + self::$_startFile = $backtrace[count($backtrace) - 1]['file']; + + // Pid file. + if (empty(self::$pidFile)) { + self::$pidFile = __DIR__ . "/../" . str_replace('/', '_', self::$_startFile) . ".pid"; + } + + // Log file. + if (empty(self::$logFile)) { self::$logFile = __DIR__ . '/../workerman.log'; } - // 标记状态为启动中 + touch(self::$logFile); + chmod(self::$logFile, 0622); + + // State. self::$_status = self::STATUS_STARTING; - // 启动时间戳 + + // For statistics. self::$_globalStatistics['start_timestamp'] = time(); - // 设置status文件位置 - self::$_statisticsFile = sys_get_temp_dir().'/workerman.status'; - // 尝试设置进程名称(需要php>=5.5或者安装了proctitle扩展) + self::$_statisticsFile = sys_get_temp_dir() . '/workerman.status'; + + // Process title. self::setProcessTitle('WorkerMan: master process start_file=' . self::$_startFile); - - // 初始化定时器 + + // Init data for worker id. + self::initId(); + + // Timer init. Timer::init(); } - + /** - * 初始化所有的worker实例,主要工作为获得格式化所需数据及监听端口 + * Init All worker instances. + * * @return void */ protected static function initWorkers() { - foreach(self::$_workers as $worker) - { - // 没有设置worker名称,则使用none代替 - if(empty($worker->name)) - { + foreach (self::$_workers as $worker) { + // Worker name. + if (empty($worker->name)) { $worker->name = 'none'; } - // 获得所有worker名称中最大长度 + + // Get maximum length of worker name. $worker_name_length = strlen($worker->name); - if(self::$_maxWorkerNameLength < $worker_name_length) - { + if (self::$_maxWorkerNameLength < $worker_name_length) { self::$_maxWorkerNameLength = $worker_name_length; } - // 获得所有_socketName中最大长度 + + // Get maximum length of socket name. $socket_name_length = strlen($worker->getSocketName()); - if(self::$_maxSocketNameLength < $socket_name_length) - { + if (self::$_maxSocketNameLength < $socket_name_length) { self::$_maxSocketNameLength = $socket_name_length; } - // 获得运行用户名的最大长度 - if(empty($worker->user) || posix_getuid() !== 0) - { + + // Get unix user of the worker process. + if (empty($worker->user)) { $worker->user = self::getCurrentUser(); + } else { + if (posix_getuid() !== 0 && $worker->user != self::getCurrentUser()) { + self::log('Warning: You must have the root privileges to change uid and gid.'); + } } + + // Get maximum length of unix user name. $user_name_length = strlen($worker->user); - if(self::$_maxUserNameLength < $user_name_length) - { + if (self::$_maxUserNameLength < $user_name_length) { self::$_maxUserNameLength = $user_name_length; } - // 监听端口 - $worker->listen(); + + // Listen. + if (!$worker->reusePort) { + $worker->listen(); + } } } - + /** - * 获得运行当前进程的用户名 + * Init idMap. + * return void + */ + protected static function initId() + { + foreach (self::$_workers as $worker_id => $worker) { + self::$_idMap[$worker_id] = array_fill(0, $worker->count, 0); + } + } + + /** + * Get unix user of current porcess. + * * @return string */ protected static function getCurrentUser() @@ -402,146 +544,154 @@ class Worker $user_info = posix_getpwuid(posix_getuid()); return $user_info['name']; } - + /** - * 展示启动界面 + * Display staring UI. + * * @return void */ protected static function displayUI() { echo "\033[1A\n\033[K-----------------------\033[47;30m WORKERMAN \033[0m-----------------------------\n\033[0m"; - echo 'Workerman version:' . Worker::VERSION . " PHP version:".PHP_VERSION."\n"; + echo 'Workerman version:', Worker::VERSION, " PHP version:", PHP_VERSION, "\n"; echo "------------------------\033[47;30m WORKERS \033[0m-------------------------------\n"; - echo "\033[47;30muser\033[0m",str_pad('', self::$_maxUserNameLength+2-strlen('user')), "\033[47;30mworker\033[0m",str_pad('', self::$_maxWorkerNameLength+2-strlen('worker')), "\033[47;30mlisten\033[0m",str_pad('', self::$_maxSocketNameLength+2-strlen('listen')), "\033[47;30mprocesses\033[0m \033[47;30m","status\033[0m\n"; - foreach(self::$_workers as $worker) - { - echo str_pad($worker->user, self::$_maxUserNameLength+2),str_pad($worker->name, self::$_maxWorkerNameLength+2),str_pad($worker->getSocketName(), self::$_maxSocketNameLength+2), str_pad(' '.$worker->count, 9), " \033[32;40m [OK] \033[0m\n";; + echo "\033[47;30muser\033[0m", str_pad('', + self::$_maxUserNameLength + 2 - strlen('user')), "\033[47;30mworker\033[0m", str_pad('', + self::$_maxWorkerNameLength + 2 - strlen('worker')), "\033[47;30mlisten\033[0m", str_pad('', + self::$_maxSocketNameLength + 2 - strlen('listen')), "\033[47;30mprocesses\033[0m \033[47;30m", "status\033[0m\n"; + + foreach (self::$_workers as $worker) { + echo str_pad($worker->user, self::$_maxUserNameLength + 2), str_pad($worker->name, + self::$_maxWorkerNameLength + 2), str_pad($worker->getSocketName(), + self::$_maxSocketNameLength + 2), str_pad(' ' . $worker->count, 9), " \033[32;40m [OK] \033[0m\n";; } echo "----------------------------------------------------------------\n"; + if (self::$daemonize) { + global $argv; + $start_file = $argv[0]; + echo "Input \"php $start_file stop\" to quit. Start success.\n"; + } else { + echo "Press Ctrl-C to quit. Start success.\n"; + } } - + /** - * 解析运行命令 + * Parse command. * php yourfile.php start | stop | restart | reload | status + * * @return void */ - public static function parseCommand() + protected static function parseCommand() { - // 检查运行命令的参数 global $argv; - $start_file = $argv[0]; - if(!isset($argv[1])) - { - exit("Usage: php yourfile.php {start|stop|restart|reload|status}\n"); + // Check argv; + $start_file = $argv[0]; + if (!isset($argv[1])) { + exit("Usage: php yourfile.php {start|stop|restart|reload|status|kill}\n"); } - - // 命令 - $command = trim($argv[1]); - - // 子命令,目前只支持-d + + // Get command. + $command = trim($argv[1]); $command2 = isset($argv[2]) ? $argv[2] : ''; - - // 记录日志 - self::log("Workerman[$start_file] $command"); - - // 检查主进程是否在运行 - $master_pid = @file_get_contents(self::$pidFile); - $master_is_alive = $master_pid && @posix_kill($master_pid, 0); - if($master_is_alive) - { - if($command === 'start') - { - self::log("Workerman[$start_file] is running"); + + // Start command. + $mode = ''; + if ($command === 'start') { + if ($command2 === '-d' || Worker::$daemonize) { + $mode = 'in DAEMON mode'; + } else { + $mode = 'in DEBUG mode'; } } - elseif($command !== 'start' && $command !== 'restart') - { + self::log("Workerman[$start_file] $command $mode"); + + // Get master process PID. + $master_pid = @file_get_contents(self::$pidFile); + $master_is_alive = $master_pid && @posix_kill($master_pid, 0); + // Master is still alive? + if ($master_is_alive) { + if ($command === 'start' && posix_getpid() != $master_pid) { + self::log("Workerman[$start_file] already running"); + exit; + } + } elseif ($command !== 'start' && $command !== 'restart' && $command !== 'kill') { self::log("Workerman[$start_file] not run"); + exit; } - - // 根据命令做相应处理 - switch($command) - { - // 启动 workerman + + // execute command. + switch ($command) { + case 'kill': + exec("ps aux | grep $start_file | grep -v grep | awk '{print $2}' |xargs kill -SIGINT"); + usleep(100000); + exec("ps aux | grep $start_file | grep -v grep | awk '{print $2}' |xargs kill -SIGKILL"); + break; case 'start': - if($command2 == '-d') - { + if ($command2 === '-d') { Worker::$daemonize = true; } break; - // 显示 workerman 运行状态 case 'status': - // 尝试删除统计文件,避免脏数据 - if(is_file(self::$_statisticsFile)) - { + if (is_file(self::$_statisticsFile)) { @unlink(self::$_statisticsFile); } - // 向主进程发送 SIGUSR2 信号 ,然后主进程会向所有子进程发送 SIGUSR2 信号 - // 所有进程收到 SIGUSR2 信号后会向 $_statisticsFile 写入自己的状态 + // Master process will send status signal to all child processes. posix_kill($master_pid, SIGUSR2); - // 睡眠100毫秒,等待子进程将自己的状态写入$_statisticsFile指定的文件 + // Waiting amoment. usleep(100000); - // 展示状态 - readfile(self::$_statisticsFile); + // Display statisitcs data from a disk file. + @readfile(self::$_statisticsFile); exit(0); - // 重启 workerman case 'restart': - // 停止 workeran case 'stop': self::log("Workerman[$start_file] is stoping ..."); - // 想主进程发送SIGINT信号,主进程会向所有子进程发送SIGINT信号 + // Send stop signal to master process. $master_pid && posix_kill($master_pid, SIGINT); - // 如果 $timeout 秒后主进程没有退出则展示失败界面 - $timeout = 5; + // Timeout. + $timeout = 5; $start_time = time(); - while(1) - { - // 检查主进程是否存活 + // Check master process is still alive? + while (1) { $master_is_alive = $master_pid && posix_kill($master_pid, 0); - if($master_is_alive) - { - // 检查是否超过$timeout时间 - if(time() - $start_time >= $timeout) - { + if ($master_is_alive) { + // Timeout? + if (time() - $start_time >= $timeout) { self::log("Workerman[$start_file] stop fail"); exit; } + // Waiting amoment. usleep(10000); continue; } + // Stop success. self::log("Workerman[$start_file] stop success"); - // 是restart命令 - if($command === 'stop') - { + if ($command === 'stop') { exit(0); } - // -d 说明是以守护进程的方式启动 - if($command2 == '-d') - { + if ($command2 === '-d') { Worker::$daemonize = true; } break; } break; - // 平滑重启 workerman case 'reload': posix_kill($master_pid, SIGUSR1); self::log("Workerman[$start_file] reload"); exit; - // 未知命令 default : - exit("Usage: php yourfile.php {start|stop|restart|reload|status}\n"); + exit("Usage: php yourfile.php {start|stop|restart|reload|status|kill}\n"); } } - + /** - * 安装信号处理函数 + * Install signal handler. + * * @return void */ protected static function installSignal() { // stop - pcntl_signal(SIGINT, array('\Workerman\Worker', 'signalHandler'), false); + pcntl_signal(SIGINT, array('\Workerman\Worker', 'signalHandler'), false); // reload pcntl_signal(SIGUSR1, array('\Workerman\Worker', 'signalHandler'), false); // status @@ -549,45 +699,46 @@ class Worker // ignore pcntl_signal(SIGPIPE, SIG_IGN, false); } - + /** - * 为子进程重新安装信号处理函数,使用全局事件轮询监听信号 + * Reinstall signal handler. + * * @return void */ protected static function reinstallSignal() { // uninstall stop signal handler - pcntl_signal(SIGINT, SIG_IGN, false); + pcntl_signal(SIGINT, SIG_IGN, false); // uninstall reload signal handler pcntl_signal(SIGUSR1, SIG_IGN, false); // uninstall status signal handler pcntl_signal(SIGUSR2, SIG_IGN, false); // reinstall stop signal handler self::$globalEvent->add(SIGINT, EventInterface::EV_SIGNAL, array('\Workerman\Worker', 'signalHandler')); - // uninstall reload signal handler - self::$globalEvent->add(SIGUSR1, EventInterface::EV_SIGNAL,array('\Workerman\Worker', 'signalHandler')); - // uninstall status signal handler + // reinstall reload signal handler + self::$globalEvent->add(SIGUSR1, EventInterface::EV_SIGNAL, array('\Workerman\Worker', 'signalHandler')); + // reinstall status signal handler self::$globalEvent->add(SIGUSR2, EventInterface::EV_SIGNAL, array('\Workerman\Worker', 'signalHandler')); } - + /** - * 信号处理函数 + * Signal handler. + * * @param int $signal */ public static function signalHandler($signal) { - switch($signal) - { - // stop + switch ($signal) { + // Stop. case SIGINT: self::stopAll(); break; - // reload + // Reload. case SIGUSR1: - self::$_pidsToRestart = self::getAllWorkerPids();; + self::$_pidsToRestart = self::getAllWorkerPids(); self::reload(); break; - // show status + // Show status. case SIGUSR2: self::writeStatisticsToStatusFile(); break; @@ -595,91 +746,96 @@ class Worker } /** - * 尝试以守护进程的方式运行 + * Run as deamon mode. + * * @throws Exception */ protected static function daemonize() { - if(!self::$daemonize) - { + if (!self::$daemonize) { return; } umask(0); $pid = pcntl_fork(); - if(-1 == $pid) - { + if (-1 === $pid) { throw new Exception('fork fail'); - } - elseif($pid > 0) - { + } elseif ($pid > 0) { exit(0); } - if(-1 == posix_setsid()) - { + if (-1 === posix_setsid()) { throw new Exception("setsid fail"); } - // fork again avoid SVR4 system regain the control of terminal + // Fork again avoid SVR4 system regain the control of terminal. $pid = pcntl_fork(); - if(-1 == $pid) - { + if (-1 === $pid) { throw new Exception("fork fail"); - } - elseif(0 !== $pid) - { + } elseif (0 !== $pid) { exit(0); } } /** - * 重定向标准输入输出 + * Redirect standard input and output. + * * @throws Exception */ protected static function resetStd() { - if(!self::$daemonize) - { + if (!self::$daemonize) { return; } global $STDOUT, $STDERR; - $handle = fopen(self::$stdoutFile,"a"); - if($handle) - { + $handle = fopen(self::$stdoutFile, "a"); + if ($handle) { unset($handle); @fclose(STDOUT); @fclose(STDERR); - $STDOUT = fopen(self::$stdoutFile,"a"); - $STDERR = fopen(self::$stdoutFile,"a"); - } - else - { + $STDOUT = fopen(self::$stdoutFile, "a"); + $STDERR = fopen(self::$stdoutFile, "a"); + } else { throw new Exception('can not open stdoutFile ' . self::$stdoutFile); } } - + /** - * 保存pid到文件中,方便运行命令时查找主进程pid + * Save pid. + * * @throws Exception */ protected static function saveMasterPid() { self::$_masterPid = posix_getpid(); - if(false === @file_put_contents(self::$pidFile, self::$_masterPid)) - { + if (false === @file_put_contents(self::$pidFile, self::$_masterPid)) { throw new Exception('can not save pid to ' . self::$pidFile); } } - + /** - * 获得所有子进程的pid + * Get event loop name. + * + * @return string + */ + protected static function getEventLoopName() + { + foreach (self::$_availableEventLoops as $name) { + if (extension_loaded($name)) { + self::$_eventLoopName = $name; + break; + } + } + return self::$_eventLoopName; + } + + /** + * Get all pids of worker processes. + * * @return array */ protected static function getAllWorkerPids() { - $pid_array = array(); - foreach(self::$_pidMap as $worker_pid_array) - { - foreach($worker_pid_array as $worker_pid) - { + $pid_array = array(); + foreach (self::$_pidMap as $worker_pid_array) { + foreach ($worker_pid_array as $worker_pid) { $pid_array[$worker_pid] = $worker_pid; } } @@ -687,363 +843,412 @@ class Worker } /** - * 创建子进程 + * Fork some worker processes. + * * @return void */ protected static function forkWorkers() { - foreach(self::$_workers as $worker) - { - // 启动过程中需要得到运行用户名的最大长度,在status时格式化展示 - if(self::$_status === self::STATUS_STARTING) - { - if(empty($worker->name)) - { + foreach (self::$_workers as $worker) { + if (self::$_status === self::STATUS_STARTING) { + if (empty($worker->name)) { $worker->name = $worker->getSocketName(); } $worker_name_length = strlen($worker->name); - if(self::$_maxWorkerNameLength < $worker_name_length) - { + if (self::$_maxWorkerNameLength < $worker_name_length) { self::$_maxWorkerNameLength = $worker_name_length; } } - - // 创建子进程 - while(count(self::$_pidMap[$worker->workerId]) < $worker->count) - { - self::forkOneWorker($worker); + + while (count(self::$_pidMap[$worker->workerId]) < $worker->count) { + static::forkOneWorker($worker); } } } /** - * 创建一个子进程 + * Fork one worker process. + * * @param Worker $worker * @throws Exception */ protected static function forkOneWorker($worker) { $pid = pcntl_fork(); - // 主进程记录子进程pid - if($pid > 0) - { + // Get available worker id. + $id = self::getId($worker->workerId, 0); + // For master process. + if ($pid > 0) { self::$_pidMap[$worker->workerId][$pid] = $pid; - } - // 子进程运行 - elseif(0 === $pid) - { - self::$_pidMap = array(); + self::$_idMap[$worker->workerId][$id] = $pid; + } // For child processes. + elseif (0 === $pid) { + if ($worker->reusePort) { + $worker->listen(); + } + if (self::$_status === self::STATUS_STARTING) { + self::resetStd(); + } + self::$_pidMap = array(); self::$_workers = array($worker->workerId => $worker); Timer::delAll(); self::setProcessTitle('WorkerMan: worker process ' . $worker->name . ' ' . $worker->getSocketName()); - self::setProcessUser($worker->user); + $worker->setUserAndGroup(); + $worker->id = $id; $worker->run(); exit(250); - } - else - { + } else { throw new Exception("forkOneWorker fail"); } } - + /** - * 尝试设置运行当前进程的用户 + * Get worker id. + * + * @param int $worker_id + * @param int $pid + */ + protected static function getId($worker_id, $pid) + { + $id = array_search($pid, self::$_idMap[$worker_id]); + if ($id === false) { + echo "getId fail\n"; + } + return $id; + } + + /** + * Set unix user and group for current process. + * * @return void */ - protected static function setProcessUser($user_name) + public function setUserAndGroup() { - if(empty($user_name) || posix_getuid() !== 0) - { + // Get uid. + $user_info = posix_getpwnam($this->user); + if (!$user_info) { + self::log("Warning: User {$this->user} not exsits"); return; } - $user_info = posix_getpwnam($user_name); - if($user_info['uid'] != posix_getuid() || $user_info['gid'] != posix_getgid()) - { - if(!posix_setgid($user_info['gid']) || !posix_setuid($user_info['uid'])) - { - self::log( 'Notice : Can not run woker as '.$user_name." , You shuld be root\n", true); + $uid = $user_info['uid']; + // Get gid. + if ($this->group) { + $group_info = posix_getgrnam($this->group); + if (!$group_info) { + self::log("Warning: Group {$this->group} not exsits"); + return; + } + $gid = $group_info['gid']; + } else { + $gid = $user_info['gid']; + } + + // Set uid and gid. + if ($uid != posix_getuid() || $gid != posix_getgid()) { + if (!posix_setgid($gid) || !posix_initgroups($user_info['name'], $gid) || !posix_setuid($uid)) { + self::log("Warning: change gid or uid fail."); } } } - /** - * 设置当前进程的名称,在ps aux命令中有用 - * 注意 需要php>=5.5或者安装了protitle扩展 + * Set process name. + * * @param string $title * @return void */ protected static function setProcessTitle($title) { // >=php 5.5 - if (function_exists('cli_set_process_title')) - { + if (function_exists('cli_set_process_title')) { @cli_set_process_title($title); - } - // 需要扩展 - elseif(extension_loaded('proctitle') && function_exists('setproctitle')) - { + } // Need proctitle when php<=5.5 . + elseif (extension_loaded('proctitle') && function_exists('setproctitle')) { @setproctitle($title); } } - + /** - * 监控所有子进程的退出事件及退出码 + * Monitor all child processes. + * * @return void */ protected static function monitorWorkers() { self::$_status = self::STATUS_RUNNING; - while(1) - { - // 如果有信号到来,尝试触发信号处理函数 + while (1) { + // Calls signal handlers for pending signals. pcntl_signal_dispatch(); - // 挂起进程,直到有子进程退出或者被信号打断 + // Suspends execution of the current process until a child has exited, or until a signal is delivered $status = 0; - $pid = pcntl_wait($status, WUNTRACED); - // 有子进程退出 - if($pid > 0) - { - // 查找是哪个进程组的,然后再启动新的进程补上 - foreach(self::$_pidMap as $worker_id => $worker_pid_array) - { - if(isset($worker_pid_array[$pid])) - { + $pid = pcntl_wait($status, WUNTRACED); + // Calls signal handlers for pending signals again. + pcntl_signal_dispatch(); + // If a child has already exited. + if ($pid > 0) { + // Find out witch worker process exited. + foreach (self::$_pidMap as $worker_id => $worker_pid_array) { + if (isset($worker_pid_array[$pid])) { $worker = self::$_workers[$worker_id]; - // 检查退出状态 - if($status !== 0) - { - self::log("worker[".$worker->name.":$pid] exit with status $status"); + // Exit status. + if ($status !== 0) { + self::log("worker[" . $worker->name . ":$pid] exit with status $status"); } - - // 统计,运行status命令时使用 - if(!isset(self::$_globalStatistics['worker_exit_info'][$worker_id][$status])) - { + + // For Statistics. + if (!isset(self::$_globalStatistics['worker_exit_info'][$worker_id][$status])) { self::$_globalStatistics['worker_exit_info'][$worker_id][$status] = 0; } self::$_globalStatistics['worker_exit_info'][$worker_id][$status]++; - - // 清除子进程信息 + + // Clear process data. unset(self::$_pidMap[$worker_id][$pid]); - + + // Mark id is available. + $id = self::getId($worker_id, $pid); + self::$_idMap[$worker_id][$id] = 0; + break; } } - // 如果不是关闭状态,则补充新的进程 - if(self::$_status !== self::STATUS_SHUTDOWN) - { + // Is still running state then fork a new worker process. + if (self::$_status !== self::STATUS_SHUTDOWN) { self::forkWorkers(); - // 如果该进程是因为运行reload命令退出,则继续执行reload流程 - if(isset(self::$_pidsToRestart[$pid])) - { + // If reloading continue. + if (isset(self::$_pidsToRestart[$pid])) { unset(self::$_pidsToRestart[$pid]); self::reload(); } - } - else - { - // 如果是关闭状态,并且所有进程退出完毕,则主进程退出 - if(!self::getAllWorkerPids()) - { + } else { + // If shutdown state and all child processes exited then master process exit. + if (!self::getAllWorkerPids()) { self::exitAndClearAll(); } } - } - else - { - // 如果是关闭状态,并且所有进程退出完毕,则主进程退出 - if(self::$_status === self::STATUS_SHUTDOWN && !self::getAllWorkerPids()) - { - self::exitAndClearAll(); + } else { + // If shutdown state and all child processes exited then master process exit. + if (self::$_status === self::STATUS_SHUTDOWN && !self::getAllWorkerPids()) { + self::exitAndClearAll(); } } } } - + /** - * 退出当前进程 + * Exit current process. + * * @return void */ protected static function exitAndClearAll() { + foreach (self::$_workers as $worker) { + $socket_name = $worker->getSocketName(); + if ($worker->transport === 'unix' && $socket_name) { + list(, $address) = explode(':', $socket_name, 2); + @unlink($address); + } + } @unlink(self::$pidFile); - self::log("Workerman[".basename(self::$_startFile)."] has been stopped"); + self::log("Workerman[" . basename(self::$_startFile) . "] has been stopped"); exit(0); } - + /** - * 执行平滑重启流程 + * Execute reload. + * * @return void */ protected static function reload() { - // 主进程部分 - if(self::$_masterPid === posix_getpid()) - { - // 设置为平滑重启状态 - if(self::$_status !== self::STATUS_RELOADING && self::$_status !== self::STATUS_SHUTDOWN) - { - self::log("Workerman[".basename(self::$_startFile)."] reloading"); + // For master process. + if (self::$_masterPid === posix_getpid()) { + // Set reloading state. + if (self::$_status !== self::STATUS_RELOADING && self::$_status !== self::STATUS_SHUTDOWN) { + self::log("Workerman[" . basename(self::$_startFile) . "] reloading"); self::$_status = self::STATUS_RELOADING; } - - // 如果有worker设置了reloadable=false,则过滤掉 + + // Send reload signal to all child processes. $reloadable_pid_array = array(); - foreach(self::$_pidMap as $worker_id =>$worker_pid_array) - { + foreach (self::$_pidMap as $worker_id => $worker_pid_array) { $worker = self::$_workers[$worker_id]; - if($worker->reloadable) - { - foreach($worker_pid_array as $pid) - { + if ($worker->reloadable) { + foreach ($worker_pid_array as $pid) { $reloadable_pid_array[$pid] = $pid; } + } else { + foreach ($worker_pid_array as $pid) { + // Send reload signal to a worker process which reloadable is false. + posix_kill($pid, SIGUSR1); + } } } - - // 得到所有可以重启的进程 - self::$_pidsToRestart = array_intersect(self::$_pidsToRestart , $reloadable_pid_array); - - // 平滑重启完毕 - if(empty(self::$_pidsToRestart)) - { - if(self::$_status !== self::STATUS_SHUTDOWN) - { + + // Get all pids that are waiting reload. + self::$_pidsToRestart = array_intersect(self::$_pidsToRestart, $reloadable_pid_array); + + // Reload complete. + if (empty(self::$_pidsToRestart)) { + if (self::$_status !== self::STATUS_SHUTDOWN) { self::$_status = self::STATUS_RUNNING; } return; } - // 继续执行平滑重启流程 - $one_worker_pid = current(self::$_pidsToRestart ); - // 给子进程发送平滑重启信号 + // Continue reload. + $one_worker_pid = current(self::$_pidsToRestart); + // Send reload signal to a worker process. posix_kill($one_worker_pid, SIGUSR1); - // 定时器,如果子进程在KILL_WORKER_TIMER_TIME秒后没有退出,则强行杀死 + // If the process does not exit after self::KILL_WORKER_TIMER_TIME seconds try to kill it. Timer::add(self::KILL_WORKER_TIMER_TIME, 'posix_kill', array($one_worker_pid, SIGKILL), false); - } - // 子进程部分 - else - { - // 如果当前worker的reloadable属性为真,则执行退出 + } // For child processes. + else { $worker = current(self::$_workers); - if($worker->reloadable) - { + // Try to emit onWorkerReload callback. + if ($worker->onWorkerReload) { + try { + call_user_func($worker->onWorkerReload, $worker); + } catch (\Exception $e) { + self::log($e); + exit(250); + } catch (\Error $e) { + self::log($e); + exit(250); + } + } + + if ($worker->reloadable) { self::stopAll(); } } - } - + } + /** - * 执行关闭流程 + * Stop. + * * @return void */ public static function stopAll() { self::$_status = self::STATUS_SHUTDOWN; - // 主进程部分 - if(self::$_masterPid === posix_getpid()) - { - self::log("Workerman[".basename(self::$_startFile)."] Stopping ..."); + // For master process. + if (self::$_masterPid === posix_getpid()) { + self::log("Workerman[" . basename(self::$_startFile) . "] Stopping ..."); $worker_pid_array = self::getAllWorkerPids(); - // 向所有子进程发送SIGINT信号,表明关闭服务 - foreach($worker_pid_array as $worker_pid) - { + // Send stop signal to all child processes. + foreach ($worker_pid_array as $worker_pid) { posix_kill($worker_pid, SIGINT); - Timer::add(self::KILL_WORKER_TIMER_TIME, 'posix_kill', array($worker_pid, SIGKILL),false); + Timer::add(self::KILL_WORKER_TIMER_TIME, 'posix_kill', array($worker_pid, SIGKILL), false); } - } - // 子进程部分 - else - { - // 执行stop逻辑 - foreach(self::$_workers as $worker) - { + } // For child processes. + else { + // Execute exit. + foreach (self::$_workers as $worker) { $worker->stop(); } exit(0); } } - + /** - * 将当前进程的统计信息写入到统计文件 + * Write statistics data to disk. + * * @return void */ protected static function writeStatisticsToStatusFile() { - // 主进程部分 - if(self::$_masterPid === posix_getpid()) - { + // For master process. + if (self::$_masterPid === posix_getpid()) { $loadavg = sys_getloadavg(); - file_put_contents(self::$_statisticsFile, "---------------------------------------GLOBAL STATUS--------------------------------------------\n"); - file_put_contents(self::$_statisticsFile, 'Workerman version:' . Worker::VERSION . " PHP version:".PHP_VERSION."\n", FILE_APPEND); - file_put_contents(self::$_statisticsFile, 'start time:'. date('Y-m-d H:i:s', self::$_globalStatistics['start_timestamp']).' run ' . floor((time()-self::$_globalStatistics['start_timestamp'])/(24*60*60)). ' days ' . floor(((time()-self::$_globalStatistics['start_timestamp'])%(24*60*60))/(60*60)) . " hours \n", FILE_APPEND); - file_put_contents(self::$_statisticsFile, 'load average: ' . implode(", ", $loadavg) . "\n", FILE_APPEND); - file_put_contents(self::$_statisticsFile, count(self::$_pidMap) . ' workers ' . count(self::getAllWorkerPids())." processes\n", FILE_APPEND); - file_put_contents(self::$_statisticsFile, str_pad('worker_name', self::$_maxWorkerNameLength) . " exit_status exit_count\n", FILE_APPEND); - foreach(self::$_pidMap as $worker_id =>$worker_pid_array) - { + file_put_contents(self::$_statisticsFile, + "---------------------------------------GLOBAL STATUS--------------------------------------------\n"); + file_put_contents(self::$_statisticsFile, + 'Workerman version:' . Worker::VERSION . " PHP version:" . PHP_VERSION . "\n", FILE_APPEND); + file_put_contents(self::$_statisticsFile, 'start time:' . date('Y-m-d H:i:s', + self::$_globalStatistics['start_timestamp']) . ' run ' . floor((time() - self::$_globalStatistics['start_timestamp']) / (24 * 60 * 60)) . ' days ' . floor(((time() - self::$_globalStatistics['start_timestamp']) % (24 * 60 * 60)) / (60 * 60)) . " hours \n", + FILE_APPEND); + $load_str = 'load average: ' . implode(", ", $loadavg); + file_put_contents(self::$_statisticsFile, + str_pad($load_str, 33) . 'event-loop:' . self::getEventLoopName() . "\n", FILE_APPEND); + file_put_contents(self::$_statisticsFile, + count(self::$_pidMap) . ' workers ' . count(self::getAllWorkerPids()) . " processes\n", + FILE_APPEND); + file_put_contents(self::$_statisticsFile, + str_pad('worker_name', self::$_maxWorkerNameLength) . " exit_status exit_count\n", FILE_APPEND); + foreach (self::$_pidMap as $worker_id => $worker_pid_array) { $worker = self::$_workers[$worker_id]; - if(isset(self::$_globalStatistics['worker_exit_info'][$worker_id])) - { - foreach(self::$_globalStatistics['worker_exit_info'][$worker_id] as $worker_exit_status=>$worker_exit_count) - { - file_put_contents(self::$_statisticsFile, str_pad($worker->name, self::$_maxWorkerNameLength) . " " . str_pad($worker_exit_status, 16). " $worker_exit_count\n", FILE_APPEND); + if (isset(self::$_globalStatistics['worker_exit_info'][$worker_id])) { + foreach (self::$_globalStatistics['worker_exit_info'][$worker_id] as $worker_exit_status => $worker_exit_count) { + file_put_contents(self::$_statisticsFile, + str_pad($worker->name, self::$_maxWorkerNameLength) . " " . str_pad($worker_exit_status, + 16) . " $worker_exit_count\n", FILE_APPEND); } - } - else - { - file_put_contents(self::$_statisticsFile, str_pad($worker->name, self::$_maxWorkerNameLength) . " " . str_pad(0, 16). " 0\n", FILE_APPEND); + } else { + file_put_contents(self::$_statisticsFile, + str_pad($worker->name, self::$_maxWorkerNameLength) . " " . str_pad(0, 16) . " 0\n", + FILE_APPEND); } } - file_put_contents(self::$_statisticsFile, "---------------------------------------PROCESS STATUS-------------------------------------------\n", FILE_APPEND); - file_put_contents(self::$_statisticsFile, "pid\tmemory ".str_pad('listening', self::$_maxSocketNameLength)." ".str_pad('worker_name', self::$_maxWorkerNameLength)." connections ".str_pad('total_request', 13)." ".str_pad('send_fail', 9)." ".str_pad('throw_exception', 15)."\n", FILE_APPEND); - + file_put_contents(self::$_statisticsFile, + "---------------------------------------PROCESS STATUS-------------------------------------------\n", + FILE_APPEND); + file_put_contents(self::$_statisticsFile, + "pid\tmemory " . str_pad('listening', self::$_maxSocketNameLength) . " " . str_pad('worker_name', + self::$_maxWorkerNameLength) . " connections " . str_pad('total_request', + 13) . " " . str_pad('send_fail', 9) . " " . str_pad('throw_exception', 15) . "\n", FILE_APPEND); + chmod(self::$_statisticsFile, 0722); - - foreach(self::getAllWorkerPids() as $worker_pid) - { + + foreach (self::getAllWorkerPids() as $worker_pid) { posix_kill($worker_pid, SIGUSR2); } return; } - - // 子进程部分 - $worker = current(self::$_workers); - $wrker_status_str = posix_getpid()."\t".str_pad(round(memory_get_usage()/(1024*1024),2)."M", 7)." " .str_pad($worker->getSocketName(), self::$_maxSocketNameLength) ." ".str_pad(($worker->name == $worker->getSocketName() ? 'none' : $worker->name), self::$_maxWorkerNameLength)." "; - $wrker_status_str .= str_pad(ConnectionInterface::$statistics['connection_count'], 11)." ".str_pad(ConnectionInterface::$statistics['total_request'], 14)." ".str_pad(ConnectionInterface::$statistics['send_fail'],9)." ".str_pad(ConnectionInterface::$statistics['throw_exception'],15)."\n"; - file_put_contents(self::$_statisticsFile, $wrker_status_str, FILE_APPEND); + + // For child processes. + /** @var Worker $worker */ + $worker = current(self::$_workers); + $worker_status_str = posix_getpid() . "\t" . str_pad(round(memory_get_usage(true) / (1024 * 1024), 2) . "M", + 7) . " " . str_pad($worker->getSocketName(), + self::$_maxSocketNameLength) . " " . str_pad(($worker->name === $worker->getSocketName() ? 'none' : $worker->name), + self::$_maxWorkerNameLength) . " "; + $worker_status_str .= str_pad(ConnectionInterface::$statistics['connection_count'], + 11) . " " . str_pad(ConnectionInterface::$statistics['total_request'], + 14) . " " . str_pad(ConnectionInterface::$statistics['send_fail'], + 9) . " " . str_pad(ConnectionInterface::$statistics['throw_exception'], 15) . "\n"; + file_put_contents(self::$_statisticsFile, $worker_status_str, FILE_APPEND); } - + /** - * 检查错误 + * Check errors when current process exited. + * * @return void */ public static function checkErrors() { - if(self::STATUS_SHUTDOWN != self::$_status) - { + if (self::STATUS_SHUTDOWN != self::$_status) { $error_msg = "WORKER EXIT UNEXPECTED "; - $errors = error_get_last(); - if($errors && ($errors['type'] == E_ERROR || - $errors['type'] == E_PARSE || - $errors['type'] == E_CORE_ERROR || - $errors['type'] == E_COMPILE_ERROR || - $errors['type'] == E_RECOVERABLE_ERROR )) - { + $errors = error_get_last(); + if ($errors && ($errors['type'] === E_ERROR || + $errors['type'] === E_PARSE || + $errors['type'] === E_CORE_ERROR || + $errors['type'] === E_COMPILE_ERROR || + $errors['type'] === E_RECOVERABLE_ERROR) + ) { $error_msg .= self::getErrorType($errors['type']) . " {$errors['message']} in {$errors['file']} on line {$errors['line']}"; } self::log($error_msg); } } - + /** - * 获取错误类型对应的意义 + * Get error message by error code. + * * @param integer $type * @return string */ protected static function getErrorType($type) { - switch($type) - { + switch ($type) { case E_ERROR: // 1 // return 'E_ERROR'; case E_WARNING: // 2 // @@ -1077,261 +1282,289 @@ class Worker } return ""; } - + /** - * 记录日志 + * Log. + * * @param string $msg * @return void */ - protected static function log($msg) + public static function log($msg) { - $msg = $msg."\n"; - if(self::$_status === self::STATUS_STARTING || !self::$daemonize) - { + $msg = $msg . "\n"; + if (!self::$daemonize) { echo $msg; } - file_put_contents(self::$logFile, date('Y-m-d H:i:s') . " " . $msg, FILE_APPEND | LOCK_EX); + file_put_contents(self::$logFile, date('Y-m-d H:i:s') . ' ' . 'pid:'. posix_getpid() . ' ' . $msg, FILE_APPEND | LOCK_EX); } - + /** - * worker构造函数 + * Construct. + * * @param string $socket_name - * @return void + * @param array $context_option */ public function __construct($socket_name = '', $context_option = array()) { - // 保存worker实例 - $this->workerId = spl_object_hash($this); + // Save all worker instances. + $this->workerId = spl_object_hash($this); self::$_workers[$this->workerId] = $this; - self::$_pidMap[$this->workerId] = array(); - - // 获得实例化文件路径,用于自动加载设置根目录 - $backrace = debug_backtrace(); - $this->_appInitPath = dirname($backrace[0]['file']); - - // 设置socket上下文 - if($socket_name) - { + self::$_pidMap[$this->workerId] = array(); + + // Get autoload root path. + $backtrace = debug_backtrace(); + $this->_autoloadRootPath = dirname($backtrace[0]['file']); + + // Context for socket. + if ($socket_name) { $this->_socketName = $socket_name; - if(!isset($context_option['socket']['backlog'])) - { - $context_option['socket']['backlog'] = self::DEFAUL_BACKLOG; + if (!isset($context_option['socket']['backlog'])) { + $context_option['socket']['backlog'] = self::DEFAULT_BACKLOG; } $this->_context = stream_context_create($context_option); } + + // Set an empty onMessage callback. + $this->onMessage = function () { + }; } - + /** - * 监听端口 + * Listen port. + * * @throws Exception */ public function listen() { - // 设置自动加载根目录 - Autoloader::setRootPath($this->_appInitPath); - - if(!$this->_socketName) - { + if (!$this->_socketName || $this->_mainSocket) { return; } - // 获得应用层通讯协议以及监听的地址 + + // Autoload. + Autoloader::setRootPath($this->_autoloadRootPath); + + $local_socket = $this->_socketName; + // Get the application layer communication protocol and listening address. list($scheme, $address) = explode(':', $this->_socketName, 2); - // 如果有指定应用层协议,则检查对应的协议类是否存在 - if($scheme != 'tcp' && $scheme != 'udp') - { - $scheme = ucfirst($scheme); - $this->_protocol = '\\Protocols\\'.$scheme; - if(!class_exists($this->_protocol)) - { - $this->_protocol = "\\Workerman\\Protocols\\$scheme"; - if(!class_exists($this->_protocol)) - { + // Check application layer protocol class. + if (!isset(self::$_builtinTransports[$scheme])) { + $scheme = ucfirst($scheme); + $this->protocol = '\\Protocols\\' . $scheme; + if (!class_exists($this->protocol)) { + $this->protocol = "\\Workerman\\Protocols\\$scheme"; + if (!class_exists($this->protocol)) { throw new Exception("class \\Protocols\\$scheme not exist"); } } + $local_socket = $this->transport . ":" . $address; + } else { + $this->transport = self::$_builtinTransports[$scheme]; } - elseif($scheme === 'udp') - { - $this->transport = 'udp'; + + // Flag. + $flags = $this->transport === 'udp' ? STREAM_SERVER_BIND : STREAM_SERVER_BIND | STREAM_SERVER_LISTEN; + $errno = 0; + $errmsg = ''; + // SO_REUSEPORT. + if ($this->reusePort) { + stream_context_set_option($this->_context, 'socket', 'so_reuseport', 1); } - - // flag - $flags = $this->transport === 'udp' ? STREAM_SERVER_BIND : STREAM_SERVER_BIND | STREAM_SERVER_LISTEN; - $this->_mainSocket = stream_socket_server($this->transport.":".$address, $errno, $errmsg, $flags, $this->_context); - if(!$this->_mainSocket) - { + if ($this->transport === 'unix') { + umask(0); + list(, $address) = explode(':', $this->_socketName, 2); + if (!is_file($address)) { + register_shutdown_function(function () use ($address) { + @unlink($address); + }); + } + } + // Create an Internet or Unix domain server socket. + $this->_mainSocket = stream_socket_server($local_socket, $errno, $errmsg, $flags, $this->_context); + if (!$this->_mainSocket) { throw new Exception($errmsg); } - - // 尝试打开tcp的keepalive,关闭TCP Nagle算法 - if(function_exists('socket_import_stream')) - { - $socket = socket_import_stream($this->_mainSocket ); + + // Try to open keepalive for tcp and disable Nagle algorithm. + if (function_exists('socket_import_stream') && $this->transport === 'tcp') { + $socket = socket_import_stream($this->_mainSocket); @socket_set_option($socket, SOL_SOCKET, SO_KEEPALIVE, 1); - @socket_set_option($socket, SOL_SOCKET, TCP_NODELAY, 1); + @socket_set_option($socket, SOL_TCP, TCP_NODELAY, 1); } - - // 设置非阻塞 + + // Non blocking. stream_set_blocking($this->_mainSocket, 0); - - // 放到全局事件轮询中监听_mainSocket可读事件(客户端连接事件) - if(self::$globalEvent) - { - if($this->transport !== 'udp') - { + + // Register a listener to be notified when server socket is ready to read. + if (self::$globalEvent) { + if ($this->transport !== 'udp') { self::$globalEvent->add($this->_mainSocket, EventInterface::EV_READ, array($this, 'acceptConnection')); - } - else - { - self::$globalEvent->add($this->_mainSocket, EventInterface::EV_READ, array($this, 'acceptUdpConnection')); + } else { + self::$globalEvent->add($this->_mainSocket, EventInterface::EV_READ, + array($this, 'acceptUdpConnection')); } } } - + /** - * 获得 socket name + * Get socket name. + * * @return string */ public function getSocketName() { - return $this->_socketName ? $this->_socketName : 'none'; + return $this->_socketName ? lcfirst($this->_socketName) : 'none'; } - + /** - * 运行worker实例 + * Run worker instance. + * + * @return void */ public function run() { - // 注册进程退出回调,用来检查是否有错误 + //Update process state. + self::$_status = self::STATUS_RUNNING; + + // Register shutdown function for checking errors. register_shutdown_function(array("\\Workerman\\Worker", 'checkErrors')); - - // 设置自动加载根目录 - Autoloader::setRootPath($this->_appInitPath); - - // 如果没有全局事件轮询,则创建一个 - if(!self::$globalEvent) - { - if(extension_loaded('libevent')) - { - self::$globalEvent = new Libevent(); - } - else - { - self::$globalEvent = new Select(); - } - // 监听_mainSocket上的可读事件(客户端连接事件) - if($this->_socketName) - { - if($this->transport !== 'udp') - { - self::$globalEvent->add($this->_mainSocket, EventInterface::EV_READ, array($this, 'acceptConnection')); - } - else - { - self::$globalEvent->add($this->_mainSocket, EventInterface::EV_READ, array($this, 'acceptUdpConnection')); + + // Set autoload root path. + Autoloader::setRootPath($this->_autoloadRootPath); + + // Create a global event loop. + if (!self::$globalEvent) { + $eventLoopClass = "\\Workerman\\Events\\" . ucfirst(self::getEventLoopName()); + self::$globalEvent = new $eventLoopClass; + // Register a listener to be notified when server socket is ready to read. + if ($this->_socketName) { + if ($this->transport !== 'udp') { + self::$globalEvent->add($this->_mainSocket, EventInterface::EV_READ, + array($this, 'acceptConnection')); + } else { + self::$globalEvent->add($this->_mainSocket, EventInterface::EV_READ, + array($this, 'acceptUdpConnection')); } } } - - // 重新安装事件处理函数,使用全局事件轮询监听信号事件 + + // Reinstall signal. self::reinstallSignal(); - - // 用全局事件轮询初始化定时器 + + // Init Timer. Timer::init(self::$globalEvent); - - // 如果有设置进程启动回调,则执行 - if($this->onWorkerStart) - { - call_user_func($this->onWorkerStart, $this); + + // Try to emit onWorkerStart callback. + if ($this->onWorkerStart) { + try { + call_user_func($this->onWorkerStart, $this); + } catch (\Exception $e) { + self::log($e); + exit(250); + } catch (\Error $e) { + self::log($e); + exit(250); + } } - - // 子进程主循环 + + // Main loop. self::$globalEvent->loop(); } - + /** - * 停止当前worker实例 + * Stop current worker instance. + * * @return void */ public function stop() { - // 如果有设置进程终止回调,则执行 - if($this->onWorkerStop) - { - call_user_func($this->onWorkerStop, $this); + // Try to emit onWorkerStop callback. + if ($this->onWorkerStop) { + try { + call_user_func($this->onWorkerStop, $this); + } catch (\Exception $e) { + self::log($e); + exit(250); + } catch (\Error $e) { + self::log($e); + exit(250); + } } - // 删除相关监听事件,关闭_mainSocket + // Remove listener for server socket. self::$globalEvent->del($this->_mainSocket, EventInterface::EV_READ); @fclose($this->_mainSocket); } /** - * 接收一个客户端连接 - * @param resources $socket + * Accept a connection. + * + * @param resource $socket * @return void */ public function acceptConnection($socket) { - // 获得客户端连接 - $new_socket = @stream_socket_accept($socket, 0); - // 惊群现象,忽略 - if(false === $new_socket) - { + // Accept a connection on server socket. + $new_socket = @stream_socket_accept($socket, 0, $remote_address); + // Thundering herd. + if (!$new_socket) { return; } - // 统计数据 - ConnectionInterface::$statistics['connection_count']++; - // 初始化连接对象 - $connection = new TcpConnection($new_socket); - $connection->worker = $this; - $connection->protocol = $this->_protocol; - $connection->onMessage = $this->onMessage; - $connection->onClose = $this->onClose; - $connection->onError = $this->onError; - $connection->onBufferDrain = $this->onBufferDrain; - $connection->onBufferFull = $this->onBufferFull; - $this->connections[(int)$new_socket] = $connection; - - // 如果有设置连接回调,则执行 - if($this->onConnect) - { - try - { + + // TcpConnection. + $connection = new TcpConnection($new_socket, $remote_address); + $this->connections[$connection->id] = $connection; + $connection->worker = $this; + $connection->protocol = $this->protocol; + $connection->onMessage = $this->onMessage; + $connection->onClose = $this->onClose; + $connection->onError = $this->onError; + $connection->onBufferDrain = $this->onBufferDrain; + $connection->onBufferFull = $this->onBufferFull; + + // Try to emit onConnect callback. + if ($this->onConnect) { + try { call_user_func($this->onConnect, $connection); - } - catch(Exception $e) - { - ConnectionInterface::$statistics['throw_exception']++; + } catch (\Exception $e) { self::log($e); + exit(250); + } catch (\Error $e) { + self::log($e); + exit(250); } } } - + /** - * 处理udp连接(udp其实是无连接的,这里为保证和tcp连接接口一致) + * For udp package. + * * @param resource $socket + * @return bool */ public function acceptUdpConnection($socket) { - $recv_buffer = stream_socket_recvfrom($socket , self::MAX_UDP_PACKEG_SIZE, 0, $remote_address); - if(false === $recv_buffer || empty($remote_address)) - { + $recv_buffer = stream_socket_recvfrom($socket, self::MAX_UDP_PACKAGE_SIZE, 0, $remote_address); + if (false === $recv_buffer || empty($remote_address)) { return false; } - // 模拟一个连接对象 - $connection = new UdpConnection($socket, $remote_address); - if($this->onMessage) - { - $parser = $this->_protocol; - ConnectionInterface::$statistics['total_request']++; - try - { - call_user_func($this->onMessage, $connection, $parser::decode($recv_buffer, $connection)); + // UdpConnection. + $connection = new UdpConnection($socket, $remote_address); + $connection->protocol = $this->protocol; + if ($this->onMessage) { + if ($this->protocol) { + $parser = $this->protocol; + $recv_buffer = $parser::decode($recv_buffer, $connection); } - catch(Exception $e) - { - ConnectionInterface::$statistics['throw_exception']++; + ConnectionInterface::$statistics['total_request']++; + try { + call_user_func($this->onMessage, $connection, $recv_buffer); + } catch (\Exception $e) { + self::log($e); + exit(250); + } catch (\Error $e) { + self::log($e); + exit(250); } } + return true; } } diff --git a/Workerman/composer.json b/Workerman/composer.json new file mode 100644 index 0000000..290db99 --- /dev/null +++ b/Workerman/composer.json @@ -0,0 +1,33 @@ +{ + "name" : "workerman/workerman", + "type" : "project", + "keywords": ["event-loop", "asynchronous"], + "homepage": "http://www.workerman.net", + "license" : "MIT", + "description": "An asynchronous event driven PHP framework for easily building fast, scalable network applications.", + "authors" : [ + { + "name" : "walkor", + "email" : "walkor@workerman.net", + "homepage" : "http://www.workerman.net", + "role": "Developer" + } + ], + "support" : { + "email" : "walkor@workerman.net", + "issues": "https://github.com/walkor/workerman/issues", + "forum" : "http://wenda.workerman.net/", + "wiki" : "http://doc3.workerman.net/index.html", + "source": "https://github.com/walkor/workerman" + }, + "require": { + "php": ">=5.3" + }, + "suggest": { + "ext-libevent": "For better performance." + }, + "autoload": { + "psr-4": {"Workerman\\": "./"} + }, + "minimum-stability":"dev" +}