├── LICENSE ├── README.md ├── request.php ├── subscribe.php └── workerman ├── Autoloader.php ├── Connection ├── AsyncTcpConnection.php ├── AsyncUdpConnection.php ├── ConnectionInterface.php ├── TcpConnection.php └── UdpConnection.php ├── Events ├── Ev.php ├── Event.php ├── EventInterface.php ├── Libevent.php ├── React │ ├── ExtEventLoop.php │ ├── LibEventLoop.php │ └── StreamSelectLoop.php └── Select.php ├── Lib ├── Constants.php └── Timer.php ├── Protocols ├── Frame.php ├── Http.php ├── Http │ └── mime.types ├── ProtocolInterface.php ├── Text.php ├── Websocket.php └── Ws.php ├── WebServer.php ├── Worker.php └── composer.json /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Huobi-API 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 环境要求 2 | (php>=5.3.3) 3 | (workerman框架 demo中带的仅供测试) 4 | 5 | ## 运行 6 | 下载该demo 7 | 8 | ### 1.订阅 9 | ```php 10 | onWorkerStart = function($worker) { 18 | // ssl需要访问443端口 19 | $con = new AsyncTcpConnection('ws://api.huobi.pro:443/ws'); 20 | 21 | // 设置以ssl加密方式访问,使之成为wss 22 | $con->transport = 'ssl'; 23 | 24 | $con->onConnect = function($con) { 25 | $data = json_encode([ 26 | 'req' => $GLOBALS['req_str'], 27 | 'id' => 'id' . time() 28 | ]); 29 | $con->send($data); 30 | }; 31 | 32 | $con->onMessage = function($con, $data) { 33 | $data = gzdecode($data); 34 | $data = json_decode($data, true); 35 | if(isset($data['ping'])) { 36 | $con->send(json_encode([ 37 | "pong" => $data['ping'] 38 | ])); 39 | }else{ 40 | call_user_func_array($GLOBALS['callback'], array($data)); 41 | } 42 | }; 43 | 44 | $con->connect(); 45 | }; 46 | 47 | Worker::runAll(); 48 | } 49 | -------------------------------------------------------------------------------- /subscribe.php: -------------------------------------------------------------------------------- 1 | onWorkerStart = function($worker) { 18 | // ssl需要访问443端口 19 | $con = new AsyncTcpConnection('ws://api.huobi.pro:443/ws'); 20 | 21 | // 设置以ssl加密方式访问,使之成为wss 22 | $con->transport = 'ssl'; 23 | 24 | $con->onConnect = function($con) { 25 | $data = json_encode([ 26 | 'sub' => $GLOBALS['sub_str'], 27 | 'id' => 'depth' . time() 28 | ]); 29 | $con->send($data); 30 | }; 31 | 32 | $con->onMessage = function($con, $data) { 33 | $data = gzdecode($data); 34 | $data = json_decode($data, true); 35 | if(isset($data['ping'])) { 36 | $con->send(json_encode([ 37 | "pong" => $data['ping'] 38 | ])); 39 | }else{ 40 | call_user_func_array($GLOBALS['callback'], array($data)); 41 | } 42 | }; 43 | 44 | $con->connect(); 45 | }; 46 | 47 | Worker::runAll(); 48 | } 49 | -------------------------------------------------------------------------------- /workerman/Autoloader.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | namespace Workerman; 15 | 16 | // 包含常量定义文件 17 | require_once __DIR__.'/Lib/Constants.php'; 18 | 19 | /** 20 | * 自动加载类 21 | * @author walkor 22 | */ 23 | class Autoloader 24 | { 25 | // 应用的初始化目录,作为加载类文件的参考目录 26 | protected static $_appInitPath = ''; 27 | 28 | /** 29 | * 设置应用初始化目录 30 | * @param string $root_path 31 | * @return void 32 | */ 33 | public static function setRootPath($root_path) 34 | { 35 | self::$_appInitPath = $root_path; 36 | } 37 | 38 | /** 39 | * 根据命名空间加载文件 40 | * @param string $name 41 | * @return boolean 42 | */ 43 | public static function loadByNamespace($name) 44 | { 45 | // 相对路径 46 | $class_path = str_replace('\\', DIRECTORY_SEPARATOR ,$name); 47 | // 如果是Workerman命名空间,则在当前目录寻找类文件 48 | if(strpos($name, 'Workerman\\') === 0) 49 | { 50 | $class_file = __DIR__.substr($class_path, strlen('Workerman')).'.php'; 51 | } 52 | else 53 | { 54 | // 先尝试在应用目录寻找文件 55 | if(self::$_appInitPath) 56 | { 57 | $class_file = self::$_appInitPath . DIRECTORY_SEPARATOR . $class_path.'.php'; 58 | } 59 | // 文件不存在,则在上一层目录寻找 60 | if(empty($class_file) || !is_file($class_file)) 61 | { 62 | $class_file = __DIR__.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR . "$class_path.php"; 63 | } 64 | } 65 | 66 | // 找到文件 67 | if(is_file($class_file)) 68 | { 69 | // 加载 70 | require_once($class_file); 71 | if(class_exists($name, false)) 72 | { 73 | return true; 74 | } 75 | } 76 | return false; 77 | } 78 | } 79 | // 设置类自动加载回调函数 80 | spl_autoload_register('\Workerman\Autoloader::loadByNamespace'); -------------------------------------------------------------------------------- /workerman/Connection/AsyncTcpConnection.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | namespace Workerman\Connection; 15 | 16 | use Workerman\Events\EventInterface; 17 | use Workerman\Lib\Timer; 18 | use Workerman\Worker; 19 | use Exception; 20 | 21 | /** 22 | * AsyncTcpConnection. 23 | */ 24 | class AsyncTcpConnection extends TcpConnection 25 | { 26 | /** 27 | * Emitted when socket connection is successfully established. 28 | * 29 | * @var callback 30 | */ 31 | public $onConnect = null; 32 | 33 | /** 34 | * Transport layer protocol. 35 | * 36 | * @var string 37 | */ 38 | public $transport = 'tcp'; 39 | 40 | /** 41 | * Status. 42 | * 43 | * @var int 44 | */ 45 | protected $_status = self::STATUS_INITIAL; 46 | 47 | /** 48 | * Remote host. 49 | * 50 | * @var string 51 | */ 52 | protected $_remoteHost = ''; 53 | 54 | /** 55 | * Remote port. 56 | * 57 | * @var int 58 | */ 59 | protected $_remotePort = 80; 60 | 61 | /** 62 | * Connect start time. 63 | * 64 | * @var string 65 | */ 66 | protected $_connectStartTime = 0; 67 | 68 | /** 69 | * Remote URI. 70 | * 71 | * @var string 72 | */ 73 | protected $_remoteURI = ''; 74 | 75 | /** 76 | * Context option. 77 | * 78 | * @var resource 79 | */ 80 | protected $_contextOption = null; 81 | 82 | /** 83 | * Reconnect timer. 84 | * 85 | * @var int 86 | */ 87 | protected $_reconnectTimer = null; 88 | 89 | 90 | /** 91 | * PHP built-in protocols. 92 | * 93 | * @var array 94 | */ 95 | protected static $_builtinTransports = array( 96 | 'tcp' => 'tcp', 97 | 'udp' => 'udp', 98 | 'unix' => 'unix', 99 | 'ssl' => 'ssl', 100 | 'sslv2' => 'sslv2', 101 | 'sslv3' => 'sslv3', 102 | 'tls' => 'tls' 103 | ); 104 | 105 | /** 106 | * Construct. 107 | * 108 | * @param string $remote_address 109 | * @param array $context_option 110 | * @throws Exception 111 | */ 112 | public function __construct($remote_address, $context_option = null) 113 | { 114 | $address_info = parse_url($remote_address); 115 | if (!$address_info) { 116 | list($scheme, $this->_remoteAddress) = explode(':', $remote_address, 2); 117 | if (!$this->_remoteAddress) { 118 | echo new \Exception('bad remote_address'); 119 | } 120 | } else { 121 | if (!isset($address_info['port'])) { 122 | $address_info['port'] = 80; 123 | } 124 | if (!isset($address_info['path'])) { 125 | $address_info['path'] = '/'; 126 | } 127 | if (!isset($address_info['query'])) { 128 | $address_info['query'] = ''; 129 | } else { 130 | $address_info['query'] = '?' . $address_info['query']; 131 | } 132 | $this->_remoteAddress = "{$address_info['host']}:{$address_info['port']}"; 133 | $this->_remoteHost = $address_info['host']; 134 | $this->_remotePort = $address_info['port']; 135 | $this->_remoteURI = "{$address_info['path']}{$address_info['query']}"; 136 | $scheme = isset($address_info['scheme']) ? $address_info['scheme'] : 'tcp'; 137 | } 138 | 139 | $this->id = $this->_id = self::$_idRecorder++; 140 | // Check application layer protocol class. 141 | if (!isset(self::$_builtinTransports[$scheme])) { 142 | $scheme = ucfirst($scheme); 143 | $this->protocol = '\\Protocols\\' . $scheme; 144 | if (!class_exists($this->protocol)) { 145 | $this->protocol = "\\Workerman\\Protocols\\$scheme"; 146 | if (!class_exists($this->protocol)) { 147 | throw new Exception("class \\Protocols\\$scheme not exist"); 148 | } 149 | } 150 | } else { 151 | $this->transport = self::$_builtinTransports[$scheme]; 152 | } 153 | 154 | // For statistics. 155 | self::$statistics['connection_count']++; 156 | $this->maxSendBufferSize = self::$defaultMaxSendBufferSize; 157 | $this->_contextOption = $context_option; 158 | static::$connections[$this->id] = $this; 159 | } 160 | 161 | /** 162 | * Do connect. 163 | * 164 | * @return void 165 | */ 166 | public function connect() 167 | { 168 | if ($this->_status !== self::STATUS_INITIAL && $this->_status !== self::STATUS_CLOSING && 169 | $this->_status !== self::STATUS_CLOSED) { 170 | return; 171 | } 172 | $this->_status = self::STATUS_CONNECTING; 173 | $this->_connectStartTime = microtime(true); 174 | // Open socket connection asynchronously. 175 | if ($this->_contextOption) { 176 | $context = stream_context_create($this->_contextOption); 177 | $this->_socket = stream_socket_client("{$this->transport}://{$this->_remoteHost}:{$this->_remotePort}", $errno, $errstr, 0, 178 | STREAM_CLIENT_ASYNC_CONNECT, $context); 179 | } else { 180 | $this->_socket = stream_socket_client("{$this->transport}://{$this->_remoteHost}:{$this->_remotePort}", $errno, $errstr, 0, 181 | STREAM_CLIENT_ASYNC_CONNECT); 182 | } 183 | // If failed attempt to emit onError callback. 184 | if (!$this->_socket) { 185 | $this->emitError(WORKERMAN_CONNECT_FAIL, $errstr); 186 | if ($this->_status === self::STATUS_CLOSING) { 187 | $this->destroy(); 188 | } 189 | if ($this->_status === self::STATUS_CLOSED) { 190 | $this->onConnect = null; 191 | } 192 | return; 193 | } 194 | // Add socket to global event loop waiting connection is successfully established or faild. 195 | Worker::$globalEvent->add($this->_socket, EventInterface::EV_WRITE, array($this, 'checkConnection')); 196 | // For windows. 197 | if(DIRECTORY_SEPARATOR === '\\') { 198 | Worker::$globalEvent->add($this->_socket, EventInterface::EV_EXCEPT, array($this, 'checkConnection')); 199 | } 200 | } 201 | 202 | /** 203 | * Reconnect. 204 | * 205 | * @param int $after 206 | * @return void 207 | */ 208 | public function reConnect($after = 0) { 209 | $this->_status = self::STATUS_INITIAL; 210 | if ($this->_reconnectTimer) { 211 | Timer::del($this->_reconnectTimer); 212 | } 213 | if ($after > 0) { 214 | $this->_reconnectTimer = Timer::add($after, array($this, 'connect'), null, false); 215 | return; 216 | } 217 | $this->connect(); 218 | } 219 | 220 | /** 221 | * Get remote address. 222 | * 223 | * @return string 224 | */ 225 | public function getRemoteHost() 226 | { 227 | return $this->_remoteHost; 228 | } 229 | 230 | /** 231 | * Get remote URI. 232 | * 233 | * @return string 234 | */ 235 | public function getRemoteURI() 236 | { 237 | return $this->_remoteURI; 238 | } 239 | 240 | /** 241 | * Try to emit onError callback. 242 | * 243 | * @param int $code 244 | * @param string $msg 245 | * @return void 246 | */ 247 | protected function emitError($code, $msg) 248 | { 249 | $this->_status = self::STATUS_CLOSING; 250 | if ($this->onError) { 251 | try { 252 | call_user_func($this->onError, $this, $code, $msg); 253 | } catch (\Exception $e) { 254 | Worker::log($e); 255 | exit(250); 256 | } catch (\Error $e) { 257 | Worker::log($e); 258 | exit(250); 259 | } 260 | } 261 | } 262 | 263 | /** 264 | * Check connection is successfully established or faild. 265 | * 266 | * @param resource $socket 267 | * @return void 268 | */ 269 | public function checkConnection($socket) 270 | { 271 | // Remove EV_EXPECT for windows. 272 | if(DIRECTORY_SEPARATOR === '\\') { 273 | Worker::$globalEvent->del($socket, EventInterface::EV_EXCEPT); 274 | } 275 | // Check socket state. 276 | if ($address = stream_socket_get_name($socket, true)) { 277 | // Remove write listener. 278 | Worker::$globalEvent->del($socket, EventInterface::EV_WRITE); 279 | // Nonblocking. 280 | stream_set_blocking($socket, 0); 281 | // Compatible with hhvm 282 | if (function_exists('stream_set_read_buffer')) { 283 | stream_set_read_buffer($socket, 0); 284 | } 285 | // Try to open keepalive for tcp and disable Nagle algorithm. 286 | if (function_exists('socket_import_stream') && $this->transport === 'tcp') { 287 | $raw_socket = socket_import_stream($socket); 288 | socket_set_option($raw_socket, SOL_SOCKET, SO_KEEPALIVE, 1); 289 | socket_set_option($raw_socket, SOL_TCP, TCP_NODELAY, 1); 290 | } 291 | // Register a listener waiting read event. 292 | Worker::$globalEvent->add($socket, EventInterface::EV_READ, array($this, 'baseRead')); 293 | // There are some data waiting to send. 294 | if ($this->_sendBuffer) { 295 | Worker::$globalEvent->add($socket, EventInterface::EV_WRITE, array($this, 'baseWrite')); 296 | } 297 | $this->_status = self::STATUS_ESTABLISHED; 298 | $this->_remoteAddress = $address; 299 | $this->_sslHandshakeCompleted = true; 300 | 301 | // Try to emit onConnect callback. 302 | if ($this->onConnect) { 303 | try { 304 | call_user_func($this->onConnect, $this); 305 | } catch (\Exception $e) { 306 | Worker::log($e); 307 | exit(250); 308 | } catch (\Error $e) { 309 | Worker::log($e); 310 | exit(250); 311 | } 312 | } 313 | // Try to emit protocol::onConnect 314 | if (method_exists($this->protocol, 'onConnect')) { 315 | try { 316 | call_user_func(array($this->protocol, 'onConnect'), $this); 317 | } catch (\Exception $e) { 318 | Worker::log($e); 319 | exit(250); 320 | } catch (\Error $e) { 321 | Worker::log($e); 322 | exit(250); 323 | } 324 | } 325 | } else { 326 | // Connection failed. 327 | $this->emitError(WORKERMAN_CONNECT_FAIL, 'connect ' . $this->_remoteAddress . ' fail after ' . round(microtime(true) - $this->_connectStartTime, 4) . ' seconds'); 328 | if ($this->_status === self::STATUS_CLOSING) { 329 | $this->destroy(); 330 | } 331 | if ($this->_status === self::STATUS_CLOSED) { 332 | $this->onConnect = null; 333 | } 334 | } 335 | } 336 | } 337 | -------------------------------------------------------------------------------- /workerman/Connection/AsyncUdpConnection.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | namespace Workerman\Connection; 15 | 16 | use Workerman\Events\EventInterface; 17 | use Workerman\Worker; 18 | use Exception; 19 | 20 | /** 21 | * AsyncTcpConnection. 22 | */ 23 | class AsyncUdpConnection extends UdpConnection 24 | { 25 | /** 26 | * Construct. 27 | * 28 | * @param string $remote_address 29 | * @throws Exception 30 | */ 31 | public function __construct($remote_address) 32 | { 33 | // Get the application layer communication protocol and listening address. 34 | list($scheme, $address) = explode(':', $remote_address, 2); 35 | // Check application layer protocol class. 36 | if ($scheme !== 'udp') { 37 | $scheme = ucfirst($scheme); 38 | $this->protocol = '\\Protocols\\' . $scheme; 39 | if (!class_exists($this->protocol)) { 40 | $this->protocol = "\\Workerman\\Protocols\\$scheme"; 41 | if (!class_exists($this->protocol)) { 42 | throw new Exception("class \\Protocols\\$scheme not exist"); 43 | } 44 | } 45 | } 46 | 47 | $this->_remoteAddress = substr($address, 2); 48 | $this->_socket = stream_socket_client("udp://{$this->_remoteAddress}"); 49 | Worker::$globalEvent->add($this->_socket, EventInterface::EV_READ, array($this, 'baseRead')); 50 | } 51 | 52 | /** 53 | * For udp package. 54 | * 55 | * @param resource $socket 56 | * @return bool 57 | */ 58 | public function baseRead($socket) 59 | { 60 | $recv_buffer = stream_socket_recvfrom($socket, Worker::MAX_UDP_PACKAGE_SIZE, 0, $remote_address); 61 | if (false === $recv_buffer || empty($remote_address)) { 62 | return false; 63 | } 64 | 65 | if ($this->onMessage) { 66 | if ($this->protocol) { 67 | $parser = $this->protocol; 68 | $recv_buffer = $parser::decode($recv_buffer, $this); 69 | } 70 | ConnectionInterface::$statistics['total_request']++; 71 | try { 72 | call_user_func($this->onMessage, $this, $recv_buffer); 73 | } catch (\Exception $e) { 74 | self::log($e); 75 | exit(250); 76 | } catch (\Error $e) { 77 | self::log($e); 78 | exit(250); 79 | } 80 | } 81 | return true; 82 | } 83 | 84 | 85 | /** 86 | * Close connection. 87 | * 88 | * @param mixed $data 89 | * @return bool 90 | */ 91 | public function close($data = null, $raw = false) 92 | { 93 | if ($data !== null) { 94 | $this->send($data, $raw); 95 | } 96 | Worker::$globalEvent->del($this->_socket, EventInterface::EV_READ); 97 | fclose($this->_socket); 98 | return true; 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /workerman/Connection/ConnectionInterface.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | namespace Workerman\Connection; 15 | 16 | /** 17 | * ConnectionInterface. 18 | */ 19 | abstract class ConnectionInterface 20 | { 21 | /** 22 | * Statistics for status command. 23 | * 24 | * @var array 25 | */ 26 | public static $statistics = array( 27 | 'connection_count' => 0, 28 | 'total_request' => 0, 29 | 'throw_exception' => 0, 30 | 'send_fail' => 0, 31 | ); 32 | 33 | /** 34 | * Emitted when data is received. 35 | * 36 | * @var callback 37 | */ 38 | public $onMessage = null; 39 | 40 | /** 41 | * Emitted when the other end of the socket sends a FIN packet. 42 | * 43 | * @var callback 44 | */ 45 | public $onClose = null; 46 | 47 | /** 48 | * Emitted when an error occurs with connection. 49 | * 50 | * @var callback 51 | */ 52 | public $onError = null; 53 | 54 | /** 55 | * Sends data on the connection. 56 | * 57 | * @param string $send_buffer 58 | * @return void|boolean 59 | */ 60 | abstract public function send($send_buffer); 61 | 62 | /** 63 | * Get remote IP. 64 | * 65 | * @return string 66 | */ 67 | abstract public function getRemoteIp(); 68 | 69 | /** 70 | * Get remote port. 71 | * 72 | * @return int 73 | */ 74 | abstract public function getRemotePort(); 75 | 76 | /** 77 | * Get remote address. 78 | * 79 | * @return string 80 | */ 81 | abstract public function getRemoteAddress(); 82 | 83 | /** 84 | * Get remote IP. 85 | * 86 | * @return string 87 | */ 88 | abstract public function getLocalIp(); 89 | 90 | /** 91 | * Get remote port. 92 | * 93 | * @return int 94 | */ 95 | abstract public function getLocalPort(); 96 | 97 | /** 98 | * Get remote address. 99 | * 100 | * @return string 101 | */ 102 | abstract public function getLocalAddress(); 103 | 104 | /** 105 | * Is ipv4. 106 | * 107 | * @return bool 108 | */ 109 | abstract public function isIPv4(); 110 | 111 | /** 112 | * Is ipv6. 113 | * 114 | * @return bool 115 | */ 116 | abstract public function isIPv6(); 117 | 118 | /** 119 | * Close connection. 120 | * 121 | * @param $data 122 | * @return void 123 | */ 124 | abstract public function close($data = null); 125 | } 126 | -------------------------------------------------------------------------------- /workerman/Connection/TcpConnection.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | namespace Workerman\Connection; 15 | 16 | use Workerman\Events\EventInterface; 17 | use Workerman\Worker; 18 | use Exception; 19 | 20 | /** 21 | * TcpConnection. 22 | */ 23 | class TcpConnection extends ConnectionInterface 24 | { 25 | /** 26 | * Read buffer size. 27 | * 28 | * @var int 29 | */ 30 | const READ_BUFFER_SIZE = 65535; 31 | 32 | /** 33 | * Status initial. 34 | * 35 | * @var int 36 | */ 37 | const STATUS_INITIAL = 0; 38 | 39 | /** 40 | * Status connecting. 41 | * 42 | * @var int 43 | */ 44 | const STATUS_CONNECTING = 1; 45 | 46 | /** 47 | * Status connection established. 48 | * 49 | * @var int 50 | */ 51 | const STATUS_ESTABLISHED = 2; 52 | 53 | /** 54 | * Status closing. 55 | * 56 | * @var int 57 | */ 58 | const STATUS_CLOSING = 4; 59 | 60 | /** 61 | * Status closed. 62 | * 63 | * @var int 64 | */ 65 | const STATUS_CLOSED = 8; 66 | 67 | /** 68 | * Emitted when data is received. 69 | * 70 | * @var callback 71 | */ 72 | public $onMessage = null; 73 | 74 | /** 75 | * Emitted when the other end of the socket sends a FIN packet. 76 | * 77 | * @var callback 78 | */ 79 | public $onClose = null; 80 | 81 | /** 82 | * Emitted when an error occurs with connection. 83 | * 84 | * @var callback 85 | */ 86 | public $onError = null; 87 | 88 | /** 89 | * Emitted when the send buffer becomes full. 90 | * 91 | * @var callback 92 | */ 93 | public $onBufferFull = null; 94 | 95 | /** 96 | * Emitted when the send buffer becomes empty. 97 | * 98 | * @var callback 99 | */ 100 | public $onBufferDrain = null; 101 | 102 | /** 103 | * Application layer protocol. 104 | * The format is like this Workerman\\Protocols\\Http. 105 | * 106 | * @var \Workerman\Protocols\ProtocolInterface 107 | */ 108 | public $protocol = null; 109 | 110 | /** 111 | * Transport (tcp/udp/unix/ssl). 112 | * 113 | * @var string 114 | */ 115 | public $transport = 'tcp'; 116 | 117 | /** 118 | * Which worker belong to. 119 | * 120 | * @var Worker 121 | */ 122 | public $worker = null; 123 | 124 | /** 125 | * Bytes read. 126 | * 127 | * @var int 128 | */ 129 | public $bytesRead = 0; 130 | 131 | /** 132 | * Bytes written. 133 | * 134 | * @var int 135 | */ 136 | public $bytesWritten = 0; 137 | 138 | /** 139 | * Connection->id. 140 | * 141 | * @var int 142 | */ 143 | public $id = 0; 144 | 145 | /** 146 | * A copy of $worker->id which used to clean up the connection in worker->connections 147 | * 148 | * @var int 149 | */ 150 | protected $_id = 0; 151 | 152 | /** 153 | * Sets the maximum send buffer size for the current connection. 154 | * OnBufferFull callback will be emited When the send buffer is full. 155 | * 156 | * @var int 157 | */ 158 | public $maxSendBufferSize = 1048576; 159 | 160 | /** 161 | * Default send buffer size. 162 | * 163 | * @var int 164 | */ 165 | public static $defaultMaxSendBufferSize = 1048576; 166 | 167 | /** 168 | * Maximum acceptable packet size. 169 | * 170 | * @var int 171 | */ 172 | public static $maxPackageSize = 10485760; 173 | 174 | /** 175 | * Id recorder. 176 | * 177 | * @var int 178 | */ 179 | protected static $_idRecorder = 1; 180 | 181 | /** 182 | * Socket 183 | * 184 | * @var resource 185 | */ 186 | protected $_socket = null; 187 | 188 | /** 189 | * Send buffer. 190 | * 191 | * @var string 192 | */ 193 | protected $_sendBuffer = ''; 194 | 195 | /** 196 | * Receive buffer. 197 | * 198 | * @var string 199 | */ 200 | protected $_recvBuffer = ''; 201 | 202 | /** 203 | * Current package length. 204 | * 205 | * @var int 206 | */ 207 | protected $_currentPackageLength = 0; 208 | 209 | /** 210 | * Connection status. 211 | * 212 | * @var int 213 | */ 214 | protected $_status = self::STATUS_ESTABLISHED; 215 | 216 | /** 217 | * Remote address. 218 | * 219 | * @var string 220 | */ 221 | protected $_remoteAddress = ''; 222 | 223 | /** 224 | * Is paused. 225 | * 226 | * @var bool 227 | */ 228 | protected $_isPaused = false; 229 | 230 | /** 231 | * SSL handshake completed or not. 232 | * 233 | * @var bool 234 | */ 235 | protected $_sslHandshakeCompleted = false; 236 | 237 | /** 238 | * All connection instances. 239 | * 240 | * @var array 241 | */ 242 | public static $connections = array(); 243 | 244 | /** 245 | * Status to string. 246 | * 247 | * @var array 248 | */ 249 | public static $_statusToString = array( 250 | self::STATUS_INITIAL => 'INITIAL', 251 | self::STATUS_CONNECTING => 'CONNECTING', 252 | self::STATUS_ESTABLISHED => 'ESTABLISHED', 253 | self::STATUS_CLOSING => 'CLOSING', 254 | self::STATUS_CLOSED => 'CLOSED', 255 | ); 256 | 257 | /** 258 | * Construct. 259 | * 260 | * @param resource $socket 261 | * @param string $remote_address 262 | */ 263 | public function __construct($socket, $remote_address = '') 264 | { 265 | self::$statistics['connection_count']++; 266 | $this->id = $this->_id = self::$_idRecorder++; 267 | if(self::$_idRecorder === PHP_INT_MAX){ 268 | self::$_idRecorder = 0; 269 | } 270 | $this->_socket = $socket; 271 | stream_set_blocking($this->_socket, 0); 272 | // Compatible with hhvm 273 | if (function_exists('stream_set_read_buffer')) { 274 | stream_set_read_buffer($this->_socket, 0); 275 | } 276 | Worker::$globalEvent->add($this->_socket, EventInterface::EV_READ, array($this, 'baseRead')); 277 | $this->maxSendBufferSize = self::$defaultMaxSendBufferSize; 278 | $this->_remoteAddress = $remote_address; 279 | static::$connections[$this->id] = $this; 280 | } 281 | 282 | /** 283 | * Get status. 284 | * 285 | * @param bool $raw_output 286 | * 287 | * @return int 288 | */ 289 | public function getStatus($raw_output = true) 290 | { 291 | if ($raw_output) { 292 | return $this->_status; 293 | } 294 | return self::$_statusToString[$this->_status]; 295 | } 296 | 297 | /** 298 | * Sends data on the connection. 299 | * 300 | * @param string $send_buffer 301 | * @param bool $raw 302 | * @return void|bool|null 303 | */ 304 | public function send($send_buffer, $raw = false) 305 | { 306 | if ($this->_status === self::STATUS_CLOSING || $this->_status === self::STATUS_CLOSED) { 307 | return false; 308 | } 309 | 310 | // Try to call protocol::encode($send_buffer) before sending. 311 | if (false === $raw && $this->protocol !== null) { 312 | $parser = $this->protocol; 313 | $send_buffer = $parser::encode($send_buffer, $this); 314 | if ($send_buffer === '') { 315 | return null; 316 | } 317 | } 318 | 319 | if ($this->_status !== self::STATUS_ESTABLISHED || 320 | ($this->transport === 'ssl' && $this->_sslHandshakeCompleted !== true) 321 | ) { 322 | if ($this->_sendBuffer) { 323 | if ($this->bufferIsFull()) { 324 | self::$statistics['send_fail']++; 325 | return false; 326 | } 327 | } 328 | $this->_sendBuffer .= $send_buffer; 329 | $this->checkBufferWillFull(); 330 | return null; 331 | } 332 | 333 | 334 | // Attempt to send data directly. 335 | if ($this->_sendBuffer === '') { 336 | $len = @fwrite($this->_socket, $send_buffer, 8192); 337 | // send successful. 338 | if ($len === strlen($send_buffer)) { 339 | $this->bytesWritten += $len; 340 | return true; 341 | } 342 | // Send only part of the data. 343 | if ($len > 0) { 344 | $this->_sendBuffer = substr($send_buffer, $len); 345 | $this->bytesWritten += $len; 346 | } else { 347 | // Connection closed? 348 | if (!is_resource($this->_socket) || feof($this->_socket)) { 349 | self::$statistics['send_fail']++; 350 | if ($this->onError) { 351 | try { 352 | call_user_func($this->onError, $this, WORKERMAN_SEND_FAIL, 'client closed'); 353 | } catch (\Exception $e) { 354 | Worker::log($e); 355 | exit(250); 356 | } catch (\Error $e) { 357 | Worker::log($e); 358 | exit(250); 359 | } 360 | } 361 | $this->destroy(); 362 | return false; 363 | } 364 | $this->_sendBuffer = $send_buffer; 365 | } 366 | Worker::$globalEvent->add($this->_socket, EventInterface::EV_WRITE, array($this, 'baseWrite')); 367 | // Check if the send buffer will be full. 368 | $this->checkBufferWillFull(); 369 | return null; 370 | } else { 371 | if ($this->bufferIsFull()) { 372 | self::$statistics['send_fail']++; 373 | return false; 374 | } 375 | 376 | $this->_sendBuffer .= $send_buffer; 377 | // Check if the send buffer is full. 378 | $this->checkBufferWillFull(); 379 | } 380 | } 381 | 382 | /** 383 | * Get remote IP. 384 | * 385 | * @return string 386 | */ 387 | public function getRemoteIp() 388 | { 389 | $pos = strrpos($this->_remoteAddress, ':'); 390 | if ($pos) { 391 | return substr($this->_remoteAddress, 0, $pos); 392 | } 393 | return ''; 394 | } 395 | 396 | /** 397 | * Get remote port. 398 | * 399 | * @return int 400 | */ 401 | public function getRemotePort() 402 | { 403 | if ($this->_remoteAddress) { 404 | return (int)substr(strrchr($this->_remoteAddress, ':'), 1); 405 | } 406 | return 0; 407 | } 408 | 409 | /** 410 | * Get remote address. 411 | * 412 | * @return string 413 | */ 414 | public function getRemoteAddress() 415 | { 416 | return $this->_remoteAddress; 417 | } 418 | 419 | /** 420 | * Get local IP. 421 | * 422 | * @return string 423 | */ 424 | public function getLocalIp() 425 | { 426 | $address = $this->getLocalAddress(); 427 | $pos = strrpos($address, ':'); 428 | if (!$pos) { 429 | return ''; 430 | } 431 | return substr($address, 0, $pos); 432 | } 433 | 434 | /** 435 | * Get local port. 436 | * 437 | * @return int 438 | */ 439 | public function getLocalPort() 440 | { 441 | $address = $this->getLocalAddress(); 442 | $pos = strrpos($address, ':'); 443 | if (!$pos) { 444 | return 0; 445 | } 446 | return (int)substr(strrchr($address, ':'), 1); 447 | } 448 | 449 | /** 450 | * Get local address. 451 | * 452 | * @return string 453 | */ 454 | public function getLocalAddress() 455 | { 456 | return (string)@stream_socket_get_name($this->_socket, false); 457 | } 458 | 459 | /** 460 | * Get send buffer queue size. 461 | * 462 | * @return integer 463 | */ 464 | public function getSendBufferQueueSize() 465 | { 466 | return strlen($this->_sendBuffer); 467 | } 468 | 469 | /** 470 | * Get recv buffer queue size. 471 | * 472 | * @return integer 473 | */ 474 | public function getRecvBufferQueueSize() 475 | { 476 | return strlen($this->_recvBuffer); 477 | } 478 | 479 | /** 480 | * Is ipv4. 481 | * 482 | * return bool. 483 | */ 484 | public function isIpV4() 485 | { 486 | if ($this->transport === 'unix') { 487 | return false; 488 | } 489 | return strpos($this->getRemoteIp(), ':') === false; 490 | } 491 | 492 | /** 493 | * Is ipv6. 494 | * 495 | * return bool. 496 | */ 497 | public function isIpV6() 498 | { 499 | if ($this->transport === 'unix') { 500 | return false; 501 | } 502 | return strpos($this->getRemoteIp(), ':') !== false; 503 | } 504 | 505 | /** 506 | * Pauses the reading of data. That is onMessage will not be emitted. Useful to throttle back an upload. 507 | * 508 | * @return void 509 | */ 510 | public function pauseRecv() 511 | { 512 | Worker::$globalEvent->del($this->_socket, EventInterface::EV_READ); 513 | $this->_isPaused = true; 514 | } 515 | 516 | /** 517 | * Resumes reading after a call to pauseRecv. 518 | * 519 | * @return void 520 | */ 521 | public function resumeRecv() 522 | { 523 | if ($this->_isPaused === true) { 524 | Worker::$globalEvent->add($this->_socket, EventInterface::EV_READ, array($this, 'baseRead')); 525 | $this->_isPaused = false; 526 | $this->baseRead($this->_socket, false); 527 | } 528 | } 529 | 530 | /** 531 | * Base read handler. 532 | * 533 | * @param resource $socket 534 | * @param bool $check_eof 535 | * @return void 536 | */ 537 | public function baseRead($socket, $check_eof = true) 538 | { 539 | // SSL handshake. 540 | if ($this->transport === 'ssl' && $this->_sslHandshakeCompleted !== true) { 541 | $ret = stream_socket_enable_crypto($socket, true, STREAM_CRYPTO_METHOD_SSLv2_SERVER | 542 | STREAM_CRYPTO_METHOD_SSLv3_SERVER | STREAM_CRYPTO_METHOD_SSLv23_SERVER); 543 | // Negotiation has failed. 544 | if(false === $ret) { 545 | if (!feof($socket)) { 546 | echo "\nSSL Handshake fail. \nBuffer:".bin2hex(fread($socket, 8182))."\n"; 547 | } 548 | return $this->destroy(); 549 | } elseif(0 === $ret) { 550 | // There isn't enough data and should try again. 551 | return; 552 | } 553 | if (isset($this->onSslHandshake)) { 554 | try { 555 | call_user_func($this->onSslHandshake, $this); 556 | } catch (\Exception $e) { 557 | Worker::log($e); 558 | exit(250); 559 | } catch (\Error $e) { 560 | Worker::log($e); 561 | exit(250); 562 | } 563 | } 564 | $this->_sslHandshakeCompleted = true; 565 | if ($this->_sendBuffer) { 566 | Worker::$globalEvent->add($socket, EventInterface::EV_WRITE, array($this, 'baseWrite')); 567 | } 568 | return; 569 | } 570 | 571 | $buffer = @fread($socket, self::READ_BUFFER_SIZE); 572 | 573 | // Check connection closed. 574 | if ($buffer === '' || $buffer === false) { 575 | if ($check_eof && (feof($socket) || !is_resource($socket) || $buffer === false)) { 576 | $this->destroy(); 577 | return; 578 | } 579 | } else { 580 | $this->bytesRead += strlen($buffer); 581 | $this->_recvBuffer .= $buffer; 582 | } 583 | 584 | // If the application layer protocol has been set up. 585 | if ($this->protocol !== null) { 586 | $parser = $this->protocol; 587 | while ($this->_recvBuffer !== '' && !$this->_isPaused) { 588 | // The current packet length is known. 589 | if ($this->_currentPackageLength) { 590 | // Data is not enough for a package. 591 | if ($this->_currentPackageLength > strlen($this->_recvBuffer)) { 592 | break; 593 | } 594 | } else { 595 | // Get current package length. 596 | $this->_currentPackageLength = $parser::input($this->_recvBuffer, $this); 597 | // The packet length is unknown. 598 | if ($this->_currentPackageLength === 0) { 599 | break; 600 | } elseif ($this->_currentPackageLength > 0 && $this->_currentPackageLength <= self::$maxPackageSize) { 601 | // Data is not enough for a package. 602 | if ($this->_currentPackageLength > strlen($this->_recvBuffer)) { 603 | break; 604 | } 605 | } // Wrong package. 606 | else { 607 | echo 'error package. package_length=' . var_export($this->_currentPackageLength, true); 608 | $this->destroy(); 609 | return; 610 | } 611 | } 612 | 613 | // The data is enough for a packet. 614 | self::$statistics['total_request']++; 615 | // The current packet length is equal to the length of the buffer. 616 | if (strlen($this->_recvBuffer) === $this->_currentPackageLength) { 617 | $one_request_buffer = $this->_recvBuffer; 618 | $this->_recvBuffer = ''; 619 | } else { 620 | // Get a full package from the buffer. 621 | $one_request_buffer = substr($this->_recvBuffer, 0, $this->_currentPackageLength); 622 | // Remove the current package from the receive buffer. 623 | $this->_recvBuffer = substr($this->_recvBuffer, $this->_currentPackageLength); 624 | } 625 | // Reset the current packet length to 0. 626 | $this->_currentPackageLength = 0; 627 | if (!$this->onMessage) { 628 | continue; 629 | } 630 | try { 631 | // Decode request buffer before Emitting onMessage callback. 632 | call_user_func($this->onMessage, $this, $parser::decode($one_request_buffer, $this)); 633 | } catch (\Exception $e) { 634 | Worker::log($e); 635 | exit(250); 636 | } catch (\Error $e) { 637 | Worker::log($e); 638 | exit(250); 639 | } 640 | } 641 | return; 642 | } 643 | 644 | if ($this->_recvBuffer === '' || $this->_isPaused) { 645 | return; 646 | } 647 | 648 | // Applications protocol is not set. 649 | self::$statistics['total_request']++; 650 | if (!$this->onMessage) { 651 | $this->_recvBuffer = ''; 652 | return; 653 | } 654 | try { 655 | call_user_func($this->onMessage, $this, $this->_recvBuffer); 656 | } catch (\Exception $e) { 657 | Worker::log($e); 658 | exit(250); 659 | } catch (\Error $e) { 660 | Worker::log($e); 661 | exit(250); 662 | } 663 | // Clean receive buffer. 664 | $this->_recvBuffer = ''; 665 | } 666 | 667 | /** 668 | * Base write handler. 669 | * 670 | * @return void|bool 671 | */ 672 | public function baseWrite() 673 | { 674 | $len = @fwrite($this->_socket, $this->_sendBuffer, 8192); 675 | if ($len === strlen($this->_sendBuffer)) { 676 | $this->bytesWritten += $len; 677 | Worker::$globalEvent->del($this->_socket, EventInterface::EV_WRITE); 678 | $this->_sendBuffer = ''; 679 | // Try to emit onBufferDrain callback when the send buffer becomes empty. 680 | if ($this->onBufferDrain) { 681 | try { 682 | call_user_func($this->onBufferDrain, $this); 683 | } catch (\Exception $e) { 684 | Worker::log($e); 685 | exit(250); 686 | } catch (\Error $e) { 687 | Worker::log($e); 688 | exit(250); 689 | } 690 | } 691 | if ($this->_status === self::STATUS_CLOSING) { 692 | $this->destroy(); 693 | } 694 | return true; 695 | } 696 | if ($len > 0) { 697 | $this->bytesWritten += $len; 698 | $this->_sendBuffer = substr($this->_sendBuffer, $len); 699 | } else { 700 | self::$statistics['send_fail']++; 701 | $this->destroy(); 702 | } 703 | } 704 | 705 | /** 706 | * This method pulls all the data out of a readable stream, and writes it to the supplied destination. 707 | * 708 | * @param TcpConnection $dest 709 | * @return void 710 | */ 711 | public function pipe($dest) 712 | { 713 | $source = $this; 714 | $this->onMessage = function ($source, $data) use ($dest) { 715 | $dest->send($data); 716 | }; 717 | $this->onClose = function ($source) use ($dest) { 718 | $dest->destroy(); 719 | }; 720 | $dest->onBufferFull = function ($dest) use ($source) { 721 | $source->pauseRecv(); 722 | }; 723 | $dest->onBufferDrain = function ($dest) use ($source) { 724 | $source->resumeRecv(); 725 | }; 726 | } 727 | 728 | /** 729 | * Remove $length of data from receive buffer. 730 | * 731 | * @param int $length 732 | * @return void 733 | */ 734 | public function consumeRecvBuffer($length) 735 | { 736 | $this->_recvBuffer = substr($this->_recvBuffer, $length); 737 | } 738 | 739 | /** 740 | * Close connection. 741 | * 742 | * @param mixed $data 743 | * @param bool $raw 744 | * @return void 745 | */ 746 | public function close($data = null, $raw = false) 747 | { 748 | if ($this->_status === self::STATUS_CLOSING || $this->_status === self::STATUS_CLOSED) { 749 | return; 750 | } else { 751 | if ($data !== null) { 752 | $this->send($data, $raw); 753 | } 754 | $this->_status = self::STATUS_CLOSING; 755 | } 756 | if ($this->_sendBuffer === '') { 757 | $this->destroy(); 758 | } 759 | } 760 | 761 | /** 762 | * Get the real socket. 763 | * 764 | * @return resource 765 | */ 766 | public function getSocket() 767 | { 768 | return $this->_socket; 769 | } 770 | 771 | /** 772 | * Check whether the send buffer will be full. 773 | * 774 | * @return void 775 | */ 776 | protected function checkBufferWillFull() 777 | { 778 | if ($this->maxSendBufferSize <= strlen($this->_sendBuffer)) { 779 | if ($this->onBufferFull) { 780 | try { 781 | call_user_func($this->onBufferFull, $this); 782 | } catch (\Exception $e) { 783 | Worker::log($e); 784 | exit(250); 785 | } catch (\Error $e) { 786 | Worker::log($e); 787 | exit(250); 788 | } 789 | } 790 | } 791 | } 792 | 793 | /** 794 | * Whether send buffer is full. 795 | * 796 | * @return bool 797 | */ 798 | protected function bufferIsFull() 799 | { 800 | // Buffer has been marked as full but still has data to send then the packet is discarded. 801 | if ($this->maxSendBufferSize <= strlen($this->_sendBuffer)) { 802 | if ($this->onError) { 803 | try { 804 | call_user_func($this->onError, $this, WORKERMAN_SEND_FAIL, 'send buffer full and drop package'); 805 | } catch (\Exception $e) { 806 | Worker::log($e); 807 | exit(250); 808 | } catch (\Error $e) { 809 | Worker::log($e); 810 | exit(250); 811 | } 812 | } 813 | return true; 814 | } 815 | return false; 816 | } 817 | 818 | /** 819 | * Destroy connection. 820 | * 821 | * @return void 822 | */ 823 | public function destroy() 824 | { 825 | // Avoid repeated calls. 826 | if ($this->_status === self::STATUS_CLOSED) { 827 | return; 828 | } 829 | // Remove event listener. 830 | Worker::$globalEvent->del($this->_socket, EventInterface::EV_READ); 831 | Worker::$globalEvent->del($this->_socket, EventInterface::EV_WRITE); 832 | // Close socket. 833 | @fclose($this->_socket); 834 | // Remove from worker->connections. 835 | if ($this->worker) { 836 | unset($this->worker->connections[$this->_id]); 837 | } 838 | unset(static::$connections[$this->_id]); 839 | $this->_status = self::STATUS_CLOSED; 840 | // Try to emit onClose callback. 841 | if ($this->onClose) { 842 | try { 843 | call_user_func($this->onClose, $this); 844 | } catch (\Exception $e) { 845 | Worker::log($e); 846 | exit(250); 847 | } catch (\Error $e) { 848 | Worker::log($e); 849 | exit(250); 850 | } 851 | } 852 | // Try to emit protocol::onClose 853 | if (method_exists($this->protocol, 'onClose')) { 854 | try { 855 | call_user_func(array($this->protocol, 'onClose'), $this); 856 | } catch (\Exception $e) { 857 | Worker::log($e); 858 | exit(250); 859 | } catch (\Error $e) { 860 | Worker::log($e); 861 | exit(250); 862 | } 863 | } 864 | if ($this->_status === self::STATUS_CLOSED) { 865 | // Cleaning up the callback to avoid memory leaks. 866 | $this->onMessage = $this->onClose = $this->onError = $this->onBufferFull = $this->onBufferDrain = null; 867 | } 868 | } 869 | 870 | /** 871 | * Destruct. 872 | * 873 | * @return void 874 | */ 875 | public function __destruct() 876 | { 877 | self::$statistics['connection_count']--; 878 | } 879 | } 880 | -------------------------------------------------------------------------------- /workerman/Connection/UdpConnection.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | namespace Workerman\Connection; 15 | 16 | /** 17 | * UdpConnection. 18 | */ 19 | class UdpConnection extends ConnectionInterface 20 | { 21 | /** 22 | * Application layer protocol. 23 | * The format is like this Workerman\\Protocols\\Http. 24 | * 25 | * @var \Workerman\Protocols\ProtocolInterface 26 | */ 27 | public $protocol = null; 28 | 29 | /** 30 | * Udp socket. 31 | * 32 | * @var resource 33 | */ 34 | protected $_socket = null; 35 | 36 | /** 37 | * Remote address. 38 | * 39 | * @var string 40 | */ 41 | protected $_remoteAddress = ''; 42 | 43 | /** 44 | * Construct. 45 | * 46 | * @param resource $socket 47 | * @param string $remote_address 48 | */ 49 | public function __construct($socket, $remote_address) 50 | { 51 | $this->_socket = $socket; 52 | $this->_remoteAddress = $remote_address; 53 | } 54 | 55 | /** 56 | * Sends data on the connection. 57 | * 58 | * @param string $send_buffer 59 | * @param bool $raw 60 | * @return void|boolean 61 | */ 62 | public function send($send_buffer, $raw = false) 63 | { 64 | if (false === $raw && $this->protocol) { 65 | $parser = $this->protocol; 66 | $send_buffer = $parser::encode($send_buffer, $this); 67 | if ($send_buffer === '') { 68 | return null; 69 | } 70 | } 71 | return strlen($send_buffer) === stream_socket_sendto($this->_socket, $send_buffer, 0, $this->_remoteAddress); 72 | } 73 | 74 | /** 75 | * Get remote IP. 76 | * 77 | * @return string 78 | */ 79 | public function getRemoteIp() 80 | { 81 | $pos = strrpos($this->_remoteAddress, ':'); 82 | if ($pos) { 83 | return trim(substr($this->_remoteAddress, 0, $pos), '[]'); 84 | } 85 | return ''; 86 | } 87 | 88 | /** 89 | * Get remote port. 90 | * 91 | * @return int 92 | */ 93 | public function getRemotePort() 94 | { 95 | if ($this->_remoteAddress) { 96 | return (int)substr(strrchr($this->_remoteAddress, ':'), 1); 97 | } 98 | return 0; 99 | } 100 | 101 | /** 102 | * Get remote address. 103 | * 104 | * @return string 105 | */ 106 | public function getRemoteAddress() 107 | { 108 | return $this->_remoteAddress; 109 | } 110 | 111 | /** 112 | * Get local IP. 113 | * 114 | * @return string 115 | */ 116 | public function getLocalIp() 117 | { 118 | $address = $this->getLocalAddress(); 119 | $pos = strrpos($address, ':'); 120 | if (!$pos) { 121 | return ''; 122 | } 123 | return substr($address, 0, $pos); 124 | } 125 | 126 | /** 127 | * Get local port. 128 | * 129 | * @return int 130 | */ 131 | public function getLocalPort() 132 | { 133 | $address = $this->getLocalAddress(); 134 | $pos = strrpos($address, ':'); 135 | if (!$pos) { 136 | return 0; 137 | } 138 | return (int)substr(strrchr($address, ':'), 1); 139 | } 140 | 141 | /** 142 | * Get local address. 143 | * 144 | * @return string 145 | */ 146 | public function getLocalAddress() 147 | { 148 | return (string)@stream_socket_get_name($this->_socket, false); 149 | } 150 | 151 | /** 152 | * Is ipv4. 153 | * 154 | * return bool. 155 | */ 156 | public function isIpV4() 157 | { 158 | if ($this->transport === 'unix') { 159 | return false; 160 | } 161 | return strpos($this->getRemoteIp(), ':') === false; 162 | } 163 | 164 | /** 165 | * Is ipv6. 166 | * 167 | * return bool. 168 | */ 169 | public function isIpV6() 170 | { 171 | if ($this->transport === 'unix') { 172 | return false; 173 | } 174 | return strpos($this->getRemoteIp(), ':') !== false; 175 | } 176 | 177 | /** 178 | * Close connection. 179 | * 180 | * @param mixed $data 181 | * @param bool $raw 182 | * @return bool 183 | */ 184 | public function close($data = null, $raw = false) 185 | { 186 | if ($data !== null) { 187 | $this->send($data, $raw); 188 | } 189 | return true; 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /workerman/Events/Ev.php: -------------------------------------------------------------------------------- 1 | 10 | * @link http://www.workerman.net/ 11 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 12 | */ 13 | namespace Workerman\Events; 14 | 15 | use Workerman\Worker; 16 | 17 | /** 18 | * ev eventloop 19 | */ 20 | class Ev implements EventInterface 21 | { 22 | /** 23 | * All listeners for read/write event. 24 | * 25 | * @var array 26 | */ 27 | protected $_allEvents = array(); 28 | 29 | /** 30 | * Event listeners of signal. 31 | * 32 | * @var array 33 | */ 34 | protected $_eventSignal = array(); 35 | 36 | /** 37 | * All timer event listeners. 38 | * [func, args, event, flag, time_interval] 39 | * 40 | * @var array 41 | */ 42 | protected $_eventTimer = array(); 43 | 44 | /** 45 | * Timer id. 46 | * 47 | * @var int 48 | */ 49 | protected static $_timerId = 1; 50 | 51 | /** 52 | * Add a timer. 53 | * {@inheritdoc} 54 | */ 55 | public function add($fd, $flag, $func, $args = null) 56 | { 57 | $callback = function ($event, $socket) use ($fd, $func) { 58 | try { 59 | call_user_func($func, $fd); 60 | } catch (\Exception $e) { 61 | Worker::log($e); 62 | exit(250); 63 | } catch (\Error $e) { 64 | Worker::log($e); 65 | exit(250); 66 | } 67 | }; 68 | switch ($flag) { 69 | case self::EV_SIGNAL: 70 | $event = new \EvSignal($fd, $callback); 71 | $this->_eventSignal[$fd] = $event; 72 | return true; 73 | case self::EV_TIMER: 74 | case self::EV_TIMER_ONCE: 75 | $repeat = $flag == self::EV_TIMER_ONCE ? 0 : $fd; 76 | $param = array($func, (array)$args, $flag, $fd, self::$_timerId); 77 | $event = new \EvTimer($fd, $repeat, array($this, 'timerCallback'), $param); 78 | $this->_eventTimer[self::$_timerId] = $event; 79 | return self::$_timerId++; 80 | default : 81 | $fd_key = (int)$fd; 82 | $real_flag = $flag === self::EV_READ ? \Ev::READ : \Ev::WRITE; 83 | $event = new \EvIo($fd, $real_flag, $callback); 84 | $this->_allEvents[$fd_key][$flag] = $event; 85 | return true; 86 | } 87 | 88 | } 89 | 90 | /** 91 | * Remove a timer. 92 | * {@inheritdoc} 93 | */ 94 | public function del($fd, $flag) 95 | { 96 | switch ($flag) { 97 | case self::EV_READ: 98 | case self::EV_WRITE: 99 | $fd_key = (int)$fd; 100 | if (isset($this->_allEvents[$fd_key][$flag])) { 101 | $this->_allEvents[$fd_key][$flag]->stop(); 102 | unset($this->_allEvents[$fd_key][$flag]); 103 | } 104 | if (empty($this->_allEvents[$fd_key])) { 105 | unset($this->_allEvents[$fd_key]); 106 | } 107 | break; 108 | case self::EV_SIGNAL: 109 | $fd_key = (int)$fd; 110 | if (isset($this->_eventSignal[$fd_key])) { 111 | $this->_eventSignal[$fd_key]->stop(); 112 | unset($this->_eventSignal[$fd_key]); 113 | } 114 | break; 115 | case self::EV_TIMER: 116 | case self::EV_TIMER_ONCE: 117 | if (isset($this->_eventTimer[$fd])) { 118 | $this->_eventTimer[$fd]->stop(); 119 | unset($this->_eventTimer[$fd]); 120 | } 121 | break; 122 | } 123 | return true; 124 | } 125 | 126 | /** 127 | * Timer callback. 128 | * 129 | * @param \EvWatcher $event 130 | */ 131 | public function timerCallback($event) 132 | { 133 | $param = $event->data; 134 | $timer_id = $param[4]; 135 | if ($param[2] === self::EV_TIMER_ONCE) { 136 | $this->_eventTimer[$timer_id]->stop(); 137 | unset($this->_eventTimer[$timer_id]); 138 | } 139 | try { 140 | call_user_func_array($param[0], $param[1]); 141 | } catch (\Exception $e) { 142 | Worker::log($e); 143 | exit(250); 144 | } catch (\Error $e) { 145 | Worker::log($e); 146 | exit(250); 147 | } 148 | } 149 | 150 | /** 151 | * Remove all timers. 152 | * 153 | * @return void 154 | */ 155 | public function clearAllTimer() 156 | { 157 | foreach ($this->_eventTimer as $event) { 158 | $event->stop(); 159 | } 160 | $this->_eventTimer = array(); 161 | } 162 | 163 | /** 164 | * Main loop. 165 | * 166 | * @see EventInterface::loop() 167 | */ 168 | public function loop() 169 | { 170 | \Ev::run(); 171 | } 172 | 173 | /** 174 | * Destroy loop. 175 | * 176 | * @return void 177 | */ 178 | public function destroy() 179 | { 180 | foreach ($this->_allEvents as $event) { 181 | $event->stop(); 182 | } 183 | } 184 | 185 | /** 186 | * Get timer count. 187 | * 188 | * @return integer 189 | */ 190 | public function getTimerCount() 191 | { 192 | return count($this->_eventTimer); 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /workerman/Events/Event.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright 有个鬼<42765633@qq.com> 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | namespace Workerman\Events; 15 | 16 | use Workerman\Worker; 17 | 18 | /** 19 | * libevent eventloop 20 | */ 21 | class Event implements EventInterface 22 | { 23 | /** 24 | * Event base. 25 | * @var object 26 | */ 27 | protected $_eventBase = null; 28 | 29 | /** 30 | * All listeners for read/write event. 31 | * @var array 32 | */ 33 | protected $_allEvents = array(); 34 | 35 | /** 36 | * Event listeners of signal. 37 | * @var array 38 | */ 39 | protected $_eventSignal = array(); 40 | 41 | /** 42 | * All timer event listeners. 43 | * [func, args, event, flag, time_interval] 44 | * @var array 45 | */ 46 | protected $_eventTimer = array(); 47 | 48 | /** 49 | * Timer id. 50 | * @var int 51 | */ 52 | protected static $_timerId = 1; 53 | 54 | /** 55 | * construct 56 | * @return void 57 | */ 58 | public function __construct() 59 | { 60 | $this->_eventBase = new \EventBase(); 61 | } 62 | 63 | /** 64 | * @see EventInterface::add() 65 | */ 66 | public function add($fd, $flag, $func, $args=array()) 67 | { 68 | switch ($flag) { 69 | case self::EV_SIGNAL: 70 | 71 | $fd_key = (int)$fd; 72 | $event = \Event::signal($this->_eventBase, $fd, $func); 73 | if (!$event||!$event->add()) { 74 | return false; 75 | } 76 | $this->_eventSignal[$fd_key] = $event; 77 | return true; 78 | 79 | case self::EV_TIMER: 80 | case self::EV_TIMER_ONCE: 81 | 82 | $param = array($func, (array)$args, $flag, $fd, self::$_timerId); 83 | $event = new \Event($this->_eventBase, -1, \Event::TIMEOUT|\Event::PERSIST, array($this, "timerCallback"), $param); 84 | if (!$event||!$event->addTimer($fd)) { 85 | return false; 86 | } 87 | $this->_eventTimer[self::$_timerId] = $event; 88 | return self::$_timerId++; 89 | 90 | default : 91 | $fd_key = (int)$fd; 92 | $real_flag = $flag === self::EV_READ ? \Event::READ | \Event::PERSIST : \Event::WRITE | \Event::PERSIST; 93 | $event = new \Event($this->_eventBase, $fd, $real_flag, $func, $fd); 94 | if (!$event||!$event->add()) { 95 | return false; 96 | } 97 | $this->_allEvents[$fd_key][$flag] = $event; 98 | return true; 99 | } 100 | } 101 | 102 | /** 103 | * @see Events\EventInterface::del() 104 | */ 105 | public function del($fd, $flag) 106 | { 107 | switch ($flag) { 108 | 109 | case self::EV_READ: 110 | case self::EV_WRITE: 111 | 112 | $fd_key = (int)$fd; 113 | if (isset($this->_allEvents[$fd_key][$flag])) { 114 | $this->_allEvents[$fd_key][$flag]->del(); 115 | unset($this->_allEvents[$fd_key][$flag]); 116 | } 117 | if (empty($this->_allEvents[$fd_key])) { 118 | unset($this->_allEvents[$fd_key]); 119 | } 120 | break; 121 | 122 | case self::EV_SIGNAL: 123 | $fd_key = (int)$fd; 124 | if (isset($this->_eventSignal[$fd_key])) { 125 | $this->_eventSignal[$fd_key]->del(); 126 | unset($this->_eventSignal[$fd_key]); 127 | } 128 | break; 129 | 130 | case self::EV_TIMER: 131 | case self::EV_TIMER_ONCE: 132 | if (isset($this->_eventTimer[$fd])) { 133 | $this->_eventTimer[$fd]->del(); 134 | unset($this->_eventTimer[$fd]); 135 | } 136 | break; 137 | } 138 | return true; 139 | } 140 | 141 | /** 142 | * Timer callback. 143 | * @param null $fd 144 | * @param int $what 145 | * @param int $timer_id 146 | */ 147 | public function timerCallback($fd, $what, $param) 148 | { 149 | $timer_id = $param[4]; 150 | 151 | if ($param[2] === self::EV_TIMER_ONCE) { 152 | $this->_eventTimer[$timer_id]->del(); 153 | unset($this->_eventTimer[$timer_id]); 154 | } 155 | 156 | try { 157 | call_user_func_array($param[0], $param[1]); 158 | } catch (\Exception $e) { 159 | Worker::log($e); 160 | exit(250); 161 | } catch (\Error $e) { 162 | Worker::log($e); 163 | exit(250); 164 | } 165 | } 166 | 167 | /** 168 | * @see Events\EventInterface::clearAllTimer() 169 | * @return void 170 | */ 171 | public function clearAllTimer() 172 | { 173 | foreach ($this->_eventTimer as $event) { 174 | $event->del(); 175 | } 176 | $this->_eventTimer = array(); 177 | } 178 | 179 | 180 | /** 181 | * @see EventInterface::loop() 182 | */ 183 | public function loop() 184 | { 185 | $this->_eventBase->loop(); 186 | } 187 | 188 | /** 189 | * Destroy loop. 190 | * 191 | * @return void 192 | */ 193 | public function destroy() 194 | { 195 | foreach ($this->_eventSignal as $event) { 196 | $event->del(); 197 | } 198 | } 199 | 200 | /** 201 | * Get timer count. 202 | * 203 | * @return integer 204 | */ 205 | public function getTimerCount() 206 | { 207 | return count($this->_eventTimer); 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /workerman/Events/EventInterface.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | namespace Workerman\Events; 15 | 16 | interface EventInterface 17 | { 18 | /** 19 | * Read event. 20 | * 21 | * @var int 22 | */ 23 | const EV_READ = 1; 24 | 25 | /** 26 | * Write event. 27 | * 28 | * @var int 29 | */ 30 | const EV_WRITE = 2; 31 | 32 | /** 33 | * Except event 34 | * 35 | * @var int 36 | */ 37 | const EV_EXCEPT = 3; 38 | 39 | /** 40 | * Signal event. 41 | * 42 | * @var int 43 | */ 44 | const EV_SIGNAL = 4; 45 | 46 | /** 47 | * Timer event. 48 | * 49 | * @var int 50 | */ 51 | const EV_TIMER = 8; 52 | 53 | /** 54 | * Timer once event. 55 | * 56 | * @var int 57 | */ 58 | const EV_TIMER_ONCE = 16; 59 | 60 | /** 61 | * Add event listener to event loop. 62 | * 63 | * @param mixed $fd 64 | * @param int $flag 65 | * @param callable $func 66 | * @param mixed $args 67 | * @return bool 68 | */ 69 | public function add($fd, $flag, $func, $args = null); 70 | 71 | /** 72 | * Remove event listener from event loop. 73 | * 74 | * @param mixed $fd 75 | * @param int $flag 76 | * @return bool 77 | */ 78 | public function del($fd, $flag); 79 | 80 | /** 81 | * Remove all timers. 82 | * 83 | * @return void 84 | */ 85 | public function clearAllTimer(); 86 | 87 | /** 88 | * Main loop. 89 | * 90 | * @return void 91 | */ 92 | public function loop(); 93 | 94 | /** 95 | * Destroy loop. 96 | * 97 | * @return mixed 98 | */ 99 | public function destroy(); 100 | 101 | /** 102 | * Get Timer count. 103 | * 104 | * @return mixed 105 | */ 106 | public function getTimerCount(); 107 | } 108 | -------------------------------------------------------------------------------- /workerman/Events/Libevent.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | namespace Workerman\Events; 15 | 16 | use Workerman\Worker; 17 | 18 | /** 19 | * libevent eventloop 20 | */ 21 | class Libevent implements EventInterface 22 | { 23 | /** 24 | * Event base. 25 | * 26 | * @var resource 27 | */ 28 | protected $_eventBase = null; 29 | 30 | /** 31 | * All listeners for read/write event. 32 | * 33 | * @var array 34 | */ 35 | protected $_allEvents = array(); 36 | 37 | /** 38 | * Event listeners of signal. 39 | * 40 | * @var array 41 | */ 42 | protected $_eventSignal = array(); 43 | 44 | /** 45 | * All timer event listeners. 46 | * [func, args, event, flag, time_interval] 47 | * 48 | * @var array 49 | */ 50 | protected $_eventTimer = array(); 51 | 52 | /** 53 | * construct 54 | */ 55 | public function __construct() 56 | { 57 | $this->_eventBase = event_base_new(); 58 | } 59 | 60 | /** 61 | * {@inheritdoc} 62 | */ 63 | public function add($fd, $flag, $func, $args = array()) 64 | { 65 | switch ($flag) { 66 | case self::EV_SIGNAL: 67 | $fd_key = (int)$fd; 68 | $real_flag = EV_SIGNAL | EV_PERSIST; 69 | $this->_eventSignal[$fd_key] = event_new(); 70 | if (!event_set($this->_eventSignal[$fd_key], $fd, $real_flag, $func, null)) { 71 | return false; 72 | } 73 | if (!event_base_set($this->_eventSignal[$fd_key], $this->_eventBase)) { 74 | return false; 75 | } 76 | if (!event_add($this->_eventSignal[$fd_key])) { 77 | return false; 78 | } 79 | return true; 80 | case self::EV_TIMER: 81 | case self::EV_TIMER_ONCE: 82 | $event = event_new(); 83 | $timer_id = (int)$event; 84 | if (!event_set($event, 0, EV_TIMEOUT, array($this, 'timerCallback'), $timer_id)) { 85 | return false; 86 | } 87 | 88 | if (!event_base_set($event, $this->_eventBase)) { 89 | return false; 90 | } 91 | 92 | $time_interval = $fd * 1000000; 93 | if (!event_add($event, $time_interval)) { 94 | return false; 95 | } 96 | $this->_eventTimer[$timer_id] = array($func, (array)$args, $event, $flag, $time_interval); 97 | return $timer_id; 98 | 99 | default : 100 | $fd_key = (int)$fd; 101 | $real_flag = $flag === self::EV_READ ? EV_READ | EV_PERSIST : EV_WRITE | EV_PERSIST; 102 | 103 | $event = event_new(); 104 | 105 | if (!event_set($event, $fd, $real_flag, $func, null)) { 106 | return false; 107 | } 108 | 109 | if (!event_base_set($event, $this->_eventBase)) { 110 | return false; 111 | } 112 | 113 | if (!event_add($event)) { 114 | return false; 115 | } 116 | 117 | $this->_allEvents[$fd_key][$flag] = $event; 118 | 119 | return true; 120 | } 121 | 122 | } 123 | 124 | /** 125 | * {@inheritdoc} 126 | */ 127 | public function del($fd, $flag) 128 | { 129 | switch ($flag) { 130 | case self::EV_READ: 131 | case self::EV_WRITE: 132 | $fd_key = (int)$fd; 133 | if (isset($this->_allEvents[$fd_key][$flag])) { 134 | event_del($this->_allEvents[$fd_key][$flag]); 135 | unset($this->_allEvents[$fd_key][$flag]); 136 | } 137 | if (empty($this->_allEvents[$fd_key])) { 138 | unset($this->_allEvents[$fd_key]); 139 | } 140 | break; 141 | case self::EV_SIGNAL: 142 | $fd_key = (int)$fd; 143 | if (isset($this->_eventSignal[$fd_key])) { 144 | event_del($this->_eventSignal[$fd_key]); 145 | unset($this->_eventSignal[$fd_key]); 146 | } 147 | break; 148 | case self::EV_TIMER: 149 | case self::EV_TIMER_ONCE: 150 | // 这里 fd 为timerid 151 | if (isset($this->_eventTimer[$fd])) { 152 | event_del($this->_eventTimer[$fd][2]); 153 | unset($this->_eventTimer[$fd]); 154 | } 155 | break; 156 | } 157 | return true; 158 | } 159 | 160 | /** 161 | * Timer callback. 162 | * 163 | * @param mixed $_null1 164 | * @param int $_null2 165 | * @param mixed $timer_id 166 | */ 167 | protected function timerCallback($_null1, $_null2, $timer_id) 168 | { 169 | if ($this->_eventTimer[$timer_id][3] === self::EV_TIMER) { 170 | event_add($this->_eventTimer[$timer_id][2], $this->_eventTimer[$timer_id][4]); 171 | } 172 | try { 173 | call_user_func_array($this->_eventTimer[$timer_id][0], $this->_eventTimer[$timer_id][1]); 174 | } catch (\Exception $e) { 175 | Worker::log($e); 176 | exit(250); 177 | } catch (\Error $e) { 178 | Worker::log($e); 179 | exit(250); 180 | } 181 | if (isset($this->_eventTimer[$timer_id]) && $this->_eventTimer[$timer_id][3] === self::EV_TIMER_ONCE) { 182 | $this->del($timer_id, self::EV_TIMER_ONCE); 183 | } 184 | } 185 | 186 | /** 187 | * {@inheritdoc} 188 | */ 189 | public function clearAllTimer() 190 | { 191 | foreach ($this->_eventTimer as $task_data) { 192 | event_del($task_data[2]); 193 | } 194 | $this->_eventTimer = array(); 195 | } 196 | 197 | /** 198 | * {@inheritdoc} 199 | */ 200 | public function loop() 201 | { 202 | event_base_loop($this->_eventBase); 203 | } 204 | 205 | /** 206 | * Destroy loop. 207 | * 208 | * @return void 209 | */ 210 | public function destroy() 211 | { 212 | foreach ($this->_eventSignal as $event) { 213 | event_del($event); 214 | } 215 | } 216 | 217 | /** 218 | * Get timer count. 219 | * 220 | * @return integer 221 | */ 222 | public function getTimerCount() 223 | { 224 | return count($this->_eventTimer); 225 | } 226 | } 227 | 228 | -------------------------------------------------------------------------------- /workerman/Events/React/ExtEventLoop.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | namespace Workerman\Events\React; 15 | use Workerman\Events\EventInterface; 16 | 17 | /** 18 | * Class ExtEventLoop 19 | * @package Workerman\Events\React 20 | */ 21 | class ExtEventLoop extends \React\EventLoop\ExtEventLoop 22 | { 23 | /** 24 | * Event base. 25 | * 26 | * @var EventBase 27 | */ 28 | protected $_eventBase = null; 29 | 30 | /** 31 | * All signal Event instances. 32 | * 33 | * @var array 34 | */ 35 | protected $_signalEvents = array(); 36 | 37 | /** 38 | * @var array 39 | */ 40 | protected $_timerIdMap = array(); 41 | 42 | /** 43 | * @var int 44 | */ 45 | protected $_timerIdIndex = 0; 46 | 47 | /** 48 | * Add event listener to event loop. 49 | * 50 | * @param $fd 51 | * @param $flag 52 | * @param $func 53 | * @param array $args 54 | * @return bool 55 | */ 56 | public function add($fd, $flag, $func, $args = array()) 57 | { 58 | $args = (array)$args; 59 | switch ($flag) { 60 | case EventInterface::EV_READ: 61 | return $this->addReadStream($fd, $func); 62 | case EventInterface::EV_WRITE: 63 | return $this->addWriteStream($fd, $func); 64 | case EventInterface::EV_SIGNAL: 65 | return $this->addSignal($fd, $func); 66 | case EventInterface::EV_TIMER: 67 | $timer_id = ++$this->_timerIdIndex; 68 | $timer_obj = $this->addPeriodicTimer($fd, function() use ($func, $args) { 69 | call_user_func_array($func, $args); 70 | }); 71 | $this->_timerIdMap[$timer_id] = $timer_obj; 72 | return $timer_id; 73 | case EventInterface::EV_TIMER_ONCE: 74 | $timer_id = ++$this->_timerIdIndex; 75 | $timer_obj = $this->addTimer($fd, function() use ($func, $args, $timer_id) { 76 | unset($this->_timerIdMap[$timer_id]); 77 | call_user_func_array($func, $args); 78 | }); 79 | $this->_timerIdMap[$timer_id] = $timer_obj; 80 | return $timer_id; 81 | } 82 | return false; 83 | } 84 | 85 | /** 86 | * Remove event listener from event loop. 87 | * 88 | * @param mixed $fd 89 | * @param int $flag 90 | * @return bool 91 | */ 92 | public function del($fd, $flag) 93 | { 94 | switch ($flag) { 95 | case EventInterface::EV_READ: 96 | return $this->removeReadStream($fd); 97 | case EventInterface::EV_WRITE: 98 | return $this->removeWriteStream($fd); 99 | case EventInterface::EV_SIGNAL: 100 | return $this->removeSignal($fd); 101 | case EventInterface::EV_TIMER: 102 | case EventInterface::EV_TIMER_ONCE; 103 | if (isset($this->_timerIdMap[$fd])){ 104 | $timer_obj = $this->_timerIdMap[$fd]; 105 | unset($this->_timerIdMap[$fd]); 106 | $this->cancelTimer($timer_obj); 107 | return true; 108 | } 109 | } 110 | return false; 111 | } 112 | 113 | 114 | /** 115 | * Main loop. 116 | * 117 | * @return void 118 | */ 119 | public function loop() 120 | { 121 | $this->run(); 122 | } 123 | 124 | /** 125 | * Construct 126 | */ 127 | public function __construct() 128 | { 129 | parent::__construct(); 130 | $class = new \ReflectionClass('\React\EventLoop\ExtEventLoop'); 131 | $property = $class->getProperty('eventBase'); 132 | $property->setAccessible(true); 133 | $this->_eventBase = $property->getValue($this); 134 | } 135 | 136 | /** 137 | * Add signal handler. 138 | * 139 | * @param $signal 140 | * @param $callback 141 | * @return bool 142 | */ 143 | public function addSignal($signal, $callback) 144 | { 145 | $event = \Event::signal($this->_eventBase, $signal, $callback); 146 | if (!$event||!$event->add()) { 147 | return false; 148 | } 149 | $this->_signalEvents[$signal] = $event; 150 | } 151 | 152 | /** 153 | * Remove signal handler. 154 | * 155 | * @param $signal 156 | */ 157 | public function removeSignal($signal) 158 | { 159 | if (isset($this->_signalEvents[$signal])) { 160 | $this->_signalEvents[$signal]->del(); 161 | unset($this->_signalEvents[$signal]); 162 | } 163 | } 164 | 165 | /** 166 | * Destroy loop. 167 | * 168 | * @return void 169 | */ 170 | public function destroy() 171 | { 172 | foreach ($this->_signalEvents as $event) { 173 | $event->del(); 174 | } 175 | } 176 | 177 | /** 178 | * Get timer count. 179 | * 180 | * @return integer 181 | */ 182 | public function getTimerCount() 183 | { 184 | return count($this->_timerIdMap); 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /workerman/Events/React/LibEventLoop.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | namespace Workerman\Events\React; 15 | use Workerman\Events\EventInterface; 16 | 17 | /** 18 | * Class LibEventLoop 19 | * @package Workerman\Events\React 20 | */ 21 | class LibEventLoop extends \React\EventLoop\LibEventLoop 22 | { 23 | /** 24 | * Event base. 25 | * 26 | * @var event_base resource 27 | */ 28 | protected $_eventBase = null; 29 | 30 | /** 31 | * All signal Event instances. 32 | * 33 | * @var array 34 | */ 35 | protected $_signalEvents = array(); 36 | 37 | /** 38 | * @var array 39 | */ 40 | protected $_timerIdMap = array(); 41 | 42 | /** 43 | * @var int 44 | */ 45 | protected $_timerIdIndex = 0; 46 | 47 | /** 48 | * Add event listener to event loop. 49 | * 50 | * @param $fd 51 | * @param $flag 52 | * @param $func 53 | * @param array $args 54 | * @return bool 55 | */ 56 | public function add($fd, $flag, $func, $args = array()) 57 | { 58 | $args = (array)$args; 59 | switch ($flag) { 60 | case EventInterface::EV_READ: 61 | return $this->addReadStream($fd, $func); 62 | case EventInterface::EV_WRITE: 63 | return $this->addWriteStream($fd, $func); 64 | case EventInterface::EV_SIGNAL: 65 | return $this->addSignal($fd, $func); 66 | case EventInterface::EV_TIMER: 67 | $timer_id = ++$this->_timerIdIndex; 68 | $timer_obj = $this->addPeriodicTimer($fd, function() use ($func, $args) { 69 | call_user_func_array($func, $args); 70 | }); 71 | $this->_timerIdMap[$timer_id] = $timer_obj; 72 | return $timer_id; 73 | case EventInterface::EV_TIMER_ONCE: 74 | $timer_id = ++$this->_timerIdIndex; 75 | $timer_obj = $this->addTimer($fd, function() use ($func, $args, $timer_id) { 76 | unset($this->_timerIdMap[$timer_id]); 77 | call_user_func_array($func, $args); 78 | }); 79 | $this->_timerIdMap[$timer_id] = $timer_obj; 80 | return $timer_id; 81 | } 82 | return false; 83 | } 84 | 85 | /** 86 | * Remove event listener from event loop. 87 | * 88 | * @param mixed $fd 89 | * @param int $flag 90 | * @return bool 91 | */ 92 | public function del($fd, $flag) 93 | { 94 | switch ($flag) { 95 | case EventInterface::EV_READ: 96 | return $this->removeReadStream($fd); 97 | case EventInterface::EV_WRITE: 98 | return $this->removeWriteStream($fd); 99 | case EventInterface::EV_SIGNAL: 100 | return $this->removeSignal($fd); 101 | case EventInterface::EV_TIMER: 102 | case EventInterface::EV_TIMER_ONCE; 103 | if (isset($this->_timerIdMap[$fd])){ 104 | $timer_obj = $this->_timerIdMap[$fd]; 105 | unset($this->_timerIdMap[$fd]); 106 | $this->cancelTimer($timer_obj); 107 | return true; 108 | } 109 | } 110 | return false; 111 | } 112 | 113 | 114 | /** 115 | * Main loop. 116 | * 117 | * @return void 118 | */ 119 | public function loop() 120 | { 121 | $this->run(); 122 | } 123 | 124 | /** 125 | * Construct. 126 | */ 127 | public function __construct() 128 | { 129 | parent::__construct(); 130 | $class = new \ReflectionClass('\React\EventLoop\LibEventLoop'); 131 | $property = $class->getProperty('eventBase'); 132 | $property->setAccessible(true); 133 | $this->_eventBase = $property->getValue($this); 134 | } 135 | 136 | /** 137 | * Add signal handler. 138 | * 139 | * @param $signal 140 | * @param $callback 141 | * @return bool 142 | */ 143 | public function addSignal($signal, $callback) 144 | { 145 | $event = event_new(); 146 | $this->_signalEvents[$signal] = $event; 147 | event_set($event, $signal, EV_SIGNAL | EV_PERSIST, $callback); 148 | event_base_set($event, $this->_eventBase); 149 | event_add($event); 150 | } 151 | 152 | /** 153 | * Remove signal handler. 154 | * 155 | * @param $signal 156 | */ 157 | public function removeSignal($signal) 158 | { 159 | if (isset($this->_signalEvents[$signal])) { 160 | $event = $this->_signalEvents[$signal]; 161 | event_del($event); 162 | unset($this->_signalEvents[$signal]); 163 | } 164 | } 165 | 166 | /** 167 | * Destroy loop. 168 | * 169 | * @return void 170 | */ 171 | public function destroy() 172 | { 173 | foreach ($this->_signalEvents as $event) { 174 | event_del($event); 175 | } 176 | } 177 | 178 | /** 179 | * Get timer count. 180 | * 181 | * @return integer 182 | */ 183 | public function getTimerCount() 184 | { 185 | return count($this->_timerIdMap); 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /workerman/Events/React/StreamSelectLoop.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | namespace Workerman\Events\React; 15 | use Workerman\Events\EventInterface; 16 | 17 | /** 18 | * Class StreamSelectLoop 19 | * @package Workerman\Events\React 20 | */ 21 | class StreamSelectLoop extends \React\EventLoop\StreamSelectLoop 22 | { 23 | /** 24 | * @var array 25 | */ 26 | protected $_timerIdMap = array(); 27 | 28 | /** 29 | * @var int 30 | */ 31 | protected $_timerIdIndex = 0; 32 | 33 | /** 34 | * Add event listener to event loop. 35 | * 36 | * @param $fd 37 | * @param $flag 38 | * @param $func 39 | * @param array $args 40 | * @return bool 41 | */ 42 | public function add($fd, $flag, $func, $args = array()) 43 | { 44 | $args = (array)$args; 45 | switch ($flag) { 46 | case EventInterface::EV_READ: 47 | return $this->addReadStream($fd, $func); 48 | case EventInterface::EV_WRITE: 49 | return $this->addWriteStream($fd, $func); 50 | case EventInterface::EV_SIGNAL: 51 | return $this->addSignal($fd, $func); 52 | case EventInterface::EV_TIMER: 53 | $timer_obj = $this->addPeriodicTimer($fd, function() use ($func, $args) { 54 | call_user_func_array($func, $args); 55 | }); 56 | $this->_timerIdMap[++$this->_timerIdIndex] = $timer_obj; 57 | return $this->_timerIdIndex; 58 | case EventInterface::EV_TIMER_ONCE: 59 | $index = ++$this->_timerIdIndex; 60 | $timer_obj = $this->addTimer($fd, function() use ($func, $args, $index) { 61 | $this->del($index,EventInterface::EV_TIMER_ONCE); 62 | call_user_func_array($func, $args); 63 | }); 64 | $this->_timerIdMap[$index] = $timer_obj; 65 | return $this->_timerIdIndex; 66 | } 67 | return false; 68 | } 69 | 70 | /** 71 | * Remove event listener from event loop. 72 | * 73 | * @param mixed $fd 74 | * @param int $flag 75 | * @return bool 76 | */ 77 | public function del($fd, $flag) 78 | { 79 | switch ($flag) { 80 | case EventInterface::EV_READ: 81 | return $this->removeReadStream($fd); 82 | case EventInterface::EV_WRITE: 83 | return $this->removeWriteStream($fd); 84 | case EventInterface::EV_SIGNAL: 85 | return $this->removeSignal($fd); 86 | case EventInterface::EV_TIMER: 87 | case EventInterface::EV_TIMER_ONCE; 88 | if (isset($this->_timerIdMap[$fd])){ 89 | $timer_obj = $this->_timerIdMap[$fd]; 90 | unset($this->_timerIdMap[$fd]); 91 | $this->cancelTimer($timer_obj); 92 | return true; 93 | } 94 | } 95 | return false; 96 | } 97 | 98 | 99 | /** 100 | * Main loop. 101 | * 102 | * @return void 103 | */ 104 | public function loop() 105 | { 106 | $this->run(); 107 | } 108 | 109 | /** 110 | * Add signal handler. 111 | * 112 | * @param $signal 113 | * @param $callback 114 | * @return bool 115 | */ 116 | public function addSignal($signal, $callback) 117 | { 118 | if(DIRECTORY_SEPARATOR === '/') { 119 | pcntl_signal($signal, $callback); 120 | } 121 | } 122 | 123 | /** 124 | * Remove signal handler. 125 | * 126 | * @param $signal 127 | */ 128 | public function removeSignal($signal) 129 | { 130 | if(DIRECTORY_SEPARATOR === '/') { 131 | pcntl_signal($signal, SIG_IGN); 132 | } 133 | } 134 | 135 | /** 136 | * Emulate a stream_select() implementation that does not break when passed 137 | * empty stream arrays. 138 | * 139 | * @param array &$read An array of read streams to select upon. 140 | * @param array &$write An array of write streams to select upon. 141 | * @param integer|null $timeout Activity timeout in microseconds, or null to wait forever. 142 | * 143 | * @return integer|false The total number of streams that are ready for read/write. 144 | * Can return false if stream_select() is interrupted by a signal. 145 | */ 146 | protected function streamSelect(array &$read, array &$write, $timeout) 147 | { 148 | if ($read || $write) { 149 | $except = null; 150 | // Calls signal handlers for pending signals 151 | if(DIRECTORY_SEPARATOR === '/') { 152 | pcntl_signal_dispatch(); 153 | } 154 | // suppress warnings that occur, when stream_select is interrupted by a signal 155 | return @stream_select($read, $write, $except, $timeout === null ? null : 0, $timeout); 156 | } 157 | 158 | // Calls signal handlers for pending signals 159 | if(DIRECTORY_SEPARATOR === '/') { 160 | pcntl_signal_dispatch(); 161 | } 162 | $timeout && usleep($timeout); 163 | 164 | return 0; 165 | } 166 | 167 | /** 168 | * Destroy loop. 169 | * 170 | * @return void 171 | */ 172 | public function destroy() 173 | { 174 | 175 | } 176 | 177 | /** 178 | * Get timer count. 179 | * 180 | * @return integer 181 | */ 182 | public function getTimerCount() 183 | { 184 | return count($this->_timerIdMap); 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /workerman/Events/Select.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | namespace Workerman\Events; 15 | 16 | /** 17 | * select eventloop 18 | */ 19 | class Select implements EventInterface 20 | { 21 | /** 22 | * All listeners for read/write event. 23 | * 24 | * @var array 25 | */ 26 | public $_allEvents = array(); 27 | 28 | /** 29 | * Event listeners of signal. 30 | * 31 | * @var array 32 | */ 33 | public $_signalEvents = array(); 34 | 35 | /** 36 | * Fds waiting for read event. 37 | * 38 | * @var array 39 | */ 40 | protected $_readFds = array(); 41 | 42 | /** 43 | * Fds waiting for write event. 44 | * 45 | * @var array 46 | */ 47 | protected $_writeFds = array(); 48 | 49 | /** 50 | * Fds waiting for except event. 51 | * 52 | * @var array 53 | */ 54 | protected $_exceptFds = array(); 55 | 56 | /** 57 | * Timer scheduler. 58 | * {['data':timer_id, 'priority':run_timestamp], ..} 59 | * 60 | * @var \SplPriorityQueue 61 | */ 62 | protected $_scheduler = null; 63 | 64 | /** 65 | * All timer event listeners. 66 | * [[func, args, flag, timer_interval], ..] 67 | * 68 | * @var array 69 | */ 70 | protected $_eventTimer = array(); 71 | 72 | /** 73 | * Timer id. 74 | * 75 | * @var int 76 | */ 77 | protected $_timerId = 1; 78 | 79 | /** 80 | * Select timeout. 81 | * 82 | * @var int 83 | */ 84 | protected $_selectTimeout = 100000000; 85 | 86 | /** 87 | * Paired socket channels 88 | * 89 | * @var array 90 | */ 91 | protected $channel = array(); 92 | 93 | /** 94 | * Construct. 95 | */ 96 | public function __construct() 97 | { 98 | // Create a pipeline and put into the collection of the read to read the descriptor to avoid empty polling. 99 | $this->channel = stream_socket_pair(DIRECTORY_SEPARATOR === '/' ? STREAM_PF_UNIX : STREAM_PF_INET, 100 | STREAM_SOCK_STREAM, STREAM_IPPROTO_IP); 101 | if($this->channel) { 102 | stream_set_blocking($this->channel[0], 0); 103 | $this->_readFds[0] = $this->channel[0]; 104 | } 105 | // Init SplPriorityQueue. 106 | $this->_scheduler = new \SplPriorityQueue(); 107 | $this->_scheduler->setExtractFlags(\SplPriorityQueue::EXTR_BOTH); 108 | } 109 | 110 | /** 111 | * {@inheritdoc} 112 | */ 113 | public function add($fd, $flag, $func, $args = array()) 114 | { 115 | switch ($flag) { 116 | case self::EV_READ: 117 | $fd_key = (int)$fd; 118 | $this->_allEvents[$fd_key][$flag] = array($func, $fd); 119 | $this->_readFds[$fd_key] = $fd; 120 | break; 121 | case self::EV_WRITE: 122 | $fd_key = (int)$fd; 123 | $this->_allEvents[$fd_key][$flag] = array($func, $fd); 124 | $this->_writeFds[$fd_key] = $fd; 125 | break; 126 | case self::EV_EXCEPT: 127 | $fd_key = (int)$fd; 128 | $this->_allEvents[$fd_key][$flag] = array($func, $fd); 129 | $this->_exceptFds[$fd_key] = $fd; 130 | break; 131 | case self::EV_SIGNAL: 132 | // Windows not support signal. 133 | if(DIRECTORY_SEPARATOR !== '/') { 134 | return false; 135 | } 136 | $fd_key = (int)$fd; 137 | $this->_signalEvents[$fd_key][$flag] = array($func, $fd); 138 | pcntl_signal($fd, array($this, 'signalHandler')); 139 | break; 140 | case self::EV_TIMER: 141 | case self::EV_TIMER_ONCE: 142 | $timer_id = $this->_timerId++; 143 | $run_time = microtime(true) + $fd; 144 | $this->_scheduler->insert($timer_id, -$run_time); 145 | $this->_eventTimer[$timer_id] = array($func, (array)$args, $flag, $fd); 146 | $select_timeout = ($run_time - microtime(true)) * 1000000; 147 | if( $this->_selectTimeout > $select_timeout ){ 148 | $this->_selectTimeout = $select_timeout; 149 | } 150 | return $timer_id; 151 | } 152 | 153 | return true; 154 | } 155 | 156 | /** 157 | * Signal handler. 158 | * 159 | * @param int $signal 160 | */ 161 | public function signalHandler($signal) 162 | { 163 | call_user_func_array($this->_signalEvents[$signal][self::EV_SIGNAL][0], array($signal)); 164 | } 165 | 166 | /** 167 | * {@inheritdoc} 168 | */ 169 | public function del($fd, $flag) 170 | { 171 | $fd_key = (int)$fd; 172 | switch ($flag) { 173 | case self::EV_READ: 174 | unset($this->_allEvents[$fd_key][$flag], $this->_readFds[$fd_key]); 175 | if (empty($this->_allEvents[$fd_key])) { 176 | unset($this->_allEvents[$fd_key]); 177 | } 178 | return true; 179 | case self::EV_WRITE: 180 | unset($this->_allEvents[$fd_key][$flag], $this->_writeFds[$fd_key]); 181 | if (empty($this->_allEvents[$fd_key])) { 182 | unset($this->_allEvents[$fd_key]); 183 | } 184 | return true; 185 | case self::EV_EXCEPT: 186 | unset($this->_allEvents[$fd_key][$flag], $this->_exceptFds[$fd_key]); 187 | if(empty($this->_allEvents[$fd_key])) 188 | { 189 | unset($this->_allEvents[$fd_key]); 190 | } 191 | return true; 192 | case self::EV_SIGNAL: 193 | if(DIRECTORY_SEPARATOR !== '/') { 194 | return false; 195 | } 196 | unset($this->_signalEvents[$fd_key]); 197 | pcntl_signal($fd, SIG_IGN); 198 | break; 199 | case self::EV_TIMER: 200 | case self::EV_TIMER_ONCE; 201 | unset($this->_eventTimer[$fd_key]); 202 | return true; 203 | } 204 | return false; 205 | } 206 | 207 | /** 208 | * Tick for timer. 209 | * 210 | * @return void 211 | */ 212 | protected function tick() 213 | { 214 | while (!$this->_scheduler->isEmpty()) { 215 | $scheduler_data = $this->_scheduler->top(); 216 | $timer_id = $scheduler_data['data']; 217 | $next_run_time = -$scheduler_data['priority']; 218 | $time_now = microtime(true); 219 | $this->_selectTimeout = ($next_run_time - $time_now) * 1000000; 220 | if ($this->_selectTimeout <= 0) { 221 | $this->_scheduler->extract(); 222 | 223 | if (!isset($this->_eventTimer[$timer_id])) { 224 | continue; 225 | } 226 | 227 | // [func, args, flag, timer_interval] 228 | $task_data = $this->_eventTimer[$timer_id]; 229 | if ($task_data[2] === self::EV_TIMER) { 230 | $next_run_time = $time_now + $task_data[3]; 231 | $this->_scheduler->insert($timer_id, -$next_run_time); 232 | } 233 | call_user_func_array($task_data[0], $task_data[1]); 234 | if (isset($this->_eventTimer[$timer_id]) && $task_data[2] === self::EV_TIMER_ONCE) { 235 | $this->del($timer_id, self::EV_TIMER_ONCE); 236 | } 237 | continue; 238 | } 239 | return; 240 | } 241 | $this->_selectTimeout = 100000000; 242 | } 243 | 244 | /** 245 | * {@inheritdoc} 246 | */ 247 | public function clearAllTimer() 248 | { 249 | $this->_scheduler = new \SplPriorityQueue(); 250 | $this->_scheduler->setExtractFlags(\SplPriorityQueue::EXTR_BOTH); 251 | $this->_eventTimer = array(); 252 | } 253 | 254 | /** 255 | * {@inheritdoc} 256 | */ 257 | public function loop() 258 | { 259 | $e = null; 260 | while (1) { 261 | if(DIRECTORY_SEPARATOR === '/') { 262 | // Calls signal handlers for pending signals 263 | pcntl_signal_dispatch(); 264 | } 265 | 266 | $read = $this->_readFds; 267 | $write = $this->_writeFds; 268 | $except = $this->_writeFds; 269 | 270 | // Waiting read/write/signal/timeout events. 271 | $ret = @stream_select($read, $write, $except, 0, $this->_selectTimeout); 272 | 273 | if (!$this->_scheduler->isEmpty()) { 274 | $this->tick(); 275 | } 276 | 277 | if (!$ret) { 278 | continue; 279 | } 280 | 281 | if ($read) { 282 | foreach ($read as $fd) { 283 | $fd_key = (int)$fd; 284 | if (isset($this->_allEvents[$fd_key][self::EV_READ])) { 285 | call_user_func_array($this->_allEvents[$fd_key][self::EV_READ][0], 286 | array($this->_allEvents[$fd_key][self::EV_READ][1])); 287 | } 288 | } 289 | } 290 | 291 | if ($write) { 292 | foreach ($write as $fd) { 293 | $fd_key = (int)$fd; 294 | if (isset($this->_allEvents[$fd_key][self::EV_WRITE])) { 295 | call_user_func_array($this->_allEvents[$fd_key][self::EV_WRITE][0], 296 | array($this->_allEvents[$fd_key][self::EV_WRITE][1])); 297 | } 298 | } 299 | } 300 | 301 | if($except) { 302 | foreach($except as $fd) { 303 | $fd_key = (int) $fd; 304 | if(isset($this->_allEvents[$fd_key][self::EV_EXCEPT])) { 305 | call_user_func_array($this->_allEvents[$fd_key][self::EV_EXCEPT][0], 306 | array($this->_allEvents[$fd_key][self::EV_EXCEPT][1])); 307 | } 308 | } 309 | } 310 | } 311 | } 312 | 313 | /** 314 | * Destroy loop. 315 | * 316 | * @return void 317 | */ 318 | public function destroy() 319 | { 320 | 321 | } 322 | 323 | /** 324 | * Get timer count. 325 | * 326 | * @return integer 327 | */ 328 | public function getTimerCount() 329 | { 330 | return count($this->_eventTimer); 331 | } 332 | } 333 | -------------------------------------------------------------------------------- /workerman/Lib/Constants.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | 15 | // Date.timezone 16 | if (!ini_get('date.timezone')) { 17 | date_default_timezone_set('Asia/Shanghai'); 18 | } 19 | // Display errors. 20 | ini_set('display_errors', 'on'); 21 | // Reporting all. 22 | error_reporting(E_ALL); 23 | 24 | // Reset opcache. 25 | if (function_exists('opcache_reset')) { 26 | opcache_reset(); 27 | } 28 | 29 | // For onError callback. 30 | define('WORKERMAN_CONNECT_FAIL', 1); 31 | // For onError callback. 32 | define('WORKERMAN_SEND_FAIL', 2); 33 | 34 | // Compatible with php7 35 | if(!class_exists('Error')) 36 | { 37 | class Error extends Exception 38 | { 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /workerman/Lib/Timer.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | namespace Workerman\Lib; 15 | 16 | use Workerman\Events\EventInterface; 17 | use Exception; 18 | 19 | /** 20 | * Timer. 21 | * 22 | * example: 23 | * Workerman\Lib\Timer::add($time_interval, callback, array($arg1, $arg2..)); 24 | */ 25 | class Timer 26 | { 27 | /** 28 | * Tasks that based on ALARM signal. 29 | * [ 30 | * run_time => [[$func, $args, $persistent, time_interval],[$func, $args, $persistent, time_interval],..]], 31 | * run_time => [[$func, $args, $persistent, time_interval],[$func, $args, $persistent, time_interval],..]], 32 | * .. 33 | * ] 34 | * 35 | * @var array 36 | */ 37 | protected static $_tasks = array(); 38 | 39 | /** 40 | * event 41 | * 42 | * @var \Workerman\Events\EventInterface 43 | */ 44 | protected static $_event = null; 45 | 46 | /** 47 | * Init. 48 | * 49 | * @param \Workerman\Events\EventInterface $event 50 | * @return void 51 | */ 52 | public static function init($event = null) 53 | { 54 | if ($event) { 55 | self::$_event = $event; 56 | } else { 57 | pcntl_signal(SIGALRM, array('\Workerman\Lib\Timer', 'signalHandle'), false); 58 | } 59 | } 60 | 61 | /** 62 | * ALARM signal handler. 63 | * 64 | * @return void 65 | */ 66 | public static function signalHandle() 67 | { 68 | if (!self::$_event) { 69 | pcntl_alarm(1); 70 | self::tick(); 71 | } 72 | } 73 | 74 | /** 75 | * Add a timer. 76 | * 77 | * @param int $time_interval 78 | * @param callback $func 79 | * @param mixed $args 80 | * @param bool $persistent 81 | * @return int/false 82 | */ 83 | public static function add($time_interval, $func, $args = array(), $persistent = true) 84 | { 85 | if ($time_interval <= 0) { 86 | echo new Exception("bad time_interval"); 87 | return false; 88 | } 89 | 90 | if (self::$_event) { 91 | return self::$_event->add($time_interval, 92 | $persistent ? EventInterface::EV_TIMER : EventInterface::EV_TIMER_ONCE, $func, $args); 93 | } 94 | 95 | if (!is_callable($func)) { 96 | echo new Exception("not callable"); 97 | return false; 98 | } 99 | 100 | if (empty(self::$_tasks)) { 101 | pcntl_alarm(1); 102 | } 103 | 104 | $time_now = time(); 105 | $run_time = $time_now + $time_interval; 106 | if (!isset(self::$_tasks[$run_time])) { 107 | self::$_tasks[$run_time] = array(); 108 | } 109 | self::$_tasks[$run_time][] = array($func, (array)$args, $persistent, $time_interval); 110 | return 1; 111 | } 112 | 113 | 114 | /** 115 | * Tick. 116 | * 117 | * @return void 118 | */ 119 | public static function tick() 120 | { 121 | if (empty(self::$_tasks)) { 122 | pcntl_alarm(0); 123 | return; 124 | } 125 | 126 | $time_now = time(); 127 | foreach (self::$_tasks as $run_time => $task_data) { 128 | if ($time_now >= $run_time) { 129 | foreach ($task_data as $index => $one_task) { 130 | $task_func = $one_task[0]; 131 | $task_args = $one_task[1]; 132 | $persistent = $one_task[2]; 133 | $time_interval = $one_task[3]; 134 | try { 135 | call_user_func_array($task_func, $task_args); 136 | } catch (\Exception $e) { 137 | echo $e; 138 | } 139 | if ($persistent) { 140 | self::add($time_interval, $task_func, $task_args); 141 | } 142 | } 143 | unset(self::$_tasks[$run_time]); 144 | } 145 | } 146 | } 147 | 148 | /** 149 | * Remove a timer. 150 | * 151 | * @param mixed $timer_id 152 | * @return bool 153 | */ 154 | public static function del($timer_id) 155 | { 156 | if (self::$_event) { 157 | return self::$_event->del($timer_id, EventInterface::EV_TIMER); 158 | } 159 | 160 | return false; 161 | } 162 | 163 | /** 164 | * Remove all timers. 165 | * 166 | * @return void 167 | */ 168 | public static function delAll() 169 | { 170 | self::$_tasks = array(); 171 | pcntl_alarm(0); 172 | if (self::$_event) { 173 | self::$_event->clearAllTimer(); 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /workerman/Protocols/Frame.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | namespace Workerman\Protocols; 15 | 16 | use Workerman\Connection\TcpConnection; 17 | 18 | /** 19 | * Frame Protocol. 20 | */ 21 | class Frame 22 | { 23 | /** 24 | * Check the integrity of the package. 25 | * 26 | * @param string $buffer 27 | * @param TcpConnection $connection 28 | * @return int 29 | */ 30 | public static function input($buffer, TcpConnection $connection) 31 | { 32 | if (strlen($buffer) < 4) { 33 | return 0; 34 | } 35 | $unpack_data = unpack('Ntotal_length', $buffer); 36 | return $unpack_data['total_length']; 37 | } 38 | 39 | /** 40 | * Decode. 41 | * 42 | * @param string $buffer 43 | * @return string 44 | */ 45 | public static function decode($buffer) 46 | { 47 | return substr($buffer, 4); 48 | } 49 | 50 | /** 51 | * Encode. 52 | * 53 | * @param string $buffer 54 | * @return string 55 | */ 56 | public static function encode($buffer) 57 | { 58 | $total_length = 4 + strlen($buffer); 59 | return pack('N', $total_length) . $buffer; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /workerman/Protocols/Http.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | namespace Workerman\Protocols; 15 | 16 | use Workerman\Connection\TcpConnection; 17 | use Workerman\Worker; 18 | 19 | /** 20 | * http protocol 21 | */ 22 | class Http 23 | { 24 | /** 25 | * The supported HTTP methods 26 | * @var array 27 | */ 28 | public static $methods = array('GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'OPTIONS'); 29 | 30 | /** 31 | * Check the integrity of the package. 32 | * 33 | * @param string $recv_buffer 34 | * @param TcpConnection $connection 35 | * @return int 36 | */ 37 | public static function input($recv_buffer, TcpConnection $connection) 38 | { 39 | if (!strpos($recv_buffer, "\r\n\r\n")) { 40 | // Judge whether the package length exceeds the limit. 41 | if (strlen($recv_buffer) >= TcpConnection::$maxPackageSize) { 42 | $connection->close(); 43 | return 0; 44 | } 45 | return 0; 46 | } 47 | 48 | list($header,) = explode("\r\n\r\n", $recv_buffer, 2); 49 | $method = substr($header, 0, strpos($header, ' ')); 50 | 51 | if(in_array($method, static::$methods)) { 52 | return static::getRequestSize($header, $method); 53 | }else{ 54 | $connection->send("HTTP/1.1 400 Bad Request\r\n\r\n", true); 55 | return 0; 56 | } 57 | } 58 | 59 | /** 60 | * Get whole size of the request 61 | * includes the request headers and request body. 62 | * @param string $header The request headers 63 | * @param string $method The request method 64 | * @return integer 65 | */ 66 | protected static function getRequestSize($header, $method) 67 | { 68 | if($method === 'GET' || $method === 'OPTIONS' || $method === 'HEAD') { 69 | return strlen($header) + 4; 70 | } 71 | $match = array(); 72 | if (preg_match("/\r\nContent-Length: ?(\d+)/i", $header, $match)) { 73 | $content_length = isset($match[1]) ? $match[1] : 0; 74 | return $content_length + strlen($header) + 4; 75 | } 76 | return 0; 77 | } 78 | 79 | /** 80 | * Parse $_POST、$_GET、$_COOKIE. 81 | * 82 | * @param string $recv_buffer 83 | * @param TcpConnection $connection 84 | * @return array 85 | */ 86 | public static function decode($recv_buffer, TcpConnection $connection) 87 | { 88 | // Init. 89 | $_POST = $_GET = $_COOKIE = $_REQUEST = $_SESSION = $_FILES = array(); 90 | $GLOBALS['HTTP_RAW_POST_DATA'] = ''; 91 | // Clear cache. 92 | HttpCache::$header = array('Connection' => 'Connection: keep-alive'); 93 | HttpCache::$instance = new HttpCache(); 94 | // $_SERVER 95 | $_SERVER = array( 96 | 'QUERY_STRING' => '', 97 | 'REQUEST_METHOD' => '', 98 | 'REQUEST_URI' => '', 99 | 'SERVER_PROTOCOL' => '', 100 | 'SERVER_SOFTWARE' => 'workerman/'.Worker::VERSION, 101 | 'SERVER_NAME' => '', 102 | 'HTTP_HOST' => '', 103 | 'HTTP_USER_AGENT' => '', 104 | 'HTTP_ACCEPT' => '', 105 | 'HTTP_ACCEPT_LANGUAGE' => '', 106 | 'HTTP_ACCEPT_ENCODING' => '', 107 | 'HTTP_COOKIE' => '', 108 | 'HTTP_CONNECTION' => '', 109 | 'REMOTE_ADDR' => '', 110 | 'REMOTE_PORT' => '0', 111 | 'REQUEST_TIME' => time() 112 | ); 113 | 114 | // Parse headers. 115 | list($http_header, $http_body) = explode("\r\n\r\n", $recv_buffer, 2); 116 | $header_data = explode("\r\n", $http_header); 117 | 118 | list($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI'], $_SERVER['SERVER_PROTOCOL']) = explode(' ', 119 | $header_data[0]); 120 | 121 | $http_post_boundary = ''; 122 | unset($header_data[0]); 123 | foreach ($header_data as $content) { 124 | // \r\n\r\n 125 | if (empty($content)) { 126 | continue; 127 | } 128 | list($key, $value) = explode(':', $content, 2); 129 | $key = str_replace('-', '_', strtoupper($key)); 130 | $value = trim($value); 131 | $_SERVER['HTTP_' . $key] = $value; 132 | switch ($key) { 133 | // HTTP_HOST 134 | case 'HOST': 135 | $tmp = explode(':', $value); 136 | $_SERVER['SERVER_NAME'] = $tmp[0]; 137 | if (isset($tmp[1])) { 138 | $_SERVER['SERVER_PORT'] = $tmp[1]; 139 | } 140 | break; 141 | // cookie 142 | case 'COOKIE': 143 | parse_str(str_replace('; ', '&', $_SERVER['HTTP_COOKIE']), $_COOKIE); 144 | break; 145 | // content-type 146 | case 'CONTENT_TYPE': 147 | if (!preg_match('/boundary="?(\S+)"?/', $value, $match)) { 148 | if ($pos = strpos($value, ';')) { 149 | $_SERVER['CONTENT_TYPE'] = substr($value, 0, $pos); 150 | } else { 151 | $_SERVER['CONTENT_TYPE'] = $value; 152 | } 153 | } else { 154 | $_SERVER['CONTENT_TYPE'] = 'multipart/form-data'; 155 | $http_post_boundary = '--' . $match[1]; 156 | } 157 | break; 158 | case 'CONTENT_LENGTH': 159 | $_SERVER['CONTENT_LENGTH'] = $value; 160 | break; 161 | } 162 | } 163 | 164 | // Parse $_POST. 165 | if ($_SERVER['REQUEST_METHOD'] === 'POST') { 166 | if (isset($_SERVER['CONTENT_TYPE'])) { 167 | switch ($_SERVER['CONTENT_TYPE']) { 168 | case 'multipart/form-data': 169 | self::parseUploadFiles($http_body, $http_post_boundary); 170 | break; 171 | case 'application/x-www-form-urlencoded': 172 | parse_str($http_body, $_POST); 173 | break; 174 | } 175 | } 176 | } 177 | 178 | // HTTP_RAW_REQUEST_DATA HTTP_RAW_POST_DATA 179 | $GLOBALS['HTTP_RAW_REQUEST_DATA'] = $GLOBALS['HTTP_RAW_POST_DATA'] = $http_body; 180 | 181 | // QUERY_STRING 182 | $_SERVER['QUERY_STRING'] = parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY); 183 | if ($_SERVER['QUERY_STRING']) { 184 | // $GET 185 | parse_str($_SERVER['QUERY_STRING'], $_GET); 186 | } else { 187 | $_SERVER['QUERY_STRING'] = ''; 188 | } 189 | 190 | // REQUEST 191 | $_REQUEST = array_merge($_GET, $_POST); 192 | 193 | // REMOTE_ADDR REMOTE_PORT 194 | $_SERVER['REMOTE_ADDR'] = $connection->getRemoteIp(); 195 | $_SERVER['REMOTE_PORT'] = $connection->getRemotePort(); 196 | 197 | return array('get' => $_GET, 'post' => $_POST, 'cookie' => $_COOKIE, 'server' => $_SERVER, 'files' => $_FILES); 198 | } 199 | 200 | /** 201 | * Http encode. 202 | * 203 | * @param string $content 204 | * @param TcpConnection $connection 205 | * @return string 206 | */ 207 | public static function encode($content, TcpConnection $connection) 208 | { 209 | // Default http-code. 210 | if (!isset(HttpCache::$header['Http-Code'])) { 211 | $header = "HTTP/1.1 200 OK\r\n"; 212 | } else { 213 | $header = HttpCache::$header['Http-Code'] . "\r\n"; 214 | unset(HttpCache::$header['Http-Code']); 215 | } 216 | 217 | // Content-Type 218 | if (!isset(HttpCache::$header['Content-Type'])) { 219 | $header .= "Content-Type: text/html;charset=utf-8\r\n"; 220 | } 221 | 222 | // other headers 223 | foreach (HttpCache::$header as $key => $item) { 224 | if ('Set-Cookie' === $key && is_array($item)) { 225 | foreach ($item as $it) { 226 | $header .= $it . "\r\n"; 227 | } 228 | } else { 229 | $header .= $item . "\r\n"; 230 | } 231 | } 232 | 233 | // header 234 | $header .= "Server: workerman/" . Worker::VERSION . "\r\nContent-Length: " . strlen($content) . "\r\n\r\n"; 235 | 236 | // save session 237 | self::sessionWriteClose(); 238 | 239 | // the whole http package 240 | return $header . $content; 241 | } 242 | 243 | /** 244 | * 设置http头 245 | * 246 | * @return bool|void 247 | */ 248 | public static function header($content, $replace = true, $http_response_code = 0) 249 | { 250 | if (PHP_SAPI != 'cli') { 251 | return $http_response_code ? header($content, $replace, $http_response_code) : header($content, $replace); 252 | } 253 | if (strpos($content, 'HTTP') === 0) { 254 | $key = 'Http-Code'; 255 | } else { 256 | $key = strstr($content, ":", true); 257 | if (empty($key)) { 258 | return false; 259 | } 260 | } 261 | 262 | if ('location' === strtolower($key) && !$http_response_code) { 263 | return self::header($content, true, 302); 264 | } 265 | 266 | if (isset(HttpCache::$codes[$http_response_code])) { 267 | HttpCache::$header['Http-Code'] = "HTTP/1.1 $http_response_code " . HttpCache::$codes[$http_response_code]; 268 | if ($key === 'Http-Code') { 269 | return true; 270 | } 271 | } 272 | 273 | if ($key === 'Set-Cookie') { 274 | HttpCache::$header[$key][] = $content; 275 | } else { 276 | HttpCache::$header[$key] = $content; 277 | } 278 | 279 | return true; 280 | } 281 | 282 | /** 283 | * Remove header. 284 | * 285 | * @param string $name 286 | * @return void 287 | */ 288 | public static function headerRemove($name) 289 | { 290 | if (PHP_SAPI != 'cli') { 291 | header_remove($name); 292 | return; 293 | } 294 | unset(HttpCache::$header[$name]); 295 | } 296 | 297 | /** 298 | * Set cookie. 299 | * 300 | * @param string $name 301 | * @param string $value 302 | * @param integer $maxage 303 | * @param string $path 304 | * @param string $domain 305 | * @param bool $secure 306 | * @param bool $HTTPOnly 307 | * @return bool|void 308 | */ 309 | public static function setcookie( 310 | $name, 311 | $value = '', 312 | $maxage = 0, 313 | $path = '', 314 | $domain = '', 315 | $secure = false, 316 | $HTTPOnly = false 317 | ) { 318 | if (PHP_SAPI != 'cli') { 319 | return setcookie($name, $value, $maxage, $path, $domain, $secure, $HTTPOnly); 320 | } 321 | return self::header( 322 | 'Set-Cookie: ' . $name . '=' . rawurlencode($value) 323 | . (empty($domain) ? '' : '; Domain=' . $domain) 324 | . (empty($maxage) ? '' : '; Max-Age=' . $maxage) 325 | . (empty($path) ? '' : '; Path=' . $path) 326 | . (!$secure ? '' : '; Secure') 327 | . (!$HTTPOnly ? '' : '; HttpOnly'), false); 328 | } 329 | 330 | /** 331 | * sessionStart 332 | * 333 | * @return bool 334 | */ 335 | public static function sessionStart() 336 | { 337 | if (PHP_SAPI != 'cli') { 338 | return session_start(); 339 | } 340 | 341 | self::tryGcSessions(); 342 | 343 | if (HttpCache::$instance->sessionStarted) { 344 | echo "already sessionStarted\n"; 345 | return true; 346 | } 347 | HttpCache::$instance->sessionStarted = true; 348 | // Generate a SID. 349 | if (!isset($_COOKIE[HttpCache::$sessionName]) || !is_file(HttpCache::$sessionPath . '/ses' . $_COOKIE[HttpCache::$sessionName])) { 350 | $file_name = tempnam(HttpCache::$sessionPath, 'ses'); 351 | if (!$file_name) { 352 | return false; 353 | } 354 | HttpCache::$instance->sessionFile = $file_name; 355 | $session_id = substr(basename($file_name), strlen('ses')); 356 | return self::setcookie( 357 | HttpCache::$sessionName 358 | , $session_id 359 | , ini_get('session.cookie_lifetime') 360 | , ini_get('session.cookie_path') 361 | , ini_get('session.cookie_domain') 362 | , ini_get('session.cookie_secure') 363 | , ini_get('session.cookie_httponly') 364 | ); 365 | } 366 | if (!HttpCache::$instance->sessionFile) { 367 | HttpCache::$instance->sessionFile = HttpCache::$sessionPath . '/ses' . $_COOKIE[HttpCache::$sessionName]; 368 | } 369 | // Read session from session file. 370 | if (HttpCache::$instance->sessionFile) { 371 | $raw = file_get_contents(HttpCache::$instance->sessionFile); 372 | if ($raw) { 373 | $_SESSION = unserialize($raw); 374 | } 375 | } 376 | return true; 377 | } 378 | 379 | /** 380 | * Save session. 381 | * 382 | * @return bool 383 | */ 384 | public static function sessionWriteClose() 385 | { 386 | if (PHP_SAPI != 'cli') { 387 | return session_write_close(); 388 | } 389 | if (!empty(HttpCache::$instance->sessionStarted) && !empty($_SESSION)) { 390 | $session_str = serialize($_SESSION); 391 | if ($session_str && HttpCache::$instance->sessionFile) { 392 | return file_put_contents(HttpCache::$instance->sessionFile, $session_str); 393 | } 394 | } 395 | return empty($_SESSION); 396 | } 397 | 398 | /** 399 | * End, like call exit in php-fpm. 400 | * 401 | * @param string $msg 402 | * @throws \Exception 403 | */ 404 | public static function end($msg = '') 405 | { 406 | if (PHP_SAPI != 'cli') { 407 | exit($msg); 408 | } 409 | if ($msg) { 410 | echo $msg; 411 | } 412 | throw new \Exception('jump_exit'); 413 | } 414 | 415 | /** 416 | * Get mime types. 417 | * 418 | * @return string 419 | */ 420 | public static function getMimeTypesFile() 421 | { 422 | return __DIR__ . '/Http/mime.types'; 423 | } 424 | 425 | /** 426 | * Parse $_FILES. 427 | * 428 | * @param string $http_body 429 | * @param string $http_post_boundary 430 | * @return void 431 | */ 432 | protected static function parseUploadFiles($http_body, $http_post_boundary) 433 | { 434 | $http_body = substr($http_body, 0, strlen($http_body) - (strlen($http_post_boundary) + 4)); 435 | $boundary_data_array = explode($http_post_boundary . "\r\n", $http_body); 436 | if ($boundary_data_array[0] === '') { 437 | unset($boundary_data_array[0]); 438 | } 439 | $key = -1; 440 | foreach ($boundary_data_array as $boundary_data_buffer) { 441 | list($boundary_header_buffer, $boundary_value) = explode("\r\n\r\n", $boundary_data_buffer, 2); 442 | // Remove \r\n from the end of buffer. 443 | $boundary_value = substr($boundary_value, 0, -2); 444 | $key ++; 445 | foreach (explode("\r\n", $boundary_header_buffer) as $item) { 446 | list($header_key, $header_value) = explode(": ", $item); 447 | $header_key = strtolower($header_key); 448 | switch ($header_key) { 449 | case "content-disposition": 450 | // Is file data. 451 | if (preg_match('/name="(.*?)"; filename="(.*?)"$/', $header_value, $match)) { 452 | // Parse $_FILES. 453 | $_FILES[$key] = array( 454 | 'name' => $match[1], 455 | 'file_name' => $match[2], 456 | 'file_data' => $boundary_value, 457 | 'file_size' => strlen($boundary_value), 458 | ); 459 | continue; 460 | } // Is post field. 461 | else { 462 | // Parse $_POST. 463 | if (preg_match('/name="(.*?)"$/', $header_value, $match)) { 464 | $_POST[$match[1]] = $boundary_value; 465 | } 466 | } 467 | break; 468 | case "content-type": 469 | // add file_type 470 | $_FILES[$key]['file_type'] = trim($header_value); 471 | break; 472 | } 473 | } 474 | } 475 | } 476 | 477 | /** 478 | * Try GC sessions. 479 | * 480 | * @return void 481 | */ 482 | public static function tryGcSessions() 483 | { 484 | if (HttpCache::$sessionGcProbability <= 0 || 485 | HttpCache::$sessionGcDivisor <= 0 || 486 | rand(1, HttpCache::$sessionGcDivisor) > HttpCache::$sessionGcProbability) { 487 | return; 488 | } 489 | 490 | $time_now = time(); 491 | foreach(glob(HttpCache::$sessionPath.'/ses*') as $file) { 492 | if(is_file($file) && $time_now - filemtime($file) > HttpCache::$sessionGcMaxLifeTime) { 493 | unlink($file); 494 | } 495 | } 496 | } 497 | } 498 | 499 | /** 500 | * Http cache for the current http response. 501 | */ 502 | class HttpCache 503 | { 504 | public static $codes = array( 505 | 100 => 'Continue', 506 | 101 => 'Switching Protocols', 507 | 200 => 'OK', 508 | 201 => 'Created', 509 | 202 => 'Accepted', 510 | 203 => 'Non-Authoritative Information', 511 | 204 => 'No Content', 512 | 205 => 'Reset Content', 513 | 206 => 'Partial Content', 514 | 300 => 'Multiple Choices', 515 | 301 => 'Moved Permanently', 516 | 302 => 'Found', 517 | 303 => 'See Other', 518 | 304 => 'Not Modified', 519 | 305 => 'Use Proxy', 520 | 306 => '(Unused)', 521 | 307 => 'Temporary Redirect', 522 | 400 => 'Bad Request', 523 | 401 => 'Unauthorized', 524 | 402 => 'Payment Required', 525 | 403 => 'Forbidden', 526 | 404 => 'Not Found', 527 | 405 => 'Method Not Allowed', 528 | 406 => 'Not Acceptable', 529 | 407 => 'Proxy Authentication Required', 530 | 408 => 'Request Timeout', 531 | 409 => 'Conflict', 532 | 410 => 'Gone', 533 | 411 => 'Length Required', 534 | 412 => 'Precondition Failed', 535 | 413 => 'Request Entity Too Large', 536 | 414 => 'Request-URI Too Long', 537 | 415 => 'Unsupported Media Type', 538 | 416 => 'Requested Range Not Satisfiable', 539 | 417 => 'Expectation Failed', 540 | 422 => 'Unprocessable Entity', 541 | 423 => 'Locked', 542 | 500 => 'Internal Server Error', 543 | 501 => 'Not Implemented', 544 | 502 => 'Bad Gateway', 545 | 503 => 'Service Unavailable', 546 | 504 => 'Gateway Timeout', 547 | 505 => 'HTTP Version Not Supported', 548 | ); 549 | 550 | /** 551 | * @var HttpCache 552 | */ 553 | public static $instance = null; 554 | public static $header = array(); 555 | public static $sessionPath = ''; 556 | public static $sessionName = ''; 557 | public static $sessionGcProbability = 1; 558 | public static $sessionGcDivisor = 1000; 559 | public static $sessionGcMaxLifeTime = 1440; 560 | public $sessionStarted = false; 561 | public $sessionFile = ''; 562 | 563 | public static function init() 564 | { 565 | self::$sessionName = ini_get('session.name'); 566 | self::$sessionPath = @session_save_path(); 567 | if (!self::$sessionPath || strpos(self::$sessionPath, 'tcp://') === 0) { 568 | self::$sessionPath = sys_get_temp_dir(); 569 | } 570 | 571 | if ($gc_probability = ini_get('session.gc_probability')) { 572 | self::$sessionGcProbability = $gc_probability; 573 | } 574 | 575 | if ($gc_divisor = ini_get('session.gc_divisor')) { 576 | self::$sessionGcDivisor = $gc_divisor; 577 | } 578 | 579 | if ($gc_max_life_time = ini_get('session.gc_maxlifetime')) { 580 | self::$sessionGcMaxLifeTime = $gc_max_life_time; 581 | } 582 | } 583 | } 584 | 585 | HttpCache::init(); 586 | -------------------------------------------------------------------------------- /workerman/Protocols/Http/mime.types: -------------------------------------------------------------------------------- 1 | 2 | types { 3 | text/html html htm shtml; 4 | text/css css; 5 | text/xml xml; 6 | image/gif gif; 7 | image/jpeg jpeg jpg; 8 | application/x-javascript js; 9 | application/atom+xml atom; 10 | application/rss+xml rss; 11 | 12 | text/mathml mml; 13 | text/plain txt; 14 | text/vnd.sun.j2me.app-descriptor jad; 15 | text/vnd.wap.wml wml; 16 | text/x-component htc; 17 | 18 | image/png png; 19 | image/tiff tif tiff; 20 | image/vnd.wap.wbmp wbmp; 21 | image/x-icon ico; 22 | image/x-jng jng; 23 | image/x-ms-bmp bmp; 24 | image/svg+xml svg svgz; 25 | image/webp webp; 26 | 27 | application/java-archive jar war ear; 28 | application/mac-binhex40 hqx; 29 | application/msword doc; 30 | application/pdf pdf; 31 | application/postscript ps eps ai; 32 | application/rtf rtf; 33 | application/vnd.ms-excel xls; 34 | application/vnd.ms-powerpoint ppt; 35 | application/vnd.wap.wmlc wmlc; 36 | application/vnd.google-earth.kml+xml kml; 37 | application/vnd.google-earth.kmz kmz; 38 | application/x-7z-compressed 7z; 39 | application/x-cocoa cco; 40 | application/x-java-archive-diff jardiff; 41 | application/x-java-jnlp-file jnlp; 42 | application/x-makeself run; 43 | application/x-perl pl pm; 44 | application/x-pilot prc pdb; 45 | application/x-rar-compressed rar; 46 | application/x-redhat-package-manager rpm; 47 | application/x-sea sea; 48 | application/x-shockwave-flash swf; 49 | application/x-stuffit sit; 50 | application/x-tcl tcl tk; 51 | application/x-x509-ca-cert der pem crt; 52 | application/x-xpinstall xpi; 53 | application/xhtml+xml xhtml; 54 | application/zip zip; 55 | 56 | application/octet-stream bin exe dll; 57 | application/octet-stream deb; 58 | application/octet-stream dmg; 59 | application/octet-stream eot; 60 | application/octet-stream iso img; 61 | application/octet-stream msi msp msm; 62 | 63 | audio/midi mid midi kar; 64 | audio/mpeg mp3; 65 | audio/ogg ogg; 66 | audio/x-m4a m4a; 67 | audio/x-realaudio ra; 68 | 69 | video/3gpp 3gpp 3gp; 70 | video/mp4 mp4; 71 | video/mpeg mpeg mpg; 72 | video/quicktime mov; 73 | video/webm webm; 74 | video/x-flv flv; 75 | video/x-m4v m4v; 76 | video/x-mng mng; 77 | video/x-ms-asf asx asf; 78 | video/x-ms-wmv wmv; 79 | video/x-msvideo avi; 80 | } 81 | -------------------------------------------------------------------------------- /workerman/Protocols/ProtocolInterface.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | namespace Workerman\Protocols; 15 | 16 | use Workerman\Connection\ConnectionInterface; 17 | 18 | /** 19 | * Protocol interface 20 | */ 21 | interface ProtocolInterface 22 | { 23 | /** 24 | * Check the integrity of the package. 25 | * Please return the length of package. 26 | * If length is unknow please return 0 that mean wating more data. 27 | * If the package has something wrong please return false the connection will be closed. 28 | * 29 | * @param ConnectionInterface $connection 30 | * @param string $recv_buffer 31 | * @return int|false 32 | */ 33 | public static function input($recv_buffer, ConnectionInterface $connection); 34 | 35 | /** 36 | * Decode package and emit onMessage($message) callback, $message is the result that decode returned. 37 | * 38 | * @param ConnectionInterface $connection 39 | * @param string $recv_buffer 40 | * @return mixed 41 | */ 42 | public static function decode($recv_buffer, ConnectionInterface $connection); 43 | 44 | /** 45 | * Encode package brefore sending to client. 46 | * 47 | * @param ConnectionInterface $connection 48 | * @param mixed $data 49 | * @return string 50 | */ 51 | public static function encode($data, ConnectionInterface $connection); 52 | } 53 | -------------------------------------------------------------------------------- /workerman/Protocols/Text.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | namespace Workerman\Protocols; 15 | 16 | use Workerman\Connection\TcpConnection; 17 | 18 | /** 19 | * Text Protocol. 20 | */ 21 | class Text 22 | { 23 | /** 24 | * Check the integrity of the package. 25 | * 26 | * @param string $buffer 27 | * @param TcpConnection $connection 28 | * @return int 29 | */ 30 | public static function input($buffer, TcpConnection $connection) 31 | { 32 | // Judge whether the package length exceeds the limit. 33 | if (strlen($buffer) >= TcpConnection::$maxPackageSize) { 34 | $connection->close(); 35 | return 0; 36 | } 37 | // Find the position of "\n". 38 | $pos = strpos($buffer, "\n"); 39 | // No "\n", packet length is unknown, continue to wait for the data so return 0. 40 | if ($pos === false) { 41 | return 0; 42 | } 43 | // Return the current package length. 44 | return $pos + 1; 45 | } 46 | 47 | /** 48 | * Encode. 49 | * 50 | * @param string $buffer 51 | * @return string 52 | */ 53 | public static function encode($buffer) 54 | { 55 | // Add "\n" 56 | return $buffer . "\n"; 57 | } 58 | 59 | /** 60 | * Decode. 61 | * 62 | * @param string $buffer 63 | * @return string 64 | */ 65 | public static function decode($buffer) 66 | { 67 | // Remove "\n" 68 | return trim($buffer); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /workerman/Protocols/Websocket.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | namespace Workerman\Protocols; 15 | 16 | use Workerman\Connection\ConnectionInterface; 17 | use Workerman\Connection\TcpConnection; 18 | use Workerman\Worker; 19 | 20 | /** 21 | * WebSocket protocol. 22 | */ 23 | class Websocket implements \Workerman\Protocols\ProtocolInterface 24 | { 25 | /** 26 | * Websocket blob type. 27 | * 28 | * @var string 29 | */ 30 | const BINARY_TYPE_BLOB = "\x81"; 31 | 32 | /** 33 | * Websocket arraybuffer type. 34 | * 35 | * @var string 36 | */ 37 | const BINARY_TYPE_ARRAYBUFFER = "\x82"; 38 | 39 | /** 40 | * Check the integrity of the package. 41 | * 42 | * @param string $buffer 43 | * @param ConnectionInterface $connection 44 | * @return int 45 | */ 46 | public static function input($buffer, ConnectionInterface $connection) 47 | { 48 | // Receive length. 49 | $recv_len = strlen($buffer); 50 | // We need more data. 51 | if ($recv_len < 2) { 52 | return 0; 53 | } 54 | 55 | // Has not yet completed the handshake. 56 | if (empty($connection->websocketHandshake)) { 57 | return static::dealHandshake($buffer, $connection); 58 | } 59 | 60 | // Buffer websocket frame data. 61 | if ($connection->websocketCurrentFrameLength) { 62 | // We need more frame data. 63 | if ($connection->websocketCurrentFrameLength > $recv_len) { 64 | // Return 0, because it is not clear the full packet length, waiting for the frame of fin=1. 65 | return 0; 66 | } 67 | } else { 68 | $firstbyte = ord($buffer[0]); 69 | $secondbyte = ord($buffer[1]); 70 | $data_len = $secondbyte & 127; 71 | $is_fin_frame = $firstbyte >> 7; 72 | $masked = $secondbyte >> 7; 73 | $opcode = $firstbyte & 0xf; 74 | switch ($opcode) { 75 | case 0x0: 76 | break; 77 | // Blob type. 78 | case 0x1: 79 | break; 80 | // Arraybuffer type. 81 | case 0x2: 82 | break; 83 | // Close package. 84 | case 0x8: 85 | // Try to emit onWebSocketClose callback. 86 | if (isset($connection->onWebSocketClose) || isset($connection->worker->onWebSocketClose)) { 87 | try { 88 | call_user_func(isset($connection->onWebSocketClose)?$connection->onWebSocketClose:$connection->worker->onWebSocketClose, $connection); 89 | } catch (\Exception $e) { 90 | Worker::log($e); 91 | exit(250); 92 | } catch (\Error $e) { 93 | Worker::log($e); 94 | exit(250); 95 | } 96 | } // Close connection. 97 | else { 98 | $connection->close(); 99 | } 100 | return 0; 101 | // Ping package. 102 | case 0x9: 103 | // Try to emit onWebSocketPing callback. 104 | if (isset($connection->onWebSocketPing) || isset($connection->worker->onWebSocketPing)) { 105 | try { 106 | call_user_func(isset($connection->onWebSocketPing)?$connection->onWebSocketPing:$connection->worker->onWebSocketPing, $connection); 107 | } catch (\Exception $e) { 108 | Worker::log($e); 109 | exit(250); 110 | } catch (\Error $e) { 111 | Worker::log($e); 112 | exit(250); 113 | } 114 | } // Send pong package to client. 115 | else { 116 | $connection->send(pack('H*', '8a00'), true); 117 | } 118 | 119 | // Consume data from receive buffer. 120 | if (!$data_len) { 121 | $head_len = $masked ? 6 : 2; 122 | $connection->consumeRecvBuffer($head_len); 123 | if ($recv_len > $head_len) { 124 | return static::input(substr($buffer, $head_len), $connection); 125 | } 126 | return 0; 127 | } 128 | break; 129 | // Pong package. 130 | case 0xa: 131 | // Try to emit onWebSocketPong callback. 132 | if (isset($connection->onWebSocketPong) || isset($connection->worker->onWebSocketPong)) { 133 | try { 134 | call_user_func(isset($connection->onWebSocketPong)?$connection->onWebSocketPong:$connection->worker->onWebSocketPong, $connection); 135 | } catch (\Exception $e) { 136 | Worker::log($e); 137 | exit(250); 138 | } catch (\Error $e) { 139 | Worker::log($e); 140 | exit(250); 141 | } 142 | } 143 | // Consume data from receive buffer. 144 | if (!$data_len) { 145 | $head_len = $masked ? 6 : 2; 146 | $connection->consumeRecvBuffer($head_len); 147 | if ($recv_len > $head_len) { 148 | return static::input(substr($buffer, $head_len), $connection); 149 | } 150 | return 0; 151 | } 152 | break; 153 | // Wrong opcode. 154 | default : 155 | echo "error opcode $opcode and close websocket connection. Buffer:" . bin2hex($buffer) . "\n"; 156 | $connection->close(); 157 | return 0; 158 | } 159 | 160 | // Calculate packet length. 161 | $head_len = 6; 162 | if ($data_len === 126) { 163 | $head_len = 8; 164 | if ($head_len > $recv_len) { 165 | return 0; 166 | } 167 | $pack = unpack('nn/ntotal_len', $buffer); 168 | $data_len = $pack['total_len']; 169 | } else { 170 | if ($data_len === 127) { 171 | $head_len = 14; 172 | if ($head_len > $recv_len) { 173 | return 0; 174 | } 175 | $arr = unpack('n/N2c', $buffer); 176 | $data_len = $arr['c1']*4294967296 + $arr['c2']; 177 | } 178 | } 179 | $current_frame_length = $head_len + $data_len; 180 | 181 | $total_package_size = strlen($connection->websocketDataBuffer) + $current_frame_length; 182 | if ($total_package_size > TcpConnection::$maxPackageSize) { 183 | echo "error package. package_length=$total_package_size\n"; 184 | $connection->close(); 185 | return 0; 186 | } 187 | 188 | if ($is_fin_frame) { 189 | return $current_frame_length; 190 | } else { 191 | $connection->websocketCurrentFrameLength = $current_frame_length; 192 | } 193 | } 194 | 195 | // Received just a frame length data. 196 | if ($connection->websocketCurrentFrameLength === $recv_len) { 197 | static::decode($buffer, $connection); 198 | $connection->consumeRecvBuffer($connection->websocketCurrentFrameLength); 199 | $connection->websocketCurrentFrameLength = 0; 200 | return 0; 201 | } // The length of the received data is greater than the length of a frame. 202 | elseif ($connection->websocketCurrentFrameLength < $recv_len) { 203 | static::decode(substr($buffer, 0, $connection->websocketCurrentFrameLength), $connection); 204 | $connection->consumeRecvBuffer($connection->websocketCurrentFrameLength); 205 | $current_frame_length = $connection->websocketCurrentFrameLength; 206 | $connection->websocketCurrentFrameLength = 0; 207 | // Continue to read next frame. 208 | return static::input(substr($buffer, $current_frame_length), $connection); 209 | } // The length of the received data is less than the length of a frame. 210 | else { 211 | return 0; 212 | } 213 | } 214 | 215 | /** 216 | * Websocket encode. 217 | * 218 | * @param string $buffer 219 | * @param ConnectionInterface $connection 220 | * @return string 221 | */ 222 | public static function encode($buffer, ConnectionInterface $connection) 223 | { 224 | if (!is_scalar($buffer)) { 225 | throw new \Exception("You can't send(" . gettype($buffer) . ") to client, you need to convert it to a string. "); 226 | } 227 | $len = strlen($buffer); 228 | if (empty($connection->websocketType)) { 229 | $connection->websocketType = static::BINARY_TYPE_BLOB; 230 | } 231 | 232 | $first_byte = $connection->websocketType; 233 | 234 | if ($len <= 125) { 235 | $encode_buffer = $first_byte . chr($len) . $buffer; 236 | } else { 237 | if ($len <= 65535) { 238 | $encode_buffer = $first_byte . chr(126) . pack("n", $len) . $buffer; 239 | } else { 240 | $encode_buffer = $first_byte . chr(127) . pack("xxxxN", $len) . $buffer; 241 | } 242 | } 243 | 244 | // Handshake not completed so temporary buffer websocket data waiting for send. 245 | if (empty($connection->websocketHandshake)) { 246 | if (empty($connection->tmpWebsocketData)) { 247 | $connection->tmpWebsocketData = ''; 248 | } 249 | // If buffer has already full then discard the current package. 250 | if (strlen($connection->tmpWebsocketData) > $connection->maxSendBufferSize) { 251 | if ($connection->onError) { 252 | try { 253 | call_user_func($connection->onError, $connection, WORKERMAN_SEND_FAIL, 'send buffer full and drop package'); 254 | } catch (\Exception $e) { 255 | Worker::log($e); 256 | exit(250); 257 | } catch (\Error $e) { 258 | Worker::log($e); 259 | exit(250); 260 | } 261 | } 262 | return ''; 263 | } 264 | $connection->tmpWebsocketData .= $encode_buffer; 265 | // Check buffer is full. 266 | if ($connection->maxSendBufferSize <= strlen($connection->tmpWebsocketData)) { 267 | if ($connection->onBufferFull) { 268 | try { 269 | call_user_func($connection->onBufferFull, $connection); 270 | } catch (\Exception $e) { 271 | Worker::log($e); 272 | exit(250); 273 | } catch (\Error $e) { 274 | Worker::log($e); 275 | exit(250); 276 | } 277 | } 278 | } 279 | 280 | // Return empty string. 281 | return ''; 282 | } 283 | 284 | return $encode_buffer; 285 | } 286 | 287 | /** 288 | * Websocket decode. 289 | * 290 | * @param string $buffer 291 | * @param ConnectionInterface $connection 292 | * @return string 293 | */ 294 | public static function decode($buffer, ConnectionInterface $connection) 295 | { 296 | $masks = $data = $decoded = null; 297 | $len = ord($buffer[1]) & 127; 298 | if ($len === 126) { 299 | $masks = substr($buffer, 4, 4); 300 | $data = substr($buffer, 8); 301 | } else { 302 | if ($len === 127) { 303 | $masks = substr($buffer, 10, 4); 304 | $data = substr($buffer, 14); 305 | } else { 306 | $masks = substr($buffer, 2, 4); 307 | $data = substr($buffer, 6); 308 | } 309 | } 310 | for ($index = 0; $index < strlen($data); $index++) { 311 | $decoded .= $data[$index] ^ $masks[$index % 4]; 312 | } 313 | if ($connection->websocketCurrentFrameLength) { 314 | $connection->websocketDataBuffer .= $decoded; 315 | return $connection->websocketDataBuffer; 316 | } else { 317 | if ($connection->websocketDataBuffer !== '') { 318 | $decoded = $connection->websocketDataBuffer . $decoded; 319 | $connection->websocketDataBuffer = ''; 320 | } 321 | return $decoded; 322 | } 323 | } 324 | 325 | /** 326 | * Websocket handshake. 327 | * 328 | * @param string $buffer 329 | * @param \Workerman\Connection\TcpConnection $connection 330 | * @return int 331 | */ 332 | protected static function dealHandshake($buffer, $connection) 333 | { 334 | // HTTP protocol. 335 | if (0 === strpos($buffer, 'GET')) { 336 | // Find \r\n\r\n. 337 | $heder_end_pos = strpos($buffer, "\r\n\r\n"); 338 | if (!$heder_end_pos) { 339 | return 0; 340 | } 341 | $header_length = $heder_end_pos + 4; 342 | 343 | // Get Sec-WebSocket-Key. 344 | $Sec_WebSocket_Key = ''; 345 | if (preg_match("/Sec-WebSocket-Key: *(.*?)\r\n/i", $buffer, $match)) { 346 | $Sec_WebSocket_Key = $match[1]; 347 | } else { 348 | $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 for detail.", 349 | true); 350 | $connection->close(); 351 | return 0; 352 | } 353 | // Calculation websocket key. 354 | $new_key = base64_encode(sha1($Sec_WebSocket_Key . "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", true)); 355 | // Handshake response data. 356 | $handshake_message = "HTTP/1.1 101 Switching Protocols\r\n"; 357 | $handshake_message .= "Upgrade: websocket\r\n"; 358 | $handshake_message .= "Sec-WebSocket-Version: 13\r\n"; 359 | $handshake_message .= "Connection: Upgrade\r\n"; 360 | $handshake_message .= "Server: workerman/".Worker::VERSION."\r\n"; 361 | $handshake_message .= "Sec-WebSocket-Accept: " . $new_key . "\r\n\r\n"; 362 | // Mark handshake complete.. 363 | $connection->websocketHandshake = true; 364 | // Websocket data buffer. 365 | $connection->websocketDataBuffer = ''; 366 | // Current websocket frame length. 367 | $connection->websocketCurrentFrameLength = 0; 368 | // Current websocket frame data. 369 | $connection->websocketCurrentFrameBuffer = ''; 370 | // Consume handshake data. 371 | $connection->consumeRecvBuffer($header_length); 372 | // Send handshake response. 373 | $connection->send($handshake_message, true); 374 | 375 | // There are data waiting to be sent. 376 | if (!empty($connection->tmpWebsocketData)) { 377 | $connection->send($connection->tmpWebsocketData, true); 378 | $connection->tmpWebsocketData = ''; 379 | } 380 | // blob or arraybuffer 381 | if (empty($connection->websocketType)) { 382 | $connection->websocketType = static::BINARY_TYPE_BLOB; 383 | } 384 | // Try to emit onWebSocketConnect callback. 385 | if (isset($connection->onWebSocketConnect) || isset($connection->worker->onWebSocketConnect)) { 386 | static::parseHttpHeader($buffer); 387 | try { 388 | call_user_func(isset($connection->onWebSocketConnect)?$connection->onWebSocketConnect:$connection->worker->onWebSocketConnect, $connection, $buffer); 389 | } catch (\Exception $e) { 390 | Worker::log($e); 391 | exit(250); 392 | } catch (\Error $e) { 393 | Worker::log($e); 394 | exit(250); 395 | } 396 | if (!empty($_SESSION) && class_exists('\GatewayWorker\Lib\Context')) { 397 | $connection->session = \GatewayWorker\Lib\Context::sessionEncode($_SESSION); 398 | } 399 | $_GET = $_SERVER = $_SESSION = $_COOKIE = array(); 400 | } 401 | if (strlen($buffer) > $header_length) { 402 | return static::input(substr($buffer, $header_length), $connection); 403 | } 404 | return 0; 405 | } // Is flash policy-file-request. 406 | elseif (0 === strpos($buffer, 'send($policy_xml, true); 409 | $connection->consumeRecvBuffer(strlen($buffer)); 410 | return 0; 411 | } 412 | // Bad websocket handshake request. 413 | $connection->send("HTTP/1.1 400 Bad Request\r\n\r\n400 Bad Request
Invalid handshake data for websocket.
See http://wiki.workerman.net/Error1 for detail.", 414 | true); 415 | $connection->close(); 416 | return 0; 417 | } 418 | 419 | /** 420 | * Parse http header. 421 | * 422 | * @param string $buffer 423 | * @return void 424 | */ 425 | protected static function parseHttpHeader($buffer) 426 | { 427 | // Parse headers. 428 | list($http_header, ) = explode("\r\n\r\n", $buffer, 2); 429 | $header_data = explode("\r\n", $http_header); 430 | 431 | if ($_SERVER) { 432 | $_SERVER = array(); 433 | } 434 | 435 | list($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI'], $_SERVER['SERVER_PROTOCOL']) = explode(' ', 436 | $header_data[0]); 437 | 438 | unset($header_data[0]); 439 | foreach ($header_data as $content) { 440 | // \r\n\r\n 441 | if (empty($content)) { 442 | continue; 443 | } 444 | list($key, $value) = explode(':', $content, 2); 445 | $key = str_replace('-', '_', strtoupper($key)); 446 | $value = trim($value); 447 | $_SERVER['HTTP_' . $key] = $value; 448 | switch ($key) { 449 | // HTTP_HOST 450 | case 'HOST': 451 | $tmp = explode(':', $value); 452 | $_SERVER['SERVER_NAME'] = $tmp[0]; 453 | if (isset($tmp[1])) { 454 | $_SERVER['SERVER_PORT'] = $tmp[1]; 455 | } 456 | break; 457 | // cookie 458 | case 'COOKIE': 459 | parse_str(str_replace('; ', '&', $_SERVER['HTTP_COOKIE']), $_COOKIE); 460 | break; 461 | } 462 | } 463 | 464 | // QUERY_STRING 465 | $_SERVER['QUERY_STRING'] = parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY); 466 | if ($_SERVER['QUERY_STRING']) { 467 | // $GET 468 | parse_str($_SERVER['QUERY_STRING'], $_GET); 469 | } else { 470 | $_SERVER['QUERY_STRING'] = ''; 471 | } 472 | } 473 | } 474 | -------------------------------------------------------------------------------- /workerman/Protocols/Ws.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | namespace Workerman\Protocols; 15 | 16 | use Workerman\Worker; 17 | use Workerman\Lib\Timer; 18 | use Workerman\Connection\TcpConnection; 19 | 20 | /** 21 | * Websocket protocol for client. 22 | */ 23 | class Ws 24 | { 25 | /** 26 | * Websocket blob type. 27 | * 28 | * @var string 29 | */ 30 | const BINARY_TYPE_BLOB = "\x81"; 31 | 32 | /** 33 | * Websocket arraybuffer type. 34 | * 35 | * @var string 36 | */ 37 | const BINARY_TYPE_ARRAYBUFFER = "\x82"; 38 | 39 | /** 40 | * Check the integrity of the package. 41 | * 42 | * @param string $buffer 43 | * @param ConnectionInterface $connection 44 | * @return int 45 | */ 46 | public static function input($buffer, $connection) 47 | { 48 | if (empty($connection->handshakeStep)) { 49 | echo "recv data before handshake. Buffer:" . bin2hex($buffer) . "\n"; 50 | return false; 51 | } 52 | // Recv handshake response 53 | if ($connection->handshakeStep === 1) { 54 | return self::dealHandshake($buffer, $connection); 55 | } 56 | $recv_len = strlen($buffer); 57 | if ($recv_len < 2) { 58 | return 0; 59 | } 60 | // Buffer websocket frame data. 61 | if ($connection->websocketCurrentFrameLength) { 62 | // We need more frame data. 63 | if ($connection->websocketCurrentFrameLength > $recv_len) { 64 | // Return 0, because it is not clear the full packet length, waiting for the frame of fin=1. 65 | return 0; 66 | } 67 | } else { 68 | 69 | $firstbyte = ord($buffer[0]); 70 | $secondbyte = ord($buffer[1]); 71 | $data_len = $secondbyte & 127; 72 | $is_fin_frame = $firstbyte >> 7; 73 | $masked = $secondbyte >> 7; 74 | $opcode = $firstbyte & 0xf; 75 | 76 | switch ($opcode) { 77 | case 0x0: 78 | break; 79 | // Blob type. 80 | case 0x1: 81 | break; 82 | // Arraybuffer type. 83 | case 0x2: 84 | break; 85 | // Close package. 86 | case 0x8: 87 | // Try to emit onWebSocketClose callback. 88 | if (isset($connection->onWebSocketClose)) { 89 | try { 90 | call_user_func($connection->onWebSocketClose, $connection); 91 | } catch (\Exception $e) { 92 | Worker::log($e); 93 | exit(250); 94 | } catch (\Error $e) { 95 | Worker::log($e); 96 | exit(250); 97 | } 98 | } // Close connection. 99 | else { 100 | $connection->close(); 101 | } 102 | return 0; 103 | // Ping package. 104 | case 0x9: 105 | // Try to emit onWebSocketPing callback. 106 | if (isset($connection->onWebSocketPing)) { 107 | try { 108 | call_user_func($connection->onWebSocketPing, $connection); 109 | } catch (\Exception $e) { 110 | Worker::log($e); 111 | exit(250); 112 | } catch (\Error $e) { 113 | Worker::log($e); 114 | exit(250); 115 | } 116 | } // Send pong package to client. 117 | else { 118 | $connection->send(pack('H*', '8a00'), true); 119 | } 120 | // Consume data from receive buffer. 121 | if (!$data_len) { 122 | $head_len = $masked ? 6 : 2; 123 | $connection->consumeRecvBuffer($head_len); 124 | if ($recv_len > $head_len) { 125 | return self::input(substr($buffer, $head_len), $connection); 126 | } 127 | return 0; 128 | } 129 | break; 130 | // Pong package. 131 | case 0xa: 132 | // Try to emit onWebSocketPong callback. 133 | if (isset($connection->onWebSocketPong)) { 134 | try { 135 | call_user_func($connection->onWebSocketPong, $connection); 136 | } catch (\Exception $e) { 137 | Worker::log($e); 138 | exit(250); 139 | } catch (\Error $e) { 140 | Worker::log($e); 141 | exit(250); 142 | } 143 | } 144 | // Consume data from receive buffer. 145 | if (!$data_len) { 146 | $head_len = $masked ? 6 : 2; 147 | $connection->consumeRecvBuffer($head_len); 148 | if ($recv_len > $head_len) { 149 | return self::input(substr($buffer, $head_len), $connection); 150 | } 151 | return 0; 152 | } 153 | break; 154 | // Wrong opcode. 155 | default : 156 | echo "error opcode $opcode and close websocket connection. Buffer:" . $buffer . "\n"; 157 | $connection->close(); 158 | return 0; 159 | } 160 | // Calculate packet length. 161 | if ($data_len === 126) { 162 | if (strlen($buffer) < 6) { 163 | return 0; 164 | } 165 | $pack = unpack('nn/ntotal_len', $buffer); 166 | $current_frame_length = $pack['total_len'] + 4; 167 | } else if ($data_len === 127) { 168 | if (strlen($buffer) < 10) { 169 | return 0; 170 | } 171 | $arr = unpack('n/N2c', $buffer); 172 | $current_frame_length = $arr['c1']*4294967296 + $arr['c2'] + 10; 173 | } else { 174 | $current_frame_length = $data_len + 2; 175 | } 176 | 177 | $total_package_size = strlen($connection->websocketDataBuffer) + $current_frame_length; 178 | if ($total_package_size > TcpConnection::$maxPackageSize) { 179 | echo "error package. package_length=$total_package_size\n"; 180 | $connection->close(); 181 | return 0; 182 | } 183 | 184 | if ($is_fin_frame) { 185 | return $current_frame_length; 186 | } else { 187 | $connection->websocketCurrentFrameLength = $current_frame_length; 188 | } 189 | } 190 | // Received just a frame length data. 191 | if ($connection->websocketCurrentFrameLength === $recv_len) { 192 | self::decode($buffer, $connection); 193 | $connection->consumeRecvBuffer($connection->websocketCurrentFrameLength); 194 | $connection->websocketCurrentFrameLength = 0; 195 | return 0; 196 | } // The length of the received data is greater than the length of a frame. 197 | elseif ($connection->websocketCurrentFrameLength < $recv_len) { 198 | self::decode(substr($buffer, 0, $connection->websocketCurrentFrameLength), $connection); 199 | $connection->consumeRecvBuffer($connection->websocketCurrentFrameLength); 200 | $current_frame_length = $connection->websocketCurrentFrameLength; 201 | $connection->websocketCurrentFrameLength = 0; 202 | // Continue to read next frame. 203 | return self::input(substr($buffer, $current_frame_length), $connection); 204 | } // The length of the received data is less than the length of a frame. 205 | else { 206 | return 0; 207 | } 208 | } 209 | 210 | /** 211 | * Websocket encode. 212 | * 213 | * @param string $buffer 214 | * @param ConnectionInterface $connection 215 | * @return string 216 | */ 217 | public static function encode($payload, $connection) 218 | { 219 | if (empty($connection->websocketType)) { 220 | $connection->websocketType = self::BINARY_TYPE_BLOB; 221 | } 222 | $payload = (string)$payload; 223 | if (empty($connection->handshakeStep)) { 224 | self::sendHandshake($connection); 225 | } 226 | $mask = 1; 227 | $mask_key = "\x00\x00\x00\x00"; 228 | 229 | $pack = ''; 230 | $length = $length_flag = strlen($payload); 231 | if (65535 < $length) { 232 | $pack = pack('NN', ($length & 0xFFFFFFFF00000000) >> 32, $length & 0x00000000FFFFFFFF); 233 | $length_flag = 127; 234 | } else if (125 < $length) { 235 | $pack = pack('n*', $length); 236 | $length_flag = 126; 237 | } 238 | 239 | $head = ($mask << 7) | $length_flag; 240 | $head = $connection->websocketType . chr($head) . $pack; 241 | 242 | $frame = $head . $mask_key; 243 | // append payload to frame: 244 | for ($i = 0; $i < $length; $i++) { 245 | $frame .= $payload[$i] ^ $mask_key[$i % 4]; 246 | } 247 | if ($connection->handshakeStep === 1) { 248 | // If buffer has already full then discard the current package. 249 | if (strlen($connection->tmpWebsocketData) > $connection->maxSendBufferSize) { 250 | if ($connection->onError) { 251 | try { 252 | call_user_func($connection->onError, $connection, WORKERMAN_SEND_FAIL, 'send buffer full and drop package'); 253 | } catch (\Exception $e) { 254 | Worker::log($e); 255 | exit(250); 256 | } catch (\Error $e) { 257 | Worker::log($e); 258 | exit(250); 259 | } 260 | } 261 | return ''; 262 | } 263 | $connection->tmpWebsocketData = $connection->tmpWebsocketData . $frame; 264 | // Check buffer is full. 265 | if ($connection->maxSendBufferSize <= strlen($connection->tmpWebsocketData)) { 266 | if ($connection->onBufferFull) { 267 | try { 268 | call_user_func($connection->onBufferFull, $connection); 269 | } catch (\Exception $e) { 270 | Worker::log($e); 271 | exit(250); 272 | } catch (\Error $e) { 273 | Worker::log($e); 274 | exit(250); 275 | } 276 | } 277 | } 278 | return ''; 279 | } 280 | return $frame; 281 | } 282 | 283 | /** 284 | * Websocket decode. 285 | * 286 | * @param string $buffer 287 | * @param ConnectionInterface $connection 288 | * @return string 289 | */ 290 | public static function decode($bytes, $connection) 291 | { 292 | $masked = ord($bytes[1]) >> 7; 293 | $data_length = $masked ? ord($bytes[1]) & 127 : ord($bytes[1]); 294 | $decoded_data = ''; 295 | if ($masked === true) { 296 | if ($data_length === 126) { 297 | $mask = substr($bytes, 4, 4); 298 | $coded_data = substr($bytes, 8); 299 | } else if ($data_length === 127) { 300 | $mask = substr($bytes, 10, 4); 301 | $coded_data = substr($bytes, 14); 302 | } else { 303 | $mask = substr($bytes, 2, 4); 304 | $coded_data = substr($bytes, 6); 305 | } 306 | for ($i = 0; $i < strlen($coded_data); $i++) { 307 | $decoded_data .= $coded_data[$i] ^ $mask[$i % 4]; 308 | } 309 | } else { 310 | if ($data_length === 126) { 311 | $decoded_data = substr($bytes, 4); 312 | } else if ($data_length === 127) { 313 | $decoded_data = substr($bytes, 10); 314 | } else { 315 | $decoded_data = substr($bytes, 2); 316 | } 317 | } 318 | if ($connection->websocketCurrentFrameLength) { 319 | $connection->websocketDataBuffer .= $decoded_data; 320 | return $connection->websocketDataBuffer; 321 | } else { 322 | if ($connection->websocketDataBuffer !== '') { 323 | $decoded_data = $connection->websocketDataBuffer . $decoded_data; 324 | $connection->websocketDataBuffer = ''; 325 | } 326 | return $decoded_data; 327 | } 328 | } 329 | 330 | /** 331 | * Send websocket handshake data. 332 | * 333 | * @return void 334 | */ 335 | public static function onConnect($connection) 336 | { 337 | self::sendHandshake($connection); 338 | } 339 | 340 | /** 341 | * Clean 342 | * 343 | * @param $connection 344 | */ 345 | public static function onClose($connection) 346 | { 347 | $connection->handshakeStep = null; 348 | $connection->websocketCurrentFrameLength = 0; 349 | $connection->tmpWebsocketData = ''; 350 | $connection->websocketDataBuffer = ''; 351 | if (!empty($connection->websocketPingTimer)) { 352 | Timer::del($connection->websocketPingTimer); 353 | $connection->websocketPingTimer = null; 354 | } 355 | } 356 | 357 | /** 358 | * Send websocket handshake. 359 | * 360 | * @param \Workerman\Connection\TcpConnection $connection 361 | * @return void 362 | */ 363 | public static function sendHandshake($connection) 364 | { 365 | if (!empty($connection->handshakeStep)) { 366 | return; 367 | } 368 | // Get Host. 369 | $port = $connection->getRemotePort(); 370 | $host = $port === 80 ? $connection->getRemoteHost() : $connection->getRemoteHost() . ':' . $port; 371 | // Handshake header. 372 | $header = 'GET ' . $connection->getRemoteURI() . " HTTP/1.1\r\n". 373 | "Host: $host\r\n". 374 | "Connection: Upgrade\r\n". 375 | "Upgrade: websocket\r\n". 376 | "Origin: ". (isset($connection->websocketOrigin) ? $connection->websocketOrigin : '*') ."\r\n". 377 | "Sec-WebSocket-Version: 13\r\n". 378 | "Sec-WebSocket-Key: " . base64_encode(md5(mt_rand(), true)) . "\r\n\r\n"; 379 | $connection->send($header, true); 380 | $connection->handshakeStep = 1; 381 | $connection->websocketCurrentFrameLength = 0; 382 | $connection->websocketDataBuffer = ''; 383 | $connection->tmpWebsocketData = ''; 384 | } 385 | 386 | /** 387 | * Websocket handshake. 388 | * 389 | * @param string $buffer 390 | * @param \Workerman\Connection\TcpConnection $connection 391 | * @return int 392 | */ 393 | public static function dealHandshake($buffer, $connection) 394 | { 395 | $pos = strpos($buffer, "\r\n\r\n"); 396 | if ($pos) { 397 | // handshake complete 398 | $connection->handshakeStep = 2; 399 | $handshake_response_length = $pos + 4; 400 | // Try to emit onWebSocketConnect callback. 401 | if (isset($connection->onWebSocketConnect)) { 402 | try { 403 | call_user_func($connection->onWebSocketConnect, $connection, substr($buffer, 0, $handshake_response_length)); 404 | } catch (\Exception $e) { 405 | Worker::log($e); 406 | exit(250); 407 | } catch (\Error $e) { 408 | Worker::log($e); 409 | exit(250); 410 | } 411 | } 412 | // Headbeat. 413 | if (!empty($connection->websocketPingInterval)) { 414 | $connection->websocketPingTimer = Timer::add($connection->websocketPingInterval, function() use ($connection){ 415 | if (false === $connection->send(pack('H*', '898000000000'), true)) { 416 | Timer::del($connection->websocketPingTimer); 417 | $connection->websocketPingTimer = null; 418 | } 419 | }); 420 | } 421 | 422 | $connection->consumeRecvBuffer($handshake_response_length); 423 | if (!empty($connection->tmpWebsocketData)) { 424 | $connection->send($connection->tmpWebsocketData, true); 425 | $connection->tmpWebsocketData = ''; 426 | } 427 | if (strlen($buffer) > $handshake_response_length) { 428 | return self::input(substr($buffer, $handshake_response_length), $connection); 429 | } 430 | } 431 | return 0; 432 | } 433 | } 434 | -------------------------------------------------------------------------------- /workerman/WebServer.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | namespace Workerman; 15 | 16 | use Workerman\Protocols\Http; 17 | use Workerman\Protocols\HttpCache; 18 | 19 | /** 20 | * WebServer. 21 | */ 22 | class WebServer extends Worker 23 | { 24 | /** 25 | * Virtual host to path mapping. 26 | * 27 | * @var array ['workerman.net'=>'/home', 'www.workerman.net'=>'home/www'] 28 | */ 29 | protected $serverRoot = array(); 30 | 31 | /** 32 | * Mime mapping. 33 | * 34 | * @var array 35 | */ 36 | protected static $mimeTypeMap = array(); 37 | 38 | 39 | /** 40 | * Used to save user OnWorkerStart callback settings. 41 | * 42 | * @var callback 43 | */ 44 | protected $_onWorkerStart = null; 45 | 46 | /** 47 | * Add virtual host. 48 | * 49 | * @param string $domain 50 | * @param string $root_path 51 | * @return void 52 | */ 53 | public function addRoot($domain, $root_path) 54 | { 55 | $this->serverRoot[$domain] = $root_path; 56 | } 57 | 58 | /** 59 | * Construct. 60 | * 61 | * @param string $socket_name 62 | * @param array $context_option 63 | */ 64 | public function __construct($socket_name, $context_option = array()) 65 | { 66 | list(, $address) = explode(':', $socket_name, 2); 67 | parent::__construct('http:' . $address, $context_option); 68 | $this->name = 'WebServer'; 69 | } 70 | 71 | /** 72 | * Run webserver instance. 73 | * 74 | * @see Workerman.Worker::run() 75 | */ 76 | public function run() 77 | { 78 | $this->_onWorkerStart = $this->onWorkerStart; 79 | $this->onWorkerStart = array($this, 'onWorkerStart'); 80 | $this->onMessage = array($this, 'onMessage'); 81 | parent::run(); 82 | } 83 | 84 | /** 85 | * Emit when process start. 86 | * 87 | * @throws \Exception 88 | */ 89 | public function onWorkerStart() 90 | { 91 | if (empty($this->serverRoot)) { 92 | echo new \Exception('server root not set, please use WebServer::addRoot($domain, $root_path) to set server root path'); 93 | exit(250); 94 | } 95 | 96 | // Init mimeMap. 97 | $this->initMimeTypeMap(); 98 | 99 | // Try to emit onWorkerStart callback. 100 | if ($this->_onWorkerStart) { 101 | try { 102 | call_user_func($this->_onWorkerStart, $this); 103 | } catch (\Exception $e) { 104 | self::log($e); 105 | exit(250); 106 | } catch (\Error $e) { 107 | self::log($e); 108 | exit(250); 109 | } 110 | } 111 | } 112 | 113 | /** 114 | * Init mime map. 115 | * 116 | * @return void 117 | */ 118 | public function initMimeTypeMap() 119 | { 120 | $mime_file = Http::getMimeTypesFile(); 121 | if (!is_file($mime_file)) { 122 | $this->log("$mime_file mime.type file not fond"); 123 | return; 124 | } 125 | $items = file($mime_file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); 126 | if (!is_array($items)) { 127 | $this->log("get $mime_file mime.type content fail"); 128 | return; 129 | } 130 | foreach ($items as $content) { 131 | if (preg_match("/\s*(\S+)\s+(\S.+)/", $content, $match)) { 132 | $mime_type = $match[1]; 133 | $workerman_file_extension_var = $match[2]; 134 | $workerman_file_extension_array = explode(' ', substr($workerman_file_extension_var, 0, -1)); 135 | foreach ($workerman_file_extension_array as $workerman_file_extension) { 136 | self::$mimeTypeMap[$workerman_file_extension] = $mime_type; 137 | } 138 | } 139 | } 140 | } 141 | 142 | /** 143 | * Emit when http message coming. 144 | * 145 | * @param Connection\TcpConnection $connection 146 | * @return void 147 | */ 148 | public function onMessage($connection) 149 | { 150 | // REQUEST_URI. 151 | $workerman_url_info = parse_url($_SERVER['REQUEST_URI']); 152 | if (!$workerman_url_info) { 153 | Http::header('HTTP/1.1 400 Bad Request'); 154 | $connection->close('

400 Bad Request

'); 155 | return; 156 | } 157 | 158 | $workerman_path = isset($workerman_url_info['path']) ? $workerman_url_info['path'] : '/'; 159 | 160 | $workerman_path_info = pathinfo($workerman_path); 161 | $workerman_file_extension = isset($workerman_path_info['extension']) ? $workerman_path_info['extension'] : ''; 162 | if ($workerman_file_extension === '') { 163 | $workerman_path = ($len = strlen($workerman_path)) && $workerman_path[$len - 1] === '/' ? $workerman_path . 'index.php' : $workerman_path . '/index.php'; 164 | $workerman_file_extension = 'php'; 165 | } 166 | 167 | $workerman_root_dir = isset($this->serverRoot[$_SERVER['SERVER_NAME']]) ? $this->serverRoot[$_SERVER['SERVER_NAME']] : current($this->serverRoot); 168 | 169 | $workerman_file = "$workerman_root_dir/$workerman_path"; 170 | 171 | if ($workerman_file_extension === 'php' && !is_file($workerman_file)) { 172 | $workerman_file = "$workerman_root_dir/index.php"; 173 | if (!is_file($workerman_file)) { 174 | $workerman_file = "$workerman_root_dir/index.html"; 175 | $workerman_file_extension = 'html'; 176 | } 177 | } 178 | 179 | // File exsits. 180 | if (is_file($workerman_file)) { 181 | // Security check. 182 | if ((!($workerman_request_realpath = realpath($workerman_file)) || !($workerman_root_dir_realpath = realpath($workerman_root_dir))) || 0 !== strpos($workerman_request_realpath, 183 | $workerman_root_dir_realpath) 184 | ) { 185 | Http::header('HTTP/1.1 400 Bad Request'); 186 | $connection->close('

400 Bad Request

'); 187 | return; 188 | } 189 | 190 | $workerman_file = realpath($workerman_file); 191 | 192 | // Request php file. 193 | if ($workerman_file_extension === 'php') { 194 | $workerman_cwd = getcwd(); 195 | chdir($workerman_root_dir); 196 | ini_set('display_errors', 'off'); 197 | ob_start(); 198 | // Try to include php file. 199 | try { 200 | // $_SERVER. 201 | $_SERVER['REMOTE_ADDR'] = $connection->getRemoteIp(); 202 | $_SERVER['REMOTE_PORT'] = $connection->getRemotePort(); 203 | include $workerman_file; 204 | } catch (\Exception $e) { 205 | // Jump_exit? 206 | if ($e->getMessage() != 'jump_exit') { 207 | echo $e; 208 | } 209 | } 210 | $content = ob_get_clean(); 211 | ini_set('display_errors', 'on'); 212 | if (strtolower($_SERVER['HTTP_CONNECTION']) === "keep-alive") { 213 | $connection->send($content); 214 | } else { 215 | $connection->close($content); 216 | } 217 | chdir($workerman_cwd); 218 | return; 219 | } 220 | 221 | // Send file to client. 222 | return self::sendFile($connection, $workerman_file); 223 | } else { 224 | // 404 225 | Http::header("HTTP/1.1 404 Not Found"); 226 | $connection->close('404 File not found

404 Not Found

'); 227 | return; 228 | } 229 | } 230 | 231 | public static function sendFile($connection, $file_path) 232 | { 233 | // Check 304. 234 | $info = stat($file_path); 235 | $modified_time = $info ? date('D, d M Y H:i:s', $info['mtime']) . ' ' . date_default_timezone_get() : ''; 236 | if (!empty($_SERVER['HTTP_IF_MODIFIED_SINCE']) && $info) { 237 | // Http 304. 238 | if ($modified_time === $_SERVER['HTTP_IF_MODIFIED_SINCE']) { 239 | // 304 240 | Http::header('HTTP/1.1 304 Not Modified'); 241 | // Send nothing but http headers.. 242 | $connection->close(''); 243 | return; 244 | } 245 | } 246 | 247 | // Http header. 248 | if ($modified_time) { 249 | $modified_time = "Last-Modified: $modified_time\r\n"; 250 | } 251 | $file_size = filesize($file_path); 252 | $file_info = pathinfo($file_path); 253 | $extension = isset($file_info['extension']) ? $file_info['extension'] : ''; 254 | $file_name = isset($file_info['filename']) ? $file_info['filename'] : ''; 255 | $header = "HTTP/1.1 200 OK\r\n"; 256 | if (isset(self::$mimeTypeMap[$extension])) { 257 | $header .= "Content-Type: " . self::$mimeTypeMap[$extension] . "\r\n"; 258 | } else { 259 | $header .= "Content-Type: application/octet-stream\r\n"; 260 | $header .= "Content-Disposition: attachment; filename=\"$file_name\"\r\n"; 261 | } 262 | $header .= "Connection: keep-alive\r\n"; 263 | $header .= $modified_time; 264 | $header .= "Content-Length: $file_size\r\n\r\n"; 265 | $trunk_limit_size = 1024*1024; 266 | if ($file_size < $trunk_limit_size) { 267 | return $connection->send($header.file_get_contents($file_path), true); 268 | } 269 | $connection->send($header, true); 270 | 271 | // Read file content from disk piece by piece and send to client. 272 | $connection->fileHandler = fopen($file_path, 'r'); 273 | $do_write = function()use($connection) 274 | { 275 | // Send buffer not full. 276 | while(empty($connection->bufferFull)) 277 | { 278 | // Read from disk. 279 | $buffer = fread($connection->fileHandler, 8192); 280 | // Read eof. 281 | if($buffer === '' || $buffer === false) 282 | { 283 | return; 284 | } 285 | $connection->send($buffer, true); 286 | } 287 | }; 288 | // Send buffer full. 289 | $connection->onBufferFull = function($connection) 290 | { 291 | $connection->bufferFull = true; 292 | }; 293 | // Send buffer drain. 294 | $connection->onBufferDrain = function($connection)use($do_write) 295 | { 296 | $connection->bufferFull = false; 297 | $do_write(); 298 | }; 299 | $do_write(); 300 | } 301 | } 302 | -------------------------------------------------------------------------------- /workerman/Worker.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | namespace Workerman; 15 | 16 | require_once __DIR__ . '/Lib/Constants.php'; 17 | 18 | use \Workerman\Events\Libevent; 19 | use \Workerman\Events\Event; 20 | use \Workerman\Events\React; 21 | use \Workerman\Events\Select; 22 | use \Workerman\Events\EventInterface; 23 | use \Workerman\Connection\ConnectionInterface; 24 | use \Workerman\Connection\TcpConnection; 25 | use \Workerman\Connection\UdpConnection; 26 | use \Workerman\Lib\Timer; 27 | use \Workerman\Autoloader; 28 | use \Exception; 29 | 30 | /** 31 | * 32 | * @author walkor 33 | */ 34 | class Worker 35 | { 36 | /** 37 | * 版本号 38 | * @var string 39 | */ 40 | const VERSION = '3.5.1'; 41 | 42 | /** 43 | * 状态 启动中 44 | * @var int 45 | */ 46 | const STATUS_STARTING = 1; 47 | 48 | /** 49 | * 状态 运行中 50 | * @var int 51 | */ 52 | const STATUS_RUNNING = 2; 53 | 54 | /** 55 | * 状态 停止 56 | * @var int 57 | */ 58 | const STATUS_SHUTDOWN = 4; 59 | 60 | /** 61 | * 状态 平滑重启中 62 | * @var int 63 | */ 64 | const STATUS_RELOADING = 8; 65 | 66 | /** 67 | * 给子进程发送重启命令 KILL_WORKER_TIMER_TIME 秒后 68 | * 如果对应进程仍然未重启则强行杀死 69 | * @var int 70 | */ 71 | const KILL_WORKER_TIMER_TIME = 1; 72 | 73 | /** 74 | * 默认的backlog,即内核中用于存放未被进程认领(accept)的连接队列长度 75 | * @var int 76 | */ 77 | const DEFAUL_BACKLOG = 1024; 78 | 79 | /** 80 | * udp最大包长 81 | * @var int 82 | */ 83 | const MAX_UDP_PACKAGE_SIZE = 65535; 84 | 85 | /** 86 | * worker id 87 | * @var int 88 | */ 89 | public $id = 0; 90 | 91 | /** 92 | * worker的名称,用于在运行status命令时标记进程 93 | * @var string 94 | */ 95 | public $name = 'none'; 96 | 97 | /** 98 | * 设置当前worker实例的进程数 99 | * @var int 100 | */ 101 | public $count = 1; 102 | 103 | /** 104 | * 设置当前worker进程的运行用户,启动时需要root超级权限 105 | * @var string 106 | */ 107 | public $user = ''; 108 | 109 | /** 110 | * 当前worker进程是否可以平滑重启 111 | * @var bool 112 | */ 113 | public $reloadable = true; 114 | 115 | /** 116 | * reuse port 117 | * @var bool 118 | */ 119 | public $reusePort = false; 120 | 121 | /** 122 | * 当worker进程启动时,如果设置了$onWorkerStart回调函数,则运行 123 | * 此钩子函数一般用于进程启动后初始化工作 124 | * @var callback 125 | */ 126 | public $onWorkerStart = null; 127 | 128 | /** 129 | * 当有客户端连接时,如果设置了$onConnect回调函数,则运行 130 | * @var callback 131 | */ 132 | public $onConnect = null; 133 | 134 | /** 135 | * 当客户端连接上发来数据时,如果设置了$onMessage回调,则运行 136 | * @var callback 137 | */ 138 | public $onMessage = null; 139 | 140 | /** 141 | * 当客户端的连接关闭时,如果设置了$onClose回调,则运行 142 | * @var callback 143 | */ 144 | public $onClose = null; 145 | 146 | /** 147 | * 当客户端的连接发生错误时,如果设置了$onError回调,则运行 148 | * 错误一般为客户端断开连接导致数据发送失败、服务端的发送缓冲区满导致发送失败等 149 | * 具体错误码及错误详情会以参数的形式传递给回调,参见手册 150 | * @var callback 151 | */ 152 | public $onError = null; 153 | 154 | /** 155 | * 当连接的发送缓冲区满时,如果设置了$onBufferFull回调,则执行 156 | * @var callback 157 | */ 158 | public $onBufferFull = null; 159 | 160 | /** 161 | * 当链接的发送缓冲区被清空时,如果设置了$onBufferDrain回调,则执行 162 | * @var callback 163 | */ 164 | public $onBufferDrain = null; 165 | 166 | /** 167 | * 当前进程退出时(由于平滑重启或者服务停止导致),如果设置了此回调,则运行 168 | * @var callback 169 | */ 170 | public $onWorkerStop = null; 171 | 172 | /** 173 | * 当收到reload命令时的回调函数 174 | * @var callback 175 | */ 176 | public $onWorkerReload = null; 177 | 178 | /** 179 | * 传输层协议 180 | * @var string 181 | */ 182 | public $transport = 'tcp'; 183 | 184 | /** 185 | * 所有的客户端连接 186 | * @var array 187 | */ 188 | public $connections = array(); 189 | 190 | /** 191 | * 应用层协议,由初始化worker时指定 192 | * 例如 new worker('http://0.0.0.0:8080');指定使用http协议 193 | * @var string 194 | */ 195 | protected $protocol = null; 196 | 197 | /** 198 | * 当前worker实例初始化目录位置,用于设置应用自动加载的根目录 199 | * @var string 200 | */ 201 | protected $_autoloadRootPath = ''; 202 | 203 | /** 204 | * 是否以守护进程的方式运行。运行start时加上-d参数会自动以守护进程方式运行 205 | * 例如 php start.php start -d 206 | * @var bool 207 | */ 208 | public static $daemonize = false; 209 | 210 | /** 211 | * 重定向标准输出,即将所有echo、var_dump等终端输出写到对应文件中 212 | * 注意 此参数只有在以守护进程方式运行时有效 213 | * @var string 214 | */ 215 | public static $stdoutFile = '/dev/null'; 216 | 217 | /** 218 | * pid文件的路径及名称 219 | * 例如 Worker::$pidFile = '/tmp/workerman.pid'; 220 | * 注意 此属性一般不必手动设置,默认会放到php临时目录中 221 | * @var string 222 | */ 223 | public static $pidFile = ''; 224 | 225 | /** 226 | * 日志目录,默认在workerman根目录下,与Applications同级 227 | * 可以手动设置 228 | * 例如 Worker::$logFile = '/tmp/workerman.log'; 229 | * @var unknown_type 230 | */ 231 | public static $logFile = ''; 232 | 233 | /** 234 | * 全局事件轮询库,用于监听所有资源的可读可写事件 235 | * @var Select/Libevent 236 | */ 237 | public static $globalEvent = null; 238 | 239 | /** 240 | * 主进程停止时触发的回调,Win系统下不起作用 241 | * @var unknown_type 242 | */ 243 | public static $onMasterStop = null; 244 | 245 | /** 246 | * 事件轮询库类名 247 | * @var string 248 | */ 249 | public static $eventLoopClass = ''; 250 | 251 | /** 252 | * 主进程pid 253 | * @var int 254 | */ 255 | protected static $_masterPid = 0; 256 | 257 | /** 258 | * 监听的socket 259 | * @var stream 260 | */ 261 | protected $_mainSocket = null; 262 | 263 | /** 264 | * socket名称,包括应用层协议+ip+端口号,在初始化worker时设置 265 | * 值类似 http://0.0.0.0:80 266 | * @var string 267 | */ 268 | protected $_socketName = ''; 269 | 270 | /** 271 | * socket的上下文,具体选项设置可以在初始化worker时传递 272 | * @var context 273 | */ 274 | protected $_context = null; 275 | 276 | /** 277 | * 所有的worker实例 278 | * @var array 279 | */ 280 | protected static $_workers = array(); 281 | 282 | /** 283 | * 所有worker进程的pid 284 | * 格式为 [worker_id=>[pid=>pid, pid=>pid, ..], ..] 285 | * @var array 286 | */ 287 | protected static $_pidMap = array(); 288 | 289 | /** 290 | * 所有需要重启的进程pid 291 | * 格式为 [pid=>pid, pid=>pid] 292 | * @var array 293 | */ 294 | protected static $_pidsToRestart = array(); 295 | 296 | /** 297 | * 当前worker状态 298 | * @var int 299 | */ 300 | protected static $_status = self::STATUS_STARTING; 301 | 302 | /** 303 | * 所有worke名称(name属性)中的最大长度,用于在运行 status 命令时格式化输出 304 | * @var int 305 | */ 306 | protected static $_maxWorkerNameLength = 12; 307 | 308 | /** 309 | * 所有socket名称(_socketName属性)中的最大长度,用于在运行 status 命令时格式化输出 310 | * @var int 311 | */ 312 | protected static $_maxSocketNameLength = 12; 313 | 314 | /** 315 | * 所有user名称(user属性)中的最大长度,用于在运行 status 命令时格式化输出 316 | * @var int 317 | */ 318 | protected static $_maxUserNameLength = 12; 319 | 320 | /** 321 | * 运行 status 命令时用于保存结果的文件名 322 | * @var string 323 | */ 324 | protected static $_statisticsFile = ''; 325 | 326 | /** 327 | * 启动的全局入口文件 328 | * 例如 php start.php start ,则入口文件为start.php 329 | * @var string 330 | */ 331 | protected static $_startFile = ''; 332 | 333 | /** 334 | * 用来保存子进程句柄(windows) 335 | * @var array 336 | */ 337 | protected static $_process = array(); 338 | 339 | /** 340 | * 要执行的文件 341 | * @var array 342 | */ 343 | protected static $_startFiles = array(); 344 | 345 | /** 346 | * Available event loops. 347 | * 348 | * @var array 349 | */ 350 | protected static $_availableEventLoops = array( 351 | 'libevent' => '\Workerman\Events\Libevent', 352 | 'event' => '\Workerman\Events\Event' 353 | ); 354 | 355 | /** 356 | * PHP built-in protocols. 357 | * 358 | * @var array 359 | */ 360 | protected static $_builtinTransports = array( 361 | 'tcp' => 'tcp', 362 | 'udp' => 'udp', 363 | 'unix' => 'unix', 364 | 'ssl' => 'tcp' 365 | ); 366 | 367 | /** 368 | * 运行所有worker实例 369 | * @return void 370 | */ 371 | public static function runAll() 372 | { 373 | // 初始化环境变量 374 | static::init(); 375 | // 解析命令 376 | static::parseCommand(); 377 | // 初始化所有worker实例,主要是监听端口 378 | static::initWorkers(); 379 | // 展示启动界面 380 | static::displayUI(); 381 | // 运行所有的worker 382 | static::runAllWorkers(); 383 | // 监控worker 384 | static::monitorWorkers(); 385 | } 386 | 387 | /** 388 | * 初始化一些环境变量 389 | * @return void 390 | */ 391 | public static function init() 392 | { 393 | if(strpos(strtolower(PHP_OS), 'win') !== 0) 394 | { 395 | exit("workerman-for-win can not run in linux\n"); 396 | } 397 | if (false !== strpos(ini_get('disable_functions'), 'proc_open')) 398 | { 399 | exit("\r\nWarning: proc_open() has been disabled for security reasons. \r\n\r\nSee http://wiki.workerman.net/Error5\r\n"); 400 | } 401 | $backtrace = debug_backtrace(); 402 | static::$_startFile = $backtrace[count($backtrace)-1]['file']; 403 | // 没有设置日志文件,则生成一个默认值 404 | if(empty(static::$logFile)) 405 | { 406 | static::$logFile = __DIR__ . '/../workerman.log'; 407 | } 408 | // 标记状态为启动中 409 | static::$_status = static::STATUS_STARTING; 410 | 411 | $event_loop_class = static::getEventLoopName(); 412 | static::$globalEvent = new $event_loop_class; 413 | Timer::init(static::$globalEvent); 414 | } 415 | 416 | /** 417 | * 初始化所有的worker实例,主要工作为获得格式化所需数据及监听端口 418 | * @return void 419 | */ 420 | protected static function initWorkers() 421 | { 422 | foreach(static::$_workers as $worker) 423 | { 424 | // 没有设置worker名称,则使用none代替 425 | if(empty($worker->name)) 426 | { 427 | $worker->name = 'none'; 428 | } 429 | // 获得所有worker名称中最大长度 430 | $worker_name_length = strlen($worker->name); 431 | if(static::$_maxWorkerNameLength < $worker_name_length) 432 | { 433 | static::$_maxWorkerNameLength = $worker_name_length; 434 | } 435 | // 获得所有_socketName中最大长度 436 | $socket_name_length = strlen($worker->getSocketName()); 437 | if(static::$_maxSocketNameLength < $socket_name_length) 438 | { 439 | static::$_maxSocketNameLength = $socket_name_length; 440 | } 441 | $user_name_length = strlen($worker->user); 442 | if(static::$_maxUserNameLength < $user_name_length) 443 | { 444 | static::$_maxUserNameLength = $user_name_length; 445 | } 446 | } 447 | } 448 | 449 | /** 450 | * 运行所有的worker 451 | */ 452 | public static function runAllWorkers() 453 | { 454 | // 只有一个start文件时执行run 455 | if(count(static::$_startFiles) === 1) 456 | { 457 | // win不支持同一个页面执初始化多个worker 458 | if(count(static::$_workers) > 1) 459 | { 460 | echo "@@@ Error: multi workers init in one php file are not support @@@\r\n"; 461 | echo "@@@ Please visit http://wiki.workerman.net/Multi_woker_for_win @@@\r\n"; 462 | } 463 | elseif(count(static::$_workers) <= 0) 464 | { 465 | exit("@@@no worker inited@@@\r\n\r\n"); 466 | } 467 | 468 | // 执行worker的run方法 469 | reset(static::$_workers); 470 | $worker = current(static::$_workers); 471 | $worker->listen(); 472 | // 子进程阻塞在这里 473 | $worker->run(); 474 | exit("@@@child exit@@@\r\n"); 475 | } 476 | // 多个start文件则多进程打开 477 | elseif(count(static::$_startFiles) > 1) 478 | { 479 | static::$globalEvent = new Select(); 480 | Timer::init(static::$globalEvent); 481 | foreach(static::$_startFiles as $start_file) 482 | { 483 | static::openProcess($start_file); 484 | } 485 | } 486 | // 没有start文件提示错误 487 | else 488 | { 489 | //exit("@@@no worker inited@@@\r\n"); 490 | } 491 | } 492 | 493 | /** 494 | * 打开一个子进程 495 | * @param string $start_file 496 | */ 497 | public static function openProcess($start_file) 498 | { 499 | // 保存子进程的输出 500 | $start_file = realpath($start_file); 501 | $std_file = sys_get_temp_dir() . '/'.str_replace(array('/', "\\", ':'), '_', $start_file).'.out.txt'; 502 | // 将stdou stderr 重定向到文件 503 | $descriptorspec = array( 504 | 0 => array('pipe', 'a'), // stdin 505 | 1 => array('file', $std_file, 'w'), // stdout 506 | 2 => array('file', $std_file, 'w') // stderr 507 | ); 508 | 509 | // 保存stdin句柄,用来探测子进程是否关闭 510 | $pipes = array(); 511 | 512 | // 打开子进程 513 | $process= proc_open("php \"$start_file\" -q", $descriptorspec, $pipes); 514 | 515 | // 打开stdout stderr 文件句柄 516 | $std_handler = fopen($std_file, 'a+'); 517 | // 非阻塞 518 | stream_set_blocking($std_handler, 0); 519 | // 定时读取子进程的stdout stderr 520 | $timer_id = Timer::add(0.1, function()use($std_handler) 521 | { 522 | echo fread($std_handler, 65535); 523 | }); 524 | 525 | // 保存子进程句柄 526 | static::$_process[$start_file] = array($process, $start_file, $timer_id); 527 | } 528 | 529 | /** 530 | * 定时检查子进程是否退出了 531 | */ 532 | protected static function monitorWorkers() 533 | { 534 | // 定时检查子进程是否退出了 535 | Timer::add(0.5, "\\Workerman\\Worker::checkWorkerStatus"); 536 | 537 | // 主进程loop 538 | static::$globalEvent->loop(); 539 | } 540 | 541 | public static function checkWorkerStatus() 542 | { 543 | foreach(static::$_process as $process_data) 544 | { 545 | $process = $process_data[0]; 546 | $start_file = $process_data[1]; 547 | $timer_id = $process_data[2]; 548 | $status = proc_get_status($process); 549 | if(isset($status['running'])) 550 | { 551 | // 子进程退出了,重启一个子进程 552 | if(!$status['running']) 553 | { 554 | echo "process $start_file terminated and try to restart\n"; 555 | Timer::del($timer_id); 556 | @proc_close($process); 557 | // 重新打开一个子进程 558 | static::openProcess($start_file); 559 | } 560 | } 561 | else 562 | { 563 | echo "proc_get_status fail\n"; 564 | } 565 | } 566 | } 567 | 568 | /** 569 | * Get all worker instances. 570 | * 571 | * @return array 572 | */ 573 | public static function getAllWorkers() 574 | { 575 | return static::$_workers; 576 | } 577 | 578 | /** 579 | * Get global event-loop instance. 580 | * 581 | * @return EventInterface 582 | */ 583 | public static function getEventLoop() 584 | { 585 | return static::$globalEvent; 586 | } 587 | 588 | /** 589 | * 展示启动界面 590 | * @return void 591 | */ 592 | protected static function displayUI() 593 | { 594 | global $argv; 595 | // -q不打印 596 | if(in_array('-q', $argv)) 597 | { 598 | return; 599 | } 600 | echo "----------------------- WORKERMAN -----------------------------\n"; 601 | echo 'Workerman version:' . Worker::VERSION . " PHP version:".PHP_VERSION."\n"; 602 | echo "------------------------ WORKERS -------------------------------\n"; 603 | echo "worker",str_pad('', static::$_maxWorkerNameLength+2-strlen('worker')), "listen",str_pad('', static::$_maxSocketNameLength+2-strlen('listen')), "processes ","status\n"; 604 | foreach(static::$_workers as $worker) 605 | { 606 | echo str_pad($worker->name, static::$_maxWorkerNameLength+2),str_pad($worker->getSocketName(), static::$_maxSocketNameLength+2), str_pad(' '.$worker->count, 9), " [OK] \n";; 607 | } 608 | echo "----------------------------------------------------------------\n"; 609 | echo "Press Ctrl-C to quit. Start success.\n"; 610 | } 611 | 612 | /** 613 | * 解析运行命令 614 | * php yourfile.php start | stop | restart | reload | status 615 | * @return void 616 | */ 617 | public static function parseCommand() 618 | { 619 | global $argv; 620 | foreach($argv as $file) 621 | { 622 | $ext = pathinfo($file, PATHINFO_EXTENSION ); 623 | if($ext !== 'php') 624 | { 625 | continue; 626 | } 627 | if(is_file($file)) 628 | { 629 | static::$_startFiles[$file] = $file; 630 | include_once $file; 631 | } 632 | } 633 | } 634 | 635 | /** 636 | * 执行关闭流程 637 | * @return void 638 | */ 639 | public static function stopAll() 640 | { 641 | static::$_status = static::STATUS_SHUTDOWN; 642 | exit(0); 643 | } 644 | 645 | /** 646 | * 记录日志 647 | * @param string $msg 648 | * @return void 649 | */ 650 | public static function log($msg) 651 | { 652 | $msg = $msg."\n"; 653 | if(static::$_status === static::STATUS_STARTING || !static::$daemonize) 654 | { 655 | echo $msg; 656 | } 657 | file_put_contents(static::$logFile, date('Y-m-d H:i:s') . " " . $msg, FILE_APPEND | LOCK_EX); 658 | } 659 | 660 | /** 661 | * worker构造函数 662 | * @param string $socket_name 663 | * @return void 664 | */ 665 | public function __construct($socket_name = '', $context_option = array()) 666 | { 667 | // 保存worker实例 668 | $this->workerId = spl_object_hash($this); 669 | static::$_workers[$this->workerId] = $this; 670 | static::$_pidMap[$this->workerId] = array(); 671 | 672 | // 获得实例化文件路径,用于自动加载设置根目录 673 | $backrace = debug_backtrace(); 674 | $this->_autoloadRootPath = dirname($backrace[0]['file']); 675 | 676 | // 设置socket上下文 677 | if($socket_name) 678 | { 679 | $this->_socketName = $socket_name; 680 | if(!isset($context_option['socket']['backlog'])) 681 | { 682 | $context_option['socket']['backlog'] = static::DEFAUL_BACKLOG; 683 | } 684 | $this->_context = stream_context_create($context_option); 685 | } 686 | 687 | // 设置一个空的onMessage,当onMessage未设置时用来消费socket数据 688 | $this->onMessage = function(){}; 689 | } 690 | 691 | /** 692 | * 监听端口 693 | * @throws Exception 694 | */ 695 | public function listen() 696 | { 697 | if (!$this->_socketName || $this->_mainSocket) { 698 | return; 699 | } 700 | 701 | // Autoload. 702 | Autoloader::setRootPath($this->_autoloadRootPath); 703 | 704 | // Get the application layer communication protocol and listening address. 705 | list($scheme, $address) = explode(':', $this->_socketName, 2); 706 | // Check application layer protocol class. 707 | if (!isset(static::$_builtinTransports[$scheme])) { 708 | if(class_exists($scheme)){ 709 | $this->protocol = $scheme; 710 | } else { 711 | $scheme = ucfirst($scheme); 712 | $this->protocol = '\\Protocols\\' . $scheme; 713 | if (!class_exists($this->protocol)) { 714 | $this->protocol = "\\Workerman\\Protocols\\$scheme"; 715 | if (!class_exists($this->protocol)) { 716 | throw new Exception("class \\Protocols\\$scheme not exist"); 717 | } 718 | } 719 | } 720 | if (!isset(static::$_builtinTransports[$this->transport])) { 721 | throw new \Exception('Bad worker->transport ' . var_export($this->transport, true)); 722 | } 723 | } else { 724 | $this->transport = $scheme; 725 | } 726 | 727 | $local_socket = static::$_builtinTransports[$this->transport] . ":" . $address; 728 | 729 | // Flag. 730 | $flags = $this->transport === 'udp' ? STREAM_SERVER_BIND : STREAM_SERVER_BIND | STREAM_SERVER_LISTEN; 731 | $errno = 0; 732 | $errmsg = ''; 733 | // SO_REUSEPORT. 734 | if ($this->reusePort) { 735 | stream_context_set_option($this->_context, 'socket', 'so_reuseport', 1); 736 | } 737 | 738 | // Create an Internet or Unix domain server socket. 739 | $this->_mainSocket = stream_socket_server($local_socket, $errno, $errmsg, $flags, $this->_context); 740 | if (!$this->_mainSocket) { 741 | throw new Exception($errmsg); 742 | } 743 | 744 | if ($this->transport === 'ssl') { 745 | stream_socket_enable_crypto($this->_mainSocket, false); 746 | } 747 | 748 | // Try to open keepalive for tcp and disable Nagle algorithm. 749 | if (function_exists('socket_import_stream') && static::$_builtinTransports[$this->transport] === 'tcp') { 750 | $socket = socket_import_stream($this->_mainSocket); 751 | @socket_set_option($socket, SOL_SOCKET, SO_KEEPALIVE, 1); 752 | @socket_set_option($socket, SOL_TCP, TCP_NODELAY, 1); 753 | } 754 | 755 | // Non blocking. 756 | stream_set_blocking($this->_mainSocket, 0); 757 | 758 | // Register a listener to be notified when server socket is ready to read. 759 | if (static::$globalEvent) { 760 | if ($this->transport !== 'udp') { 761 | static::$globalEvent->add($this->_mainSocket, EventInterface::EV_READ, array($this, 'acceptConnection')); 762 | } else { 763 | static::$globalEvent->add($this->_mainSocket, EventInterface::EV_READ, 764 | array($this, 'acceptUdpConnection')); 765 | } 766 | } 767 | } 768 | 769 | /** 770 | * Get event loop name. 771 | * 772 | * @return string 773 | */ 774 | protected static function getEventLoopName() 775 | { 776 | if (static::$eventLoopClass) { 777 | return static::$eventLoopClass; 778 | } 779 | 780 | $loop_name = ''; 781 | foreach (static::$_availableEventLoops as $name=>$class) { 782 | if (extension_loaded($name)) { 783 | $loop_name = $name; 784 | break; 785 | } 786 | } 787 | 788 | if ($loop_name) { 789 | if (interface_exists('\React\EventLoop\LoopInterface')) { 790 | switch ($loop_name) { 791 | case 'libevent': 792 | static::$eventLoopClass = '\Workerman\Events\React\LibEventLoop'; 793 | break; 794 | case 'event': 795 | static::$eventLoopClass = '\Workerman\Events\React\ExtEventLoop'; 796 | break; 797 | default : 798 | static::$eventLoopClass = '\Workerman\Events\React\StreamSelectLoop'; 799 | break; 800 | } 801 | } else { 802 | static::$eventLoopClass = static::$_availableEventLoops[$loop_name]; 803 | } 804 | } else { 805 | static::$eventLoopClass = interface_exists('\React\EventLoop\LoopInterface')? '\Workerman\Events\React\StreamSelectLoop':'\Workerman\Events\Select'; 806 | } 807 | return static::$eventLoopClass; 808 | } 809 | 810 | /** 811 | * 获得 socket name 812 | * @return string 813 | */ 814 | public function getSocketName() 815 | { 816 | return $this->_socketName ? $this->_socketName : 'none'; 817 | } 818 | 819 | /** 820 | * 运行worker实例 821 | */ 822 | public function run() 823 | { 824 | // 设置自动加载根目录 825 | Autoloader::setRootPath($this->_autoloadRootPath); 826 | 827 | // Create a global event loop. 828 | if (!static::$globalEvent) { 829 | $event_loop_class = static::getEventLoopName(); 830 | static::$globalEvent = new $event_loop_class; 831 | } 832 | 833 | // 监听_mainSocket上的可读事件(客户端连接事件) 834 | if($this->_socketName) 835 | { 836 | if($this->transport !== 'udp') 837 | { 838 | static::$globalEvent->add($this->_mainSocket, EventInterface::EV_READ, array($this, 'acceptConnection')); 839 | } 840 | else 841 | { 842 | static::$globalEvent->add($this->_mainSocket, EventInterface::EV_READ, array($this, 'acceptUdpConnection')); 843 | } 844 | } 845 | 846 | // 用全局事件轮询初始化定时器 847 | Timer::init(static::$globalEvent); 848 | 849 | // 如果有设置进程启动回调,则执行 850 | if($this->onWorkerStart) 851 | { 852 | call_user_func($this->onWorkerStart, $this); 853 | } 854 | 855 | // 子进程主循环 856 | static::$globalEvent->loop(); 857 | } 858 | 859 | /** 860 | * 停止当前worker实例 861 | * @return void 862 | */ 863 | public function stop() 864 | { 865 | // 如果有设置进程终止回调,则执行 866 | if($this->onWorkerStop) 867 | { 868 | call_user_func($this->onWorkerStop, $this); 869 | } 870 | // 删除相关监听事件,关闭_mainSocket 871 | static::$globalEvent->del($this->_mainSocket, EventInterface::EV_READ); 872 | @fclose($this->_mainSocket); 873 | } 874 | 875 | /** 876 | * 接收一个客户端连接 877 | * @param resources $socket 878 | * @return void 879 | */ 880 | public function acceptConnection($socket) 881 | { 882 | // Accept a connection on server socket. 883 | $new_socket = @stream_socket_accept($socket, 0, $remote_address); 884 | // Thundering herd. 885 | if (!$new_socket) { 886 | return; 887 | } 888 | 889 | // TcpConnection. 890 | $connection = new TcpConnection($new_socket, $remote_address); 891 | $this->connections[$connection->id] = $connection; 892 | $connection->worker = $this; 893 | $connection->protocol = $this->protocol; 894 | $connection->transport = $this->transport; 895 | $connection->onMessage = $this->onMessage; 896 | $connection->onClose = $this->onClose; 897 | $connection->onError = $this->onError; 898 | $connection->onBufferDrain = $this->onBufferDrain; 899 | $connection->onBufferFull = $this->onBufferFull; 900 | 901 | // Try to emit onConnect callback. 902 | if ($this->onConnect) { 903 | try { 904 | call_user_func($this->onConnect, $connection); 905 | } catch (\Exception $e) { 906 | static::log($e); 907 | exit(250); 908 | } catch (\Error $e) { 909 | static::log($e); 910 | exit(250); 911 | } 912 | } 913 | } 914 | 915 | /** 916 | * 处理udp连接(udp其实是无连接的,这里为保证和tcp连接接口一致) 917 | * @param resource $socket 918 | */ 919 | public function acceptUdpConnection($socket) 920 | { 921 | $recv_buffer = stream_socket_recvfrom($socket, static::MAX_UDP_PACKAGE_SIZE, 0, $remote_address); 922 | if (false === $recv_buffer || empty($remote_address)) { 923 | return false; 924 | } 925 | // UdpConnection. 926 | $connection = new UdpConnection($socket, $remote_address); 927 | $connection->protocol = $this->protocol; 928 | if ($this->onMessage) { 929 | if ($this->protocol) { 930 | $parser = $this->protocol; 931 | $recv_buffer = $parser::decode($recv_buffer, $connection); 932 | } 933 | ConnectionInterface::$statistics['total_request']++; 934 | try { 935 | call_user_func($this->onMessage, $connection, $recv_buffer); 936 | } catch (\Exception $e) { 937 | static::log($e); 938 | exit(250); 939 | } catch (\Error $e) { 940 | static::log($e); 941 | exit(250); 942 | } 943 | } 944 | return true; 945 | } 946 | } 947 | -------------------------------------------------------------------------------- /workerman/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "workerman/workerman-for-win", 3 | "type" : "project", 4 | "keywords": ["event-loop", "asynchronous"], 5 | "homepage": "http://www.workerman.net", 6 | "license" : "MIT", 7 | "description": "An asynchronous event driven PHP framework for easily building fast, scalable network applications.", 8 | "authors" : [ 9 | { 10 | "name" : "walkor", 11 | "email" : "walkor@workerman.net", 12 | "homepage" : "http://www.workerman.net", 13 | "role": "Developer" 14 | } 15 | ], 16 | "support" : { 17 | "email" : "walkor@workerman.net", 18 | "issues": "https://github.com/walkor/workerman/issues", 19 | "forum" : "http://wenda.workerman.net/", 20 | "wiki" : "http://doc3.workerman.net/index.html", 21 | "source": "https://github.com/walkor/workerman" 22 | }, 23 | "require": { 24 | "php": ">=5.3" 25 | }, 26 | "autoload": { 27 | "psr-4": {"Workerman\\": "./"} 28 | }, 29 | "minimum-stability":"dev" 30 | } 31 | --------------------------------------------------------------------------------