├── .gitignore ├── 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 └── Swoole.php ├── Lib ├── Constants.php └── Timer.php ├── MIT-LICENSE.txt ├── Protocols ├── Frame.php ├── Http.php ├── Http │ └── mime.types ├── ProtocolInterface.php ├── Text.php ├── Websocket.php └── Ws.php ├── README.md ├── WebServer.php ├── Worker.php ├── composer.json ├── composer.lock ├── demon ├── http_test.php ├── tcp_test.php └── ws_test.php ├── test ├── README.md ├── daemon │ ├── daemon1.php │ ├── daemon2.php │ └── daemon3.php ├── libevnt │ ├── io │ │ ├── io.client.php │ │ ├── io.server.php │ │ └── io.server3.php │ ├── signal │ │ └── signal.php │ └── timer │ │ └── timer.php ├── other │ ├── cli_set_process_title.php │ ├── posix_getpwnam.php │ ├── posix_getuid.php │ ├── resetStd.php │ └── stream_socket_server.php ├── signal&&fork │ ├── declare1.php │ ├── declare2.php │ ├── declare3.php │ ├── pcntl_alarm.php │ ├── pcntl_signal.php │ ├── pcntl_signal2.php │ ├── pcntl_signal_dispatch.php │ ├── pcntl_signal_dispatch2.php │ └── pcntl_wait.php └── socket │ ├── Nagle算法 │ ├── socket.php │ └── socket2.php └── vendor ├── autoload.php └── composer ├── ClassLoader.php ├── LICENSE ├── autoload_classmap.php ├── autoload_namespaces.php ├── autoload_psr4.php ├── autoload_real.php ├── autoload_static.php └── installed.json /.gitignore: -------------------------------------------------------------------------------- 1 | logs 2 | .buildpath 3 | .project 4 | .settings 5 | .idea 6 | .DS_Store 7 | demon/workerman.log 8 | test/daemon/dump.rdb -------------------------------------------------------------------------------- /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 | * Autoload. 18 | */ 19 | class Autoloader 20 | { 21 | /** 22 | * Autoload root path. 23 | * 24 | * @var string 25 | */ 26 | protected static $_autoloadRootPath = ''; 27 | 28 | /** 29 | * Set autoload root path. 30 | * 31 | * @param string $root_path 32 | * @return void 33 | */ 34 | public static function setRootPath($root_path) 35 | { 36 | self::$_autoloadRootPath = $root_path; 37 | } 38 | 39 | /** 40 | * Load files by namespace. 41 | * 42 | * @param string $name 43 | * @return boolean 44 | */ 45 | public static function loadByNamespace($name) 46 | { 47 | $class_path = str_replace('\\', DIRECTORY_SEPARATOR, $name); 48 | if (strpos($name, 'Workerman\\') === 0) { 49 | $class_file = __DIR__ . substr($class_path, strlen('Workerman')) . '.php'; 50 | } else { 51 | if (self::$_autoloadRootPath) { 52 | $class_file = self::$_autoloadRootPath . DIRECTORY_SEPARATOR . $class_path . '.php'; 53 | } 54 | if (empty($class_file) || !is_file($class_file)) { 55 | $class_file = __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . "$class_path.php"; 56 | } 57 | } 58 | 59 | if (is_file($class_file)) { 60 | require_once($class_file); 61 | if (class_exists($name, false)) { 62 | return true; 63 | } 64 | } 65 | return false; 66 | } 67 | } 68 | 69 | spl_autoload_register('\Workerman\Autoloader::loadByNamespace'); -------------------------------------------------------------------------------- /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 | if(PHP_INT_MAX === self::$_idRecorder){ 141 | self::$_idRecorder = 0; 142 | } 143 | // Check application layer protocol class. 144 | if (!isset(self::$_builtinTransports[$scheme])) { 145 | $scheme = ucfirst($scheme); 146 | $this->protocol = '\\Protocols\\' . $scheme; 147 | if (!class_exists($this->protocol)) { 148 | $this->protocol = "\\Workerman\\Protocols\\$scheme"; 149 | if (!class_exists($this->protocol)) { 150 | throw new Exception("class \\Protocols\\$scheme not exist"); 151 | } 152 | } 153 | } else { 154 | $this->transport = self::$_builtinTransports[$scheme]; 155 | } 156 | 157 | // For statistics. 158 | self::$statistics['connection_count']++; 159 | $this->maxSendBufferSize = self::$defaultMaxSendBufferSize; 160 | $this->_contextOption = $context_option; 161 | static::$connections[$this->_id] = $this; 162 | } 163 | 164 | /** 165 | * Do connect. 166 | * 167 | * @return void 168 | */ 169 | public function connect() 170 | { 171 | if ($this->_status !== self::STATUS_INITIAL && $this->_status !== self::STATUS_CLOSING && 172 | $this->_status !== self::STATUS_CLOSED) { 173 | return; 174 | } 175 | $this->_status = self::STATUS_CONNECTING; 176 | $this->_connectStartTime = microtime(true); 177 | if ($this->transport !== 'unix') { 178 | // Open socket connection asynchronously. 179 | if ($this->_contextOption) { 180 | $context = stream_context_create($this->_contextOption); 181 | $this->_socket = stream_socket_client("tcp://{$this->_remoteHost}:{$this->_remotePort}", 182 | $errno, $errstr, 0, STREAM_CLIENT_ASYNC_CONNECT, $context); 183 | } else { 184 | $this->_socket = stream_socket_client("tcp://{$this->_remoteHost}:{$this->_remotePort}", 185 | $errno, $errstr, 0, STREAM_CLIENT_ASYNC_CONNECT); 186 | } 187 | } else { 188 | $this->_socket = stream_socket_client("{$this->transport}://{$this->_remoteAddress}", $errno, $errstr, 0, 189 | STREAM_CLIENT_ASYNC_CONNECT); 190 | } 191 | // If failed attempt to emit onError callback. 192 | if (!$this->_socket) { 193 | $this->emitError(WORKERMAN_CONNECT_FAIL, $errstr); 194 | if ($this->_status === self::STATUS_CLOSING) { 195 | $this->destroy(); 196 | } 197 | if ($this->_status === self::STATUS_CLOSED) { 198 | $this->onConnect = null; 199 | } 200 | return; 201 | } 202 | // Add socket to global event loop waiting connection is successfully established or faild. 203 | Worker::$globalEvent->add($this->_socket, EventInterface::EV_WRITE, array($this, 'checkConnection')); 204 | // For windows. 205 | if(DIRECTORY_SEPARATOR === '\\') { 206 | Worker::$globalEvent->add($this->_socket, EventInterface::EV_EXCEPT, array($this, 'checkConnection')); 207 | } 208 | } 209 | 210 | /** 211 | * Reconnect. 212 | * 213 | * @param int $after 214 | * @return void 215 | */ 216 | public function reConnect($after = 0) { 217 | $this->_status = self::STATUS_INITIAL; 218 | static::$connections[$this->_id] = $this; 219 | if ($this->_reconnectTimer) { 220 | Timer::del($this->_reconnectTimer); 221 | } 222 | if ($after > 0) { 223 | $this->_reconnectTimer = Timer::add($after, array($this, 'connect'), null, false); 224 | return; 225 | } 226 | $this->connect(); 227 | } 228 | 229 | /** 230 | * Get remote address. 231 | * 232 | * @return string 233 | */ 234 | public function getRemoteHost() 235 | { 236 | return $this->_remoteHost; 237 | } 238 | 239 | /** 240 | * Get remote URI. 241 | * 242 | * @return string 243 | */ 244 | public function getRemoteURI() 245 | { 246 | return $this->_remoteURI; 247 | } 248 | 249 | /** 250 | * Try to emit onError callback. 251 | * 252 | * @param int $code 253 | * @param string $msg 254 | * @return void 255 | */ 256 | protected function emitError($code, $msg) 257 | { 258 | $this->_status = self::STATUS_CLOSING; 259 | if ($this->onError) { 260 | try { 261 | call_user_func($this->onError, $this, $code, $msg); 262 | } catch (\Exception $e) { 263 | Worker::log($e); 264 | exit(250); 265 | } catch (\Error $e) { 266 | Worker::log($e); 267 | exit(250); 268 | } 269 | } 270 | } 271 | 272 | /** 273 | * Check connection is successfully established or faild. 274 | * 275 | * @param resource $socket 276 | * @return void 277 | */ 278 | public function checkConnection($socket) 279 | { 280 | // Remove EV_EXPECT for windows. 281 | if(DIRECTORY_SEPARATOR === '\\') { 282 | Worker::$globalEvent->del($socket, EventInterface::EV_EXCEPT); 283 | } 284 | 285 | // Check socket state. 286 | if ($address = stream_socket_get_name($socket, true)) { 287 | // Nonblocking. 288 | stream_set_blocking($socket, 0); 289 | // Compatible with hhvm 290 | if (function_exists('stream_set_read_buffer')) { 291 | stream_set_read_buffer($socket, 0); 292 | } 293 | // Try to open keepalive for tcp and disable Nagle algorithm. 294 | if (function_exists('socket_import_stream') && $this->transport === 'tcp') { 295 | $raw_socket = socket_import_stream($socket); 296 | socket_set_option($raw_socket, SOL_SOCKET, SO_KEEPALIVE, 1); 297 | socket_set_option($raw_socket, SOL_TCP, TCP_NODELAY, 1); 298 | } 299 | 300 | // Remove write listener. 301 | Worker::$globalEvent->del($socket, EventInterface::EV_WRITE); 302 | 303 | // SSL handshake. 304 | if ($this->transport === 'ssl') { 305 | $this->_sslHandshakeCompleted = $this->doSslHandshake($socket); 306 | } else { 307 | // There are some data waiting to send. 308 | if ($this->_sendBuffer) { 309 | Worker::$globalEvent->add($socket, EventInterface::EV_WRITE, array($this, 'baseWrite')); 310 | } 311 | } 312 | 313 | // Register a listener waiting read event. 314 | Worker::$globalEvent->add($socket, EventInterface::EV_READ, array($this, 'baseRead')); 315 | 316 | $this->_status = self::STATUS_ESTABLISHED; 317 | $this->_remoteAddress = $address; 318 | 319 | // Try to emit onConnect callback. 320 | if ($this->onConnect) { 321 | try { 322 | call_user_func($this->onConnect, $this); 323 | } catch (\Exception $e) { 324 | Worker::log($e); 325 | exit(250); 326 | } catch (\Error $e) { 327 | Worker::log($e); 328 | exit(250); 329 | } 330 | } 331 | // Try to emit protocol::onConnect 332 | if (method_exists($this->protocol, 'onConnect')) { 333 | try { 334 | call_user_func(array($this->protocol, 'onConnect'), $this); 335 | } catch (\Exception $e) { 336 | Worker::log($e); 337 | exit(250); 338 | } catch (\Error $e) { 339 | Worker::log($e); 340 | exit(250); 341 | } 342 | } 343 | } else { 344 | // Connection failed. 345 | $this->emitError(WORKERMAN_CONNECT_FAIL, 'connect ' . $this->_remoteAddress . ' fail after ' . round(microtime(true) - $this->_connectStartTime, 4) . ' seconds'); 346 | if ($this->_status === self::STATUS_CLOSING) { 347 | $this->destroy(); 348 | } 349 | if ($this->_status === self::STATUS_CLOSED) { 350 | $this->onConnect = null; 351 | } 352 | } 353 | } 354 | } 355 | -------------------------------------------------------------------------------- /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 | * Emitted when socket connection is successfully established. 27 | * 28 | * @var callback 29 | */ 30 | public $onConnect = null; 31 | 32 | /** 33 | * Emitted when socket connection closed. 34 | * 35 | * @var callback 36 | */ 37 | public $onClose = null; 38 | 39 | /** 40 | * Connected or not. 41 | * 42 | * @var bool 43 | */ 44 | protected $connected = false; 45 | 46 | /** 47 | * Construct. 48 | * 49 | * @param string $remote_address 50 | * @throws Exception 51 | */ 52 | public function __construct($remote_address) 53 | { 54 | // Get the application layer communication protocol and listening address. 55 | list($scheme, $address) = explode(':', $remote_address, 2); 56 | // Check application layer protocol class. 57 | if ($scheme !== 'udp') { 58 | $scheme = ucfirst($scheme); 59 | $this->protocol = '\\Protocols\\' . $scheme; 60 | if (!class_exists($this->protocol)) { 61 | $this->protocol = "\\Workerman\\Protocols\\$scheme"; 62 | if (!class_exists($this->protocol)) { 63 | throw new Exception("class \\Protocols\\$scheme not exist"); 64 | } 65 | } 66 | } 67 | 68 | $this->_remoteAddress = substr($address, 2); 69 | } 70 | 71 | /** 72 | * For udp package. 73 | * 74 | * @param resource $socket 75 | * @return bool 76 | */ 77 | public function baseRead($socket) 78 | { 79 | $recv_buffer = stream_socket_recvfrom($socket, Worker::MAX_UDP_PACKAGE_SIZE, 0, $remote_address); 80 | if (false === $recv_buffer || empty($remote_address)) { 81 | return false; 82 | } 83 | 84 | if ($this->onMessage) { 85 | if ($this->protocol) { 86 | $parser = $this->protocol; 87 | $recv_buffer = $parser::decode($recv_buffer, $this); 88 | } 89 | ConnectionInterface::$statistics['total_request']++; 90 | try { 91 | call_user_func($this->onMessage, $this, $recv_buffer); 92 | } catch (\Exception $e) { 93 | Worker::log($e); 94 | exit(250); 95 | } catch (\Error $e) { 96 | Worker::log($e); 97 | exit(250); 98 | } 99 | } 100 | return true; 101 | } 102 | 103 | /** 104 | * Sends data on the connection. 105 | * 106 | * @param string $send_buffer 107 | * @param bool $raw 108 | * @return void|boolean 109 | */ 110 | public function send($send_buffer, $raw = false) 111 | { 112 | if (false === $raw && $this->protocol) { 113 | $parser = $this->protocol; 114 | $send_buffer = $parser::encode($send_buffer, $this); 115 | if ($send_buffer === '') { 116 | return null; 117 | } 118 | } 119 | if ($this->connected === false) { 120 | $this->connect(); 121 | } 122 | return strlen($send_buffer) === stream_socket_sendto($this->_socket, $send_buffer, 0); 123 | } 124 | 125 | 126 | /** 127 | * Close connection. 128 | * 129 | * @param mixed $data 130 | * @param bool $raw 131 | * 132 | * @return bool 133 | */ 134 | public function close($data = null, $raw = false) 135 | { 136 | if ($data !== null) { 137 | $this->send($data, $raw); 138 | } 139 | Worker::$globalEvent->del($this->_socket, EventInterface::EV_READ); 140 | fclose($this->_socket); 141 | $this->connected = false; 142 | // Try to emit onClose callback. 143 | if ($this->onClose) { 144 | try { 145 | call_user_func($this->onClose, $this); 146 | } catch (\Exception $e) { 147 | Worker::log($e); 148 | exit(250); 149 | } catch (\Error $e) { 150 | Worker::log($e); 151 | exit(250); 152 | } 153 | } 154 | $this->onConnect = $this->onMessage = $this->onClose = null; 155 | return true; 156 | } 157 | 158 | /** 159 | * Connect. 160 | * 161 | * @return void 162 | */ 163 | public function connect() 164 | { 165 | if ($this->connected === true) { 166 | return; 167 | } 168 | $this->_socket = stream_socket_client("udp://{$this->_remoteAddress}"); 169 | if ($this->onMessage) { 170 | Worker::$globalEvent->add($this->_socket, EventInterface::EV_READ, array($this, 'baseRead')); 171 | } 172 | $this->connected = true; 173 | // Try to emit onConnect callback. 174 | if ($this->onConnect) { 175 | try { 176 | call_user_func($this->onConnect, $this); 177 | } catch (\Exception $e) { 178 | Worker::log($e); 179 | exit(250); 180 | } catch (\Error $e) { 181 | Worker::log($e); 182 | exit(250); 183 | } 184 | } 185 | } 186 | 187 | } 188 | -------------------------------------------------------------------------------- /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 local IP. 85 | * 86 | * @return string 87 | */ 88 | abstract public function getLocalIp(); 89 | 90 | /** 91 | * Get local port. 92 | * 93 | * @return int 94 | */ 95 | abstract public function getLocalPort(); 96 | 97 | /** 98 | * Get local 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | //如果一个事件被设置了EV_PERSIST,那么这个事件就是持续化的,意思就是这个事件会保持挂起状态,即使回调函数被执行。 93 | $real_flag = $flag === self::EV_READ ? \Event::READ | \Event::PERSIST : \Event::WRITE | \Event::PERSIST; 94 | $event = new \Event($this->_eventBase, $fd, $real_flag, $func, $fd); 95 | if (!$event||!$event->add()) { 96 | return false; 97 | } 98 | //event对象必须放在一个全局数组里面,否则会自动销毁。 99 | $this->_allEvents[$fd_key][$flag] = $event; 100 | return true; 101 | } 102 | } 103 | 104 | /** 105 | * @see Events\EventInterface::del() 106 | */ 107 | public function del($fd, $flag) 108 | { 109 | switch ($flag) { 110 | 111 | case self::EV_READ: 112 | case self::EV_WRITE: 113 | 114 | $fd_key = (int)$fd; 115 | if (isset($this->_allEvents[$fd_key][$flag])) { 116 | $this->_allEvents[$fd_key][$flag]->del(); 117 | unset($this->_allEvents[$fd_key][$flag]); 118 | } 119 | if (empty($this->_allEvents[$fd_key])) { 120 | unset($this->_allEvents[$fd_key]); 121 | } 122 | break; 123 | 124 | case self::EV_SIGNAL: 125 | $fd_key = (int)$fd; 126 | if (isset($this->_eventSignal[$fd_key])) { 127 | $this->_eventSignal[$fd_key]->del(); 128 | unset($this->_eventSignal[$fd_key]); 129 | } 130 | break; 131 | 132 | case self::EV_TIMER: 133 | case self::EV_TIMER_ONCE: 134 | if (isset($this->_eventTimer[$fd])) { 135 | $this->_eventTimer[$fd]->del(); 136 | unset($this->_eventTimer[$fd]); 137 | } 138 | break; 139 | } 140 | return true; 141 | } 142 | 143 | /** 144 | * Timer callback. 145 | * @param null $fd 146 | * @param int $what 147 | * @param int $timer_id 148 | */ 149 | public function timerCallback($fd, $what, $param) 150 | { 151 | $timer_id = $param[4]; 152 | 153 | if ($param[2] === self::EV_TIMER_ONCE) { 154 | $this->_eventTimer[$timer_id]->del(); 155 | unset($this->_eventTimer[$timer_id]); 156 | } 157 | 158 | try { 159 | call_user_func_array($param[0], $param[1]); 160 | } catch (\Exception $e) { 161 | Worker::log($e); 162 | exit(250); 163 | } catch (\Error $e) { 164 | Worker::log($e); 165 | exit(250); 166 | } 167 | } 168 | 169 | /** 170 | * @see Events\EventInterface::clearAllTimer() 171 | * @return void 172 | */ 173 | public function clearAllTimer() 174 | { 175 | foreach ($this->_eventTimer as $event) { 176 | $event->del(); 177 | } 178 | $this->_eventTimer = array(); 179 | } 180 | 181 | 182 | /** 183 | * @see EventInterface::loop() 184 | */ 185 | public function loop() 186 | { 187 | $this->_eventBase->loop(); 188 | } 189 | 190 | /** 191 | * Destroy loop. 192 | * 193 | * @return void 194 | */ 195 | public function destroy() 196 | { 197 | foreach ($this->_eventSignal as $event) { 198 | $event->del(); 199 | } 200 | } 201 | 202 | /** 203 | * Get timer count. 204 | * 205 | * @return integer 206 | */ 207 | public function getTimerCount() 208 | { 209 | return count($this->_eventTimer); 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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->_exceptFds; 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 | -------------------------------------------------------------------------------- /Events/Swoole.php: -------------------------------------------------------------------------------- 1 | 10 | * @link http://www.workerman.net/ 11 | * @link https://github.com/ares333/Workerman 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | namespace Workerman\Events; 15 | 16 | use Swoole\Event; 17 | use Swoole\Timer; 18 | 19 | class Swoole implements EventInterface 20 | { 21 | 22 | protected $_timer = array(); 23 | 24 | protected $_timerOnceMap = array(); 25 | 26 | protected $_fd = array(); 27 | 28 | // milisecond 29 | public static $signalDispatchInterval = 200; 30 | 31 | protected $_hasSignal = false; 32 | 33 | /** 34 | * 35 | * {@inheritdoc} 36 | * 37 | * @see \Workerman\Events\EventInterface::add() 38 | */ 39 | public function add($fd, $flag, $func, $args = null) 40 | { 41 | if (! isset($args)) { 42 | $args = array(); 43 | } 44 | switch ($flag) { 45 | case self::EV_SIGNAL: 46 | $res = pcntl_signal($fd, $func, false); 47 | if (! $this->_hasSignal && $res) { 48 | Timer::tick(static::$signalDispatchInterval, 49 | function () { 50 | pcntl_signal_dispatch(); 51 | }); 52 | $this->_hasSignal = true; 53 | } 54 | return $res; 55 | case self::EV_TIMER: 56 | case self::EV_TIMER_ONCE: 57 | $method = self::EV_TIMER == $flag ? 'tick' : 'after'; 58 | $mapId = count($this->_timerOnceMap); 59 | $timer_id = Timer::$method($fd * 1000, 60 | function ($timer_id = null) use ($func, $args, $mapId) { 61 | call_user_func_array($func, $args); 62 | // EV_TIMER_ONCE 63 | if (! isset($timer_id)) { 64 | // may be deleted in $func 65 | if (array_key_exists($mapId, $this->_timerOnceMap)) { 66 | $timer_id = $this->_timerOnceMap[$mapId]; 67 | unset($this->_timer[$timer_id], 68 | $this->_timerOnceMap[$mapId]); 69 | } 70 | } 71 | }); 72 | if ($flag == self::EV_TIMER_ONCE) { 73 | $this->_timerOnceMap[$mapId] = $timer_id; 74 | $this->_timer[$timer_id] = $mapId; 75 | } else { 76 | $this->_timer[$timer_id] = null; 77 | } 78 | return $timer_id; 79 | case self::EV_READ: 80 | case self::EV_WRITE: 81 | $fd_key = (int) $fd; 82 | if (! isset($this->_fd[$fd_key])) { 83 | if ($flag == self::EV_READ) { 84 | $res = Event::add($fd, $func, null, SWOOLE_EVENT_READ); 85 | $fd_type = SWOOLE_EVENT_READ; 86 | } else { 87 | $res = Event::add($fd, null, $func, SWOOLE_EVENT_WRITE); 88 | $fd_type = SWOOLE_EVENT_WRITE; 89 | } 90 | if ($res) { 91 | $this->_fd[$fd_key] = $fd_type; 92 | } 93 | } else { 94 | $fd_val = $this->_fd[$fd_key]; 95 | $res = true; 96 | if ($flag == self::EV_READ) { 97 | if (($fd_val & SWOOLE_EVENT_READ) != SWOOLE_EVENT_READ) { 98 | $res = Event::set($fd, $func, null, 99 | SWOOLE_EVENT_READ | SWOOLE_EVENT_WRITE); 100 | $this->_fd[$fd_key] |= SWOOLE_EVENT_READ; 101 | } 102 | } else { 103 | if (($fd_val & SWOOLE_EVENT_WRITE) != SWOOLE_EVENT_WRITE) { 104 | $res = Event::set($fd, null, $func, 105 | SWOOLE_EVENT_READ | SWOOLE_EVENT_WRITE); 106 | $this->_fd[$fd_key] |= SWOOLE_EVENT_WRITE; 107 | } 108 | } 109 | } 110 | return $res; 111 | } 112 | } 113 | 114 | /** 115 | * 116 | * {@inheritdoc} 117 | * 118 | * @see \Workerman\Events\EventInterface::del() 119 | */ 120 | public function del($fd, $flag) 121 | { 122 | switch ($flag) { 123 | case self::EV_SIGNAL: 124 | return pcntl_signal($fd, SIG_IGN, false); 125 | case self::EV_TIMER: 126 | case self::EV_TIMER_ONCE: 127 | // already remove in EV_TIMER_ONCE callback. 128 | if (! array_key_exists($fd, $this->_timer)) { 129 | return true; 130 | } 131 | $res = Timer::clear($fd); 132 | if ($res) { 133 | $mapId = $this->_timer[$fd]; 134 | if (isset($mapId)) { 135 | unset($this->_timerOnceMap[$mapId]); 136 | } 137 | unset($this->_timer[$fd]); 138 | } 139 | return $res; 140 | case self::EV_READ: 141 | case self::EV_WRITE: 142 | $fd_key = (int) $fd; 143 | if (isset($this->_fd[$fd_key])) { 144 | $fd_val = $this->_fd[$fd_key]; 145 | if ($flag == self::EV_READ) { 146 | $flag_remove = ~ SWOOLE_EVENT_READ; 147 | } else { 148 | $flag_remove = ~ SWOOLE_EVENT_WRITE; 149 | } 150 | $fd_val &= $flag_remove; 151 | if (0 === $fd_val) { 152 | $res = Event::del($fd); 153 | if ($res) { 154 | unset($this->_fd[$fd_key]); 155 | } 156 | } else { 157 | $res = Event::set($fd, null, null, $fd_val); 158 | if ($res) { 159 | $this->_fd[$fd_key] = $fd_val; 160 | } 161 | } 162 | } else { 163 | $res = true; 164 | } 165 | return $res; 166 | } 167 | } 168 | 169 | /** 170 | * 171 | * {@inheritdoc} 172 | * 173 | * @see \Workerman\Events\EventInterface::clearAllTimer() 174 | */ 175 | public function clearAllTimer() 176 | { 177 | foreach (array_keys($this->_timer) as $v) { 178 | Timer::clear($v); 179 | } 180 | $this->_timer = array(); 181 | $this->_timerOnceMap = array(); 182 | } 183 | 184 | /** 185 | * 186 | * {@inheritdoc} 187 | * 188 | * @see \Workerman\Events\EventInterface::loop() 189 | */ 190 | public function loop() 191 | { 192 | Event::wait(); 193 | } 194 | 195 | /** 196 | * 197 | * {@inheritdoc} 198 | * 199 | * @see \Workerman\Events\EventInterface::destroy() 200 | */ 201 | public function destroy() 202 | { 203 | Event::exit(); 204 | } 205 | 206 | /** 207 | * 208 | * {@inheritdoc} 209 | * 210 | * @see \Workerman\Events\EventInterface::getTimerCount() 211 | */ 212 | public function getTimerCount() 213 | { 214 | return count($this->_timer); 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | if (function_exists('pcntl_signal')) { 58 | //安装信号处理器, SIGALRM信号 59 | pcntl_signal(SIGALRM, array('\Workerman\Lib\Timer', 'signalHandle'), false); 60 | } 61 | } 62 | } 63 | 64 | /** 65 | * ALARM signal handler. 66 | * 67 | * @return void 68 | */ 69 | public static function signalHandle() 70 | { 71 | if (!self::$_event) { 72 | pcntl_alarm(1); 73 | self::tick(); 74 | } 75 | } 76 | 77 | /** 78 | * Add a timer. 79 | * 80 | * @param float $time_interval 81 | * @param callable $func 82 | * @param mixed $args 83 | * @param bool $persistent 84 | * @return int/false 85 | */ 86 | public static function add($time_interval, $func, $args = array(), $persistent = true) 87 | { 88 | if ($time_interval <= 0) { 89 | echo new Exception("bad time_interval"); 90 | return false; 91 | } 92 | 93 | if (self::$_event) { 94 | return self::$_event->add($time_interval, 95 | $persistent ? EventInterface::EV_TIMER : EventInterface::EV_TIMER_ONCE, $func, $args); 96 | } 97 | 98 | if (!is_callable($func)) { 99 | echo new Exception("not callable"); 100 | return false; 101 | } 102 | 103 | if (empty(self::$_tasks)) { 104 | pcntl_alarm(1); 105 | } 106 | 107 | $time_now = time(); 108 | $run_time = $time_now + $time_interval; 109 | if (!isset(self::$_tasks[$run_time])) { 110 | self::$_tasks[$run_time] = array(); 111 | } 112 | self::$_tasks[$run_time][] = array($func, (array)$args, $persistent, $time_interval); 113 | return 1; 114 | } 115 | 116 | 117 | /** 118 | * Tick. 119 | * 120 | * @return void 121 | */ 122 | public static function tick() 123 | { 124 | if (empty(self::$_tasks)) { 125 | pcntl_alarm(0); 126 | return; 127 | } 128 | 129 | $time_now = time(); 130 | foreach (self::$_tasks as $run_time => $task_data) { 131 | if ($time_now >= $run_time) { 132 | foreach ($task_data as $index => $one_task) { 133 | $task_func = $one_task[0]; 134 | $task_args = $one_task[1]; 135 | $persistent = $one_task[2]; 136 | $time_interval = $one_task[3]; 137 | try { 138 | call_user_func_array($task_func, $task_args); 139 | } catch (\Exception $e) { 140 | echo $e; 141 | } 142 | if ($persistent) { 143 | self::add($time_interval, $task_func, $task_args); 144 | } 145 | } 146 | unset(self::$_tasks[$run_time]); 147 | } 148 | } 149 | } 150 | 151 | /** 152 | * Remove a timer. 153 | * 154 | * @param mixed $timer_id 155 | * @return bool 156 | */ 157 | public static function del($timer_id) 158 | { 159 | if (self::$_event) { 160 | return self::$_event->del($timer_id, EventInterface::EV_TIMER); 161 | } 162 | 163 | return false; 164 | } 165 | 166 | /** 167 | * Remove all timers. 168 | * 169 | * @return void 170 | */ 171 | public static function delAll() 172 | { 173 | self::$_tasks = array(); 174 | //如果seconds设置为0,将不会创建alarm信号 175 | pcntl_alarm(0); 176 | if (self::$_event) { 177 | //清除定时器 178 | self::$_event->clearAllTimer(); 179 | } 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /MIT-LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2009-2015 walkor and contributors (see https://github.com/walkor/workerman/contributors) 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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. 解析http协议 2个换行分别为$http_header $http_body 115 | list($http_header, $http_body) = explode("\r\n\r\n", $recv_buffer, 2); 116 | //分割头内容 117 | $header_data = explode("\r\n", $http_header); 118 | 119 | list($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI'], $_SERVER['SERVER_PROTOCOL']) = explode(' ', 120 | $header_data[0]); 121 | 122 | $http_post_boundary = ''; 123 | unset($header_data[0]); 124 | //解析头信息 125 | foreach ($header_data as $content) { 126 | // \r\n\r\n 127 | if (empty($content)) { 128 | continue; 129 | } 130 | //$content -> Accept-Language:zh-cn 131 | list($key, $value) = explode(':', $content, 2); 132 | $key = str_replace('-', '_', strtoupper($key)); 133 | $value = trim($value); 134 | $_SERVER['HTTP_' . $key] = $value; 135 | switch ($key) { 136 | // HTTP_HOST 137 | case 'HOST': 138 | $tmp = explode(':', $value); 139 | $_SERVER['SERVER_NAME'] = $tmp[0]; 140 | if (isset($tmp[1])) { 141 | $_SERVER['SERVER_PORT'] = $tmp[1]; 142 | } 143 | break; 144 | // cookie 145 | case 'COOKIE': 146 | parse_str(str_replace('; ', '&', $_SERVER['HTTP_COOKIE']), $_COOKIE); 147 | break; 148 | // content-type 149 | case 'CONTENT_TYPE': 150 | if (!preg_match('/boundary="?(\S+)"?/', $value, $match)) { 151 | if ($pos = strpos($value, ';')) { 152 | $_SERVER['CONTENT_TYPE'] = substr($value, 0, $pos); 153 | } else { 154 | $_SERVER['CONTENT_TYPE'] = $value; 155 | } 156 | } else { 157 | $_SERVER['CONTENT_TYPE'] = 'multipart/form-data'; 158 | $http_post_boundary = '--' . $match[1]; 159 | } 160 | break; 161 | case 'CONTENT_LENGTH': 162 | $_SERVER['CONTENT_LENGTH'] = $value; 163 | break; 164 | } 165 | } 166 | 167 | // Parse $_POST. 168 | if ($_SERVER['REQUEST_METHOD'] === 'POST') { 169 | if (isset($_SERVER['CONTENT_TYPE'])) { 170 | switch ($_SERVER['CONTENT_TYPE']) { 171 | case 'multipart/form-data': 172 | self::parseUploadFiles($http_body, $http_post_boundary); 173 | break; 174 | case 'application/json': 175 | $_POST = json_decode($http_body, true); 176 | break; 177 | case 'application/x-www-form-urlencoded': 178 | parse_str($http_body, $_POST); 179 | break; 180 | case 'application/json': 181 | $_POST = json_decode($http_body, true); 182 | break; 183 | } 184 | } 185 | } 186 | 187 | // Parse other HTTP action parameters 188 | if ($_SERVER['REQUEST_METHOD'] != 'GET' && $_SERVER['REQUEST_METHOD'] != "POST") { 189 | $data = array(); 190 | if ($_SERVER['HTTP_CONTENT_TYPE'] === "application/x-www-form-urlencoded") { 191 | parse_str($http_body, $data); 192 | } elseif ($_SERVER['HTTP_CONTENT_TYPE'] === "application/json") { 193 | $data = json_decode($http_body, true); 194 | } 195 | $_REQUEST = array_merge($_REQUEST, $data); 196 | } 197 | 198 | // HTTP_RAW_REQUEST_DATA HTTP_RAW_POST_DATA 199 | $GLOBALS['HTTP_RAW_REQUEST_DATA'] = $GLOBALS['HTTP_RAW_POST_DATA'] = $http_body; 200 | 201 | // QUERY_STRING 202 | $_SERVER['QUERY_STRING'] = parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY); 203 | if ($_SERVER['QUERY_STRING']) { 204 | // $GET 205 | parse_str($_SERVER['QUERY_STRING'], $_GET); 206 | } else { 207 | $_SERVER['QUERY_STRING'] = ''; 208 | } 209 | 210 | // REQUEST 211 | $_REQUEST = array_merge($_GET, $_POST, $_REQUEST); 212 | 213 | // REMOTE_ADDR REMOTE_PORT 214 | $_SERVER['REMOTE_ADDR'] = $connection->getRemoteIp(); 215 | $_SERVER['REMOTE_PORT'] = $connection->getRemotePort(); 216 | return array('get' => $_GET, 'post' => $_POST, 'cookie' => $_COOKIE, 'server' => $_SERVER, 'files' => $_FILES); 217 | } 218 | 219 | /** 220 | * Http encode. 221 | * 222 | * @param string $content 223 | * @param TcpConnection $connection 224 | * @return string 225 | */ 226 | public static function encode($content, TcpConnection $connection) 227 | { 228 | // Default http-code. 229 | if (!isset(HttpCache::$header['Http-Code'])) { 230 | $header = "HTTP/1.1 200 OK\r\n"; 231 | } else { 232 | $header = HttpCache::$header['Http-Code'] . "\r\n"; 233 | unset(HttpCache::$header['Http-Code']); 234 | } 235 | 236 | // Content-Type 237 | if (!isset(HttpCache::$header['Content-Type'])) { 238 | $header .= "Content-Type: text/html;charset=utf-8\r\n"; 239 | } 240 | 241 | // other headers 242 | foreach (HttpCache::$header as $key => $item) { 243 | if ('Set-Cookie' === $key && is_array($item)) { 244 | foreach ($item as $it) { 245 | $header .= $it . "\r\n"; 246 | } 247 | } else { 248 | $header .= $item . "\r\n"; 249 | } 250 | } 251 | 252 | // header 253 | $header .= "Server: workerman/" . Worker::VERSION . "\r\nContent-Length: " . strlen($content) . "\r\n\r\n"; 254 | 255 | // save session 256 | self::sessionWriteClose(); 257 | 258 | // the whole http package 259 | return $header . $content; 260 | } 261 | 262 | /** 263 | * 设置http头 264 | * 265 | * @return bool|void 266 | */ 267 | public static function header($content, $replace = true, $http_response_code = 0) 268 | { 269 | if (PHP_SAPI != 'cli') { 270 | return $http_response_code ? header($content, $replace, $http_response_code) : header($content, $replace); 271 | } 272 | if (strpos($content, 'HTTP') === 0) { 273 | $key = 'Http-Code'; 274 | } else { 275 | $key = strstr($content, ":", true); 276 | if (empty($key)) { 277 | return false; 278 | } 279 | } 280 | 281 | if ('location' === strtolower($key) && !$http_response_code) { 282 | return self::header($content, true, 302); 283 | } 284 | 285 | if (isset(HttpCache::$codes[$http_response_code])) { 286 | HttpCache::$header['Http-Code'] = "HTTP/1.1 $http_response_code " . HttpCache::$codes[$http_response_code]; 287 | if ($key === 'Http-Code') { 288 | return true; 289 | } 290 | } 291 | 292 | if ($key === 'Set-Cookie') { 293 | HttpCache::$header[$key][] = $content; 294 | } else { 295 | HttpCache::$header[$key] = $content; 296 | } 297 | 298 | return true; 299 | } 300 | 301 | /** 302 | * Remove header. 303 | * 304 | * @param string $name 305 | * @return void 306 | */ 307 | public static function headerRemove($name) 308 | { 309 | if (PHP_SAPI != 'cli') { 310 | header_remove($name); 311 | return; 312 | } 313 | unset(HttpCache::$header[$name]); 314 | } 315 | 316 | /** 317 | * Set cookie. 318 | * 319 | * @param string $name 320 | * @param string $value 321 | * @param integer $maxage 322 | * @param string $path 323 | * @param string $domain 324 | * @param bool $secure 325 | * @param bool $HTTPOnly 326 | * @return bool|void 327 | */ 328 | public static function setcookie( 329 | $name, 330 | $value = '', 331 | $maxage = 0, 332 | $path = '', 333 | $domain = '', 334 | $secure = false, 335 | $HTTPOnly = false 336 | ) { 337 | if (PHP_SAPI != 'cli') { 338 | return setcookie($name, $value, $maxage, $path, $domain, $secure, $HTTPOnly); 339 | } 340 | return self::header( 341 | 'Set-Cookie: ' . $name . '=' . rawurlencode($value) 342 | . (empty($domain) ? '' : '; Domain=' . $domain) 343 | . (empty($maxage) ? '' : '; Max-Age=' . $maxage) 344 | . (empty($path) ? '' : '; Path=' . $path) 345 | . (!$secure ? '' : '; Secure') 346 | . (!$HTTPOnly ? '' : '; HttpOnly'), false); 347 | } 348 | 349 | /** 350 | * sessionCreateId 351 | * 352 | * @param string|prefix $prefix 353 | * 354 | * @return string 355 | */ 356 | public static function sessionCreateId($prefix = null) 357 | { 358 | return session_create_id($prefix); 359 | } 360 | 361 | /** 362 | * sessionId 363 | * 364 | * @param string $id 365 | * 366 | * @return string|null 367 | */ 368 | public static function sessionId($id = null) 369 | { 370 | if (PHP_SAPI != 'cli') { 371 | return $id ? session_id($id) : session_id(); 372 | } 373 | if (static::sessionStarted() && HttpCache::$instance->sessionFile) { 374 | return str_replace('sess_', '', basename(HttpCache::$instance->sessionFile)); 375 | } 376 | return ''; 377 | } 378 | 379 | /** 380 | * sessionName 381 | * 382 | * @param string $name 383 | * 384 | * @return string 385 | */ 386 | public static function sessionName($name = null) 387 | { 388 | if (PHP_SAPI != 'cli') { 389 | return $name ? session_name($name) : session_name(); 390 | } 391 | $session_name = HttpCache::$sessionName; 392 | if ($name && ! static::sessionStarted()) { 393 | HttpCache::$sessionName = $name; 394 | } 395 | return $session_name; 396 | } 397 | 398 | /** 399 | * sessionSavePath 400 | * 401 | * @param string $path 402 | * 403 | * @return void 404 | */ 405 | public static function sessionSavePath($path = null) 406 | { 407 | if (PHP_SAPI != 'cli') { 408 | return $path ? session_save_path($path) : session_save_path(); 409 | } 410 | if ($path && is_dir($path) && is_writable($path) && !static::sessionStarted()) { 411 | HttpCache::$sessionPath = $path; 412 | } 413 | return HttpCache::$sessionPath; 414 | } 415 | 416 | /** 417 | * sessionStarted 418 | * 419 | * @return bool 420 | */ 421 | public static function sessionStarted() 422 | { 423 | if (!HttpCache::$instance) return false; 424 | 425 | return HttpCache::$instance->sessionStarted; 426 | } 427 | 428 | /** 429 | * sessionStart 430 | * 431 | * @return bool 432 | */ 433 | public static function sessionStart() 434 | { 435 | if (PHP_SAPI != 'cli') { 436 | return session_start(); 437 | } 438 | 439 | self::tryGcSessions(); 440 | 441 | if (HttpCache::$instance->sessionStarted) { 442 | echo "already sessionStarted\n"; 443 | return true; 444 | } 445 | HttpCache::$instance->sessionStarted = true; 446 | // Generate a SID. 447 | if (!isset($_COOKIE[HttpCache::$sessionName]) || !is_file(HttpCache::$sessionPath . '/sess_' . $_COOKIE[HttpCache::$sessionName])) { 448 | // Create a unique session_id and the associated file name. 449 | while (true) { 450 | $session_id = static::sessionCreateId(); 451 | if (!is_file($file_name = HttpCache::$sessionPath . '/sess_' . $session_id)) break; 452 | } 453 | HttpCache::$instance->sessionFile = $file_name; 454 | return self::setcookie( 455 | HttpCache::$sessionName 456 | , $session_id 457 | , ini_get('session.cookie_lifetime') 458 | , ini_get('session.cookie_path') 459 | , ini_get('session.cookie_domain') 460 | , ini_get('session.cookie_secure') 461 | , ini_get('session.cookie_httponly') 462 | ); 463 | } 464 | if (!HttpCache::$instance->sessionFile) { 465 | HttpCache::$instance->sessionFile = HttpCache::$sessionPath . '/sess_' . $_COOKIE[HttpCache::$sessionName]; 466 | } 467 | // Read session from session file. 468 | if (HttpCache::$instance->sessionFile) { 469 | $raw = file_get_contents(HttpCache::$instance->sessionFile); 470 | if ($raw) { 471 | $_SESSION = unserialize($raw); 472 | } 473 | } 474 | return true; 475 | } 476 | 477 | /** 478 | * Save session. 479 | * 480 | * @return bool 481 | */ 482 | public static function sessionWriteClose() 483 | { 484 | if (PHP_SAPI != 'cli') { 485 | return session_write_close(); 486 | } 487 | if (!empty(HttpCache::$instance->sessionStarted) && !empty($_SESSION)) { 488 | $session_str = serialize($_SESSION); 489 | if ($session_str && HttpCache::$instance->sessionFile) { 490 | return file_put_contents(HttpCache::$instance->sessionFile, $session_str); 491 | } 492 | } 493 | return empty($_SESSION); 494 | } 495 | 496 | /** 497 | * End, like call exit in php-fpm. 498 | * 499 | * @param string $msg 500 | * @throws \Exception 501 | */ 502 | public static function end($msg = '') 503 | { 504 | if (PHP_SAPI != 'cli') { 505 | exit($msg); 506 | } 507 | if ($msg) { 508 | echo $msg; 509 | } 510 | throw new \Exception('jump_exit'); 511 | } 512 | 513 | /** 514 | * Get mime types. 515 | * 516 | * @return string 517 | */ 518 | public static function getMimeTypesFile() 519 | { 520 | return __DIR__ . '/Http/mime.types'; 521 | } 522 | 523 | /** 524 | * Parse $_FILES. 525 | * 526 | * @param string $http_body 527 | * @param string $http_post_boundary 528 | * @return void 529 | */ 530 | protected static function parseUploadFiles($http_body, $http_post_boundary) 531 | { 532 | $http_body = substr($http_body, 0, strlen($http_body) - (strlen($http_post_boundary) + 4)); 533 | $boundary_data_array = explode($http_post_boundary . "\r\n", $http_body); 534 | if ($boundary_data_array[0] === '') { 535 | unset($boundary_data_array[0]); 536 | } 537 | $key = -1; 538 | foreach ($boundary_data_array as $boundary_data_buffer) { 539 | list($boundary_header_buffer, $boundary_value) = explode("\r\n\r\n", $boundary_data_buffer, 2); 540 | // Remove \r\n from the end of buffer. 541 | $boundary_value = substr($boundary_value, 0, -2); 542 | $key ++; 543 | foreach (explode("\r\n", $boundary_header_buffer) as $item) { 544 | list($header_key, $header_value) = explode(": ", $item); 545 | $header_key = strtolower($header_key); 546 | switch ($header_key) { 547 | case "content-disposition": 548 | // Is file data. 549 | if (preg_match('/name="(.*?)"; filename="(.*?)"$/', $header_value, $match)) { 550 | // Parse $_FILES. 551 | $_FILES[$key] = array( 552 | 'name' => $match[1], 553 | 'file_name' => $match[2], 554 | 'file_data' => $boundary_value, 555 | 'file_size' => strlen($boundary_value), 556 | ); 557 | continue; 558 | } // Is post field. 559 | else { 560 | // Parse $_POST. 561 | if (preg_match('/name="(.*?)"$/', $header_value, $match)) { 562 | $_POST[$match[1]] = $boundary_value; 563 | } 564 | } 565 | break; 566 | case "content-type": 567 | // add file_type 568 | $_FILES[$key]['file_type'] = trim($header_value); 569 | break; 570 | } 571 | } 572 | } 573 | } 574 | 575 | /** 576 | * Try GC sessions. 577 | * 578 | * @return void 579 | */ 580 | public static function tryGcSessions() 581 | { 582 | if (HttpCache::$sessionGcProbability <= 0 || 583 | HttpCache::$sessionGcDivisor <= 0 || 584 | rand(1, HttpCache::$sessionGcDivisor) > HttpCache::$sessionGcProbability) { 585 | return; 586 | } 587 | 588 | $time_now = time(); 589 | foreach(glob(HttpCache::$sessionPath.'/ses*') as $file) { 590 | if(is_file($file) && $time_now - filemtime($file) > HttpCache::$sessionGcMaxLifeTime) { 591 | unlink($file); 592 | } 593 | } 594 | } 595 | } 596 | 597 | /** 598 | * Http cache for the current http response. 599 | */ 600 | class HttpCache 601 | { 602 | public static $codes = array( 603 | 100 => 'Continue', 604 | 101 => 'Switching Protocols', 605 | 200 => 'OK', 606 | 201 => 'Created', 607 | 202 => 'Accepted', 608 | 203 => 'Non-Authoritative Information', 609 | 204 => 'No Content', 610 | 205 => 'Reset Content', 611 | 206 => 'Partial Content', 612 | 300 => 'Multiple Choices', 613 | 301 => 'Moved Permanently', 614 | 302 => 'Found', 615 | 303 => 'See Other', 616 | 304 => 'Not Modified', 617 | 305 => 'Use Proxy', 618 | 306 => '(Unused)', 619 | 307 => 'Temporary Redirect', 620 | 400 => 'Bad Request', 621 | 401 => 'Unauthorized', 622 | 402 => 'Payment Required', 623 | 403 => 'Forbidden', 624 | 404 => 'Not Found', 625 | 405 => 'Method Not Allowed', 626 | 406 => 'Not Acceptable', 627 | 407 => 'Proxy Authentication Required', 628 | 408 => 'Request Timeout', 629 | 409 => 'Conflict', 630 | 410 => 'Gone', 631 | 411 => 'Length Required', 632 | 412 => 'Precondition Failed', 633 | 413 => 'Request Entity Too Large', 634 | 414 => 'Request-URI Too Long', 635 | 415 => 'Unsupported Media Type', 636 | 416 => 'Requested Range Not Satisfiable', 637 | 417 => 'Expectation Failed', 638 | 422 => 'Unprocessable Entity', 639 | 423 => 'Locked', 640 | 500 => 'Internal Server Error', 641 | 501 => 'Not Implemented', 642 | 502 => 'Bad Gateway', 643 | 503 => 'Service Unavailable', 644 | 504 => 'Gateway Timeout', 645 | 505 => 'HTTP Version Not Supported', 646 | ); 647 | 648 | /** 649 | * @var HttpCache 650 | */ 651 | public static $instance = null; 652 | public static $header = array(); 653 | public static $sessionPath = ''; 654 | public static $sessionName = ''; 655 | public static $sessionGcProbability = 1; 656 | public static $sessionGcDivisor = 1000; 657 | public static $sessionGcMaxLifeTime = 1440; 658 | public $sessionStarted = false; 659 | public $sessionFile = ''; 660 | 661 | public static function init() 662 | { 663 | if (!self::$sessionName) { 664 | self::$sessionName = ini_get('session.name'); 665 | } 666 | 667 | if (!self::$sessionPath) { 668 | self::$sessionPath = @session_save_path(); 669 | } 670 | 671 | if (!self::$sessionPath || strpos(self::$sessionPath, 'tcp://') === 0) { 672 | self::$sessionPath = sys_get_temp_dir(); 673 | } 674 | 675 | if ($gc_probability = ini_get('session.gc_probability')) { 676 | self::$sessionGcProbability = $gc_probability; 677 | } 678 | 679 | if ($gc_divisor = ini_get('session.gc_divisor')) { 680 | self::$sessionGcDivisor = $gc_divisor; 681 | } 682 | 683 | if ($gc_max_life_time = ini_get('session.gc_maxlifetime')) { 684 | self::$sessionGcMaxLifeTime = $gc_max_life_time; 685 | } 686 | } 687 | } 688 | 689 | HttpCache::init(); 690 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 < 6) { 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 | 74 | if (!$masked) { 75 | echo "frame not masked\n"; 76 | $connection->close(); 77 | return 0; 78 | } 79 | 80 | $opcode = $firstbyte & 0xf; 81 | switch ($opcode) { 82 | case 0x0: 83 | break; 84 | // Blob type. 85 | case 0x1: 86 | break; 87 | // Arraybuffer type. 88 | case 0x2: 89 | break; 90 | // Close package. 91 | case 0x8: 92 | // Try to emit onWebSocketClose callback. 93 | if (isset($connection->onWebSocketClose) || isset($connection->worker->onWebSocketClose)) { 94 | try { 95 | call_user_func(isset($connection->onWebSocketClose)?$connection->onWebSocketClose:$connection->worker->onWebSocketClose, $connection); 96 | } catch (\Exception $e) { 97 | Worker::log($e); 98 | exit(250); 99 | } catch (\Error $e) { 100 | Worker::log($e); 101 | exit(250); 102 | } 103 | } // Close connection. 104 | else { 105 | $connection->close(); 106 | } 107 | return 0; 108 | // Ping package. 109 | case 0x9: 110 | // Try to emit onWebSocketPing callback. 111 | if (isset($connection->onWebSocketPing) || isset($connection->worker->onWebSocketPing)) { 112 | try { 113 | call_user_func(isset($connection->onWebSocketPing)?$connection->onWebSocketPing:$connection->worker->onWebSocketPing, $connection); 114 | } catch (\Exception $e) { 115 | Worker::log($e); 116 | exit(250); 117 | } catch (\Error $e) { 118 | Worker::log($e); 119 | exit(250); 120 | } 121 | } // Send pong package to client. 122 | else { 123 | $connection->send(pack('H*', '8a00'), true); 124 | } 125 | 126 | // Consume data from receive buffer. 127 | if (!$data_len) { 128 | $head_len = 6; 129 | $connection->consumeRecvBuffer($head_len); 130 | if ($recv_len > $head_len) { 131 | return static::input(substr($buffer, $head_len), $connection); 132 | } 133 | return 0; 134 | } 135 | break; 136 | // Pong package. 137 | case 0xa: 138 | // Try to emit onWebSocketPong callback. 139 | if (isset($connection->onWebSocketPong) || isset($connection->worker->onWebSocketPong)) { 140 | try { 141 | call_user_func(isset($connection->onWebSocketPong)?$connection->onWebSocketPong:$connection->worker->onWebSocketPong, $connection); 142 | } catch (\Exception $e) { 143 | Worker::log($e); 144 | exit(250); 145 | } catch (\Error $e) { 146 | Worker::log($e); 147 | exit(250); 148 | } 149 | } 150 | // Consume data from receive buffer. 151 | if (!$data_len) { 152 | $head_len = 6; 153 | $connection->consumeRecvBuffer($head_len); 154 | if ($recv_len > $head_len) { 155 | return static::input(substr($buffer, $head_len), $connection); 156 | } 157 | return 0; 158 | } 159 | break; 160 | // Wrong opcode. 161 | default : 162 | echo "error opcode $opcode and close websocket connection. Buffer:" . bin2hex($buffer) . "\n"; 163 | $connection->close(); 164 | return 0; 165 | } 166 | 167 | // Calculate packet length. 168 | $head_len = 6; 169 | if ($data_len === 126) { 170 | $head_len = 8; 171 | if ($head_len > $recv_len) { 172 | return 0; 173 | } 174 | $pack = unpack('nn/ntotal_len', $buffer); 175 | $data_len = $pack['total_len']; 176 | } else { 177 | if ($data_len === 127) { 178 | $head_len = 14; 179 | if ($head_len > $recv_len) { 180 | return 0; 181 | } 182 | $arr = unpack('n/N2c', $buffer); 183 | $data_len = $arr['c1']*4294967296 + $arr['c2']; 184 | } 185 | } 186 | $current_frame_length = $head_len + $data_len; 187 | 188 | $total_package_size = strlen($connection->websocketDataBuffer) + $current_frame_length; 189 | if ($total_package_size > TcpConnection::$maxPackageSize) { 190 | echo "error package. package_length=$total_package_size\n"; 191 | $connection->close(); 192 | return 0; 193 | } 194 | 195 | if ($is_fin_frame) { 196 | return $current_frame_length; 197 | } else { 198 | $connection->websocketCurrentFrameLength = $current_frame_length; 199 | } 200 | } 201 | 202 | // Received just a frame length data. 203 | if ($connection->websocketCurrentFrameLength === $recv_len) { 204 | static::decode($buffer, $connection); 205 | $connection->consumeRecvBuffer($connection->websocketCurrentFrameLength); 206 | $connection->websocketCurrentFrameLength = 0; 207 | return 0; 208 | } // The length of the received data is greater than the length of a frame. 209 | elseif ($connection->websocketCurrentFrameLength < $recv_len) { 210 | static::decode(substr($buffer, 0, $connection->websocketCurrentFrameLength), $connection); 211 | $connection->consumeRecvBuffer($connection->websocketCurrentFrameLength); 212 | $current_frame_length = $connection->websocketCurrentFrameLength; 213 | $connection->websocketCurrentFrameLength = 0; 214 | // Continue to read next frame. 215 | return static::input(substr($buffer, $current_frame_length), $connection); 216 | } // The length of the received data is less than the length of a frame. 217 | else { 218 | return 0; 219 | } 220 | } 221 | 222 | /** 223 | * Websocket encode. 224 | * 225 | * @param string $buffer 226 | * @param ConnectionInterface $connection 227 | * @return string 228 | */ 229 | public static function encode($buffer, ConnectionInterface $connection) 230 | { 231 | if (!is_scalar($buffer)) { 232 | throw new \Exception("You can't send(" . gettype($buffer) . ") to client, you need to convert it to a string. "); 233 | } 234 | $len = strlen($buffer); 235 | if (empty($connection->websocketType)) { 236 | $connection->websocketType = static::BINARY_TYPE_BLOB; 237 | } 238 | 239 | $first_byte = $connection->websocketType; 240 | 241 | if ($len <= 125) { 242 | $encode_buffer = $first_byte . chr($len) . $buffer; 243 | } else { 244 | if ($len <= 65535) { 245 | $encode_buffer = $first_byte . chr(126) . pack("n", $len) . $buffer; 246 | } else { 247 | $encode_buffer = $first_byte . chr(127) . pack("xxxxN", $len) . $buffer; 248 | } 249 | } 250 | 251 | // Handshake not completed so temporary buffer websocket data waiting for send. 252 | if (empty($connection->websocketHandshake)) { 253 | if (empty($connection->tmpWebsocketData)) { 254 | $connection->tmpWebsocketData = ''; 255 | } 256 | // If buffer has already full then discard the current package. 257 | if (strlen($connection->tmpWebsocketData) > $connection->maxSendBufferSize) { 258 | if ($connection->onError) { 259 | try { 260 | call_user_func($connection->onError, $connection, WORKERMAN_SEND_FAIL, 'send buffer full and drop package'); 261 | } catch (\Exception $e) { 262 | Worker::log($e); 263 | exit(250); 264 | } catch (\Error $e) { 265 | Worker::log($e); 266 | exit(250); 267 | } 268 | } 269 | return ''; 270 | } 271 | $connection->tmpWebsocketData .= $encode_buffer; 272 | // Check buffer is full. 273 | if ($connection->maxSendBufferSize <= strlen($connection->tmpWebsocketData)) { 274 | if ($connection->onBufferFull) { 275 | try { 276 | call_user_func($connection->onBufferFull, $connection); 277 | } catch (\Exception $e) { 278 | Worker::log($e); 279 | exit(250); 280 | } catch (\Error $e) { 281 | Worker::log($e); 282 | exit(250); 283 | } 284 | } 285 | } 286 | 287 | // Return empty string. 288 | return ''; 289 | } 290 | 291 | return $encode_buffer; 292 | } 293 | 294 | /** 295 | * Websocket decode. 296 | * 297 | * @param string $buffer 298 | * @param ConnectionInterface $connection 299 | * @return string 300 | */ 301 | public static function decode($buffer, ConnectionInterface $connection) 302 | { 303 | $masks = $data = $decoded = null; 304 | $len = ord($buffer[1]) & 127; 305 | if ($len === 126) { 306 | $masks = substr($buffer, 4, 4); 307 | $data = substr($buffer, 8); 308 | } else { 309 | if ($len === 127) { 310 | $masks = substr($buffer, 10, 4); 311 | $data = substr($buffer, 14); 312 | } else { 313 | $masks = substr($buffer, 2, 4); 314 | $data = substr($buffer, 6); 315 | } 316 | } 317 | for ($index = 0; $index < strlen($data); $index++) { 318 | $decoded .= $data[$index] ^ $masks[$index % 4]; 319 | } 320 | if ($connection->websocketCurrentFrameLength) { 321 | $connection->websocketDataBuffer .= $decoded; 322 | return $connection->websocketDataBuffer; 323 | } else { 324 | if ($connection->websocketDataBuffer !== '') { 325 | $decoded = $connection->websocketDataBuffer . $decoded; 326 | $connection->websocketDataBuffer = ''; 327 | } 328 | return $decoded; 329 | } 330 | } 331 | 332 | /** 333 | * Websocket handshake. 334 | * 335 | * @param string $buffer 336 | * @param \Workerman\Connection\TcpConnection $connection 337 | * @return int 338 | */ 339 | protected static function dealHandshake($buffer, $connection) 340 | { 341 | // HTTP protocol. 342 | if (0 === strpos($buffer, 'GET')) { 343 | // Find \r\n\r\n. 344 | $heder_end_pos = strpos($buffer, "\r\n\r\n"); 345 | if (!$heder_end_pos) { 346 | return 0; 347 | } 348 | $header_length = $heder_end_pos + 4; 349 | 350 | // Get Sec-WebSocket-Key. 351 | $Sec_WebSocket_Key = ''; 352 | if (preg_match("/Sec-WebSocket-Key: *(.*?)\r\n/i", $buffer, $match)) { 353 | $Sec_WebSocket_Key = $match[1]; 354 | } else { 355 | $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.", 356 | true); 357 | $connection->close(); 358 | return 0; 359 | } 360 | // Calculation websocket key. 361 | $new_key = base64_encode(sha1($Sec_WebSocket_Key . "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", true)); 362 | // Handshake response data. 363 | $handshake_message = "HTTP/1.1 101 Switching Protocols\r\n"; 364 | $handshake_message .= "Upgrade: websocket\r\n"; 365 | $handshake_message .= "Sec-WebSocket-Version: 13\r\n"; 366 | $handshake_message .= "Connection: Upgrade\r\n"; 367 | $handshake_message .= "Sec-WebSocket-Accept: " . $new_key . "\r\n"; 368 | 369 | // Websocket data buffer. 370 | $connection->websocketDataBuffer = ''; 371 | // Current websocket frame length. 372 | $connection->websocketCurrentFrameLength = 0; 373 | // Current websocket frame data. 374 | $connection->websocketCurrentFrameBuffer = ''; 375 | // Consume handshake data. 376 | $connection->consumeRecvBuffer($header_length); 377 | 378 | // blob or arraybuffer 379 | if (empty($connection->websocketType)) { 380 | $connection->websocketType = static::BINARY_TYPE_BLOB; 381 | } 382 | 383 | $has_server_header = false; 384 | 385 | // Try to emit onWebSocketConnect callback. 386 | if (isset($connection->onWebSocketConnect) || isset($connection->worker->onWebSocketConnect)) { 387 | static::parseHttpHeader($buffer); 388 | try { 389 | call_user_func(isset($connection->onWebSocketConnect)?$connection->onWebSocketConnect:$connection->worker->onWebSocketConnect, $connection, $buffer); 390 | } catch (\Exception $e) { 391 | Worker::log($e); 392 | exit(250); 393 | } catch (\Error $e) { 394 | Worker::log($e); 395 | exit(250); 396 | } 397 | if (!empty($_SESSION) && class_exists('\GatewayWorker\Lib\Context')) { 398 | $connection->session = \GatewayWorker\Lib\Context::sessionEncode($_SESSION); 399 | } 400 | $_GET = $_SERVER = $_SESSION = $_COOKIE = array(); 401 | 402 | if (isset($connection->headers)) { 403 | if (is_array($connection->headers)) { 404 | foreach ($connection->headers as $header) { 405 | if (strpos($header, 'Server:') === 0) { 406 | $has_server_header = true; 407 | } 408 | $handshake_message .= "$header\r\n"; 409 | } 410 | } else { 411 | $handshake_message .= "$connection->headers\r\n"; 412 | } 413 | } 414 | } 415 | if (!$has_server_header) { 416 | $handshake_message .= "Server: workerman/".Worker::VERSION."\r\n"; 417 | } 418 | $handshake_message .= "\r\n"; 419 | // Send handshake response. 420 | $connection->send($handshake_message, true); 421 | // Mark handshake complete.. 422 | $connection->websocketHandshake = true; 423 | // There are data waiting to be sent. 424 | if (!empty($connection->tmpWebsocketData)) { 425 | $connection->send($connection->tmpWebsocketData, true); 426 | $connection->tmpWebsocketData = ''; 427 | } 428 | if (strlen($buffer) > $header_length) { 429 | return static::input(substr($buffer, $header_length), $connection); 430 | } 431 | return 0; 432 | } // Is flash policy-file-request. 433 | elseif (0 === strpos($buffer, 'send($policy_xml, true); 436 | $connection->consumeRecvBuffer(strlen($buffer)); 437 | return 0; 438 | } 439 | // Bad websocket handshake request. 440 | $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.", 441 | true); 442 | $connection->close(); 443 | return 0; 444 | } 445 | 446 | /** 447 | * Parse http header. 448 | * 449 | * @param string $buffer 450 | * @return void 451 | */ 452 | protected static function parseHttpHeader($buffer) 453 | { 454 | // Parse headers. 455 | list($http_header, ) = explode("\r\n\r\n", $buffer, 2); 456 | $header_data = explode("\r\n", $http_header); 457 | 458 | if ($_SERVER) { 459 | $_SERVER = array(); 460 | } 461 | 462 | list($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI'], $_SERVER['SERVER_PROTOCOL']) = explode(' ', 463 | $header_data[0]); 464 | 465 | unset($header_data[0]); 466 | foreach ($header_data as $content) { 467 | // \r\n\r\n 468 | if (empty($content)) { 469 | continue; 470 | } 471 | list($key, $value) = explode(':', $content, 2); 472 | $key = str_replace('-', '_', strtoupper($key)); 473 | $value = trim($value); 474 | $_SERVER['HTTP_' . $key] = $value; 475 | switch ($key) { 476 | // HTTP_HOST 477 | case 'HOST': 478 | $tmp = explode(':', $value); 479 | $_SERVER['SERVER_NAME'] = $tmp[0]; 480 | if (isset($tmp[1])) { 481 | $_SERVER['SERVER_PORT'] = $tmp[1]; 482 | } 483 | break; 484 | // cookie 485 | case 'COOKIE': 486 | parse_str(str_replace('; ', '&', $_SERVER['HTTP_COOKIE']), $_COOKIE); 487 | break; 488 | } 489 | } 490 | 491 | // QUERY_STRING 492 | $_SERVER['QUERY_STRING'] = parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY); 493 | if ($_SERVER['QUERY_STRING']) { 494 | // $GET 495 | parse_str($_SERVER['QUERY_STRING'], $_GET); 496 | } else { 497 | $_SERVER['QUERY_STRING'] = ''; 498 | } 499 | } 500 | } 501 | -------------------------------------------------------------------------------- /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 | 75 | if ($masked) { 76 | echo "frame masked\n"; 77 | $connection->close(); 78 | return 0; 79 | } 80 | 81 | $opcode = $firstbyte & 0xf; 82 | 83 | switch ($opcode) { 84 | case 0x0: 85 | break; 86 | // Blob type. 87 | case 0x1: 88 | break; 89 | // Arraybuffer type. 90 | case 0x2: 91 | break; 92 | // Close package. 93 | case 0x8: 94 | // Try to emit onWebSocketClose callback. 95 | if (isset($connection->onWebSocketClose)) { 96 | try { 97 | call_user_func($connection->onWebSocketClose, $connection); 98 | } catch (\Exception $e) { 99 | Worker::log($e); 100 | exit(250); 101 | } catch (\Error $e) { 102 | Worker::log($e); 103 | exit(250); 104 | } 105 | } // Close connection. 106 | else { 107 | $connection->close(); 108 | } 109 | return 0; 110 | // Ping package. 111 | case 0x9: 112 | // Try to emit onWebSocketPing callback. 113 | if (isset($connection->onWebSocketPing)) { 114 | try { 115 | call_user_func($connection->onWebSocketPing, $connection); 116 | } catch (\Exception $e) { 117 | Worker::log($e); 118 | exit(250); 119 | } catch (\Error $e) { 120 | Worker::log($e); 121 | exit(250); 122 | } 123 | } // Send pong package to client. 124 | else { 125 | $connection->send(pack('H*', '8a00'), true); 126 | } 127 | // Consume data from receive buffer. 128 | if (!$data_len) { 129 | $head_len = 2; 130 | $connection->consumeRecvBuffer($head_len); 131 | if ($recv_len > $head_len) { 132 | return self::input(substr($buffer, $head_len), $connection); 133 | } 134 | return 0; 135 | } 136 | break; 137 | // Pong package. 138 | case 0xa: 139 | // Try to emit onWebSocketPong callback. 140 | if (isset($connection->onWebSocketPong)) { 141 | try { 142 | call_user_func($connection->onWebSocketPong, $connection); 143 | } catch (\Exception $e) { 144 | Worker::log($e); 145 | exit(250); 146 | } catch (\Error $e) { 147 | Worker::log($e); 148 | exit(250); 149 | } 150 | } 151 | // Consume data from receive buffer. 152 | if (!$data_len) { 153 | $head_len = 2; 154 | $connection->consumeRecvBuffer($head_len); 155 | if ($recv_len > $head_len) { 156 | return self::input(substr($buffer, $head_len), $connection); 157 | } 158 | return 0; 159 | } 160 | break; 161 | // Wrong opcode. 162 | default : 163 | echo "error opcode $opcode and close websocket connection. Buffer:" . $buffer . "\n"; 164 | $connection->close(); 165 | return 0; 166 | } 167 | // Calculate packet length. 168 | if ($data_len === 126) { 169 | if (strlen($buffer) < 4) { 170 | return 0; 171 | } 172 | $pack = unpack('nn/ntotal_len', $buffer); 173 | $current_frame_length = $pack['total_len'] + 4; 174 | } else if ($data_len === 127) { 175 | if (strlen($buffer) < 10) { 176 | return 0; 177 | } 178 | $arr = unpack('n/N2c', $buffer); 179 | $current_frame_length = $arr['c1']*4294967296 + $arr['c2'] + 10; 180 | } else { 181 | $current_frame_length = $data_len + 2; 182 | } 183 | 184 | $total_package_size = strlen($connection->websocketDataBuffer) + $current_frame_length; 185 | if ($total_package_size > TcpConnection::$maxPackageSize) { 186 | echo "error package. package_length=$total_package_size\n"; 187 | $connection->close(); 188 | return 0; 189 | } 190 | 191 | if ($is_fin_frame) { 192 | return $current_frame_length; 193 | } else { 194 | $connection->websocketCurrentFrameLength = $current_frame_length; 195 | } 196 | } 197 | // Received just a frame length data. 198 | if ($connection->websocketCurrentFrameLength === $recv_len) { 199 | self::decode($buffer, $connection); 200 | $connection->consumeRecvBuffer($connection->websocketCurrentFrameLength); 201 | $connection->websocketCurrentFrameLength = 0; 202 | return 0; 203 | } // The length of the received data is greater than the length of a frame. 204 | elseif ($connection->websocketCurrentFrameLength < $recv_len) { 205 | self::decode(substr($buffer, 0, $connection->websocketCurrentFrameLength), $connection); 206 | $connection->consumeRecvBuffer($connection->websocketCurrentFrameLength); 207 | $current_frame_length = $connection->websocketCurrentFrameLength; 208 | $connection->websocketCurrentFrameLength = 0; 209 | // Continue to read next frame. 210 | return self::input(substr($buffer, $current_frame_length), $connection); 211 | } // The length of the received data is less than the length of a frame. 212 | else { 213 | return 0; 214 | } 215 | } 216 | 217 | /** 218 | * Websocket encode. 219 | * 220 | * @param string $buffer 221 | * @param ConnectionInterface $connection 222 | * @return string 223 | */ 224 | public static function encode($payload, $connection) 225 | { 226 | if (empty($connection->websocketType)) { 227 | $connection->websocketType = self::BINARY_TYPE_BLOB; 228 | } 229 | $payload = (string)$payload; 230 | if (empty($connection->handshakeStep)) { 231 | self::sendHandshake($connection); 232 | } 233 | $mask = 1; 234 | $mask_key = "\x00\x00\x00\x00"; 235 | 236 | $pack = ''; 237 | $length = $length_flag = strlen($payload); 238 | if (65535 < $length) { 239 | $pack = pack('NN', ($length & 0xFFFFFFFF00000000) >> 32, $length & 0x00000000FFFFFFFF); 240 | $length_flag = 127; 241 | } else if (125 < $length) { 242 | $pack = pack('n*', $length); 243 | $length_flag = 126; 244 | } 245 | 246 | $head = ($mask << 7) | $length_flag; 247 | $head = $connection->websocketType . chr($head) . $pack; 248 | 249 | $frame = $head . $mask_key; 250 | // append payload to frame: 251 | for ($i = 0; $i < $length; $i++) { 252 | $frame .= $payload[$i] ^ $mask_key[$i % 4]; 253 | } 254 | if ($connection->handshakeStep === 1) { 255 | // If buffer has already full then discard the current package. 256 | if (strlen($connection->tmpWebsocketData) > $connection->maxSendBufferSize) { 257 | if ($connection->onError) { 258 | try { 259 | call_user_func($connection->onError, $connection, WORKERMAN_SEND_FAIL, 'send buffer full and drop package'); 260 | } catch (\Exception $e) { 261 | Worker::log($e); 262 | exit(250); 263 | } catch (\Error $e) { 264 | Worker::log($e); 265 | exit(250); 266 | } 267 | } 268 | return ''; 269 | } 270 | $connection->tmpWebsocketData = $connection->tmpWebsocketData . $frame; 271 | // Check buffer is full. 272 | if ($connection->maxSendBufferSize <= strlen($connection->tmpWebsocketData)) { 273 | if ($connection->onBufferFull) { 274 | try { 275 | call_user_func($connection->onBufferFull, $connection); 276 | } catch (\Exception $e) { 277 | Worker::log($e); 278 | exit(250); 279 | } catch (\Error $e) { 280 | Worker::log($e); 281 | exit(250); 282 | } 283 | } 284 | } 285 | return ''; 286 | } 287 | return $frame; 288 | } 289 | 290 | /** 291 | * Websocket decode. 292 | * 293 | * @param string $buffer 294 | * @param ConnectionInterface $connection 295 | * @return string 296 | */ 297 | public static function decode($bytes, $connection) 298 | { 299 | $data_length = ord($bytes[1]); 300 | 301 | if ($data_length === 126) { 302 | $decoded_data = substr($bytes, 4); 303 | } else if ($data_length === 127) { 304 | $decoded_data = substr($bytes, 10); 305 | } else { 306 | $decoded_data = substr($bytes, 2); 307 | } 308 | if ($connection->websocketCurrentFrameLength) { 309 | $connection->websocketDataBuffer .= $decoded_data; 310 | return $connection->websocketDataBuffer; 311 | } else { 312 | if ($connection->websocketDataBuffer !== '') { 313 | $decoded_data = $connection->websocketDataBuffer . $decoded_data; 314 | $connection->websocketDataBuffer = ''; 315 | } 316 | return $decoded_data; 317 | } 318 | } 319 | 320 | /** 321 | * Send websocket handshake data. 322 | * 323 | * @return void 324 | */ 325 | public static function onConnect($connection) 326 | { 327 | self::sendHandshake($connection); 328 | } 329 | 330 | /** 331 | * Clean 332 | * 333 | * @param $connection 334 | */ 335 | public static function onClose($connection) 336 | { 337 | $connection->handshakeStep = null; 338 | $connection->websocketCurrentFrameLength = 0; 339 | $connection->tmpWebsocketData = ''; 340 | $connection->websocketDataBuffer = ''; 341 | if (!empty($connection->websocketPingTimer)) { 342 | Timer::del($connection->websocketPingTimer); 343 | $connection->websocketPingTimer = null; 344 | } 345 | } 346 | 347 | /** 348 | * Send websocket handshake. 349 | * 350 | * @param \Workerman\Connection\TcpConnection $connection 351 | * @return void 352 | */ 353 | public static function sendHandshake($connection) 354 | { 355 | if (!empty($connection->handshakeStep)) { 356 | return; 357 | } 358 | // Get Host. 359 | $port = $connection->getRemotePort(); 360 | $host = $port === 80 ? $connection->getRemoteHost() : $connection->getRemoteHost() . ':' . $port; 361 | // Handshake header. 362 | $connection->websocketSecKey = base64_encode(md5(mt_rand(), true)); 363 | $userHeader = ''; 364 | if (!empty($connection->wsHttpHeader)) { 365 | if (is_array($connection->wsHttpHeader)){ 366 | foreach($connection->wsHttpHeader as $k=>$v){ 367 | $userHeader .= "$k: $v\r\n"; 368 | } 369 | }else{ 370 | $userHeader .= $connection->wsHttpHeader; 371 | } 372 | $userHeader = "\r\n".trim($userHeader); 373 | } 374 | $header = 'GET ' . $connection->getRemoteURI() . " HTTP/1.1\r\n". 375 | "Host: $host\r\n". 376 | "Connection: Upgrade\r\n". 377 | "Upgrade: websocket\r\n". 378 | "Origin: ". (isset($connection->websocketOrigin) ? $connection->websocketOrigin : '*') ."\r\n". 379 | (isset($connection->WSClientProtocol)?"Sec-WebSocket-Protocol: ".$connection->WSClientProtocol."\r\n":''). 380 | "Sec-WebSocket-Version: 13\r\n". 381 | "Sec-WebSocket-Key: " . $connection->websocketSecKey . $userHeader . "\r\n\r\n"; 382 | $connection->send($header, true); 383 | $connection->handshakeStep = 1; 384 | $connection->websocketCurrentFrameLength = 0; 385 | $connection->websocketDataBuffer = ''; 386 | $connection->tmpWebsocketData = ''; 387 | } 388 | 389 | /** 390 | * Websocket handshake. 391 | * 392 | * @param string $buffer 393 | * @param \Workerman\Connection\TcpConnection $connection 394 | * @return int 395 | */ 396 | public static function dealHandshake($buffer, $connection) 397 | { 398 | $pos = strpos($buffer, "\r\n\r\n"); 399 | if ($pos) { 400 | //checking Sec-WebSocket-Accept 401 | if (preg_match("/Sec-WebSocket-Accept: *(.*?)\r\n/i", $buffer, $match)) { 402 | if ($match[1] !== base64_encode(sha1($connection->websocketSecKey . "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", true))) { 403 | echo "Sec-WebSocket-Accept not match. Header:\n" . substr($buffer, 0, $pos) . "\n"; 404 | $connection->close(); 405 | return 0; 406 | } 407 | } else { 408 | echo "Sec-WebSocket-Accept not found. Header:\n" . substr($buffer, 0, $pos) . "\n"; 409 | $connection->close(); 410 | return 0; 411 | } 412 | 413 | // handshake complete 414 | 415 | // Get WebSocket subprotocol (if specified by server) 416 | if (preg_match("/Sec-WebSocket-Protocol: *(.*?)\r\n/i", $buffer, $match)) { 417 | $connection->WSServerProtocol = trim($match[1]); 418 | } 419 | 420 | $connection->handshakeStep = 2; 421 | $handshake_response_length = $pos + 4; 422 | // Try to emit onWebSocketConnect callback. 423 | if (isset($connection->onWebSocketConnect)) { 424 | try { 425 | call_user_func($connection->onWebSocketConnect, $connection, substr($buffer, 0, $handshake_response_length)); 426 | } catch (\Exception $e) { 427 | Worker::log($e); 428 | exit(250); 429 | } catch (\Error $e) { 430 | Worker::log($e); 431 | exit(250); 432 | } 433 | } 434 | // Headbeat. 435 | if (!empty($connection->websocketPingInterval)) { 436 | $connection->websocketPingTimer = Timer::add($connection->websocketPingInterval, function() use ($connection){ 437 | if (false === $connection->send(pack('H*', '898000000000'), true)) { 438 | Timer::del($connection->websocketPingTimer); 439 | $connection->websocketPingTimer = null; 440 | } 441 | }); 442 | } 443 | 444 | $connection->consumeRecvBuffer($handshake_response_length); 445 | if (!empty($connection->tmpWebsocketData)) { 446 | $connection->send($connection->tmpWebsocketData, true); 447 | $connection->tmpWebsocketData = ''; 448 | } 449 | if (strlen($buffer) > $handshake_response_length) { 450 | return self::input(substr($buffer, $handshake_response_length), $connection); 451 | } 452 | } 453 | return 0; 454 | } 455 | 456 | public static function WSSetProtocol($connection, $params) { 457 | $connection->WSClientProtocol = $params[0]; 458 | } 459 | 460 | public static function WSGetServerProtocol($connection) { 461 | return (property_exists($connection, 'WSServerProtocol')?$connection->WSServerProtocol:null); 462 | } 463 | 464 | } 465 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | workerman源码研究 2 | 3 | test文件夹内为所需知识点相关脚本 -------------------------------------------------------------------------------- /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 $config 51 | * @return void 52 | */ 53 | public function addRoot($domain, $config) 54 | { 55 | if (is_string($config)) { 56 | $config = array('root' => $config); 57 | } 58 | $this->serverRoot[$domain] = $config; 59 | } 60 | 61 | /** 62 | * Construct. 63 | * 64 | * @param string $socket_name 65 | * @param array $context_option 66 | */ 67 | public function __construct($socket_name, $context_option = array()) 68 | { 69 | list(, $address) = explode(':', $socket_name, 2); 70 | parent::__construct('http:' . $address, $context_option); 71 | $this->name = 'WebServer'; 72 | } 73 | 74 | /** 75 | * Run webserver instance. 76 | * 77 | * @see Workerman.Worker::run() 78 | */ 79 | public function run() 80 | { 81 | $this->_onWorkerStart = $this->onWorkerStart; 82 | $this->onWorkerStart = array($this, 'onWorkerStart'); 83 | $this->onMessage = array($this, 'onMessage'); 84 | parent::run(); 85 | } 86 | 87 | /** 88 | * Emit when process start. 89 | * 90 | * @throws \Exception 91 | */ 92 | public function onWorkerStart() 93 | { 94 | if (empty($this->serverRoot)) { 95 | echo new \Exception('server root not set, please use WebServer::addRoot($domain, $root_path) to set server root path'); 96 | exit(250); 97 | } 98 | 99 | // Init mimeMap. 100 | $this->initMimeTypeMap(); 101 | 102 | // Try to emit onWorkerStart callback. 103 | if ($this->_onWorkerStart) { 104 | try { 105 | call_user_func($this->_onWorkerStart, $this); 106 | } catch (\Exception $e) { 107 | self::log($e); 108 | exit(250); 109 | } catch (\Error $e) { 110 | self::log($e); 111 | exit(250); 112 | } 113 | } 114 | } 115 | 116 | /** 117 | * Init mime map. 118 | * 119 | * @return void 120 | */ 121 | public function initMimeTypeMap() 122 | { 123 | $mime_file = Http::getMimeTypesFile(); 124 | if (!is_file($mime_file)) { 125 | $this->log("$mime_file mime.type file not fond"); 126 | return; 127 | } 128 | $items = file($mime_file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); 129 | if (!is_array($items)) { 130 | $this->log("get $mime_file mime.type content fail"); 131 | return; 132 | } 133 | foreach ($items as $content) { 134 | if (preg_match("/\s*(\S+)\s+(\S.+)/", $content, $match)) { 135 | $mime_type = $match[1]; 136 | $workerman_file_extension_var = $match[2]; 137 | $workerman_file_extension_array = explode(' ', substr($workerman_file_extension_var, 0, -1)); 138 | foreach ($workerman_file_extension_array as $workerman_file_extension) { 139 | self::$mimeTypeMap[$workerman_file_extension] = $mime_type; 140 | } 141 | } 142 | } 143 | } 144 | 145 | /** 146 | * Emit when http message coming. 147 | * 148 | * @param Connection\TcpConnection $connection 149 | * @return void 150 | */ 151 | public function onMessage($connection) 152 | { 153 | // REQUEST_URI. 154 | $workerman_url_info = parse_url($_SERVER['REQUEST_URI']); 155 | if (!$workerman_url_info) { 156 | Http::header('HTTP/1.1 400 Bad Request'); 157 | $connection->close('

400 Bad Request

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

400 Bad Request

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

404 Not Found

'; 235 | } 236 | $connection->close($html404); 237 | return; 238 | } 239 | } 240 | 241 | public static function sendFile($connection, $file_path) 242 | { 243 | // Check 304. 244 | $info = stat($file_path); 245 | $modified_time = $info ? date('D, d M Y H:i:s', $info['mtime']) . ' ' . date_default_timezone_get() : ''; 246 | if (!empty($_SERVER['HTTP_IF_MODIFIED_SINCE']) && $info) { 247 | // Http 304. 248 | if ($modified_time === $_SERVER['HTTP_IF_MODIFIED_SINCE']) { 249 | // 304 250 | Http::header('HTTP/1.1 304 Not Modified'); 251 | // Send nothing but http headers.. 252 | $connection->close(''); 253 | return; 254 | } 255 | } 256 | 257 | // Http header. 258 | if ($modified_time) { 259 | $modified_time = "Last-Modified: $modified_time\r\n"; 260 | } 261 | $file_size = filesize($file_path); 262 | $file_info = pathinfo($file_path); 263 | $extension = isset($file_info['extension']) ? $file_info['extension'] : ''; 264 | $file_name = isset($file_info['filename']) ? $file_info['filename'] : ''; 265 | $header = "HTTP/1.1 200 OK\r\n"; 266 | if (isset(self::$mimeTypeMap[$extension])) { 267 | $header .= "Content-Type: " . self::$mimeTypeMap[$extension] . "\r\n"; 268 | } else { 269 | $header .= "Content-Type: application/octet-stream\r\n"; 270 | $header .= "Content-Disposition: attachment; filename=\"$file_name\"\r\n"; 271 | } 272 | $header .= "Connection: keep-alive\r\n"; 273 | $header .= $modified_time; 274 | $header .= "Content-Length: $file_size\r\n\r\n"; 275 | $trunk_limit_size = 1024*1024; 276 | if ($file_size < $trunk_limit_size) { 277 | return $connection->send($header.file_get_contents($file_path), true); 278 | } 279 | $connection->send($header, true); 280 | 281 | // Read file content from disk piece by piece and send to client. 282 | $connection->fileHandler = fopen($file_path, 'r'); 283 | $do_write = function()use($connection) 284 | { 285 | // Send buffer not full. 286 | while(empty($connection->bufferFull)) 287 | { 288 | // Read from disk. 289 | $buffer = fread($connection->fileHandler, 8192); 290 | // Read eof. 291 | if($buffer === '' || $buffer === false) 292 | { 293 | return; 294 | } 295 | $connection->send($buffer, true); 296 | } 297 | }; 298 | // Send buffer full. 299 | $connection->onBufferFull = function($connection) 300 | { 301 | $connection->bufferFull = true; 302 | }; 303 | // Send buffer drain. 304 | $connection->onBufferDrain = function($connection)use($do_write) 305 | { 306 | $connection->bufferFull = false; 307 | $do_write(); 308 | }; 309 | $do_write(); 310 | } 311 | } 312 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "workerman/workerman", 3 | "type": "library", 4 | "keywords": [ 5 | "event-loop", 6 | "asynchronous" 7 | ], 8 | "homepage": "http://www.workerman.net", 9 | "license": "MIT", 10 | "description": "An asynchronous event driven PHP framework for easily building fast, scalable network applications.", 11 | "authors": [ 12 | { 13 | "name": "walkor", 14 | "email": "walkor@workerman.net", 15 | "homepage": "http://www.workerman.net", 16 | "role": "Developer" 17 | } 18 | ], 19 | "support": { 20 | "email": "walkor@workerman.net", 21 | "issues": "https://github.com/walkor/workerman/issues", 22 | "forum": "http://wenda.workerman.net/", 23 | "wiki": "http://doc3.workerman.net/index.html", 24 | "source": "https://github.com/walkor/workerman" 25 | }, 26 | "require": { 27 | "php": ">=5.3" 28 | }, 29 | "suggest": { 30 | "ext-event": "For better performance. " 31 | }, 32 | "autoload": { 33 | "psr-4": { 34 | "Workerman\\": "./" 35 | } 36 | }, 37 | "minimum-stability": "dev" 38 | } 39 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "84d5dc458a4155da5fe907b9c4a2e936", 8 | "packages": [], 9 | "packages-dev": [], 10 | "aliases": [], 11 | "minimum-stability": "dev", 12 | "stability-flags": [], 13 | "prefer-stable": false, 14 | "prefer-lowest": false, 15 | "platform": { 16 | "php": ">=5.3" 17 | }, 18 | "platform-dev": [] 19 | } 20 | -------------------------------------------------------------------------------- /demon/http_test.php: -------------------------------------------------------------------------------- 1 | count = 1; 13 | 14 | //设置Worker子进程启动时的回调函数,每个子进程启动时都会执行 15 | $http_worker->onWorkerStart = function($worker){ 16 | //echo "worker starting...\n"; 17 | }; 18 | 19 | $http_worker->onConnect = function($connection) 20 | { 21 | //echo "new connection from ip".$connection->getRemoteIp()."\n"; 22 | }; 23 | 24 | // 接收到浏览器发送的数据时回复hello world给浏览器 25 | $http_worker->onMessage = function($connection, $data) 26 | { 27 | //print_r($data); 28 | //$data -> $parser::decode($one_request_buffer, $this)返回的 array('get' => $_GET, 'post' => $_POST, 'cookie' => $_COOKIE, 'server' => $_SERVER, 'files' => $_FILES); 29 | // 向浏览器发送hello world 30 | //$connection -> \Workerman\Connection\{协议}Connection 31 | $connection->send('hello world'); 32 | }; 33 | 34 | // 运行worker 35 | Worker::runAll(); -------------------------------------------------------------------------------- /demon/tcp_test.php: -------------------------------------------------------------------------------- 1 | count = 1; 11 | 12 | // 当客户端发来数据时 13 | $tcp_worker->onMessage = function($connection, $data) 14 | { 15 | // 向客户端发送hello $data 16 | //$connection->send('hello ' . $data); 17 | }; 18 | 19 | // 运行worker 20 | Worker::runAll(); -------------------------------------------------------------------------------- /demon/ws_test.php: -------------------------------------------------------------------------------- 1 | count = 4; 10 | 11 | //设置Worker子进程启动时的回调函数,每个子进程启动时都会执行 12 | $ws_worker->onWorkerStart = function($worker){ 13 | echo "worker starting...\n"; 14 | }; 15 | 16 | $ws_worker->onConnect = function($connection) 17 | { 18 | echo "new connection from ip".$connection->getRemoteIp()."\n"; 19 | }; 20 | 21 | // 当收到客户端发来的数据后返回hello $data给客户端 22 | $ws_worker->onMessage = function($connection, $data) 23 | { 24 | // 向客户端发送hello $data 25 | $connection->send('hello ' . $data); 26 | }; 27 | 28 | // 运行worker 29 | Worker::runAll(); -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | # workerman 2 | workerman源码分析 3 | 4 | - 前置知识 5 | - [事件驱动Libevent](libevent) 6 | - [信号](signal) 7 | - [守护进程](daemon) 8 | - [Socket编程]() -------------------------------------------------------------------------------- /test/daemon/daemon1.php: -------------------------------------------------------------------------------- 1 | 0){ 11 | //1.父进程直接退出 12 | exit; 13 | } 14 | 15 | //执行到这里就是子进程 16 | //2.建立一个有别于终端的新session以脱离终端 17 | $sid = posix_setsid(); 18 | if (!$sid) { 19 | die("setsid failed!\n"); 20 | } 21 | 22 | //这一步不是必须的 23 | $pid = pcntl_fork(); 24 | if($pid < 0){ 25 | die("fork(1) failed!\n"); 26 | }elseif($pid > 0){ 27 | exit; //父进程退出, 剩下子进程成为最终的独立进程 28 | } 29 | 30 | 31 | //3.设置当前进程的工作目录为根目录,不依赖于其他 32 | chdir("/"); 33 | //4.umask设置为0确保将来进程有最大的文件操作权限 34 | umask(0); 35 | 36 | //5.关闭标准I/O流 37 | if (defined('STDIN')) 38 | fclose(STDIN); 39 | if (defined('STDOUT')) 40 | fclose(STDOUT); 41 | if (defined('STDERR')) 42 | fclose(STDERR); 43 | } 44 | 45 | daemon(); 46 | 47 | $redis = new Redis(); 48 | $redis->connect('127.0.0.1', 6379); 49 | 50 | while (true) { 51 | //echo 1; 不要任何输出echo 因为标准输入流关闭了,会异常导致进程终止 52 | $redis->set("name", "lemon".mt_rand()); 53 | sleep(3); 54 | } -------------------------------------------------------------------------------- /test/daemon/daemon2.php: -------------------------------------------------------------------------------- 1 | 0 父进程 5 | umask(0); 6 | $pid = pcntl_fork(); 7 | if (-1 === $pid) { 8 | throw new Exception('fork fail'); 9 | } elseif ($pid > 0) { 10 | //父进程直接退出 11 | exit(0); 12 | } 13 | 14 | //建立一个有别于终端的新session以脱离终端 15 | if (-1 === posix_setsid()) { 16 | throw new Exception("setsid fail"); 17 | } 18 | // Fork again avoid SVR4 system regain the control of terminal. 19 | $pid = pcntl_fork(); 20 | if (-1 === $pid) { 21 | throw new Exception("fork fail"); 22 | } elseif (0 !== $pid) { 23 | exit(0); 24 | } 25 | } 26 | 27 | daemon(); 28 | 29 | $redis = new Redis(); 30 | $redis->connect('127.0.0.1', 6379); 31 | 32 | while (true) { 33 | //echo 1; 不要任何输出echo 因为标准输入流关闭了,会异常导致进程终止 34 | $redis->set("name", "lemon".mt_rand()); 35 | sleep(3); 36 | } -------------------------------------------------------------------------------- /test/daemon/daemon3.php: -------------------------------------------------------------------------------- 1 | 0) { 12 | exit(0); 13 | } 14 | if (-1 === posix_setsid()) { 15 | throw new Exception("setsid fail"); 16 | } 17 | // Fork again avoid SVR4 system regain the control of terminal. 18 | $pid = pcntl_fork(); 19 | if (-1 === $pid) { 20 | throw new Exception("fork fail"); 21 | } elseif (0 !== $pid) { 22 | exit(0); 23 | } 24 | } 25 | 26 | //后台运行 27 | daemon(); 28 | 29 | $num = 3; 30 | $_pid = posix_getpid(); 31 | $pidMap[$_pid] = []; 32 | 33 | $redis = new Redis(); 34 | $redis->connect('127.0.0.1', 6379); 35 | 36 | 37 | while (count($pidMap[$_pid]) < $num){ 38 | 39 | $pid = pcntl_fork(); 40 | 41 | if ($pid > 0) { 42 | $pidMap[$_pid][] = count($pidMap[$_pid]); 43 | } elseif (0 === $pid) { 44 | cli_set_process_title('zclemon: work'.count($pidMap[$_pid])); 45 | while (true) { 46 | $redis->set("name".count($pidMap[$_pid]), "lemon".mt_rand()); 47 | sleep(3); 48 | } 49 | 50 | //子进程退出 51 | exit(); 52 | 53 | } else{ 54 | throw new Exception("forkOneWorker fail"); 55 | } 56 | } 57 | 58 | function moniterWorker(){ 59 | while (1){ 60 | //父进程阻塞着等待子进程的退出,防止僵死进程 61 | //WUNTRACED 如果子进程进入暂停执行情况则马上返回,但结束状态不予以理会 62 | $pid = pcntl_wait($status, WUNTRACED); 63 | if ($pid > 0){ 64 | echo '子进程是',$pid,',状态是exit'; 65 | } 66 | } 67 | } 68 | 69 | //监控进程 70 | moniterWorker(); -------------------------------------------------------------------------------- /test/libevnt/io/io.client.php: -------------------------------------------------------------------------------- 1 | \n"; 6 | } else { 7 | fwrite($fd,"hello world"); 8 | //echo fread($fd,20); 9 | fclose($fd); 10 | } -------------------------------------------------------------------------------- /test/libevnt/io/io.server.php: -------------------------------------------------------------------------------- 1 | add(); 29 | 30 | //调度在等待的事件 31 | $event_base->loop(); 32 | 33 | -------------------------------------------------------------------------------- /test/libevnt/io/io.server3.php: -------------------------------------------------------------------------------- 1 | add()) { 11 | return false; 12 | } 13 | 14 | //关键点1 15 | $arr[posix_getpid()][] = $event; 16 | } 17 | function baseRead($socket){ 18 | $buffer = @fread($socket, 2); 19 | echo $buffer."\n"; 20 | } 21 | 22 | function acceptConnection($socket){ 23 | 24 | $new_socket = @stream_socket_accept($socket, 0); 25 | // Thundering herd. 26 | if (!$new_socket) { 27 | return; 28 | } 29 | 30 | stream_set_blocking($new_socket, 0); 31 | //关键点2 32 | stream_set_read_buffer($new_socket, 0); 33 | 34 | add($new_socket,'baseRead'); 35 | } 36 | 37 | $socketmain = stream_socket_server('tcp://127.0.0.1:4455', $errno, $errmsg, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN); 38 | //非阻塞 39 | stream_set_blocking($socketmain,0); 40 | 41 | add($socketmain,'acceptConnection'); 42 | 43 | $eventBase->loop(); -------------------------------------------------------------------------------- /test/libevnt/signal/signal.php: -------------------------------------------------------------------------------- 1 | 0){ 5 | //父进程 6 | sleep(5); 7 | //发送SIGINT信号 8 | posix_kill($pid,SIGINT); 9 | exit(0); 10 | } elseif ($pid == 0){ 11 | 12 | //子进程 13 | $event_base = new EventBase(); 14 | //收到SIGINT信号触发 15 | $event = Event::signal($event_base,SIGINT,function() use (&$event,&$event_base){ 16 | echo 'sigint',PHP_EOL; 17 | $event->del(); 18 | $event_base->free(); 19 | exit(0); 20 | }); 21 | 22 | $event->add(); 23 | $event_base->loop(); 24 | 25 | } else { 26 | printf("fork failed"); 27 | } 28 | 29 | -------------------------------------------------------------------------------- /test/libevnt/timer/timer.php: -------------------------------------------------------------------------------- 1 | addTimer($second); 9 | $event_base->loop(); 10 | 11 | } 12 | 13 | function setInterval($second,$callback){ 14 | $func = function() use ($second,$callback,&$func){ 15 | call_user_func($callback); 16 | setTimeOut($second,$func); 17 | }; 18 | 19 | setTimeOut($second,$func); 20 | } 21 | 22 | setInterval(1,function(){ 23 | echo "hello",PHP_EOL; 24 | }); -------------------------------------------------------------------------------- /test/other/cli_set_process_title.php: -------------------------------------------------------------------------------- 1 | test'; 4 | if (function_exists('cli_set_process_title')){ 5 | @cli_set_process_title($title); 6 | } elseif (extension_loaded('proctitle') && function_exists('setproctitle')) { 7 | @setproctitle($title); 8 | } 9 | 10 | while (1){ 11 | sleep(1); 12 | } 13 | 14 | //测试命令 ps -ef | grep php -------------------------------------------------------------------------------- /test/other/posix_getpwnam.php: -------------------------------------------------------------------------------- 1 | lemon 16 | [passwd] => x 17 | [uid] => 1000 18 | [gid] => 1000 19 | [gecos] => lemon,,, 20 | [dir] => /home/lemon 21 | [shell] => /bin/bash 22 | ) 23 | * 24 | */ -------------------------------------------------------------------------------- /test/other/resetStd.php: -------------------------------------------------------------------------------- 1 | 0){ 16 | //tick_handler() called 17 | $a += 2; 18 | //3 19 | print($a); 20 | } 21 | //执行完if -> tick_handler() called -------------------------------------------------------------------------------- /test/signal&&fork/declare2.php: -------------------------------------------------------------------------------- 1 | $timeout){ 15 | exit("超间{$timeout}秒"); 16 | } 17 | } 18 | 19 | // Zend引擎每执行一次低级语句就执行一下check_timeout 20 | register_tick_function('check_timeout'); 21 | 22 | //模拟一段耗时的业务逻辑,虽然是死循环,但是执行事件不会超过$timeout=5秒 23 | while(1){ 24 | $num = 1; 25 | } -------------------------------------------------------------------------------- /test/signal&&fork/declare3.php: -------------------------------------------------------------------------------- 1 | 0) { 26 | continue; 27 | } else { 28 | printf("fork failed"); 29 | } 30 | } -------------------------------------------------------------------------------- /test/socket/socket2.php: -------------------------------------------------------------------------------- 1 | add(); 19 | 20 | $event_base->loop(); 21 | 22 | //为了利用CPU多核的优势, 往往会fork出好几个进程同时监听一个端口. 可以通过stream_context_set_option来进行设置. 23 | //stream_context_set_option($fd, 'socket', 'so_reuseport', 1); -------------------------------------------------------------------------------- /vendor/autoload.php: -------------------------------------------------------------------------------- 1 | 7 | * Jordi Boggiano 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Composer\Autoload; 14 | 15 | /** 16 | * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. 17 | * 18 | * $loader = new \Composer\Autoload\ClassLoader(); 19 | * 20 | * // register classes with namespaces 21 | * $loader->add('Symfony\Component', __DIR__.'/component'); 22 | * $loader->add('Symfony', __DIR__.'/framework'); 23 | * 24 | * // activate the autoloader 25 | * $loader->register(); 26 | * 27 | * // to enable searching the include path (eg. for PEAR packages) 28 | * $loader->setUseIncludePath(true); 29 | * 30 | * In this example, if you try to use a class in the Symfony\Component 31 | * namespace or one of its children (Symfony\Component\Console for instance), 32 | * the autoloader will first look for the class under the component/ 33 | * directory, and it will then fallback to the framework/ directory if not 34 | * found before giving up. 35 | * 36 | * This class is loosely based on the Symfony UniversalClassLoader. 37 | * 38 | * @author Fabien Potencier 39 | * @author Jordi Boggiano 40 | * @see http://www.php-fig.org/psr/psr-0/ 41 | * @see http://www.php-fig.org/psr/psr-4/ 42 | */ 43 | class ClassLoader 44 | { 45 | // PSR-4 46 | private $prefixLengthsPsr4 = array(); 47 | private $prefixDirsPsr4 = array(); 48 | private $fallbackDirsPsr4 = array(); 49 | 50 | // PSR-0 51 | private $prefixesPsr0 = array(); 52 | private $fallbackDirsPsr0 = array(); 53 | 54 | private $useIncludePath = false; 55 | private $classMap = array(); 56 | private $classMapAuthoritative = false; 57 | private $missingClasses = array(); 58 | private $apcuPrefix; 59 | 60 | public function getPrefixes() 61 | { 62 | if (!empty($this->prefixesPsr0)) { 63 | return call_user_func_array('array_merge', $this->prefixesPsr0); 64 | } 65 | 66 | return array(); 67 | } 68 | 69 | public function getPrefixesPsr4() 70 | { 71 | return $this->prefixDirsPsr4; 72 | } 73 | 74 | public function getFallbackDirs() 75 | { 76 | return $this->fallbackDirsPsr0; 77 | } 78 | 79 | public function getFallbackDirsPsr4() 80 | { 81 | return $this->fallbackDirsPsr4; 82 | } 83 | 84 | public function getClassMap() 85 | { 86 | return $this->classMap; 87 | } 88 | 89 | /** 90 | * @param array $classMap Class to filename map 91 | */ 92 | public function addClassMap(array $classMap) 93 | { 94 | if ($this->classMap) { 95 | $this->classMap = array_merge($this->classMap, $classMap); 96 | } else { 97 | $this->classMap = $classMap; 98 | } 99 | } 100 | 101 | /** 102 | * Registers a set of PSR-0 directories for a given prefix, either 103 | * appending or prepending to the ones previously set for this prefix. 104 | * 105 | * @param string $prefix The prefix 106 | * @param array|string $paths The PSR-0 root directories 107 | * @param bool $prepend Whether to prepend the directories 108 | */ 109 | public function add($prefix, $paths, $prepend = false) 110 | { 111 | if (!$prefix) { 112 | if ($prepend) { 113 | $this->fallbackDirsPsr0 = array_merge( 114 | (array) $paths, 115 | $this->fallbackDirsPsr0 116 | ); 117 | } else { 118 | $this->fallbackDirsPsr0 = array_merge( 119 | $this->fallbackDirsPsr0, 120 | (array) $paths 121 | ); 122 | } 123 | 124 | return; 125 | } 126 | 127 | $first = $prefix[0]; 128 | if (!isset($this->prefixesPsr0[$first][$prefix])) { 129 | $this->prefixesPsr0[$first][$prefix] = (array) $paths; 130 | 131 | return; 132 | } 133 | if ($prepend) { 134 | $this->prefixesPsr0[$first][$prefix] = array_merge( 135 | (array) $paths, 136 | $this->prefixesPsr0[$first][$prefix] 137 | ); 138 | } else { 139 | $this->prefixesPsr0[$first][$prefix] = array_merge( 140 | $this->prefixesPsr0[$first][$prefix], 141 | (array) $paths 142 | ); 143 | } 144 | } 145 | 146 | /** 147 | * Registers a set of PSR-4 directories for a given namespace, either 148 | * appending or prepending to the ones previously set for this namespace. 149 | * 150 | * @param string $prefix The prefix/namespace, with trailing '\\' 151 | * @param array|string $paths The PSR-4 base directories 152 | * @param bool $prepend Whether to prepend the directories 153 | * 154 | * @throws \InvalidArgumentException 155 | */ 156 | public function addPsr4($prefix, $paths, $prepend = false) 157 | { 158 | if (!$prefix) { 159 | // Register directories for the root namespace. 160 | if ($prepend) { 161 | $this->fallbackDirsPsr4 = array_merge( 162 | (array) $paths, 163 | $this->fallbackDirsPsr4 164 | ); 165 | } else { 166 | $this->fallbackDirsPsr4 = array_merge( 167 | $this->fallbackDirsPsr4, 168 | (array) $paths 169 | ); 170 | } 171 | } elseif (!isset($this->prefixDirsPsr4[$prefix])) { 172 | // Register directories for a new namespace. 173 | $length = strlen($prefix); 174 | if ('\\' !== $prefix[$length - 1]) { 175 | throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); 176 | } 177 | $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; 178 | $this->prefixDirsPsr4[$prefix] = (array) $paths; 179 | } elseif ($prepend) { 180 | // Prepend directories for an already registered namespace. 181 | $this->prefixDirsPsr4[$prefix] = array_merge( 182 | (array) $paths, 183 | $this->prefixDirsPsr4[$prefix] 184 | ); 185 | } else { 186 | // Append directories for an already registered namespace. 187 | $this->prefixDirsPsr4[$prefix] = array_merge( 188 | $this->prefixDirsPsr4[$prefix], 189 | (array) $paths 190 | ); 191 | } 192 | } 193 | 194 | /** 195 | * Registers a set of PSR-0 directories for a given prefix, 196 | * replacing any others previously set for this prefix. 197 | * 198 | * @param string $prefix The prefix 199 | * @param array|string $paths The PSR-0 base directories 200 | */ 201 | public function set($prefix, $paths) 202 | { 203 | if (!$prefix) { 204 | $this->fallbackDirsPsr0 = (array) $paths; 205 | } else { 206 | $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; 207 | } 208 | } 209 | 210 | /** 211 | * Registers a set of PSR-4 directories for a given namespace, 212 | * replacing any others previously set for this namespace. 213 | * 214 | * @param string $prefix The prefix/namespace, with trailing '\\' 215 | * @param array|string $paths The PSR-4 base directories 216 | * 217 | * @throws \InvalidArgumentException 218 | */ 219 | public function setPsr4($prefix, $paths) 220 | { 221 | if (!$prefix) { 222 | $this->fallbackDirsPsr4 = (array) $paths; 223 | } else { 224 | $length = strlen($prefix); 225 | if ('\\' !== $prefix[$length - 1]) { 226 | throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); 227 | } 228 | $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; 229 | $this->prefixDirsPsr4[$prefix] = (array) $paths; 230 | } 231 | } 232 | 233 | /** 234 | * Turns on searching the include path for class files. 235 | * 236 | * @param bool $useIncludePath 237 | */ 238 | public function setUseIncludePath($useIncludePath) 239 | { 240 | $this->useIncludePath = $useIncludePath; 241 | } 242 | 243 | /** 244 | * Can be used to check if the autoloader uses the include path to check 245 | * for classes. 246 | * 247 | * @return bool 248 | */ 249 | public function getUseIncludePath() 250 | { 251 | return $this->useIncludePath; 252 | } 253 | 254 | /** 255 | * Turns off searching the prefix and fallback directories for classes 256 | * that have not been registered with the class map. 257 | * 258 | * @param bool $classMapAuthoritative 259 | */ 260 | public function setClassMapAuthoritative($classMapAuthoritative) 261 | { 262 | $this->classMapAuthoritative = $classMapAuthoritative; 263 | } 264 | 265 | /** 266 | * Should class lookup fail if not found in the current class map? 267 | * 268 | * @return bool 269 | */ 270 | public function isClassMapAuthoritative() 271 | { 272 | return $this->classMapAuthoritative; 273 | } 274 | 275 | /** 276 | * APCu prefix to use to cache found/not-found classes, if the extension is enabled. 277 | * 278 | * @param string|null $apcuPrefix 279 | */ 280 | public function setApcuPrefix($apcuPrefix) 281 | { 282 | $this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null; 283 | } 284 | 285 | /** 286 | * The APCu prefix in use, or null if APCu caching is not enabled. 287 | * 288 | * @return string|null 289 | */ 290 | public function getApcuPrefix() 291 | { 292 | return $this->apcuPrefix; 293 | } 294 | 295 | /** 296 | * Registers this instance as an autoloader. 297 | * 298 | * @param bool $prepend Whether to prepend the autoloader or not 299 | */ 300 | public function register($prepend = false) 301 | { 302 | spl_autoload_register(array($this, 'loadClass'), true, $prepend); 303 | } 304 | 305 | /** 306 | * Unregisters this instance as an autoloader. 307 | */ 308 | public function unregister() 309 | { 310 | spl_autoload_unregister(array($this, 'loadClass')); 311 | } 312 | 313 | /** 314 | * Loads the given class or interface. 315 | * 316 | * @param string $class The name of the class 317 | * @return bool|null True if loaded, null otherwise 318 | */ 319 | public function loadClass($class) 320 | { 321 | if ($file = $this->findFile($class)) { 322 | includeFile($file); 323 | 324 | return true; 325 | } 326 | } 327 | 328 | /** 329 | * Finds the path to the file where the class is defined. 330 | * 331 | * @param string $class The name of the class 332 | * 333 | * @return string|false The path if found, false otherwise 334 | */ 335 | public function findFile($class) 336 | { 337 | // class map lookup 338 | if (isset($this->classMap[$class])) { 339 | return $this->classMap[$class]; 340 | } 341 | if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { 342 | return false; 343 | } 344 | if (null !== $this->apcuPrefix) { 345 | $file = apcu_fetch($this->apcuPrefix.$class, $hit); 346 | if ($hit) { 347 | return $file; 348 | } 349 | } 350 | 351 | $file = $this->findFileWithExtension($class, '.php'); 352 | 353 | // Search for Hack files if we are running on HHVM 354 | if (false === $file && defined('HHVM_VERSION')) { 355 | $file = $this->findFileWithExtension($class, '.hh'); 356 | } 357 | 358 | if (null !== $this->apcuPrefix) { 359 | apcu_add($this->apcuPrefix.$class, $file); 360 | } 361 | 362 | if (false === $file) { 363 | // Remember that this class does not exist. 364 | $this->missingClasses[$class] = true; 365 | } 366 | 367 | return $file; 368 | } 369 | 370 | private function findFileWithExtension($class, $ext) 371 | { 372 | // PSR-4 lookup 373 | $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; 374 | 375 | $first = $class[0]; 376 | if (isset($this->prefixLengthsPsr4[$first])) { 377 | $subPath = $class; 378 | while (false !== $lastPos = strrpos($subPath, '\\')) { 379 | $subPath = substr($subPath, 0, $lastPos); 380 | $search = $subPath.'\\'; 381 | if (isset($this->prefixDirsPsr4[$search])) { 382 | $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); 383 | foreach ($this->prefixDirsPsr4[$search] as $dir) { 384 | if (file_exists($file = $dir . $pathEnd)) { 385 | return $file; 386 | } 387 | } 388 | } 389 | } 390 | } 391 | 392 | // PSR-4 fallback dirs 393 | foreach ($this->fallbackDirsPsr4 as $dir) { 394 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { 395 | return $file; 396 | } 397 | } 398 | 399 | // PSR-0 lookup 400 | if (false !== $pos = strrpos($class, '\\')) { 401 | // namespaced class name 402 | $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) 403 | . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); 404 | } else { 405 | // PEAR-like class name 406 | $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; 407 | } 408 | 409 | if (isset($this->prefixesPsr0[$first])) { 410 | foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { 411 | if (0 === strpos($class, $prefix)) { 412 | foreach ($dirs as $dir) { 413 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 414 | return $file; 415 | } 416 | } 417 | } 418 | } 419 | } 420 | 421 | // PSR-0 fallback dirs 422 | foreach ($this->fallbackDirsPsr0 as $dir) { 423 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 424 | return $file; 425 | } 426 | } 427 | 428 | // PSR-0 include paths. 429 | if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { 430 | return $file; 431 | } 432 | 433 | return false; 434 | } 435 | } 436 | 437 | /** 438 | * Scope isolated include. 439 | * 440 | * Prevents access to $this/self from included files. 441 | */ 442 | function includeFile($file) 443 | { 444 | include $file; 445 | } 446 | -------------------------------------------------------------------------------- /vendor/composer/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Copyright (c) Nils Adermann, Jordi Boggiano 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is furnished 9 | to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | 22 | -------------------------------------------------------------------------------- /vendor/composer/autoload_classmap.php: -------------------------------------------------------------------------------- 1 | array($baseDir . '/'), 10 | ); 11 | -------------------------------------------------------------------------------- /vendor/composer/autoload_real.php: -------------------------------------------------------------------------------- 1 | = 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); 27 | if ($useStaticLoader) { 28 | require_once __DIR__ . '/autoload_static.php'; 29 | 30 | call_user_func(\Composer\Autoload\ComposerStaticInit3e6202733432bf8729c6635ac5a01712::getInitializer($loader)); 31 | } else { 32 | $map = require __DIR__ . '/autoload_namespaces.php'; 33 | foreach ($map as $namespace => $path) { 34 | $loader->set($namespace, $path); 35 | } 36 | 37 | $map = require __DIR__ . '/autoload_psr4.php'; 38 | foreach ($map as $namespace => $path) { 39 | $loader->setPsr4($namespace, $path); 40 | } 41 | 42 | $classMap = require __DIR__ . '/autoload_classmap.php'; 43 | if ($classMap) { 44 | $loader->addClassMap($classMap); 45 | } 46 | } 47 | 48 | $loader->register(true); 49 | 50 | return $loader; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /vendor/composer/autoload_static.php: -------------------------------------------------------------------------------- 1 | 11 | array ( 12 | 'Workerman\\' => 10, 13 | ), 14 | ); 15 | 16 | public static $prefixDirsPsr4 = array ( 17 | 'Workerman\\' => 18 | array ( 19 | 0 => __DIR__ . '/../..' . '/', 20 | ), 21 | ); 22 | 23 | public static function getInitializer(ClassLoader $loader) 24 | { 25 | return \Closure::bind(function () use ($loader) { 26 | $loader->prefixLengthsPsr4 = ComposerStaticInit3e6202733432bf8729c6635ac5a01712::$prefixLengthsPsr4; 27 | $loader->prefixDirsPsr4 = ComposerStaticInit3e6202733432bf8729c6635ac5a01712::$prefixDirsPsr4; 28 | 29 | }, null, ClassLoader::class); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /vendor/composer/installed.json: -------------------------------------------------------------------------------- 1 | [] 2 | --------------------------------------------------------------------------------