├── .DS_Store ├── Workerman ├── .gitignore ├── Lib │ ├── Constants.php │ └── Timer.php ├── MIT-LICENSE.txt ├── composer.json ├── Protocols │ ├── Frame.php │ ├── ProtocolInterface.php │ ├── Text.php │ ├── Http │ │ └── mime.types │ ├── Ws.php │ ├── Websocket.php │ └── Http.php ├── Autoloader.php ├── Events │ ├── EventInterface.php │ ├── React │ │ ├── ExtEventLoop.php │ │ ├── LibEventLoop.php │ │ └── StreamSelectLoop.php │ ├── Ev.php │ ├── Event.php │ ├── Libevent.php │ └── Select.php ├── Connection │ ├── ConnectionInterface.php │ ├── UdpConnection.php │ └── AsyncTcpConnection.php ├── WebServer.php └── README.md ├── .gitattributes ├── Log └── .DS_Store ├── .idea ├── dictionaries │ └── zhaodanyang.xml ├── php.xml ├── vcs.xml ├── modules.xml ├── workerman_cor_ape.iml └── misc.xml ├── Cor ├── Net │ ├── SystemCall.php │ ├── Task.php │ ├── CpuConnection.php │ ├── Cpu.php │ └── Events │ │ └── Select.php ├── CorThread.php ├── Extend │ └── MySql.php └── CorWorker.php ├── Events.php ├── start.php └── README.md /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyfei/workerman_cor_ape/HEAD/.DS_Store -------------------------------------------------------------------------------- /Workerman/.gitignore: -------------------------------------------------------------------------------- 1 | logs 2 | .buildpath 3 | .project 4 | .settings 5 | .idea -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto -------------------------------------------------------------------------------- /Log/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyfei/workerman_cor_ape/HEAD/Log/.DS_Store -------------------------------------------------------------------------------- /.idea/dictionaries/zhaodanyang.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/php.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/workerman_cor_ape.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Cor/Net/SystemCall.php: -------------------------------------------------------------------------------- 1 | callback = $callback; 13 | } 14 | 15 | //可以将对象当做方法使用,默认就调用这个 16 | public function __invoke(Task $task, Cpu $cpu) { 17 | $callback = $this->callback; 18 | return $callback($task, $cpu); 19 | } 20 | 21 | //创建新任务 22 | public static function newTask(\Generator $coroutine) { 23 | return new SystemCall ( function (Task $task, Cpu $cpu) use ($coroutine) { 24 | $task->setSendValue ( $cpu->newTask ( $coroutine ) ); 25 | $cpu->schedule ( $task ); 26 | } ); 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /Events.php: -------------------------------------------------------------------------------- 1 | async_query("select * from t_admin"); 11 | $mysql->async_query("select * from t_admin"); 12 | $mysql->async_query("select * from t_admin"); 13 | //注意这里采用协程方式访问mysql里面的协程方法 14 | $res = yield from $mysql->async_result(); 15 | return $res; 16 | } 17 | 18 | public static function helloworld($data){ 19 | return "helloworld cor"; 20 | yield; 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /start.php: -------------------------------------------------------------------------------- 1 | count = 1; 8 | //设置执行任务的类 9 | $worker->eventHandler = "Events"; 10 | 11 | //以前怎么使用workerman,现在还可以怎么使用,毫无区别 12 | $worker->onMessage = function ($connection, $data) use ($worker) { 13 | //你也可以选择这样的方式,也就是workerman的方式,我们先注释掉,使用任务线程的send方法返回数据 14 | //$connection->send("hello workerman_cor_ape"); 15 | 16 | //这段代码会异步任务线程Evnets类里面的testMysql方法 17 | $worker->ajax("testMysql", $data, function ($body) use ($connection){ 18 | $connection->send(json_encode($body)); 19 | }); 20 | 21 | }; 22 | 23 | // 日志 24 | CorWorker::$logFile = __DIR__ . "/Log/log.log"; 25 | // 访问日志 26 | CorWorker::$stdoutFile = __DIR__ . "/Log/stadout.log"; 27 | 28 | CorWorker::runAll(); -------------------------------------------------------------------------------- /Cor/Net/Task.php: -------------------------------------------------------------------------------- 1 | taskId = $taskId; 15 | $this->coroutine = $coroutine; 16 | } 17 | 18 | public function getTaskId() { 19 | return $this->taskId; 20 | } 21 | 22 | public function setSendValue($sendValue) { 23 | $this->sendValue = $sendValue; 24 | } 25 | 26 | public function run() { 27 | if ($this->beforeFirstYield) { 28 | $this->beforeFirstYield = false; 29 | return $this->coroutine->current (); 30 | } else { 31 | ////向生成器中传入一个值,当前yield接收值,然后继续执行下一个yield 32 | $retval = $this->coroutine->send ( $this->sendValue ); 33 | $this->sendValue = null; 34 | return $retval; 35 | } 36 | } 37 | 38 | public function isFinished() { 39 | return ! $this->coroutine->valid (); 40 | } 41 | } -------------------------------------------------------------------------------- /Workerman/Lib/Constants.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | 15 | // Date.timezone 16 | if (!ini_get('date.timezone')) { 17 | date_default_timezone_set('Asia/Shanghai'); 18 | } 19 | // Display errors. 20 | ini_set('display_errors', 'on'); 21 | // Reporting all. 22 | error_reporting(E_ALL); 23 | 24 | // Reset opcache. 25 | if (function_exists('opcache_reset')) { 26 | opcache_reset(); 27 | } 28 | 29 | // For onError callback. 30 | define('WORKERMAN_CONNECT_FAIL', 1); 31 | // For onError callback. 32 | define('WORKERMAN_SEND_FAIL', 2); 33 | 34 | // Compatible with php7 35 | if(!class_exists('Error')) 36 | { 37 | class Error extends Exception 38 | { 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Workerman/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 | -------------------------------------------------------------------------------- /Workerman/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 | "ext-pcntl": "*", 29 | "ext-posix": "*" 30 | }, 31 | "suggest": { 32 | "ext-event": "For better performance. " 33 | }, 34 | "autoload": { 35 | "psr-4": { 36 | "Workerman\\": "./" 37 | } 38 | }, 39 | "minimum-stability": "dev" 40 | } 41 | -------------------------------------------------------------------------------- /Cor/CorThread.php: -------------------------------------------------------------------------------- 1 | is_exit = false; 21 | $this->cpu_unix_name = $cpu_unix_name; 22 | $this->eventHandler = $eventHandler; 23 | } 24 | 25 | public function run() 26 | { 27 | //分离子线程和父线程 28 | spl_autoload_register(function ($name) { 29 | $class_path = str_replace('\\', DIRECTORY_SEPARATOR, $name); 30 | $class_file = __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . "$class_path.php"; 31 | if (is_file($class_file)) { 32 | require_once($class_file); 33 | if (class_exists($name, false)) { 34 | return true; 35 | } 36 | } 37 | return false; 38 | }); 39 | global $cpu; 40 | $cpu = new \Cor\Net\Cpu($this->cpu_unix_name,$this); 41 | $cpu->run(); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | AngularJS 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /Cor/Extend/MySql.php: -------------------------------------------------------------------------------- 1 | host = $host; 27 | $this->user = $user; 28 | $this->password = $password; 29 | $this->database = $database; 30 | } 31 | 32 | function async_query($sql) 33 | { 34 | $con = new \mysqli($this->host, $this->user, $this->password, $this->database); 35 | $con->query($sql, MYSQLI_ASYNC); 36 | $this->conns[$con->thread_id] = $con; 37 | } 38 | 39 | function async_result() 40 | { 41 | $res = array(); 42 | $e = array(); 43 | $e2 = array(); 44 | while (count($this->conns)>0) { 45 | $reads = $this->conns; 46 | 47 | if (!mysqli_poll($reads, $e, $e2, 10)) { 48 | yield; 49 | continue; 50 | } 51 | foreach ($reads as $obj) { 52 | unset($this->conns[$obj->thread_id]); 53 | $sql_result = $obj->reap_async_query(); 54 | $sql_result_array = $sql_result->fetch_all(); 55 | $sql_result->free(); 56 | $res[] = $sql_result_array; 57 | } 58 | yield; 59 | } 60 | return $res; 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /Workerman/Protocols/Frame.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | namespace Workerman\Protocols; 15 | 16 | use Workerman\Connection\TcpConnection; 17 | 18 | /** 19 | * Frame Protocol. 20 | */ 21 | class Frame 22 | { 23 | /** 24 | * Check the integrity of the package. 25 | * 26 | * @param string $buffer 27 | * @param TcpConnection $connection 28 | * @return int 29 | */ 30 | public static function input($buffer, TcpConnection $connection) 31 | { 32 | if (strlen($buffer) < 4) { 33 | return 0; 34 | } 35 | $unpack_data = unpack('Ntotal_length', $buffer); 36 | return $unpack_data['total_length']; 37 | } 38 | 39 | /** 40 | * Decode. 41 | * 42 | * @param string $buffer 43 | * @return string 44 | */ 45 | public static function decode($buffer) 46 | { 47 | return substr($buffer, 4); 48 | } 49 | 50 | /** 51 | * Encode. 52 | * 53 | * @param string $buffer 54 | * @return string 55 | */ 56 | public static function encode($buffer) 57 | { 58 | $total_length = 4 + strlen($buffer); 59 | return pack('N', $total_length) . $buffer; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Workerman/Protocols/ProtocolInterface.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | namespace Workerman\Protocols; 15 | 16 | use Workerman\Connection\ConnectionInterface; 17 | 18 | /** 19 | * Protocol interface 20 | */ 21 | interface ProtocolInterface 22 | { 23 | /** 24 | * Check the integrity of the package. 25 | * Please return the length of package. 26 | * If length is unknow please return 0 that mean wating more data. 27 | * If the package has something wrong please return false the connection will be closed. 28 | * 29 | * @param ConnectionInterface $connection 30 | * @param string $recv_buffer 31 | * @return int|false 32 | */ 33 | public static function input($recv_buffer, ConnectionInterface $connection); 34 | 35 | /** 36 | * Decode package and emit onMessage($message) callback, $message is the result that decode returned. 37 | * 38 | * @param ConnectionInterface $connection 39 | * @param string $recv_buffer 40 | * @return mixed 41 | */ 42 | public static function decode($recv_buffer, ConnectionInterface $connection); 43 | 44 | /** 45 | * Encode package brefore sending to client. 46 | * 47 | * @param ConnectionInterface $connection 48 | * @param mixed $data 49 | * @return string 50 | */ 51 | public static function encode($data, ConnectionInterface $connection); 52 | } 53 | -------------------------------------------------------------------------------- /Workerman/Protocols/Text.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | namespace Workerman\Protocols; 15 | 16 | use Workerman\Connection\TcpConnection; 17 | 18 | /** 19 | * Text Protocol. 20 | */ 21 | class Text 22 | { 23 | /** 24 | * Check the integrity of the package. 25 | * 26 | * @param string $buffer 27 | * @param TcpConnection $connection 28 | * @return int 29 | */ 30 | public static function input($buffer, TcpConnection $connection) 31 | { 32 | // Judge whether the package length exceeds the limit. 33 | if (strlen($buffer) >= TcpConnection::$maxPackageSize) { 34 | $connection->close(); 35 | return 0; 36 | } 37 | // Find the position of "\n". 38 | $pos = strpos($buffer, "\n"); 39 | // No "\n", packet length is unknown, continue to wait for the data so return 0. 40 | if ($pos === false) { 41 | return 0; 42 | } 43 | // Return the current package length. 44 | return $pos + 1; 45 | } 46 | 47 | /** 48 | * Encode. 49 | * 50 | * @param string $buffer 51 | * @return string 52 | */ 53 | public static function encode($buffer) 54 | { 55 | // Add "\n" 56 | return $buffer . "\n"; 57 | } 58 | 59 | /** 60 | * Decode. 61 | * 62 | * @param string $buffer 63 | * @return string 64 | */ 65 | public static function decode($buffer) 66 | { 67 | // Remove "\n" 68 | return trim($buffer); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Workerman/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 | if (is_file($class_file)) { 59 | require_once($class_file); 60 | if (class_exists($name, false)) { 61 | return true; 62 | } 63 | } 64 | return false; 65 | } 66 | } 67 | 68 | spl_autoload_register('\Workerman\Autoloader::loadByNamespace'); -------------------------------------------------------------------------------- /Workerman/Events/EventInterface.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | namespace Workerman\Events; 15 | 16 | interface EventInterface 17 | { 18 | /** 19 | * Read event. 20 | * 21 | * @var int 22 | */ 23 | const EV_READ = 1; 24 | 25 | /** 26 | * Write event. 27 | * 28 | * @var int 29 | */ 30 | const EV_WRITE = 2; 31 | 32 | /** 33 | * Except event 34 | * 35 | * @var int 36 | */ 37 | const EV_EXCEPT = 3; 38 | 39 | /** 40 | * Signal event. 41 | * 42 | * @var int 43 | */ 44 | const EV_SIGNAL = 4; 45 | 46 | /** 47 | * Timer event. 48 | * 49 | * @var int 50 | */ 51 | const EV_TIMER = 8; 52 | 53 | /** 54 | * Timer once event. 55 | * 56 | * @var int 57 | */ 58 | const EV_TIMER_ONCE = 16; 59 | 60 | /** 61 | * Add event listener to event loop. 62 | * 63 | * @param mixed $fd 64 | * @param int $flag 65 | * @param callable $func 66 | * @param mixed $args 67 | * @return bool 68 | */ 69 | public function add($fd, $flag, $func, $args = null); 70 | 71 | /** 72 | * Remove event listener from event loop. 73 | * 74 | * @param mixed $fd 75 | * @param int $flag 76 | * @return bool 77 | */ 78 | public function del($fd, $flag); 79 | 80 | /** 81 | * Remove all timers. 82 | * 83 | * @return void 84 | */ 85 | public function clearAllTimer(); 86 | 87 | /** 88 | * Main loop. 89 | * 90 | * @return void 91 | */ 92 | public function loop(); 93 | 94 | /** 95 | * Destroy loop. 96 | * 97 | * @return mixed 98 | */ 99 | public function destroy(); 100 | 101 | /** 102 | * Get Timer count. 103 | * 104 | * @return mixed 105 | */ 106 | public function getTimerCount(); 107 | } 108 | -------------------------------------------------------------------------------- /Workerman/Connection/ConnectionInterface.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | namespace Workerman\Connection; 15 | 16 | /** 17 | * ConnectionInterface. 18 | */ 19 | abstract class ConnectionInterface 20 | { 21 | /** 22 | * Statistics for status command. 23 | * 24 | * @var array 25 | */ 26 | public static $statistics = array( 27 | 'connection_count' => 0, 28 | 'total_request' => 0, 29 | 'throw_exception' => 0, 30 | 'send_fail' => 0, 31 | ); 32 | 33 | /** 34 | * Emitted when data is received. 35 | * 36 | * @var callback 37 | */ 38 | public $onMessage = null; 39 | 40 | /** 41 | * Emitted when the other end of the socket sends a FIN packet. 42 | * 43 | * @var callback 44 | */ 45 | public $onClose = null; 46 | 47 | /** 48 | * Emitted when an error occurs with connection. 49 | * 50 | * @var callback 51 | */ 52 | public $onError = null; 53 | 54 | /** 55 | * Sends data on the connection. 56 | * 57 | * @param string $send_buffer 58 | * @return void|boolean 59 | */ 60 | abstract public function send($send_buffer); 61 | 62 | /** 63 | * Get remote IP. 64 | * 65 | * @return string 66 | */ 67 | abstract public function getRemoteIp(); 68 | 69 | /** 70 | * Get remote port. 71 | * 72 | * @return int 73 | */ 74 | abstract public function getRemotePort(); 75 | 76 | /** 77 | * Get remote address. 78 | * 79 | * @return string 80 | */ 81 | abstract public function getRemoteAddress(); 82 | 83 | /** 84 | * Get remote IP. 85 | * 86 | * @return string 87 | */ 88 | abstract public function getLocalIp(); 89 | 90 | /** 91 | * Get remote port. 92 | * 93 | * @return int 94 | */ 95 | abstract public function getLocalPort(); 96 | 97 | /** 98 | * Get remote address. 99 | * 100 | * @return string 101 | */ 102 | abstract public function getLocalAddress(); 103 | 104 | /** 105 | * Is ipv4. 106 | * 107 | * @return bool 108 | */ 109 | abstract public function isIPv4(); 110 | 111 | /** 112 | * Is ipv6. 113 | * 114 | * @return bool 115 | */ 116 | abstract public function isIPv6(); 117 | 118 | /** 119 | * Close connection. 120 | * 121 | * @param $data 122 | * @return void 123 | */ 124 | abstract public function close($data = null); 125 | } 126 | -------------------------------------------------------------------------------- /Cor/Net/CpuConnection.php: -------------------------------------------------------------------------------- 1 | id = self::$max_id; 26 | $conn = stream_socket_accept($socket, 0); 27 | $this->worker = $worker; 28 | $this->conn = $conn; 29 | } 30 | 31 | // 动起来,跟着我的世界一起喝彩 32 | public function read() 33 | { 34 | //尝试读取 35 | $buffer = @fread($this->conn, self::READ_BUFFER_SIZE); 36 | if ($buffer === '' || $buffer === false) { 37 | if (feof($this->conn) || !is_resource($this->conn) || $buffer === false) { 38 | $this->destroy(); 39 | return; 40 | } 41 | } 42 | //拼接上一次遗留的字符串 43 | $this->old_buffer = $this->old_buffer . $buffer; 44 | while(strlen($this->old_buffer)>2){ 45 | $len = substr($this->old_buffer, 0, 2); 46 | $body_len = unpack('Sbin_len', $len)['bin_len']; 47 | if(strlen($this->old_buffer)<($body_len+2)){ 48 | break; 49 | } 50 | $data = substr($this->old_buffer, 2, $body_len); 51 | $data = json_decode($data,true); 52 | $this->old_buffer = substr($this->old_buffer,($body_len+2),strlen($this->old_buffer)-($body_len+2)); 53 | 54 | if (is_callable($this->worker->thread->eventHandler . '::' . $data[0])) { 55 | $gen = $this->worker->thread->eventHandler . '::' . $data[0]; 56 | $response = yield from $gen ($data[2]); 57 | //如果等于0不需要返回,否则相反 58 | if($data[1]!=0){ 59 | $this->worker->_event->add($this->conn, Select::EV_WRITE, array($this, '_write'), array(json_encode(array($data[1],$response)))); 60 | } 61 | }else{ 62 | /** 如果没找到对应方法,那么也返回 */ 63 | var_dump("can't find ".$this->worker->thread->eventHandler . '::' . $data[0]); 64 | } 65 | } 66 | yield; 67 | } 68 | 69 | 70 | public function destroy() 71 | { 72 | if ($this->worker) { 73 | unset ($this->worker->connections [(int)$this->conn]); 74 | } 75 | $this->worker->_event->del($this->conn,Select::EV_READ); 76 | $this->worker->_event->del($this->conn,Select::EV_WRITE); 77 | $this->worker->thread->is_exit = false; 78 | } 79 | 80 | /** 81 | * 读回调 82 | */ 83 | public function _read($socket){ 84 | //将读取操作添加进任务队列中 85 | //尽量缩小不必要的任务切换,将任务切换使用在阻塞操作当中 86 | $this->worker->newTask ( $this->read() ); 87 | } 88 | 89 | /** 90 | * 写回调 91 | */ 92 | public function _write($key,$socket,$data){ 93 | $body_len = strlen($data); 94 | $bin_head = pack('S*', $body_len); 95 | $len = @fwrite($socket, $bin_head . $data); 96 | if ($len === strlen($bin_head . $data)) { 97 | $this->worker->_event->del($socket, Select::EV_WRITE,$key); 98 | }else{ 99 | $this->destroy(); 100 | } 101 | 102 | } 103 | } -------------------------------------------------------------------------------- /Workerman/Protocols/Http/mime.types: -------------------------------------------------------------------------------- 1 | 2 | types { 3 | text/html html htm shtml; 4 | text/css css; 5 | text/xml xml; 6 | image/gif gif; 7 | image/jpeg jpeg jpg; 8 | application/x-javascript js; 9 | application/atom+xml atom; 10 | application/rss+xml rss; 11 | 12 | text/mathml mml; 13 | text/plain txt; 14 | text/vnd.sun.j2me.app-descriptor jad; 15 | text/vnd.wap.wml wml; 16 | text/x-component htc; 17 | 18 | image/png png; 19 | image/tiff tif tiff; 20 | image/vnd.wap.wbmp wbmp; 21 | image/x-icon ico; 22 | image/x-jng jng; 23 | image/x-ms-bmp bmp; 24 | image/svg+xml svg svgz; 25 | image/webp webp; 26 | 27 | application/java-archive jar war ear; 28 | application/mac-binhex40 hqx; 29 | application/msword doc; 30 | application/pdf pdf; 31 | application/postscript ps eps ai; 32 | application/rtf rtf; 33 | application/vnd.ms-excel xls; 34 | application/vnd.ms-powerpoint ppt; 35 | application/vnd.wap.wmlc wmlc; 36 | application/vnd.google-earth.kml+xml kml; 37 | application/vnd.google-earth.kmz kmz; 38 | application/x-7z-compressed 7z; 39 | application/x-cocoa cco; 40 | application/x-java-archive-diff jardiff; 41 | application/x-java-jnlp-file jnlp; 42 | application/x-makeself run; 43 | application/x-perl pl pm; 44 | application/x-pilot prc pdb; 45 | application/x-rar-compressed rar; 46 | application/x-redhat-package-manager rpm; 47 | application/x-sea sea; 48 | application/x-shockwave-flash swf; 49 | application/x-stuffit sit; 50 | application/x-tcl tcl tk; 51 | application/x-x509-ca-cert der pem crt; 52 | application/x-xpinstall xpi; 53 | application/xhtml+xml xhtml; 54 | application/zip zip; 55 | 56 | application/octet-stream bin exe dll; 57 | application/octet-stream deb; 58 | application/octet-stream dmg; 59 | application/octet-stream eot; 60 | application/octet-stream iso img; 61 | application/octet-stream msi msp msm; 62 | 63 | audio/midi mid midi kar; 64 | audio/mpeg mp3; 65 | audio/ogg ogg; 66 | audio/x-m4a m4a; 67 | audio/x-realaudio ra; 68 | 69 | video/3gpp 3gpp 3gp; 70 | video/mp4 mp4; 71 | video/mpeg mpeg mpg; 72 | video/quicktime mov; 73 | video/webm webm; 74 | video/x-flv flv; 75 | video/x-m4v m4v; 76 | video/x-mng mng; 77 | video/x-ms-asf asx asf; 78 | video/x-ms-wmv wmv; 79 | video/x-msvideo avi; 80 | } 81 | -------------------------------------------------------------------------------- /Cor/Net/Cpu.php: -------------------------------------------------------------------------------- 1 | task 26 | 27 | // 监听端口的socket 28 | public $_mainSocket = null; 29 | 30 | 31 | public function __construct($host, $thread) 32 | { 33 | // 设置监听路径 34 | $this->host = $host; 35 | $this->thread = $thread; 36 | // 初始化唯一ID 37 | if (PHP_INT_MAX === self::$_idRecorder) { 38 | self::$_idRecorder = 0; 39 | } 40 | $this->id = self::$_idRecorder++; 41 | // 实例化数据结构队列 42 | $this->taskQueue = new \SplQueue (); 43 | // 初始化系统 44 | $this->init(); 45 | $this->_event = new Select($this); 46 | //添加端口socket监听 47 | $this->_event->add($this->_mainSocket, Select::EV_READ, array($this, '_accept')); 48 | //将核心监听任务加入队列中 49 | $this->newTask($this->_event->loop()); 50 | } 51 | 52 | 53 | /** 54 | * 传入协程对象,创建新任务,并且把任务放入任务队列 55 | */ 56 | public function newTask(\Generator $coroutine) 57 | { 58 | if (PHP_INT_MAX === $this->_taskId) { 59 | self::$_idRecorder = 0; 60 | } 61 | $tid = $this->_taskId; 62 | 63 | // 创建一个任务 64 | $task = new Task ($tid, $coroutine); 65 | // 将任务放入任务表中 66 | $this->taskMap [$tid] = $task; 67 | $this->schedule($task); 68 | return $tid; 69 | } 70 | 71 | /** 72 | * 将任务放入任务队列底部 73 | */ 74 | public function schedule(Task $task) 75 | { 76 | $this->taskQueue->enqueue($task); 77 | } 78 | 79 | /** 80 | * 任务队列是否为空 81 | */ 82 | public function taskQueueIsEmpty(){ 83 | return $this->taskQueue->isEmpty(); 84 | } 85 | 86 | /** 87 | * 开始调度任务,类似于cpu 88 | */ 89 | public function run() 90 | { 91 | // 如果有任务队列中还有任务,那么继续执行 92 | while (!$this->taskQueue->isEmpty() && !$this->thread->is_exit) { 93 | // 取出队列头部的成员 94 | $task = $this->taskQueue->dequeue(); 95 | // 执行一个刻度的任务 96 | $retval = $task->run(); 97 | 98 | // 如果任务返回的是一个系统调用,那么执行这个调用,并且把这个任务移除,这是一次性任务 99 | if ($retval instanceof SystemCall) { 100 | $retval ($task, $this); 101 | continue; 102 | } 103 | 104 | // 如果任务运行完毕了 105 | if ($task->isFinished()) { 106 | // 那么把这个任务从任务表中移除 107 | unset ($this->taskMap [$task->getTaskId()]); 108 | } else { 109 | // 如果没完成,将他放入任务链表最后 110 | $this->schedule($task); 111 | } 112 | } 113 | } 114 | 115 | /** 116 | * 有新链接回调 117 | */ 118 | public function _accept($socket){ 119 | $connection = new CpuConnection ($this, $socket); 120 | $this->connections [( int )$connection->conn] = $connection; 121 | $this->_event->add($connection->conn, Select::EV_READ, array($connection, '_read')); 122 | } 123 | 124 | /** 125 | * 初始化相关数据 126 | */ 127 | protected function init() 128 | { 129 | //如果是unix类型,那么启动之前先把之前的删除了 130 | if (strpos($this->host, "unix://") === 0) { 131 | $unix_file = str_replace("unix://", "", $this->host); 132 | if (file_exists($unix_file)) { 133 | unlink($unix_file); 134 | } 135 | } 136 | // 创建socket套接字,用来监听这个端口 137 | $socket = stream_socket_server($this->host, $errNo, $errStr); 138 | // 如果创建失败抛出异常 139 | if (!$socket) { 140 | throw new \Exception ($errStr, $errNo); 141 | exit (); 142 | } 143 | // 设置监听连接socket 144 | $this->_mainSocket = $socket; 145 | // 设置为非阻塞IO,当尝试读一个网络流,并且未读取字节的时候,立即告诉调用者结构 146 | stream_set_blocking($socket, 0); 147 | } 148 | 149 | } 150 | 151 | -------------------------------------------------------------------------------- /Workerman/Connection/UdpConnection.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | namespace Workerman\Connection; 15 | 16 | /** 17 | * UdpConnection. 18 | */ 19 | class UdpConnection extends ConnectionInterface 20 | { 21 | /** 22 | * Application layer protocol. 23 | * The format is like this Workerman\\Protocols\\Http. 24 | * 25 | * @var \Workerman\Protocols\ProtocolInterface 26 | */ 27 | public $protocol = null; 28 | 29 | /** 30 | * Udp socket. 31 | * 32 | * @var resource 33 | */ 34 | protected $_socket = null; 35 | 36 | /** 37 | * Remote address. 38 | * 39 | * @var string 40 | */ 41 | protected $_remoteAddress = ''; 42 | 43 | /** 44 | * Construct. 45 | * 46 | * @param resource $socket 47 | * @param string $remote_address 48 | */ 49 | public function __construct($socket, $remote_address) 50 | { 51 | $this->_socket = $socket; 52 | $this->_remoteAddress = $remote_address; 53 | } 54 | 55 | /** 56 | * Sends data on the connection. 57 | * 58 | * @param string $send_buffer 59 | * @param bool $raw 60 | * @return void|boolean 61 | */ 62 | public function send($send_buffer, $raw = false) 63 | { 64 | if (false === $raw && $this->protocol) { 65 | $parser = $this->protocol; 66 | $send_buffer = $parser::encode($send_buffer, $this); 67 | if ($send_buffer === '') { 68 | return null; 69 | } 70 | } 71 | return strlen($send_buffer) === stream_socket_sendto($this->_socket, $send_buffer, 0, $this->_remoteAddress); 72 | } 73 | 74 | /** 75 | * Get remote IP. 76 | * 77 | * @return string 78 | */ 79 | public function getRemoteIp() 80 | { 81 | $pos = strrpos($this->_remoteAddress, ':'); 82 | if ($pos) { 83 | return trim(substr($this->_remoteAddress, 0, $pos), '[]'); 84 | } 85 | return ''; 86 | } 87 | 88 | /** 89 | * Get remote port. 90 | * 91 | * @return int 92 | */ 93 | public function getRemotePort() 94 | { 95 | if ($this->_remoteAddress) { 96 | return (int)substr(strrchr($this->_remoteAddress, ':'), 1); 97 | } 98 | return 0; 99 | } 100 | 101 | /** 102 | * Get remote address. 103 | * 104 | * @return string 105 | */ 106 | public function getRemoteAddress() 107 | { 108 | return $this->_remoteAddress; 109 | } 110 | 111 | /** 112 | * Get local IP. 113 | * 114 | * @return string 115 | */ 116 | public function getLocalIp() 117 | { 118 | $address = $this->getLocalAddress(); 119 | $pos = strrpos($address, ':'); 120 | if (!$pos) { 121 | return ''; 122 | } 123 | return substr($address, 0, $pos); 124 | } 125 | 126 | /** 127 | * Get local port. 128 | * 129 | * @return int 130 | */ 131 | public function getLocalPort() 132 | { 133 | $address = $this->getLocalAddress(); 134 | $pos = strrpos($address, ':'); 135 | if (!$pos) { 136 | return 0; 137 | } 138 | return (int)substr(strrchr($address, ':'), 1); 139 | } 140 | 141 | /** 142 | * Get local address. 143 | * 144 | * @return string 145 | */ 146 | public function getLocalAddress() 147 | { 148 | return (string)@stream_socket_get_name($this->_socket, false); 149 | } 150 | 151 | /** 152 | * Is ipv4. 153 | * 154 | * return bool. 155 | */ 156 | public function isIpV4() 157 | { 158 | if ($this->transport === 'unix') { 159 | return false; 160 | } 161 | return strpos($this->getRemoteIp(), ':') === false; 162 | } 163 | 164 | /** 165 | * Is ipv6. 166 | * 167 | * return bool. 168 | */ 169 | public function isIpV6() 170 | { 171 | if ($this->transport === 'unix') { 172 | return false; 173 | } 174 | return strpos($this->getRemoteIp(), ':') !== false; 175 | } 176 | 177 | /** 178 | * Close connection. 179 | * 180 | * @param mixed $data 181 | * @param bool $raw 182 | * @return bool 183 | */ 184 | public function close($data = null, $raw = false) 185 | { 186 | if ($data !== null) { 187 | $this->send($data, $raw); 188 | } 189 | return true; 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /Workerman/Lib/Timer.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | namespace Workerman\Lib; 15 | 16 | use Workerman\Events\EventInterface; 17 | use Exception; 18 | 19 | /** 20 | * Timer. 21 | * 22 | * example: 23 | * Workerman\Lib\Timer::add($time_interval, callback, array($arg1, $arg2..)); 24 | */ 25 | class Timer 26 | { 27 | /** 28 | * Tasks that based on ALARM signal. 29 | * [ 30 | * run_time => [[$func, $args, $persistent, time_interval],[$func, $args, $persistent, time_interval],..]], 31 | * run_time => [[$func, $args, $persistent, time_interval],[$func, $args, $persistent, time_interval],..]], 32 | * .. 33 | * ] 34 | * 35 | * @var array 36 | */ 37 | protected static $_tasks = array(); 38 | 39 | /** 40 | * event 41 | * 42 | * @var \Workerman\Events\EventInterface 43 | */ 44 | protected static $_event = null; 45 | 46 | /** 47 | * Init. 48 | * 49 | * @param \Workerman\Events\EventInterface $event 50 | * @return void 51 | */ 52 | public static function init($event = null) 53 | { 54 | if ($event) { 55 | self::$_event = $event; 56 | } else { 57 | pcntl_signal(SIGALRM, array('\Workerman\Lib\Timer', 'signalHandle'), false); 58 | } 59 | } 60 | 61 | /** 62 | * ALARM signal handler. 63 | * 64 | * @return void 65 | */ 66 | public static function signalHandle() 67 | { 68 | if (!self::$_event) { 69 | pcntl_alarm(1); 70 | self::tick(); 71 | } 72 | } 73 | 74 | /** 75 | * Add a timer. 76 | * 77 | * @param int $time_interval 78 | * @param callback $func 79 | * @param mixed $args 80 | * @param bool $persistent 81 | * @return int/false 82 | */ 83 | public static function add($time_interval, $func, $args = array(), $persistent = true) 84 | { 85 | if ($time_interval <= 0) { 86 | echo new Exception("bad time_interval"); 87 | return false; 88 | } 89 | 90 | if (self::$_event) { 91 | return self::$_event->add($time_interval, 92 | $persistent ? EventInterface::EV_TIMER : EventInterface::EV_TIMER_ONCE, $func, $args); 93 | } 94 | 95 | if (!is_callable($func)) { 96 | echo new Exception("not callable"); 97 | return false; 98 | } 99 | 100 | if (empty(self::$_tasks)) { 101 | pcntl_alarm(1); 102 | } 103 | 104 | $time_now = time(); 105 | $run_time = $time_now + $time_interval; 106 | if (!isset(self::$_tasks[$run_time])) { 107 | self::$_tasks[$run_time] = array(); 108 | } 109 | self::$_tasks[$run_time][] = array($func, (array)$args, $persistent, $time_interval); 110 | return 1; 111 | } 112 | 113 | 114 | /** 115 | * Tick. 116 | * 117 | * @return void 118 | */ 119 | public static function tick() 120 | { 121 | if (empty(self::$_tasks)) { 122 | pcntl_alarm(0); 123 | return; 124 | } 125 | 126 | $time_now = time(); 127 | foreach (self::$_tasks as $run_time => $task_data) { 128 | if ($time_now >= $run_time) { 129 | foreach ($task_data as $index => $one_task) { 130 | $task_func = $one_task[0]; 131 | $task_args = $one_task[1]; 132 | $persistent = $one_task[2]; 133 | $time_interval = $one_task[3]; 134 | try { 135 | call_user_func_array($task_func, $task_args); 136 | } catch (\Exception $e) { 137 | echo $e; 138 | } 139 | if ($persistent) { 140 | self::add($time_interval, $task_func, $task_args); 141 | } 142 | } 143 | unset(self::$_tasks[$run_time]); 144 | } 145 | } 146 | } 147 | 148 | /** 149 | * Remove a timer. 150 | * 151 | * @param mixed $timer_id 152 | * @return bool 153 | */ 154 | public static function del($timer_id) 155 | { 156 | if (self::$_event) { 157 | return self::$_event->del($timer_id, EventInterface::EV_TIMER); 158 | } 159 | 160 | return false; 161 | } 162 | 163 | /** 164 | * Remove all timers. 165 | * 166 | * @return void 167 | */ 168 | public static function delAll() 169 | { 170 | self::$_tasks = array(); 171 | pcntl_alarm(0); 172 | if (self::$_event) { 173 | self::$_event->clearAllTimer(); 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Workerman_cor_ape 2 | 3 | 4 | ## 这是什么 5 | Workerman_cor_ape 是知名php框架 [Workerman](https://github.com/walkor/Workerman) 的强化版,在不影响任何使用方式,稳定性,性能前提下,增加了异步任务组件。 6 | ## 原理是什么 7 | Workerman每个工作进程只有一个线程,这个线程既负责收发网络消息,又负责处理业务,在业务阻塞比较多的情况下,比较浪费性能。 8 | 9 | Workerman_cor_ape框架将Workerman每个进程扩展为两个线程,分别是workerman线程,以及任务线程。 10 | `workerman线程:和原有的单线程workerman线程是相同的,无论是使用方式,还是性能。` 11 | `任务线程:负责接收workerman线程异步提交的任务,执行结束后可以将执行结果主动推送给workerman线程` 12 | 13 | 14 | 另外任务线程采用协程调度方式实现,自己控制线程等待/执行时机,最大限度压榨性能。并且方便自己扩展异步组件 15 | 16 | 17 | ## Requires 18 | 线程安全版本的 PHP7 or 更高 19 | A POSIX compatible operating system (Linux, OSX, BSD) 20 | POSIX PCNTL and **PTHREDS** extensions for PHP 21 | 22 | ## Workerman的使用方法 23 | 24 | 中文主页:[http://www.workerman.net](http://www.workerman.net) 25 | 26 | 中文文档: [http://doc.workerman.net](http://doc.workerman.net) 27 | 28 | Documentation:[https://github.com/walkor/workerman-manual](https://github.com/walkor/workerman-manual/blob/master/english/src/SUMMARY.md) 29 | 30 | 31 | ## 使用异步任务功能 32 | 33 | ### 核心方法 34 | ```php 35 | $worker->ajax("任务名字", 传递的数据, 回调的方法,超时时间); 36 | ``` 37 | ### workerman线程 38 | ```php 39 | use Cor\CorWorker; 40 | 41 | require_once __DIR__ . '/Workerman/Autoloader.php'; 42 | 43 | $worker = new CorWorker("http://0.0.0.0:1236"); 44 | $worker->count = 1; 45 | //设置任务线程执行的位置,不设置默认根目录下Events 46 | $worker->eventHandler = "Events"; 47 | 48 | //以前怎么使用workerman,现在还可以怎么使用,毫无区别 49 | $worker->onMessage = function ($connection, $data) use ($worker) { 50 | //你也可以选择这样的方式,也就是workerman的方式,我们先注释掉,使用任务线程的send方法返回数据 51 | //$connection->send("hello workerman_cor_ape"); 52 | 53 | //这段代码会异步任务线程Evnets类里面的helloworld方法 54 | $worker->ajax("testMysql", $data, function ($body) use ($connection){ 55 | $connection->send(json_encode($body)); 56 | }); 57 | 58 | }; 59 | 60 | CorWorker::runAll(); 61 | ``` 62 | 63 | ### 任务线程 64 | ```php 65 | class Events{ 66 | 67 | 68 | //任务线程任务 69 | public static function testMysql($connection,$data){ 70 | //异步执行mysql操作,这只是随手写的例子,各位老爷能了解到可以很简单实现异步mysql就好 71 | $mysql = new \Cor\Extend\MySql("127.0.0.1","账号","密码","数据库"); 72 | //查询三次 73 | $mysql->async_query("select * from t_admin"); 74 | $mysql->async_query("select * from t_admin"); 75 | $mysql->async_query("select * from t_admin"); 76 | //注意这里采用协程方式访问mysql里面的协程方法 77 | $res = yield from $mysql->async_result(); 78 | //方法内最少包含一个yield字段 79 | return $res; 80 | } 81 | 82 | } 83 | ``` 84 | 85 | 86 | ## 如何启动 87 | ```php start.php start ``` 88 | ```php start.php start -d ``` 89 | ```php start.php status ``` 90 | ```php start.php connections``` 91 | ```php start.php stop ``` 92 | ```php start.php restart ``` 93 | ```php start.php reload ``` 94 | 95 | # Workerman性能测试 96 | ``` 97 | CPU: Intel(R) Core(TM) i3-3220 CPU @ 3.30GHz and 4 processors totally 98 | Memory: 8G 99 | OS: Ubuntu 14.04 LTS 100 | Software: ab 101 | PHP: 5.5.9 102 | ``` 103 | 104 | **Codes** 105 | ```php 106 | count=3; 110 | $worker->onMessage = function($connection, $data) 111 | { 112 | $connection->send("HTTP/1.1 200 OK\r\nConnection: keep-alive\r\nServer: workerman\r\nContent-Length: 5\r\n\r\nhello"); 113 | }; 114 | Worker::runAll(); 115 | ``` 116 | **Result** 117 | 118 | ```shell 119 | ab -n1000000 -c100 -k http://127.0.0.1:1234/ 120 | This is ApacheBench, Version 2.3 <$Revision: 1528965 $> 121 | Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ 122 | Licensed to The Apache Software Foundation, http://www.apache.org/ 123 | 124 | Benchmarking 127.0.0.1 (be patient) 125 | Completed 100000 requests 126 | Completed 200000 requests 127 | Completed 300000 requests 128 | Completed 400000 requests 129 | Completed 500000 requests 130 | Completed 600000 requests 131 | Completed 700000 requests 132 | Completed 800000 requests 133 | Completed 900000 requests 134 | Completed 1000000 requests 135 | Finished 1000000 requests 136 | 137 | 138 | Server Software: workerman/3.1.4 139 | Server Hostname: 127.0.0.1 140 | Server Port: 1234 141 | 142 | Document Path: / 143 | Document Length: 5 bytes 144 | 145 | Concurrency Level: 100 146 | Time taken for tests: 7.240 seconds 147 | Complete requests: 1000000 148 | Failed requests: 0 149 | Keep-Alive requests: 1000000 150 | Total transferred: 73000000 bytes 151 | HTML transferred: 5000000 bytes 152 | Requests per second: 138124.14 [#/sec] (mean) 153 | Time per request: 0.724 [ms] (mean) 154 | Time per request: 0.007 [ms] (mean, across all concurrent requests) 155 | Transfer rate: 9846.74 [Kbytes/sec] received 156 | 157 | Connection Times (ms) 158 | min mean[+/-sd] median max 159 | Connect: 0 0 0.0 0 5 160 | Processing: 0 1 0.2 1 9 161 | Waiting: 0 1 0.2 1 9 162 | Total: 0 1 0.2 1 9 163 | 164 | Percentage of the requests served within a certain time (ms) 165 | 50% 1 166 | 66% 1 167 | 75% 1 168 | 80% 1 169 | 90% 1 170 | 95% 1 171 | 98% 1 172 | 99% 1 173 | 100% 9 (longest request) 174 | 175 | ``` 176 | 177 | ## 联系我 178 | 179 | QQ群: 1098698769 180 | 任何人都可以通过QQ群联系到我。 181 | -------------------------------------------------------------------------------- /Workerman/Events/React/ExtEventLoop.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | namespace Workerman\Events\React; 15 | use Workerman\Events\EventInterface; 16 | 17 | /** 18 | * Class ExtEventLoop 19 | * @package Workerman\Events\React 20 | */ 21 | class ExtEventLoop extends \React\EventLoop\ExtEventLoop 22 | { 23 | /** 24 | * Event base. 25 | * 26 | * @var EventBase 27 | */ 28 | protected $_eventBase = null; 29 | 30 | /** 31 | * All signal Event instances. 32 | * 33 | * @var array 34 | */ 35 | protected $_signalEvents = array(); 36 | 37 | /** 38 | * @var array 39 | */ 40 | protected $_timerIdMap = array(); 41 | 42 | /** 43 | * @var int 44 | */ 45 | protected $_timerIdIndex = 0; 46 | 47 | /** 48 | * Add event listener to event loop. 49 | * 50 | * @param $fd 51 | * @param $flag 52 | * @param $func 53 | * @param array $args 54 | * @return bool 55 | */ 56 | public function add($fd, $flag, $func, $args = array()) 57 | { 58 | $args = (array)$args; 59 | switch ($flag) { 60 | case EventInterface::EV_READ: 61 | return $this->addReadStream($fd, $func); 62 | case EventInterface::EV_WRITE: 63 | return $this->addWriteStream($fd, $func); 64 | case EventInterface::EV_SIGNAL: 65 | return $this->addSignal($fd, $func); 66 | case EventInterface::EV_TIMER: 67 | $timer_id = ++$this->_timerIdIndex; 68 | $timer_obj = $this->addPeriodicTimer($fd, function() use ($func, $args) { 69 | call_user_func_array($func, $args); 70 | }); 71 | $this->_timerIdMap[$timer_id] = $timer_obj; 72 | return $timer_id; 73 | case EventInterface::EV_TIMER_ONCE: 74 | $timer_id = ++$this->_timerIdIndex; 75 | $timer_obj = $this->addTimer($fd, function() use ($func, $args, $timer_id) { 76 | unset($this->_timerIdMap[$timer_id]); 77 | call_user_func_array($func, $args); 78 | }); 79 | $this->_timerIdMap[$timer_id] = $timer_obj; 80 | return $timer_id; 81 | } 82 | return false; 83 | } 84 | 85 | /** 86 | * Remove event listener from event loop. 87 | * 88 | * @param mixed $fd 89 | * @param int $flag 90 | * @return bool 91 | */ 92 | public function del($fd, $flag) 93 | { 94 | switch ($flag) { 95 | case EventInterface::EV_READ: 96 | return $this->removeReadStream($fd); 97 | case EventInterface::EV_WRITE: 98 | return $this->removeWriteStream($fd); 99 | case EventInterface::EV_SIGNAL: 100 | return $this->removeSignal($fd); 101 | case EventInterface::EV_TIMER: 102 | case EventInterface::EV_TIMER_ONCE; 103 | if (isset($this->_timerIdMap[$fd])){ 104 | $timer_obj = $this->_timerIdMap[$fd]; 105 | unset($this->_timerIdMap[$fd]); 106 | $this->cancelTimer($timer_obj); 107 | return true; 108 | } 109 | } 110 | return false; 111 | } 112 | 113 | 114 | /** 115 | * Main loop. 116 | * 117 | * @return void 118 | */ 119 | public function loop() 120 | { 121 | $this->run(); 122 | } 123 | 124 | /** 125 | * Construct 126 | */ 127 | public function __construct() 128 | { 129 | parent::__construct(); 130 | $class = new \ReflectionClass('\React\EventLoop\ExtEventLoop'); 131 | $property = $class->getProperty('eventBase'); 132 | $property->setAccessible(true); 133 | $this->_eventBase = $property->getValue($this); 134 | } 135 | 136 | /** 137 | * Add signal handler. 138 | * 139 | * @param $signal 140 | * @param $callback 141 | * @return bool 142 | */ 143 | public function addSignal($signal, $callback) 144 | { 145 | $event = \Event::signal($this->_eventBase, $signal, $callback); 146 | if (!$event||!$event->add()) { 147 | return false; 148 | } 149 | $this->_signalEvents[$signal] = $event; 150 | } 151 | 152 | /** 153 | * Remove signal handler. 154 | * 155 | * @param $signal 156 | */ 157 | public function removeSignal($signal) 158 | { 159 | if (isset($this->_signalEvents[$signal])) { 160 | $this->_signalEvents[$signal]->del(); 161 | unset($this->_signalEvents[$signal]); 162 | } 163 | } 164 | 165 | /** 166 | * Destroy loop. 167 | * 168 | * @return void 169 | */ 170 | public function destroy() 171 | { 172 | foreach ($this->_signalEvents as $event) { 173 | $event->del(); 174 | } 175 | } 176 | 177 | /** 178 | * Get timer count. 179 | * 180 | * @return integer 181 | */ 182 | public function getTimerCount() 183 | { 184 | return count($this->_timerIdMap); 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /Workerman/Events/React/LibEventLoop.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | namespace Workerman\Events\React; 15 | use Workerman\Events\EventInterface; 16 | 17 | /** 18 | * Class LibEventLoop 19 | * @package Workerman\Events\React 20 | */ 21 | class LibEventLoop extends \React\EventLoop\LibEventLoop 22 | { 23 | /** 24 | * Event base. 25 | * 26 | * @var event_base resource 27 | */ 28 | protected $_eventBase = null; 29 | 30 | /** 31 | * All signal Event instances. 32 | * 33 | * @var array 34 | */ 35 | protected $_signalEvents = array(); 36 | 37 | /** 38 | * @var array 39 | */ 40 | protected $_timerIdMap = array(); 41 | 42 | /** 43 | * @var int 44 | */ 45 | protected $_timerIdIndex = 0; 46 | 47 | /** 48 | * Add event listener to event loop. 49 | * 50 | * @param $fd 51 | * @param $flag 52 | * @param $func 53 | * @param array $args 54 | * @return bool 55 | */ 56 | public function add($fd, $flag, $func, $args = array()) 57 | { 58 | $args = (array)$args; 59 | switch ($flag) { 60 | case EventInterface::EV_READ: 61 | return $this->addReadStream($fd, $func); 62 | case EventInterface::EV_WRITE: 63 | return $this->addWriteStream($fd, $func); 64 | case EventInterface::EV_SIGNAL: 65 | return $this->addSignal($fd, $func); 66 | case EventInterface::EV_TIMER: 67 | $timer_id = ++$this->_timerIdIndex; 68 | $timer_obj = $this->addPeriodicTimer($fd, function() use ($func, $args) { 69 | call_user_func_array($func, $args); 70 | }); 71 | $this->_timerIdMap[$timer_id] = $timer_obj; 72 | return $timer_id; 73 | case EventInterface::EV_TIMER_ONCE: 74 | $timer_id = ++$this->_timerIdIndex; 75 | $timer_obj = $this->addTimer($fd, function() use ($func, $args, $timer_id) { 76 | unset($this->_timerIdMap[$timer_id]); 77 | call_user_func_array($func, $args); 78 | }); 79 | $this->_timerIdMap[$timer_id] = $timer_obj; 80 | return $timer_id; 81 | } 82 | return false; 83 | } 84 | 85 | /** 86 | * Remove event listener from event loop. 87 | * 88 | * @param mixed $fd 89 | * @param int $flag 90 | * @return bool 91 | */ 92 | public function del($fd, $flag) 93 | { 94 | switch ($flag) { 95 | case EventInterface::EV_READ: 96 | return $this->removeReadStream($fd); 97 | case EventInterface::EV_WRITE: 98 | return $this->removeWriteStream($fd); 99 | case EventInterface::EV_SIGNAL: 100 | return $this->removeSignal($fd); 101 | case EventInterface::EV_TIMER: 102 | case EventInterface::EV_TIMER_ONCE; 103 | if (isset($this->_timerIdMap[$fd])){ 104 | $timer_obj = $this->_timerIdMap[$fd]; 105 | unset($this->_timerIdMap[$fd]); 106 | $this->cancelTimer($timer_obj); 107 | return true; 108 | } 109 | } 110 | return false; 111 | } 112 | 113 | 114 | /** 115 | * Main loop. 116 | * 117 | * @return void 118 | */ 119 | public function loop() 120 | { 121 | $this->run(); 122 | } 123 | 124 | /** 125 | * Construct. 126 | */ 127 | public function __construct() 128 | { 129 | parent::__construct(); 130 | $class = new \ReflectionClass('\React\EventLoop\LibEventLoop'); 131 | $property = $class->getProperty('eventBase'); 132 | $property->setAccessible(true); 133 | $this->_eventBase = $property->getValue($this); 134 | } 135 | 136 | /** 137 | * Add signal handler. 138 | * 139 | * @param $signal 140 | * @param $callback 141 | * @return bool 142 | */ 143 | public function addSignal($signal, $callback) 144 | { 145 | $event = event_new(); 146 | $this->_signalEvents[$signal] = $event; 147 | event_set($event, $signal, EV_SIGNAL | EV_PERSIST, $callback); 148 | event_base_set($event, $this->_eventBase); 149 | event_add($event); 150 | } 151 | 152 | /** 153 | * Remove signal handler. 154 | * 155 | * @param $signal 156 | */ 157 | public function removeSignal($signal) 158 | { 159 | if (isset($this->_signalEvents[$signal])) { 160 | $event = $this->_signalEvents[$signal]; 161 | event_del($event); 162 | unset($this->_signalEvents[$signal]); 163 | } 164 | } 165 | 166 | /** 167 | * Destroy loop. 168 | * 169 | * @return void 170 | */ 171 | public function destroy() 172 | { 173 | foreach ($this->_signalEvents as $event) { 174 | event_del($event); 175 | } 176 | } 177 | 178 | /** 179 | * Get timer count. 180 | * 181 | * @return integer 182 | */ 183 | public function getTimerCount() 184 | { 185 | return count($this->_timerIdMap); 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /Workerman/Events/Ev.php: -------------------------------------------------------------------------------- 1 | 10 | * @link http://www.workerman.net/ 11 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 12 | */ 13 | namespace Workerman\Events; 14 | 15 | use Workerman\Worker; 16 | 17 | /** 18 | * ev eventloop 19 | */ 20 | class Ev implements EventInterface 21 | { 22 | /** 23 | * All listeners for read/write event. 24 | * 25 | * @var array 26 | */ 27 | protected $_allEvents = array(); 28 | 29 | /** 30 | * Event listeners of signal. 31 | * 32 | * @var array 33 | */ 34 | protected $_eventSignal = array(); 35 | 36 | /** 37 | * All timer event listeners. 38 | * [func, args, event, flag, time_interval] 39 | * 40 | * @var array 41 | */ 42 | protected $_eventTimer = array(); 43 | 44 | /** 45 | * Timer id. 46 | * 47 | * @var int 48 | */ 49 | protected static $_timerId = 1; 50 | 51 | /** 52 | * Add a timer. 53 | * {@inheritdoc} 54 | */ 55 | public function add($fd, $flag, $func, $args = null) 56 | { 57 | $callback = function ($event, $socket) use ($fd, $func) { 58 | try { 59 | call_user_func($func, $fd); 60 | } catch (\Exception $e) { 61 | Worker::log($e); 62 | exit(250); 63 | } catch (\Error $e) { 64 | Worker::log($e); 65 | exit(250); 66 | } 67 | }; 68 | switch ($flag) { 69 | case self::EV_SIGNAL: 70 | $event = new \EvSignal($fd, $callback); 71 | $this->_eventSignal[$fd] = $event; 72 | return true; 73 | case self::EV_TIMER: 74 | case self::EV_TIMER_ONCE: 75 | $repeat = $flag == self::EV_TIMER_ONCE ? 0 : $fd; 76 | $param = array($func, (array)$args, $flag, $fd, self::$_timerId); 77 | $event = new \EvTimer($fd, $repeat, array($this, 'timerCallback'), $param); 78 | $this->_eventTimer[self::$_timerId] = $event; 79 | return self::$_timerId++; 80 | default : 81 | $fd_key = (int)$fd; 82 | $real_flag = $flag === self::EV_READ ? \Ev::READ : \Ev::WRITE; 83 | $event = new \EvIo($fd, $real_flag, $callback); 84 | $this->_allEvents[$fd_key][$flag] = $event; 85 | return true; 86 | } 87 | 88 | } 89 | 90 | /** 91 | * Remove a timer. 92 | * {@inheritdoc} 93 | */ 94 | public function del($fd, $flag) 95 | { 96 | switch ($flag) { 97 | case self::EV_READ: 98 | case self::EV_WRITE: 99 | $fd_key = (int)$fd; 100 | if (isset($this->_allEvents[$fd_key][$flag])) { 101 | $this->_allEvents[$fd_key][$flag]->stop(); 102 | unset($this->_allEvents[$fd_key][$flag]); 103 | } 104 | if (empty($this->_allEvents[$fd_key])) { 105 | unset($this->_allEvents[$fd_key]); 106 | } 107 | break; 108 | case self::EV_SIGNAL: 109 | $fd_key = (int)$fd; 110 | if (isset($this->_eventSignal[$fd_key])) { 111 | $this->_eventSignal[$fd_key]->stop(); 112 | unset($this->_eventSignal[$fd_key]); 113 | } 114 | break; 115 | case self::EV_TIMER: 116 | case self::EV_TIMER_ONCE: 117 | if (isset($this->_eventTimer[$fd])) { 118 | $this->_eventTimer[$fd]->stop(); 119 | unset($this->_eventTimer[$fd]); 120 | } 121 | break; 122 | } 123 | return true; 124 | } 125 | 126 | /** 127 | * Timer callback. 128 | * 129 | * @param \EvWatcher $event 130 | */ 131 | public function timerCallback($event) 132 | { 133 | $param = $event->data; 134 | $timer_id = $param[4]; 135 | if ($param[2] === self::EV_TIMER_ONCE) { 136 | $this->_eventTimer[$timer_id]->stop(); 137 | unset($this->_eventTimer[$timer_id]); 138 | } 139 | try { 140 | call_user_func_array($param[0], $param[1]); 141 | } catch (\Exception $e) { 142 | Worker::log($e); 143 | exit(250); 144 | } catch (\Error $e) { 145 | Worker::log($e); 146 | exit(250); 147 | } 148 | } 149 | 150 | /** 151 | * Remove all timers. 152 | * 153 | * @return void 154 | */ 155 | public function clearAllTimer() 156 | { 157 | foreach ($this->_eventTimer as $event) { 158 | $event->stop(); 159 | } 160 | $this->_eventTimer = array(); 161 | } 162 | 163 | /** 164 | * Main loop. 165 | * 166 | * @see EventInterface::loop() 167 | */ 168 | public function loop() 169 | { 170 | \Ev::run(); 171 | } 172 | 173 | /** 174 | * Destroy loop. 175 | * 176 | * @return void 177 | */ 178 | public function destroy() 179 | { 180 | foreach ($this->_allEvents as $event) { 181 | $event->stop(); 182 | } 183 | } 184 | 185 | /** 186 | * Get timer count. 187 | * 188 | * @return integer 189 | */ 190 | public function getTimerCount() 191 | { 192 | return count($this->_eventTimer); 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /Workerman/Events/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 | -------------------------------------------------------------------------------- /Cor/CorWorker.php: -------------------------------------------------------------------------------- 1 | 7"); 37 | exit (); 38 | } 39 | 40 | if (!extension_loaded('pthreads')) { 41 | exit ("Please install pthreads extension. See http://doc3.workerman.net/install/install.html\n"); 42 | } 43 | // 运行父方法 44 | parent::runAll(); 45 | } 46 | 47 | public function run() 48 | { 49 | //设置工作类路径 50 | if (!$this->eventHandler) { 51 | $this->eventHandler = "Events"; 52 | } 53 | //管道位置 54 | $this->cpu_unix_name = 'unix:///tmp/cor_' . posix_getpid() . ".sock"; 55 | //创建任务线程 56 | $this->taskThread = new CorThread($this->cpu_unix_name, $this->eventHandler); 57 | $this->taskThread->start(); 58 | 59 | //重新设置方法 60 | $this->_onWorkerStart = $this->onWorkerStart; 61 | $this->onWorkerStart = array($this, "onWorkerStart"); 62 | $this->_onWorkerStop = $this->onWorkerStop; 63 | $this->onWorkerStop = array($this, "onWorkerStop"); 64 | 65 | // 运行父方法 66 | parent::run(); 67 | } 68 | 69 | /** 70 | * 进程start 71 | */ 72 | public function onWorkerStart($worker) 73 | { 74 | Timer::add(0.3, function () use (&$worker) { 75 | $worker->taskAsyncConn = new AsyncTcpConnection($worker->taskThread->cpu_unix_name); 76 | $worker->taskAsyncConn->onMessage = function ($conn, $data) use ($worker) { 77 | //游标 78 | $i = 0; 79 | while (true) { 80 | $len = substr($data, $i, $i + 2); 81 | $body_len = unpack('Sbin_len', $len)['bin_len']; 82 | $body = substr($data, $i + 2, $body_len); 83 | $body = json_decode($body, true); 84 | if($body[0]!=1){ 85 | if (array_key_exists($body[0], $worker->_job_funs)) { 86 | call_user_func($worker->_job_funs[$body[0]][1], $body[1]); 87 | } 88 | } 89 | $i = $i + 2 + $body_len; 90 | if ($i >= strlen($data)) { 91 | break; 92 | } 93 | } 94 | }; 95 | $worker->taskAsyncConn->onConnect = function ($con) { 96 | var_dump("job thread is ready!"); 97 | }; 98 | $worker->taskAsyncConn->onClose = function ($con)use($worker) { 99 | $this->taskThread->is_exit = true; 100 | posix_kill(posix_getppid(), SIGUSR1); 101 | }; 102 | $worker->taskAsyncConn->onError = function ($con, $code, $msg) { 103 | $this->taskThread->is_exit = true; 104 | posix_kill(posix_getppid(), SIGUSR1); 105 | }; 106 | $worker->taskAsyncConn->connect(); 107 | 108 | if ($worker->_onWorkerStart) { 109 | call_user_func($worker->_onWorkerStart, $worker); 110 | } 111 | }, array(), false); 112 | 113 | /** 114 | * 10秒钟检查一次是否有任务超时 115 | */ 116 | Timer::add(10, function () use (&$worker) { 117 | $time = time(); 118 | foreach ($worker->_job_funs as $k => $n) { 119 | if ($time >= $n[0]) { 120 | unset($worker->_job_funs[$k]); 121 | } 122 | } 123 | }); 124 | } 125 | 126 | public function onWorkerStop($worker) 127 | { 128 | $this->taskThread->is_exit = true; 129 | /** 130 | * 退出的时候删除unix文件 131 | */ 132 | if (strpos($worker->cpu_unix_name, "unix://") === 0) { 133 | $unix_file = str_replace("unix://", "", $worker->cpu_unix_name); 134 | if (file_exists($unix_file)) { 135 | unlink($unix_file); 136 | } 137 | } 138 | if ($worker->_onWorkerStop) { 139 | call_user_func($worker->_onWorkerStop, $worker); 140 | } 141 | } 142 | 143 | /** 144 | * @param $job 任务名称 145 | * @param $data 传递到任务中的数据 146 | * @param null $function 回调方法 147 | * @param int $timeout 超时时间,超过这个时间,主线程不会收到返回通知 148 | */ 149 | public function ajax($job, $data, $function = null, $timeout = 10000) 150 | { 151 | /** @var 当key等于0的时候,代表不需要异步返回 $key */ 152 | if ($function == null) { 153 | $key = 0; 154 | } else { 155 | if (PHP_INT_MAX === self::$_job_key_recorder) { 156 | self::$_job_key_recorder = 100; 157 | } 158 | self::$_job_key_recorder = self::$_job_key_recorder + 1; 159 | $key = self::$_job_key_recorder; 160 | //保存当前回调函数 161 | $this->_job_funs[$key] = array(time() + $timeout, $function); 162 | } 163 | 164 | /** 创建访问线程的消息体 */ 165 | $arr = array(); 166 | $arr[0] = $job;//任务名 167 | $arr[1] = $key;//任务唯一key 168 | $arr[2] = $data;//传递消息体 169 | $buffer = json_encode($arr); 170 | $body_len = strlen($buffer); 171 | $bin_head = pack('S*', $body_len); 172 | $this->taskAsyncConn->send($bin_head . $buffer); 173 | } 174 | 175 | /** 176 | * 重启整个进程 177 | */ 178 | public function reload_cor(){ 179 | $this->taskThread->is_exit = true; 180 | posix_kill(posix_getppid(), SIGUSR1); 181 | } 182 | } -------------------------------------------------------------------------------- /Workerman/Events/Event.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright 有个鬼<42765633@qq.com> 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | namespace Workerman\Events; 15 | 16 | use Workerman\Worker; 17 | 18 | /** 19 | * libevent eventloop 20 | */ 21 | class Event implements EventInterface 22 | { 23 | /** 24 | * Event base. 25 | * @var object 26 | */ 27 | protected $_eventBase = null; 28 | 29 | /** 30 | * All listeners for read/write event. 31 | * @var array 32 | */ 33 | protected $_allEvents = array(); 34 | 35 | /** 36 | * Event listeners of signal. 37 | * @var array 38 | */ 39 | protected $_eventSignal = array(); 40 | 41 | /** 42 | * All timer event listeners. 43 | * [func, args, event, flag, time_interval] 44 | * @var array 45 | */ 46 | protected $_eventTimer = array(); 47 | 48 | /** 49 | * Timer id. 50 | * @var int 51 | */ 52 | protected static $_timerId = 1; 53 | 54 | /** 55 | * construct 56 | * @return void 57 | */ 58 | public function __construct() 59 | { 60 | $this->_eventBase = new \EventBase(); 61 | } 62 | 63 | /** 64 | * @see EventInterface::add() 65 | */ 66 | public function add($fd, $flag, $func, $args=array()) 67 | { 68 | switch ($flag) { 69 | case self::EV_SIGNAL: 70 | 71 | $fd_key = (int)$fd; 72 | $event = \Event::signal($this->_eventBase, $fd, $func); 73 | if (!$event||!$event->add()) { 74 | return false; 75 | } 76 | $this->_eventSignal[$fd_key] = $event; 77 | return true; 78 | 79 | case self::EV_TIMER: 80 | case self::EV_TIMER_ONCE: 81 | 82 | $param = array($func, (array)$args, $flag, $fd, self::$_timerId); 83 | $event = new \Event($this->_eventBase, -1, \Event::TIMEOUT|\Event::PERSIST, array($this, "timerCallback"), $param); 84 | if (!$event||!$event->addTimer($fd)) { 85 | return false; 86 | } 87 | $this->_eventTimer[self::$_timerId] = $event; 88 | return self::$_timerId++; 89 | 90 | default : 91 | $fd_key = (int)$fd; 92 | $real_flag = $flag === self::EV_READ ? \Event::READ | \Event::PERSIST : \Event::WRITE | \Event::PERSIST; 93 | $event = new \Event($this->_eventBase, $fd, $real_flag, $func, $fd); 94 | if (!$event||!$event->add()) { 95 | return false; 96 | } 97 | $this->_allEvents[$fd_key][$flag] = $event; 98 | return true; 99 | } 100 | } 101 | 102 | /** 103 | * @see Events\EventInterface::del() 104 | */ 105 | public function del($fd, $flag) 106 | { 107 | switch ($flag) { 108 | 109 | case self::EV_READ: 110 | case self::EV_WRITE: 111 | 112 | $fd_key = (int)$fd; 113 | if (isset($this->_allEvents[$fd_key][$flag])) { 114 | $this->_allEvents[$fd_key][$flag]->del(); 115 | unset($this->_allEvents[$fd_key][$flag]); 116 | } 117 | if (empty($this->_allEvents[$fd_key])) { 118 | unset($this->_allEvents[$fd_key]); 119 | } 120 | break; 121 | 122 | case self::EV_SIGNAL: 123 | $fd_key = (int)$fd; 124 | if (isset($this->_eventSignal[$fd_key])) { 125 | $this->_eventSignal[$fd_key]->del(); 126 | unset($this->_eventSignal[$fd_key]); 127 | } 128 | break; 129 | 130 | case self::EV_TIMER: 131 | case self::EV_TIMER_ONCE: 132 | if (isset($this->_eventTimer[$fd])) { 133 | $this->_eventTimer[$fd]->del(); 134 | unset($this->_eventTimer[$fd]); 135 | } 136 | break; 137 | } 138 | return true; 139 | } 140 | 141 | /** 142 | * Timer callback. 143 | * @param null $fd 144 | * @param int $what 145 | * @param int $timer_id 146 | */ 147 | public function timerCallback($fd, $what, $param) 148 | { 149 | $timer_id = $param[4]; 150 | 151 | if ($param[2] === self::EV_TIMER_ONCE) { 152 | $this->_eventTimer[$timer_id]->del(); 153 | unset($this->_eventTimer[$timer_id]); 154 | } 155 | 156 | try { 157 | call_user_func_array($param[0], $param[1]); 158 | } catch (\Exception $e) { 159 | Worker::log($e); 160 | exit(250); 161 | } catch (\Error $e) { 162 | Worker::log($e); 163 | exit(250); 164 | } 165 | } 166 | 167 | /** 168 | * @see Events\EventInterface::clearAllTimer() 169 | * @return void 170 | */ 171 | public function clearAllTimer() 172 | { 173 | foreach ($this->_eventTimer as $event) { 174 | $event->del(); 175 | } 176 | $this->_eventTimer = array(); 177 | } 178 | 179 | 180 | /** 181 | * @see EventInterface::loop() 182 | */ 183 | public function loop() 184 | { 185 | $this->_eventBase->loop(); 186 | } 187 | 188 | /** 189 | * Destroy loop. 190 | * 191 | * @return void 192 | */ 193 | public function destroy() 194 | { 195 | foreach ($this->_eventSignal as $event) { 196 | $event->del(); 197 | } 198 | } 199 | 200 | /** 201 | * Get timer count. 202 | * 203 | * @return integer 204 | */ 205 | public function getTimerCount() 206 | { 207 | return count($this->_eventTimer); 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /Cor/Net/Events/Select.php: -------------------------------------------------------------------------------- 1 | cpu = $cup; 57 | } 58 | 59 | 60 | /** 61 | * 添加一个socket监听任务 62 | * @param $fd 63 | * @param $flag 什么类型的监听,是读监听还是写监听 64 | * @param $func 65 | * @param null $args 66 | * @param $key //写操作可能不一样,每次可能有好几个写操作挤压,因为是异步的,所以为没个操作设定唯一key,写操作可以不设定 67 | */ 68 | public function add($fd, $flag, $func, $args = array()) 69 | { 70 | array_unshift($args, $fd); 71 | switch ($flag) { 72 | case self::EV_READ: 73 | $fd_key = (int)$fd; 74 | $this->_allEvents[$fd_key][$flag] = array($func, $args); 75 | $this->_readFds[$fd_key] = $fd; 76 | break; 77 | case self::EV_WRITE: 78 | $fd_key = (int)$fd; 79 | if (empty(self::$write_key[$fd_key])){ 80 | self::$write_key[$fd_key][0] = 1;//写操作唯一表示 81 | self::$write_key[$fd_key][1] = 0;//代表读取到了哪里 82 | }else{ 83 | self::$write_key[$fd_key][0] = self::$write_key[$fd_key][0] + 1; 84 | } 85 | //写操作可能不一样,每次可能有好几个写操作挤压,因为是异步的 86 | array_unshift($args, self::$write_key[$fd_key][0]); 87 | $this->_allEvents[$fd_key][$flag][self::$write_key[$fd_key][0]] = array($func, $args); 88 | $this->_writeFds[$fd_key] = $fd; 89 | break; 90 | } 91 | return true; 92 | } 93 | 94 | /** 95 | * 从监听列表中移除一个监听任务 96 | * @param mixed $fd 97 | * @param int $flag 98 | * @return bool 99 | * $key 写入操作需要的唯一标识,指定哪个写入操作 100 | */ 101 | public function del($fd, $flag , $key=null) 102 | { 103 | $fd_key = (int)$fd; 104 | switch ($flag) { 105 | case self::EV_READ: 106 | unset($this->_allEvents[$fd_key][$flag], $this->_readFds[$fd_key]); 107 | if (empty($this->_allEvents[$fd_key])) { 108 | unset($this->_allEvents[$fd_key]); 109 | } 110 | return true; 111 | case self::EV_WRITE: 112 | unset($this->_allEvents[$fd_key][$flag][$key]); 113 | if(count($this->_allEvents[$fd_key][$flag])==0){ 114 | unset($this->_allEvents[$fd_key][$flag], $this->_writeFds[$fd_key]); 115 | } 116 | if (empty($this->_allEvents[$fd_key])) { 117 | unset($this->_allEvents[$fd_key]); 118 | } 119 | return true; 120 | } 121 | return false; 122 | } 123 | 124 | /** 125 | * Main loop. 126 | * 127 | * @return void 128 | */ 129 | public function loop() 130 | { 131 | $e = null; 132 | while ($this->is_loop) { 133 | //如果没有任务,自身也会一秒循环一次 134 | $timeout = 0; 135 | if ($this->cpu->taskQueueIsEmpty()) { 136 | $this->_check_workerman_live(); 137 | $timeout = 2; 138 | } 139 | $read = $this->_readFds; 140 | $write = $this->_writeFds; 141 | $except = null; 142 | // Waiting read/write/signal/timeout events. 143 | if (!@stream_select($read, $write, $except, $timeout)) { 144 | yield; 145 | continue; 146 | } 147 | 148 | if ($read) { 149 | foreach ($read as $fd) { 150 | $fd_key = (int)$fd; 151 | if (isset($this->_allEvents[$fd_key][self::EV_READ])) { 152 | $_e = reset($this->_allEvents[$fd_key][self::EV_READ]); 153 | call_user_func_array($this->_allEvents[$fd_key][self::EV_READ][0], 154 | $this->_allEvents[$fd_key][self::EV_READ][1]); 155 | } 156 | } 157 | } 158 | 159 | if ($write) { 160 | foreach ($write as $fd) { 161 | $fd_key = (int)$fd; 162 | //获取下一个写入key 163 | self::$write_key[$fd_key][1] = self::$write_key[$fd_key][1] + 1; 164 | if (isset($this->_allEvents[$fd_key][self::EV_WRITE][self::$write_key[$fd_key][1]])) { 165 | 166 | call_user_func_array($this->_allEvents[$fd_key][self::EV_WRITE][self::$write_key[$fd_key][1]][0], 167 | $this->_allEvents[$fd_key][self::EV_WRITE][self::$write_key[$fd_key][1]][1]); 168 | } 169 | } 170 | } 171 | 172 | /** 每次循环一次都将程序控制权交给调度器 */ 173 | yield; 174 | } 175 | } 176 | 177 | /** 178 | * Destroy loop. 179 | * 180 | * @return mixed 181 | */ 182 | public function destroy() 183 | { 184 | $this->is_loop = false; 185 | } 186 | 187 | public function _check_workerman_live(){ 188 | $data = json_encode(array(1,"")); 189 | $body_len = strlen($data); 190 | $bin_head = pack('S*', $body_len); 191 | $len = @fwrite($this->cpu->_mainSocket, $bin_head . $data); 192 | if ($len !== strlen($bin_head . $data)) { 193 | $this->cpu->thread->is_exit = false; 194 | } 195 | 196 | } 197 | 198 | } -------------------------------------------------------------------------------- /Workerman/Events/Libevent.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | namespace Workerman\Events; 15 | 16 | use Workerman\Worker; 17 | 18 | /** 19 | * libevent eventloop 20 | */ 21 | class Libevent implements EventInterface 22 | { 23 | /** 24 | * Event base. 25 | * 26 | * @var resource 27 | */ 28 | protected $_eventBase = null; 29 | 30 | /** 31 | * All listeners for read/write event. 32 | * 33 | * @var array 34 | */ 35 | protected $_allEvents = array(); 36 | 37 | /** 38 | * Event listeners of signal. 39 | * 40 | * @var array 41 | */ 42 | protected $_eventSignal = array(); 43 | 44 | /** 45 | * All timer event listeners. 46 | * [func, args, event, flag, time_interval] 47 | * 48 | * @var array 49 | */ 50 | protected $_eventTimer = array(); 51 | 52 | /** 53 | * construct 54 | */ 55 | public function __construct() 56 | { 57 | $this->_eventBase = event_base_new(); 58 | } 59 | 60 | /** 61 | * {@inheritdoc} 62 | */ 63 | public function add($fd, $flag, $func, $args = array()) 64 | { 65 | switch ($flag) { 66 | case self::EV_SIGNAL: 67 | $fd_key = (int)$fd; 68 | $real_flag = EV_SIGNAL | EV_PERSIST; 69 | $this->_eventSignal[$fd_key] = event_new(); 70 | if (!event_set($this->_eventSignal[$fd_key], $fd, $real_flag, $func, null)) { 71 | return false; 72 | } 73 | if (!event_base_set($this->_eventSignal[$fd_key], $this->_eventBase)) { 74 | return false; 75 | } 76 | if (!event_add($this->_eventSignal[$fd_key])) { 77 | return false; 78 | } 79 | return true; 80 | case self::EV_TIMER: 81 | case self::EV_TIMER_ONCE: 82 | $event = event_new(); 83 | $timer_id = (int)$event; 84 | if (!event_set($event, 0, EV_TIMEOUT, array($this, 'timerCallback'), $timer_id)) { 85 | return false; 86 | } 87 | 88 | if (!event_base_set($event, $this->_eventBase)) { 89 | return false; 90 | } 91 | 92 | $time_interval = $fd * 1000000; 93 | if (!event_add($event, $time_interval)) { 94 | return false; 95 | } 96 | $this->_eventTimer[$timer_id] = array($func, (array)$args, $event, $flag, $time_interval); 97 | return $timer_id; 98 | 99 | default : 100 | $fd_key = (int)$fd; 101 | $real_flag = $flag === self::EV_READ ? EV_READ | EV_PERSIST : EV_WRITE | EV_PERSIST; 102 | 103 | $event = event_new(); 104 | 105 | if (!event_set($event, $fd, $real_flag, $func, null)) { 106 | return false; 107 | } 108 | 109 | if (!event_base_set($event, $this->_eventBase)) { 110 | return false; 111 | } 112 | 113 | if (!event_add($event)) { 114 | return false; 115 | } 116 | 117 | $this->_allEvents[$fd_key][$flag] = $event; 118 | 119 | return true; 120 | } 121 | 122 | } 123 | 124 | /** 125 | * {@inheritdoc} 126 | */ 127 | public function del($fd, $flag) 128 | { 129 | switch ($flag) { 130 | case self::EV_READ: 131 | case self::EV_WRITE: 132 | $fd_key = (int)$fd; 133 | if (isset($this->_allEvents[$fd_key][$flag])) { 134 | event_del($this->_allEvents[$fd_key][$flag]); 135 | unset($this->_allEvents[$fd_key][$flag]); 136 | } 137 | if (empty($this->_allEvents[$fd_key])) { 138 | unset($this->_allEvents[$fd_key]); 139 | } 140 | break; 141 | case self::EV_SIGNAL: 142 | $fd_key = (int)$fd; 143 | if (isset($this->_eventSignal[$fd_key])) { 144 | event_del($this->_eventSignal[$fd_key]); 145 | unset($this->_eventSignal[$fd_key]); 146 | } 147 | break; 148 | case self::EV_TIMER: 149 | case self::EV_TIMER_ONCE: 150 | // 这里 fd 为timerid 151 | if (isset($this->_eventTimer[$fd])) { 152 | event_del($this->_eventTimer[$fd][2]); 153 | unset($this->_eventTimer[$fd]); 154 | } 155 | break; 156 | } 157 | return true; 158 | } 159 | 160 | /** 161 | * Timer callback. 162 | * 163 | * @param mixed $_null1 164 | * @param int $_null2 165 | * @param mixed $timer_id 166 | */ 167 | protected function timerCallback($_null1, $_null2, $timer_id) 168 | { 169 | if ($this->_eventTimer[$timer_id][3] === self::EV_TIMER) { 170 | event_add($this->_eventTimer[$timer_id][2], $this->_eventTimer[$timer_id][4]); 171 | } 172 | try { 173 | call_user_func_array($this->_eventTimer[$timer_id][0], $this->_eventTimer[$timer_id][1]); 174 | } catch (\Exception $e) { 175 | Worker::log($e); 176 | exit(250); 177 | } catch (\Error $e) { 178 | Worker::log($e); 179 | exit(250); 180 | } 181 | if (isset($this->_eventTimer[$timer_id]) && $this->_eventTimer[$timer_id][3] === self::EV_TIMER_ONCE) { 182 | $this->del($timer_id, self::EV_TIMER_ONCE); 183 | } 184 | } 185 | 186 | /** 187 | * {@inheritdoc} 188 | */ 189 | public function clearAllTimer() 190 | { 191 | foreach ($this->_eventTimer as $task_data) { 192 | event_del($task_data[2]); 193 | } 194 | $this->_eventTimer = array(); 195 | } 196 | 197 | /** 198 | * {@inheritdoc} 199 | */ 200 | public function loop() 201 | { 202 | event_base_loop($this->_eventBase); 203 | } 204 | 205 | /** 206 | * Destroy loop. 207 | * 208 | * @return void 209 | */ 210 | public function destroy() 211 | { 212 | foreach ($this->_eventSignal as $event) { 213 | event_del($event); 214 | } 215 | } 216 | 217 | /** 218 | * Get timer count. 219 | * 220 | * @return integer 221 | */ 222 | public function getTimerCount() 223 | { 224 | return count($this->_eventTimer); 225 | } 226 | } 227 | 228 | -------------------------------------------------------------------------------- /Workerman/Events/Select.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | namespace Workerman\Events; 15 | 16 | /** 17 | * select eventloop 18 | */ 19 | class Select implements EventInterface 20 | { 21 | /** 22 | * All listeners for read/write event. 23 | * 24 | * @var array 25 | */ 26 | public $_allEvents = array(); 27 | 28 | /** 29 | * Event listeners of signal. 30 | * 31 | * @var array 32 | */ 33 | public $_signalEvents = array(); 34 | 35 | /** 36 | * Fds waiting for read event. 37 | * 38 | * @var array 39 | */ 40 | protected $_readFds = array(); 41 | 42 | /** 43 | * Fds waiting for write event. 44 | * 45 | * @var array 46 | */ 47 | protected $_writeFds = array(); 48 | 49 | /** 50 | * Fds waiting for except event. 51 | * 52 | * @var array 53 | */ 54 | protected $_exceptFds = array(); 55 | 56 | /** 57 | * Timer scheduler. 58 | * {['data':timer_id, 'priority':run_timestamp], ..} 59 | * 60 | * @var \SplPriorityQueue 61 | */ 62 | protected $_scheduler = null; 63 | 64 | /** 65 | * All timer event listeners. 66 | * [[func, args, flag, timer_interval], ..] 67 | * 68 | * @var array 69 | */ 70 | protected $_eventTimer = array(); 71 | 72 | /** 73 | * Timer id. 74 | * 75 | * @var int 76 | */ 77 | protected $_timerId = 1; 78 | 79 | /** 80 | * Select timeout. 81 | * 82 | * @var int 83 | */ 84 | protected $_selectTimeout = 100000000; 85 | 86 | /** 87 | * Paired socket channels 88 | * 89 | * @var array 90 | */ 91 | protected $channel = array(); 92 | 93 | /** 94 | * Construct. 95 | */ 96 | public function __construct() 97 | { 98 | // Create a pipeline and put into the collection of the read to read the descriptor to avoid empty polling. 99 | $this->channel = stream_socket_pair(DIRECTORY_SEPARATOR === '/' ? STREAM_PF_UNIX : STREAM_PF_INET, 100 | STREAM_SOCK_STREAM, STREAM_IPPROTO_IP); 101 | if($this->channel) { 102 | stream_set_blocking($this->channel[0], 0); 103 | $this->_readFds[0] = $this->channel[0]; 104 | } 105 | // Init SplPriorityQueue. 106 | $this->_scheduler = new \SplPriorityQueue(); 107 | $this->_scheduler->setExtractFlags(\SplPriorityQueue::EXTR_BOTH); 108 | } 109 | 110 | /** 111 | * {@inheritdoc} 112 | */ 113 | public function add($fd, $flag, $func, $args = array()) 114 | { 115 | switch ($flag) { 116 | case self::EV_READ: 117 | $fd_key = (int)$fd; 118 | $this->_allEvents[$fd_key][$flag] = array($func, $fd); 119 | $this->_readFds[$fd_key] = $fd; 120 | break; 121 | case self::EV_WRITE: 122 | $fd_key = (int)$fd; 123 | $this->_allEvents[$fd_key][$flag] = array($func, $fd); 124 | $this->_writeFds[$fd_key] = $fd; 125 | break; 126 | case self::EV_EXCEPT: 127 | $fd_key = (int)$fd; 128 | $this->_allEvents[$fd_key][$flag] = array($func, $fd); 129 | $this->_exceptFds[$fd_key] = $fd; 130 | break; 131 | case self::EV_SIGNAL: 132 | // Windows not support signal. 133 | if(DIRECTORY_SEPARATOR !== '/') { 134 | return false; 135 | } 136 | $fd_key = (int)$fd; 137 | $this->_signalEvents[$fd_key][$flag] = array($func, $fd); 138 | pcntl_signal($fd, array($this, 'signalHandler')); 139 | break; 140 | case self::EV_TIMER: 141 | case self::EV_TIMER_ONCE: 142 | $timer_id = $this->_timerId++; 143 | $run_time = microtime(true) + $fd; 144 | $this->_scheduler->insert($timer_id, -$run_time); 145 | $this->_eventTimer[$timer_id] = array($func, (array)$args, $flag, $fd); 146 | $select_timeout = ($run_time - microtime(true)) * 1000000; 147 | if( $this->_selectTimeout > $select_timeout ){ 148 | $this->_selectTimeout = $select_timeout; 149 | } 150 | return $timer_id; 151 | } 152 | 153 | return true; 154 | } 155 | 156 | /** 157 | * Signal handler. 158 | * 159 | * @param int $signal 160 | */ 161 | public function signalHandler($signal) 162 | { 163 | call_user_func_array($this->_signalEvents[$signal][self::EV_SIGNAL][0], array($signal)); 164 | } 165 | 166 | /** 167 | * {@inheritdoc} 168 | */ 169 | public function del($fd, $flag) 170 | { 171 | $fd_key = (int)$fd; 172 | switch ($flag) { 173 | case self::EV_READ: 174 | unset($this->_allEvents[$fd_key][$flag], $this->_readFds[$fd_key]); 175 | if (empty($this->_allEvents[$fd_key])) { 176 | unset($this->_allEvents[$fd_key]); 177 | } 178 | return true; 179 | case self::EV_WRITE: 180 | unset($this->_allEvents[$fd_key][$flag], $this->_writeFds[$fd_key]); 181 | if (empty($this->_allEvents[$fd_key])) { 182 | unset($this->_allEvents[$fd_key]); 183 | } 184 | return true; 185 | case self::EV_EXCEPT: 186 | unset($this->_allEvents[$fd_key][$flag], $this->_exceptFds[$fd_key]); 187 | if(empty($this->_allEvents[$fd_key])) 188 | { 189 | unset($this->_allEvents[$fd_key]); 190 | } 191 | return true; 192 | case self::EV_SIGNAL: 193 | if(DIRECTORY_SEPARATOR !== '/') { 194 | return false; 195 | } 196 | unset($this->_signalEvents[$fd_key]); 197 | pcntl_signal($fd, SIG_IGN); 198 | break; 199 | case self::EV_TIMER: 200 | case self::EV_TIMER_ONCE; 201 | unset($this->_eventTimer[$fd_key]); 202 | return true; 203 | } 204 | return false; 205 | } 206 | 207 | /** 208 | * Tick for timer. 209 | * 210 | * @return void 211 | */ 212 | protected function tick() 213 | { 214 | while (!$this->_scheduler->isEmpty()) { 215 | $scheduler_data = $this->_scheduler->top(); 216 | $timer_id = $scheduler_data['data']; 217 | $next_run_time = -$scheduler_data['priority']; 218 | $time_now = microtime(true); 219 | $this->_selectTimeout = ($next_run_time - $time_now) * 1000000; 220 | if ($this->_selectTimeout <= 0) { 221 | $this->_scheduler->extract(); 222 | 223 | if (!isset($this->_eventTimer[$timer_id])) { 224 | continue; 225 | } 226 | 227 | // [func, args, flag, timer_interval] 228 | $task_data = $this->_eventTimer[$timer_id]; 229 | if ($task_data[2] === self::EV_TIMER) { 230 | $next_run_time = $time_now + $task_data[3]; 231 | $this->_scheduler->insert($timer_id, -$next_run_time); 232 | } 233 | call_user_func_array($task_data[0], $task_data[1]); 234 | if (isset($this->_eventTimer[$timer_id]) && $task_data[2] === self::EV_TIMER_ONCE) { 235 | $this->del($timer_id, self::EV_TIMER_ONCE); 236 | } 237 | continue; 238 | } 239 | return; 240 | } 241 | $this->_selectTimeout = 100000000; 242 | } 243 | 244 | /** 245 | * {@inheritdoc} 246 | */ 247 | public function clearAllTimer() 248 | { 249 | $this->_scheduler = new \SplPriorityQueue(); 250 | $this->_scheduler->setExtractFlags(\SplPriorityQueue::EXTR_BOTH); 251 | $this->_eventTimer = array(); 252 | } 253 | 254 | /** 255 | * {@inheritdoc} 256 | */ 257 | public function loop() 258 | { 259 | $e = null; 260 | while (1) { 261 | if(DIRECTORY_SEPARATOR === '/') { 262 | // Calls signal handlers for pending signals 263 | pcntl_signal_dispatch(); 264 | } 265 | 266 | $read = $this->_readFds; 267 | $write = $this->_writeFds; 268 | $except = $this->_writeFds; 269 | 270 | // Waiting read/write/signal/timeout events. 271 | $ret = @stream_select($read, $write, $except, 0, $this->_selectTimeout); 272 | if (!$this->_scheduler->isEmpty()) { 273 | $this->tick(); 274 | } 275 | 276 | if (!$ret) { 277 | continue; 278 | } 279 | 280 | if ($read) { 281 | foreach ($read as $fd) { 282 | $fd_key = (int)$fd; 283 | if (isset($this->_allEvents[$fd_key][self::EV_READ])) { 284 | call_user_func_array($this->_allEvents[$fd_key][self::EV_READ][0], 285 | array($this->_allEvents[$fd_key][self::EV_READ][1])); 286 | } 287 | } 288 | } 289 | 290 | if ($write) { 291 | foreach ($write as $fd) { 292 | $fd_key = (int)$fd; 293 | if (isset($this->_allEvents[$fd_key][self::EV_WRITE])) { 294 | call_user_func_array($this->_allEvents[$fd_key][self::EV_WRITE][0], 295 | array($this->_allEvents[$fd_key][self::EV_WRITE][1])); 296 | } 297 | } 298 | } 299 | 300 | if($except) { 301 | foreach($except as $fd) { 302 | $fd_key = (int) $fd; 303 | if(isset($this->_allEvents[$fd_key][self::EV_EXCEPT])) { 304 | call_user_func_array($this->_allEvents[$fd_key][self::EV_EXCEPT][0], 305 | array($this->_allEvents[$fd_key][self::EV_EXCEPT][1])); 306 | } 307 | } 308 | } 309 | } 310 | } 311 | 312 | /** 313 | * Destroy loop. 314 | * 315 | * @return void 316 | */ 317 | public function destroy() 318 | { 319 | 320 | } 321 | 322 | /** 323 | * Get timer count. 324 | * 325 | * @return integer 326 | */ 327 | public function getTimerCount() 328 | { 329 | return count($this->_eventTimer); 330 | } 331 | } 332 | -------------------------------------------------------------------------------- /Workerman/WebServer.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | namespace Workerman; 15 | 16 | use Workerman\Protocols\Http; 17 | use Workerman\Protocols\HttpCache; 18 | 19 | /** 20 | * WebServer. 21 | */ 22 | class WebServer extends Worker 23 | { 24 | /** 25 | * Virtual host to path mapping. 26 | * 27 | * @var array ['Workerman.net'=>'/home', 'www.Workerman.net'=>'home/www'] 28 | */ 29 | protected $serverRoot = array(); 30 | 31 | /** 32 | * Mime mapping. 33 | * 34 | * @var array 35 | */ 36 | protected static $mimeTypeMap = array(); 37 | 38 | 39 | /** 40 | * Used to save user OnWorkerStart callback settings. 41 | * 42 | * @var callback 43 | */ 44 | protected $_onWorkerStart = null; 45 | 46 | /** 47 | * Add virtual host. 48 | * 49 | * @param string $domain 50 | * @param string $root_path 51 | * @return void 52 | */ 53 | public function addRoot($domain, $root_path) 54 | { 55 | $this->serverRoot[$domain] = $root_path; 56 | } 57 | 58 | /** 59 | * Construct. 60 | * 61 | * @param string $socket_name 62 | * @param array $context_option 63 | */ 64 | public function __construct($socket_name, $context_option = array()) 65 | { 66 | list(, $address) = explode(':', $socket_name, 2); 67 | parent::__construct('http:' . $address, $context_option); 68 | $this->name = 'WebServer'; 69 | } 70 | 71 | /** 72 | * Run webserver instance. 73 | * 74 | * @see Workerman.Worker::run() 75 | */ 76 | public function run() 77 | { 78 | $this->_onWorkerStart = $this->onWorkerStart; 79 | $this->onWorkerStart = array($this, 'onWorkerStart'); 80 | $this->onMessage = array($this, 'onMessage'); 81 | parent::run(); 82 | } 83 | 84 | /** 85 | * Emit when process start. 86 | * 87 | * @throws \Exception 88 | */ 89 | public function onWorkerStart() 90 | { 91 | if (empty($this->serverRoot)) { 92 | echo new \Exception('server root not set, please use WebServer::addRoot($domain, $root_path) to set server root path'); 93 | exit(250); 94 | } 95 | 96 | // Init mimeMap. 97 | $this->initMimeTypeMap(); 98 | 99 | // Try to emit onWorkerStart callback. 100 | if ($this->_onWorkerStart) { 101 | try { 102 | call_user_func($this->_onWorkerStart, $this); 103 | } catch (\Exception $e) { 104 | self::log($e); 105 | exit(250); 106 | } catch (\Error $e) { 107 | self::log($e); 108 | exit(250); 109 | } 110 | } 111 | } 112 | 113 | /** 114 | * Init mime map. 115 | * 116 | * @return void 117 | */ 118 | public function initMimeTypeMap() 119 | { 120 | $mime_file = Http::getMimeTypesFile(); 121 | if (!is_file($mime_file)) { 122 | $this->log("$mime_file mime.type file not fond"); 123 | return; 124 | } 125 | $items = file($mime_file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); 126 | if (!is_array($items)) { 127 | $this->log("get $mime_file mime.type content fail"); 128 | return; 129 | } 130 | foreach ($items as $content) { 131 | if (preg_match("/\s*(\S+)\s+(\S.+)/", $content, $match)) { 132 | $mime_type = $match[1]; 133 | $workerman_file_extension_var = $match[2]; 134 | $workerman_file_extension_array = explode(' ', substr($workerman_file_extension_var, 0, -1)); 135 | foreach ($workerman_file_extension_array as $workerman_file_extension) { 136 | self::$mimeTypeMap[$workerman_file_extension] = $mime_type; 137 | } 138 | } 139 | } 140 | } 141 | 142 | /** 143 | * Emit when http message coming. 144 | * 145 | * @param Connection\TcpConnection $connection 146 | * @return void 147 | */ 148 | public function onMessage($connection) 149 | { 150 | // REQUEST_URI. 151 | $workerman_url_info = parse_url($_SERVER['REQUEST_URI']); 152 | if (!$workerman_url_info) { 153 | Http::header('HTTP/1.1 400 Bad Request'); 154 | $connection->close('

400 Bad Request

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

400 Bad Request

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

404 Not Found

'); 227 | return; 228 | } 229 | } 230 | 231 | public static function sendFile($connection, $file_path) 232 | { 233 | // Check 304. 234 | $info = stat($file_path); 235 | $modified_time = $info ? date('D, d M Y H:i:s', $info['mtime']) . ' ' . date_default_timezone_get() : ''; 236 | if (!empty($_SERVER['HTTP_IF_MODIFIED_SINCE']) && $info) { 237 | // Http 304. 238 | if ($modified_time === $_SERVER['HTTP_IF_MODIFIED_SINCE']) { 239 | // 304 240 | Http::header('HTTP/1.1 304 Not Modified'); 241 | // Send nothing but http headers.. 242 | $connection->close(''); 243 | return; 244 | } 245 | } 246 | 247 | // Http header. 248 | if ($modified_time) { 249 | $modified_time = "Last-Modified: $modified_time\r\n"; 250 | } 251 | $file_size = filesize($file_path); 252 | $file_info = pathinfo($file_path); 253 | $extension = isset($file_info['extension']) ? $file_info['extension'] : ''; 254 | $file_name = isset($file_info['filename']) ? $file_info['filename'] : ''; 255 | $header = "HTTP/1.1 200 OK\r\n"; 256 | if (isset(self::$mimeTypeMap[$extension])) { 257 | $header .= "Content-Type: " . self::$mimeTypeMap[$extension] . "\r\n"; 258 | } else { 259 | $header .= "Content-Type: application/octet-stream\r\n"; 260 | $header .= "Content-Disposition: attachment; filename=\"$file_name\"\r\n"; 261 | } 262 | $header .= "Connection: keep-alive\r\n"; 263 | $header .= $modified_time; 264 | $header .= "Content-Length: $file_size\r\n\r\n"; 265 | $trunk_limit_size = 1024*1024; 266 | if ($file_size < $trunk_limit_size) { 267 | return $connection->send($header.file_get_contents($file_path), true); 268 | } 269 | $connection->send($header, true); 270 | 271 | // Read file content from disk piece by piece and send to client. 272 | $connection->fileHandler = fopen($file_path, 'r'); 273 | $do_write = function()use($connection) 274 | { 275 | // Send buffer not full. 276 | while(empty($connection->bufferFull)) 277 | { 278 | // Read from disk. 279 | $buffer = fread($connection->fileHandler, 8192); 280 | // Read eof. 281 | if($buffer === '' || $buffer === false) 282 | { 283 | return; 284 | } 285 | $connection->send($buffer, true); 286 | } 287 | }; 288 | // Send buffer full. 289 | $connection->onBufferFull = function($connection) 290 | { 291 | $connection->bufferFull = true; 292 | }; 293 | // Send buffer drain. 294 | $connection->onBufferDrain = function($connection)use($do_write) 295 | { 296 | $connection->bufferFull = false; 297 | $do_write(); 298 | }; 299 | $do_write(); 300 | } 301 | } 302 | -------------------------------------------------------------------------------- /Workerman/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 | 15 | namespace Workerman\Connection; 16 | 17 | use Workerman\Events\EventInterface; 18 | use Workerman\Lib\Timer; 19 | use Workerman\Worker; 20 | use Exception; 21 | 22 | /** 23 | * AsyncTcpConnection. 24 | */ 25 | class AsyncTcpConnection extends TcpConnection 26 | { 27 | /** 28 | * Emitted when socket connection is successfully established. 29 | * 30 | * @var callback 31 | */ 32 | public $onConnect = null; 33 | 34 | /** 35 | * Transport layer protocol. 36 | * 37 | * @var string 38 | */ 39 | public $transport = 'tcp'; 40 | 41 | /** 42 | * Status. 43 | * 44 | * @var int 45 | */ 46 | protected $_status = self::STATUS_INITIAL; 47 | 48 | /** 49 | * Remote host. 50 | * 51 | * @var string 52 | */ 53 | protected $_remoteHost = ''; 54 | 55 | /** 56 | * Remote port. 57 | * 58 | * @var int 59 | */ 60 | protected $_remotePort = 80; 61 | 62 | /** 63 | * Connect start time. 64 | * 65 | * @var string 66 | */ 67 | protected $_connectStartTime = 0; 68 | 69 | /** 70 | * Remote URI. 71 | * 72 | * @var string 73 | */ 74 | protected $_remoteURI = ''; 75 | 76 | /** 77 | * Context option. 78 | * 79 | * @var resource 80 | */ 81 | protected $_contextOption = null; 82 | 83 | /** 84 | * Reconnect timer. 85 | * 86 | * @var int 87 | */ 88 | protected $_reconnectTimer = null; 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 | ) { 174 | return; 175 | } 176 | $this->_status = self::STATUS_CONNECTING; 177 | $this->_connectStartTime = microtime(true); 178 | // Open socket connection asynchronously. 179 | if ($this->_contextOption) { 180 | $context = stream_context_create($this->_contextOption); 181 | $this->_socket = stream_socket_client("{$this->transport}://{$this->_remoteHost}:{$this->_remotePort}", $errno, $errstr, 0, 182 | STREAM_CLIENT_ASYNC_CONNECT, $context); 183 | } else { 184 | if ($this->transport == "unix") { 185 | //zyf 186 | $this->_socket = stream_socket_client($this->transport . ":" . $this->_remoteAddress, $errno, $errstr, 0, 187 | STREAM_CLIENT_ASYNC_CONNECT); 188 | } else { 189 | $this->_socket = stream_socket_client("{$this->transport}://{$this->_remoteHost}:{$this->_remotePort}", $errno, $errstr, 0, 190 | STREAM_CLIENT_ASYNC_CONNECT); 191 | } 192 | } 193 | // If failed attempt to emit onError callback. 194 | if (!$this->_socket) { 195 | $this->emitError(WORKERMAN_CONNECT_FAIL, $errstr); 196 | if ($this->_status === self::STATUS_CLOSING) { 197 | $this->destroy(); 198 | } 199 | if ($this->_status === self::STATUS_CLOSED) { 200 | $this->onConnect = null; 201 | } 202 | return; 203 | } 204 | // Add socket to global event loop waiting connection is successfully established or faild. 205 | Worker::$globalEvent->add($this->_socket, EventInterface::EV_WRITE, array($this, 'checkConnection')); 206 | // For windows. 207 | if (DIRECTORY_SEPARATOR === '\\') { 208 | Worker::$globalEvent->add($this->_socket, EventInterface::EV_EXCEPT, array($this, 'checkConnection')); 209 | } 210 | } 211 | 212 | /** 213 | * Reconnect. 214 | * 215 | * @param int $after 216 | * @return void 217 | */ 218 | public function reConnect($after = 0) 219 | { 220 | $this->_status = self::STATUS_INITIAL; 221 | if ($this->_reconnectTimer) { 222 | Timer::del($this->_reconnectTimer); 223 | } 224 | if ($after > 0) { 225 | $this->_reconnectTimer = Timer::add($after, array($this, 'connect'), null, false); 226 | return; 227 | } 228 | $this->connect(); 229 | } 230 | 231 | /** 232 | * Get remote address. 233 | * 234 | * @return string 235 | */ 236 | public function getRemoteHost() 237 | { 238 | return $this->_remoteHost; 239 | } 240 | 241 | /** 242 | * Get remote URI. 243 | * 244 | * @return string 245 | */ 246 | public function getRemoteURI() 247 | { 248 | return $this->_remoteURI; 249 | } 250 | 251 | /** 252 | * Try to emit onError callback. 253 | * 254 | * @param int $code 255 | * @param string $msg 256 | * @return void 257 | */ 258 | protected function emitError($code, $msg) 259 | { 260 | $this->_status = self::STATUS_CLOSING; 261 | if ($this->onError) { 262 | try { 263 | call_user_func($this->onError, $this, $code, $msg); 264 | } catch (\Exception $e) { 265 | Worker::log($e); 266 | exit(250); 267 | } catch (\Error $e) { 268 | Worker::log($e); 269 | exit(250); 270 | } 271 | } 272 | } 273 | 274 | /** 275 | * Check connection is successfully established or faild. 276 | * 277 | * @param resource $socket 278 | * @return void 279 | */ 280 | public function checkConnection($socket) 281 | { 282 | // Remove EV_EXPECT for windows. 283 | if (DIRECTORY_SEPARATOR === '\\') { 284 | Worker::$globalEvent->del($socket, EventInterface::EV_EXCEPT); 285 | } 286 | // Check socket state. 287 | if ($address = stream_socket_get_name($socket, true)) { 288 | // Remove write listener. 289 | Worker::$globalEvent->del($socket, EventInterface::EV_WRITE); 290 | // Nonblocking. 291 | stream_set_blocking($socket, 0); 292 | // Compatible with hhvm 293 | if (function_exists('stream_set_read_buffer')) { 294 | stream_set_read_buffer($socket, 0); 295 | } 296 | // Try to open keepalive for tcp and disable Nagle algorithm. 297 | if (function_exists('socket_import_stream') && $this->transport === 'tcp') { 298 | $raw_socket = socket_import_stream($socket); 299 | socket_set_option($raw_socket, SOL_SOCKET, SO_KEEPALIVE, 1); 300 | socket_set_option($raw_socket, SOL_TCP, TCP_NODELAY, 1); 301 | } 302 | // Register a listener waiting read event. 303 | Worker::$globalEvent->add($socket, EventInterface::EV_READ, array($this, 'baseRead')); 304 | // There are some data waiting to send. 305 | if ($this->_sendBuffer) { 306 | Worker::$globalEvent->add($socket, EventInterface::EV_WRITE, array($this, 'baseWrite')); 307 | } 308 | $this->_status = self::STATUS_ESTABLISHED; 309 | $this->_remoteAddress = $address; 310 | $this->_sslHandshakeCompleted = true; 311 | 312 | // Try to emit onConnect callback. 313 | if ($this->onConnect) { 314 | try { 315 | call_user_func($this->onConnect, $this); 316 | } catch (\Exception $e) { 317 | Worker::log($e); 318 | exit(250); 319 | } catch (\Error $e) { 320 | Worker::log($e); 321 | exit(250); 322 | } 323 | } 324 | // Try to emit protocol::onConnect 325 | if (method_exists($this->protocol, 'onConnect')) { 326 | try { 327 | call_user_func(array($this->protocol, 'onConnect'), $this); 328 | } catch (\Exception $e) { 329 | Worker::log($e); 330 | exit(250); 331 | } catch (\Error $e) { 332 | Worker::log($e); 333 | exit(250); 334 | } 335 | } 336 | } else { 337 | // Connection failed. 338 | $this->emitError(WORKERMAN_CONNECT_FAIL, 'connect ' . $this->_remoteAddress . ' fail after ' . round(microtime(true) - $this->_connectStartTime, 4) . ' seconds'); 339 | if ($this->_status === self::STATUS_CLOSING) { 340 | $this->destroy(); 341 | } 342 | if ($this->_status === self::STATUS_CLOSED) { 343 | $this->onConnect = null; 344 | } 345 | } 346 | } 347 | } 348 | -------------------------------------------------------------------------------- /Workerman/Protocols/Ws.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | namespace Workerman\Protocols; 15 | 16 | use Workerman\Worker; 17 | use Workerman\Lib\Timer; 18 | use Workerman\Connection\TcpConnection; 19 | 20 | /** 21 | * Websocket protocol for client. 22 | */ 23 | class Ws 24 | { 25 | /** 26 | * Websocket blob type. 27 | * 28 | * @var string 29 | */ 30 | const BINARY_TYPE_BLOB = "\x81"; 31 | 32 | /** 33 | * Websocket arraybuffer type. 34 | * 35 | * @var string 36 | */ 37 | const BINARY_TYPE_ARRAYBUFFER = "\x82"; 38 | 39 | /** 40 | * Check the integrity of the package. 41 | * 42 | * @param string $buffer 43 | * @param ConnectionInterface $connection 44 | * @return int 45 | */ 46 | public static function input($buffer, $connection) 47 | { 48 | if (empty($connection->handshakeStep)) { 49 | echo "recv data before handshake. Buffer:" . bin2hex($buffer) . "\n"; 50 | return false; 51 | } 52 | // Recv handshake response 53 | if ($connection->handshakeStep === 1) { 54 | return self::dealHandshake($buffer, $connection); 55 | } 56 | $recv_len = strlen($buffer); 57 | if ($recv_len < 2) { 58 | return 0; 59 | } 60 | // Buffer websocket frame data. 61 | if ($connection->websocketCurrentFrameLength) { 62 | // We need more frame data. 63 | if ($connection->websocketCurrentFrameLength > $recv_len) { 64 | // Return 0, because it is not clear the full packet length, waiting for the frame of fin=1. 65 | return 0; 66 | } 67 | } else { 68 | 69 | $firstbyte = ord($buffer[0]); 70 | $secondbyte = ord($buffer[1]); 71 | $data_len = $secondbyte & 127; 72 | $is_fin_frame = $firstbyte >> 7; 73 | $masked = $secondbyte >> 7; 74 | $opcode = $firstbyte & 0xf; 75 | 76 | switch ($opcode) { 77 | case 0x0: 78 | break; 79 | // Blob type. 80 | case 0x1: 81 | break; 82 | // Arraybuffer type. 83 | case 0x2: 84 | break; 85 | // Close package. 86 | case 0x8: 87 | // Try to emit onWebSocketClose callback. 88 | if (isset($connection->onWebSocketClose)) { 89 | try { 90 | call_user_func($connection->onWebSocketClose, $connection); 91 | } catch (\Exception $e) { 92 | Worker::log($e); 93 | exit(250); 94 | } catch (\Error $e) { 95 | Worker::log($e); 96 | exit(250); 97 | } 98 | } // Close connection. 99 | else { 100 | $connection->close(); 101 | } 102 | return 0; 103 | // Ping package. 104 | case 0x9: 105 | // Try to emit onWebSocketPing callback. 106 | if (isset($connection->onWebSocketPing)) { 107 | try { 108 | call_user_func($connection->onWebSocketPing, $connection); 109 | } catch (\Exception $e) { 110 | Worker::log($e); 111 | exit(250); 112 | } catch (\Error $e) { 113 | Worker::log($e); 114 | exit(250); 115 | } 116 | } // Send pong package to client. 117 | else { 118 | $connection->send(pack('H*', '8a00'), true); 119 | } 120 | // Consume data from receive buffer. 121 | if (!$data_len) { 122 | $head_len = $masked ? 6 : 2; 123 | $connection->consumeRecvBuffer($head_len); 124 | if ($recv_len > $head_len) { 125 | return self::input(substr($buffer, $head_len), $connection); 126 | } 127 | return 0; 128 | } 129 | break; 130 | // Pong package. 131 | case 0xa: 132 | // Try to emit onWebSocketPong callback. 133 | if (isset($connection->onWebSocketPong)) { 134 | try { 135 | call_user_func($connection->onWebSocketPong, $connection); 136 | } catch (\Exception $e) { 137 | Worker::log($e); 138 | exit(250); 139 | } catch (\Error $e) { 140 | Worker::log($e); 141 | exit(250); 142 | } 143 | } 144 | // Consume data from receive buffer. 145 | if (!$data_len) { 146 | $head_len = $masked ? 6 : 2; 147 | $connection->consumeRecvBuffer($head_len); 148 | if ($recv_len > $head_len) { 149 | return self::input(substr($buffer, $head_len), $connection); 150 | } 151 | return 0; 152 | } 153 | break; 154 | // Wrong opcode. 155 | default : 156 | echo "error opcode $opcode and close websocket connection. Buffer:" . $buffer . "\n"; 157 | $connection->close(); 158 | return 0; 159 | } 160 | // Calculate packet length. 161 | if ($data_len === 126) { 162 | if (strlen($buffer) < 6) { 163 | return 0; 164 | } 165 | $pack = unpack('nn/ntotal_len', $buffer); 166 | $current_frame_length = $pack['total_len'] + 4; 167 | } else if ($data_len === 127) { 168 | if (strlen($buffer) < 10) { 169 | return 0; 170 | } 171 | $arr = unpack('n/N2c', $buffer); 172 | $current_frame_length = $arr['c1']*4294967296 + $arr['c2'] + 10; 173 | } else { 174 | $current_frame_length = $data_len + 2; 175 | } 176 | 177 | $total_package_size = strlen($connection->websocketDataBuffer) + $current_frame_length; 178 | if ($total_package_size > TcpConnection::$maxPackageSize) { 179 | echo "error package. package_length=$total_package_size\n"; 180 | $connection->close(); 181 | return 0; 182 | } 183 | 184 | if ($is_fin_frame) { 185 | return $current_frame_length; 186 | } else { 187 | $connection->websocketCurrentFrameLength = $current_frame_length; 188 | } 189 | } 190 | // Received just a frame length data. 191 | if ($connection->websocketCurrentFrameLength === $recv_len) { 192 | self::decode($buffer, $connection); 193 | $connection->consumeRecvBuffer($connection->websocketCurrentFrameLength); 194 | $connection->websocketCurrentFrameLength = 0; 195 | return 0; 196 | } // The length of the received data is greater than the length of a frame. 197 | elseif ($connection->websocketCurrentFrameLength < $recv_len) { 198 | self::decode(substr($buffer, 0, $connection->websocketCurrentFrameLength), $connection); 199 | $connection->consumeRecvBuffer($connection->websocketCurrentFrameLength); 200 | $current_frame_length = $connection->websocketCurrentFrameLength; 201 | $connection->websocketCurrentFrameLength = 0; 202 | // Continue to read next frame. 203 | return self::input(substr($buffer, $current_frame_length), $connection); 204 | } // The length of the received data is less than the length of a frame. 205 | else { 206 | return 0; 207 | } 208 | } 209 | 210 | /** 211 | * Websocket encode. 212 | * 213 | * @param string $buffer 214 | * @param ConnectionInterface $connection 215 | * @return string 216 | */ 217 | public static function encode($payload, $connection) 218 | { 219 | if (empty($connection->websocketType)) { 220 | $connection->websocketType = self::BINARY_TYPE_BLOB; 221 | } 222 | $payload = (string)$payload; 223 | if (empty($connection->handshakeStep)) { 224 | self::sendHandshake($connection); 225 | } 226 | $mask = 1; 227 | $mask_key = "\x00\x00\x00\x00"; 228 | 229 | $pack = ''; 230 | $length = $length_flag = strlen($payload); 231 | if (65535 < $length) { 232 | $pack = pack('NN', ($length & 0xFFFFFFFF00000000) >> 32, $length & 0x00000000FFFFFFFF); 233 | $length_flag = 127; 234 | } else if (125 < $length) { 235 | $pack = pack('n*', $length); 236 | $length_flag = 126; 237 | } 238 | 239 | $head = ($mask << 7) | $length_flag; 240 | $head = $connection->websocketType . chr($head) . $pack; 241 | 242 | $frame = $head . $mask_key; 243 | // append payload to frame: 244 | for ($i = 0; $i < $length; $i++) { 245 | $frame .= $payload[$i] ^ $mask_key[$i % 4]; 246 | } 247 | if ($connection->handshakeStep === 1) { 248 | // If buffer has already full then discard the current package. 249 | if (strlen($connection->tmpWebsocketData) > $connection->maxSendBufferSize) { 250 | if ($connection->onError) { 251 | try { 252 | call_user_func($connection->onError, $connection, WORKERMAN_SEND_FAIL, 'send buffer full and drop package'); 253 | } catch (\Exception $e) { 254 | Worker::log($e); 255 | exit(250); 256 | } catch (\Error $e) { 257 | Worker::log($e); 258 | exit(250); 259 | } 260 | } 261 | return ''; 262 | } 263 | $connection->tmpWebsocketData = $connection->tmpWebsocketData . $frame; 264 | // Check buffer is full. 265 | if ($connection->maxSendBufferSize <= strlen($connection->tmpWebsocketData)) { 266 | if ($connection->onBufferFull) { 267 | try { 268 | call_user_func($connection->onBufferFull, $connection); 269 | } catch (\Exception $e) { 270 | Worker::log($e); 271 | exit(250); 272 | } catch (\Error $e) { 273 | Worker::log($e); 274 | exit(250); 275 | } 276 | } 277 | } 278 | return ''; 279 | } 280 | return $frame; 281 | } 282 | 283 | /** 284 | * Websocket decode. 285 | * 286 | * @param string $buffer 287 | * @param ConnectionInterface $connection 288 | * @return string 289 | */ 290 | public static function decode($bytes, $connection) 291 | { 292 | $masked = ord($bytes[1]) >> 7; 293 | $data_length = $masked ? ord($bytes[1]) & 127 : ord($bytes[1]); 294 | $decoded_data = ''; 295 | if ($masked === true) { 296 | if ($data_length === 126) { 297 | $mask = substr($bytes, 4, 4); 298 | $coded_data = substr($bytes, 8); 299 | } else if ($data_length === 127) { 300 | $mask = substr($bytes, 10, 4); 301 | $coded_data = substr($bytes, 14); 302 | } else { 303 | $mask = substr($bytes, 2, 4); 304 | $coded_data = substr($bytes, 6); 305 | } 306 | for ($i = 0; $i < strlen($coded_data); $i++) { 307 | $decoded_data .= $coded_data[$i] ^ $mask[$i % 4]; 308 | } 309 | } else { 310 | if ($data_length === 126) { 311 | $decoded_data = substr($bytes, 4); 312 | } else if ($data_length === 127) { 313 | $decoded_data = substr($bytes, 10); 314 | } else { 315 | $decoded_data = substr($bytes, 2); 316 | } 317 | } 318 | if ($connection->websocketCurrentFrameLength) { 319 | $connection->websocketDataBuffer .= $decoded_data; 320 | return $connection->websocketDataBuffer; 321 | } else { 322 | if ($connection->websocketDataBuffer !== '') { 323 | $decoded_data = $connection->websocketDataBuffer . $decoded_data; 324 | $connection->websocketDataBuffer = ''; 325 | } 326 | return $decoded_data; 327 | } 328 | } 329 | 330 | /** 331 | * Send websocket handshake data. 332 | * 333 | * @return void 334 | */ 335 | public static function onConnect($connection) 336 | { 337 | self::sendHandshake($connection); 338 | } 339 | 340 | /** 341 | * Clean 342 | * 343 | * @param $connection 344 | */ 345 | public static function onClose($connection) 346 | { 347 | $connection->handshakeStep = null; 348 | $connection->websocketCurrentFrameLength = 0; 349 | $connection->tmpWebsocketData = ''; 350 | $connection->websocketDataBuffer = ''; 351 | if (!empty($connection->websocketPingTimer)) { 352 | Timer::del($connection->websocketPingTimer); 353 | $connection->websocketPingTimer = null; 354 | } 355 | } 356 | 357 | /** 358 | * Send websocket handshake. 359 | * 360 | * @param \Workerman\Connection\TcpConnection $connection 361 | * @return void 362 | */ 363 | public static function sendHandshake($connection) 364 | { 365 | if (!empty($connection->handshakeStep)) { 366 | return; 367 | } 368 | // Get Host. 369 | $port = $connection->getRemotePort(); 370 | $host = $port === 80 ? $connection->getRemoteHost() : $connection->getRemoteHost() . ':' . $port; 371 | // Handshake header. 372 | $header = 'GET ' . $connection->getRemoteURI() . " HTTP/1.1\r\n". 373 | "Host: $host\r\n". 374 | "Connection: Upgrade\r\n". 375 | "Upgrade: websocket\r\n". 376 | "Origin: ". (isset($connection->websocketOrigin) ? $connection->websocketOrigin : '*') ."\r\n". 377 | "Sec-WebSocket-Version: 13\r\n". 378 | "Sec-WebSocket-Key: " . base64_encode(md5(mt_rand(), true)) . "\r\n\r\n"; 379 | $connection->send($header, true); 380 | $connection->handshakeStep = 1; 381 | $connection->websocketCurrentFrameLength = 0; 382 | $connection->websocketDataBuffer = ''; 383 | $connection->tmpWebsocketData = ''; 384 | } 385 | 386 | /** 387 | * Websocket handshake. 388 | * 389 | * @param string $buffer 390 | * @param \Workerman\Connection\TcpConnection $connection 391 | * @return int 392 | */ 393 | public static function dealHandshake($buffer, $connection) 394 | { 395 | $pos = strpos($buffer, "\r\n\r\n"); 396 | if ($pos) { 397 | // handshake complete 398 | $connection->handshakeStep = 2; 399 | $handshake_response_length = $pos + 4; 400 | // Try to emit onWebSocketConnect callback. 401 | if (isset($connection->onWebSocketConnect)) { 402 | try { 403 | call_user_func($connection->onWebSocketConnect, $connection, substr($buffer, 0, $handshake_response_length)); 404 | } catch (\Exception $e) { 405 | Worker::log($e); 406 | exit(250); 407 | } catch (\Error $e) { 408 | Worker::log($e); 409 | exit(250); 410 | } 411 | } 412 | // Headbeat. 413 | if (!empty($connection->websocketPingInterval)) { 414 | $connection->websocketPingTimer = Timer::add($connection->websocketPingInterval, function() use ($connection){ 415 | if (false === $connection->send(pack('H*', '898000000000'), true)) { 416 | Timer::del($connection->websocketPingTimer); 417 | $connection->websocketPingTimer = null; 418 | } 419 | }); 420 | } 421 | 422 | $connection->consumeRecvBuffer($handshake_response_length); 423 | if (!empty($connection->tmpWebsocketData)) { 424 | $connection->send($connection->tmpWebsocketData, true); 425 | $connection->tmpWebsocketData = ''; 426 | } 427 | if (strlen($buffer) > $handshake_response_length) { 428 | return self::input(substr($buffer, $handshake_response_length), $connection); 429 | } 430 | } 431 | return 0; 432 | } 433 | } 434 | -------------------------------------------------------------------------------- /Workerman/README.md: -------------------------------------------------------------------------------- 1 | # Workerman 2 | [![Gitter](https://badges.gitter.im/walkor/Workerman.svg)](https://gitter.im/walkor/Workerman?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=body_badge) 3 | [![Latest Stable Version](https://poser.pugx.org/workerman/workerman/v/stable)](https://packagist.org/packages/workerman/workerman) 4 | [![Total Downloads](https://poser.pugx.org/workerman/workerman/downloads)](https://packagist.org/packages/workerman/workerman) 5 | [![Monthly Downloads](https://poser.pugx.org/workerman/workerman/d/monthly)](https://packagist.org/packages/workerman/workerman) 6 | [![Daily Downloads](https://poser.pugx.org/workerman/workerman/d/daily)](https://packagist.org/packages/workerman/workerman) 7 | [![License](https://poser.pugx.org/workerman/workerman/license)](https://packagist.org/packages/workerman/workerman) 8 | 9 | ## What is it 10 | Workerman is an asynchronous event driven PHP framework with high performance for easily building fast, scalable network applications. Supports HTTP, Websocket, SSL and other custom protocols. Supports libevent, [HHVM](https://github.com/facebook/hhvm) , [ReactPHP](https://github.com/reactphp/react). 11 | 12 | ## Requires 13 | PHP 5.3 or Higher 14 | A POSIX compatible operating system (Linux, OSX, BSD) 15 | POSIX and PCNTL extensions for PHP 16 | 17 | ## Installation 18 | 19 | ``` 20 | composer require workerman/workerman 21 | ``` 22 | 23 | ## Basic Usage 24 | 25 | ### A websocket server 26 | ```php 27 | count = 4; 36 | 37 | // Emitted when new connection come 38 | $ws_worker->onConnect = function($connection) 39 | { 40 | echo "New connection\n"; 41 | }; 42 | 43 | // Emitted when data received 44 | $ws_worker->onMessage = function($connection, $data) 45 | { 46 | // Send hello $data 47 | $connection->send('hello ' . $data); 48 | }; 49 | 50 | // Emitted when connection closed 51 | $ws_worker->onClose = function($connection) 52 | { 53 | echo "Connection closed\n"; 54 | }; 55 | 56 | // Run worker 57 | Worker::runAll(); 58 | ``` 59 | 60 | ### An http server 61 | ```php 62 | require_once __DIR__ . '/vendor/autoload.php'; 63 | use Workerman\Worker; 64 | 65 | // #### http worker #### 66 | $http_worker = new Worker("http://0.0.0.0:2345"); 67 | 68 | // 4 processes 69 | $http_worker->count = 4; 70 | 71 | // Emitted when data received 72 | $http_worker->onMessage = function($connection, $data) 73 | { 74 | // $_GET, $_POST, $_COOKIE, $_SESSION, $_SERVER, $_FILES are available 75 | var_dump($_GET, $_POST, $_COOKIE, $_SESSION, $_SERVER, $_FILES); 76 | // send data to client 77 | $connection->send("hello world \n"); 78 | }; 79 | 80 | // run all workers 81 | Worker::runAll(); 82 | ``` 83 | 84 | ### A WebServer 85 | ```php 86 | require_once __DIR__ . '/vendor/autoload.php'; 87 | use Workerman\WebServer; 88 | use Workerman\Worker; 89 | 90 | // WebServer 91 | $web = new WebServer("http://0.0.0.0:80"); 92 | 93 | // 4 processes 94 | $web->count = 4; 95 | 96 | // Set the root of domains 97 | $web->addRoot('www.your_domain.com', '/your/path/Web'); 98 | $web->addRoot('www.another_domain.com', '/another/path/Web'); 99 | // run all workers 100 | Worker::runAll(); 101 | ``` 102 | 103 | ### A tcp server 104 | ```php 105 | require_once __DIR__ . '/vendor/autoload.php'; 106 | use Workerman\Worker; 107 | 108 | // #### create socket and listen 1234 port #### 109 | $tcp_worker = new Worker("tcp://0.0.0.0:1234"); 110 | 111 | // 4 processes 112 | $tcp_worker->count = 4; 113 | 114 | // Emitted when new connection come 115 | $tcp_worker->onConnect = function($connection) 116 | { 117 | echo "New Connection\n"; 118 | }; 119 | 120 | // Emitted when data received 121 | $tcp_worker->onMessage = function($connection, $data) 122 | { 123 | // send data to client 124 | $connection->send("hello $data \n"); 125 | }; 126 | 127 | // Emitted when new connection come 128 | $tcp_worker->onClose = function($connection) 129 | { 130 | echo "Connection closed\n"; 131 | }; 132 | 133 | Worker::runAll(); 134 | ``` 135 | 136 | ### Enable SSL 137 | ```php 138 | array( 145 | 'local_cert' => '/your/path/of/server.pem', 146 | 'local_pk' => '/your/path/of/server.key', 147 | 'verify_peer' => false, 148 | ) 149 | ); 150 | 151 | // Create a Websocket server with ssl context. 152 | $ws_worker = new Worker("websocket://0.0.0.0:2346", $context); 153 | 154 | // Enable SSL. WebSocket+SSL means that Secure WebSocket (wss://). 155 | // The similar approaches for Https etc. 156 | $ws_worker->transport = 'ssl'; 157 | 158 | $ws_worker->onMessage = function($connection, $data) 159 | { 160 | // Send hello $data 161 | $connection->send('hello ' . $data); 162 | }; 163 | 164 | Worker::runAll(); 165 | ``` 166 | 167 | ### Custom protocol 168 | Protocols/MyTextProtocol.php 169 | ```php 170 | namespace Protocols; 171 | /** 172 | * User defined protocol 173 | * Format Text+"\n" 174 | */ 175 | class MyTextProtocol 176 | { 177 | public static function input($recv_buffer) 178 | { 179 | // Find the position of the first occurrence of "\n" 180 | $pos = strpos($recv_buffer, "\n"); 181 | // Not a complete package. Return 0 because the length of package can not be calculated 182 | if($pos === false) 183 | { 184 | return 0; 185 | } 186 | // Return length of the package 187 | return $pos+1; 188 | } 189 | 190 | public static function decode($recv_buffer) 191 | { 192 | return trim($recv_buffer); 193 | } 194 | 195 | public static function encode($data) 196 | { 197 | return $data."\n"; 198 | } 199 | } 200 | ``` 201 | 202 | ```php 203 | require_once __DIR__ . '/vendor/autoload.php'; 204 | use Workerman\Worker; 205 | 206 | // #### MyTextProtocol worker #### 207 | $text_worker = new Worker("MyTextProtocol://0.0.0.0:5678"); 208 | 209 | $text_worker->onConnect = function($connection) 210 | { 211 | echo "New connection\n"; 212 | }; 213 | 214 | $text_worker->onMessage = function($connection, $data) 215 | { 216 | // send data to client 217 | $connection->send("hello world \n"); 218 | }; 219 | 220 | $text_worker->onClose = function($connection) 221 | { 222 | echo "Connection closed\n"; 223 | }; 224 | 225 | // run all workers 226 | Worker::runAll(); 227 | ``` 228 | 229 | ### Timer 230 | ```php 231 | require_once __DIR__ . '/vendor/autoload.php'; 232 | use Workerman\Worker; 233 | use Workerman\Lib\Timer; 234 | 235 | $task = new Worker(); 236 | $task->onWorkerStart = function($task) 237 | { 238 | // 2.5 seconds 239 | $time_interval = 2.5; 240 | $timer_id = Timer::add($time_interval, 241 | function() 242 | { 243 | echo "Timer run\n"; 244 | } 245 | ); 246 | }; 247 | 248 | // run all workers 249 | Worker::runAll(); 250 | ``` 251 | 252 | ### AsyncTcpConnection (tcp/ws/text/frame etc...) 253 | ```php 254 | require_once __DIR__ . '/vendor/autoload.php'; 255 | use Workerman\Worker; 256 | use Workerman\Connection\AsyncTcpConnection; 257 | 258 | $worker = new Worker(); 259 | $worker->onWorkerStart = function() 260 | { 261 | // Websocket protocol for client. 262 | $ws_connection = new AsyncTcpConnection("ws://echo.websocket.org:80"); 263 | $ws_connection->onConnect = function($connection){ 264 | $connection->send('hello'); 265 | }; 266 | $ws_connection->onMessage = function($connection, $data){ 267 | echo "recv: $data\n"; 268 | }; 269 | $ws_connection->onError = function($connection, $code, $msg){ 270 | echo "error: $msg\n"; 271 | }; 272 | $ws_connection->onClose = function($connection){ 273 | echo "connection closed\n"; 274 | }; 275 | $ws_connection->connect(); 276 | }; 277 | Worker::runAll(); 278 | ``` 279 | 280 | ### Async Mysql of ReactPHP 281 | ``` 282 | composer require react/mysql 283 | ``` 284 | 285 | ```php 286 | onWorkerStart = function() { 292 | global $mysql; 293 | $loop = Worker::getEventLoop(); 294 | $mysql = new React\MySQL\Connection($loop, array( 295 | 'host' => '127.0.0.1', 296 | 'dbname' => 'dbname', 297 | 'user' => 'user', 298 | 'passwd' => 'passwd', 299 | )); 300 | $mysql->on('error', function($e){ 301 | echo $e; 302 | }); 303 | $mysql->connect(function ($e) { 304 | if($e) { 305 | echo $e; 306 | } else { 307 | echo "connect success\n"; 308 | } 309 | }); 310 | }; 311 | $worker->onMessage = function($connection, $data) { 312 | global $mysql; 313 | $mysql->query('show databases' /*trim($data)*/, function ($command, $mysql) use ($connection) { 314 | if ($command->hasError()) { 315 | $error = $command->getError(); 316 | } else { 317 | $results = $command->resultRows; 318 | $fields = $command->resultFields; 319 | $connection->send(json_encode($results)); 320 | } 321 | }); 322 | }; 323 | Worker::runAll(); 324 | ``` 325 | 326 | ### Async Redis of ReactPHP 327 | ``` 328 | composer require clue/redis-react 329 | ``` 330 | 331 | ```php 332 | onWorkerStart = function() { 341 | global $factory; 342 | $loop = Worker::getEventLoop(); 343 | $factory = new Factory($loop); 344 | }; 345 | 346 | $worker->onMessage = function($connection, $data) { 347 | global $factory; 348 | $factory->createClient('localhost:6379')->then(function (Client $client) use ($connection) { 349 | $client->set('greeting', 'Hello world'); 350 | $client->append('greeting', '!'); 351 | 352 | $client->get('greeting')->then(function ($greeting) use ($connection){ 353 | // Hello world! 354 | echo $greeting . PHP_EOL; 355 | $connection->send($greeting); 356 | }); 357 | 358 | $client->incr('invocation')->then(function ($n) use ($connection){ 359 | echo 'This is invocation #' . $n . PHP_EOL; 360 | $connection->send($n); 361 | }); 362 | }); 363 | }; 364 | 365 | Worker::runAll(); 366 | ``` 367 | 368 | ### Aysnc dns of ReactPHP 369 | ``` 370 | composer require react/dns 371 | ``` 372 | 373 | ```php 374 | require_once __DIR__ . '/vendor/autoload.php'; 375 | use Workerman\Worker; 376 | $worker = new Worker('tcp://0.0.0.0:6161'); 377 | $worker->onWorkerStart = function() { 378 | global $dns; 379 | // Get event-loop. 380 | $loop = Worker::getEventLoop(); 381 | $factory = new React\Dns\Resolver\Factory(); 382 | $dns = $factory->create('8.8.8.8', $loop); 383 | }; 384 | $worker->onMessage = function($connection, $host) { 385 | global $dns; 386 | $host = trim($host); 387 | $dns->resolve($host)->then(function($ip) use($host, $connection) { 388 | $connection->send("$host: $ip"); 389 | },function($e) use($host, $connection){ 390 | $connection->send("$host: {$e->getMessage()}"); 391 | }); 392 | }; 393 | 394 | Worker::runAll(); 395 | ``` 396 | 397 | ### Http client of ReactPHP 398 | ``` 399 | composer require react/http-client 400 | ``` 401 | 402 | ```php 403 | onWorkerStart = function() { 410 | global $client; 411 | $loop = Worker::getEventLoop(); 412 | $factory = new React\Dns\Resolver\Factory(); 413 | $dns = $factory->createCached('8.8.8.8', $loop); 414 | $factory = new React\HttpClient\Factory(); 415 | $client = $factory->create($loop, $dns); 416 | }; 417 | 418 | $worker->onMessage = function($connection, $host) { 419 | global $client; 420 | $request = $client->request('GET', trim($host)); 421 | $request->on('error', function(Exception $e) use ($connection) { 422 | $connection->send($e); 423 | }); 424 | $request->on('response', function ($response) use ($connection) { 425 | $response->on('data', function ($data, $response) use ($connection) { 426 | $connection->send($data); 427 | }); 428 | }); 429 | $request->end(); 430 | }; 431 | 432 | Worker::runAll(); 433 | ``` 434 | 435 | ### ZMQ of ReactPHP 436 | ``` 437 | composer require react/zmq 438 | ``` 439 | 440 | ```php 441 | onWorkerStart = function() { 448 | global $pull; 449 | $loop = Worker::getEventLoop(); 450 | $context = new React\ZMQ\Context($loop); 451 | $pull = $context->getSocket(ZMQ::SOCKET_PULL); 452 | $pull->bind('tcp://127.0.0.1:5555'); 453 | 454 | $pull->on('error', function ($e) { 455 | var_dump($e->getMessage()); 456 | }); 457 | 458 | $pull->on('message', function ($msg) { 459 | echo "Received: $msg\n"; 460 | }); 461 | }; 462 | 463 | Worker::runAll(); 464 | ``` 465 | 466 | ### STOMP of ReactPHP 467 | ``` 468 | composer require react/stomp 469 | ``` 470 | 471 | ```php 472 | onWorkerStart = function() { 479 | global $client; 480 | $loop = Worker::getEventLoop(); 481 | $factory = new React\Stomp\Factory($loop); 482 | $client = $factory->createClient(array('vhost' => '/', 'login' => 'guest', 'passcode' => 'guest')); 483 | 484 | $client 485 | ->connect() 486 | ->then(function ($client) use ($loop) { 487 | $client->subscribe('/topic/foo', function ($frame) { 488 | echo "Message received: {$frame->body}\n"; 489 | }); 490 | }); 491 | }; 492 | 493 | Worker::runAll(); 494 | ``` 495 | 496 | 497 | 498 | ## Available commands 499 | ```php start.php start ``` 500 | ```php start.php start -d ``` 501 | ![workerman start](http://www.workerman.net/img/workerman-start.png) 502 | ```php start.php status ``` 503 | ![workerman satus](http://www.workerman.net/img/workerman-status.png?a=123) 504 | ```php start.php connections``` 505 | ```php start.php stop ``` 506 | ```php start.php restart ``` 507 | ```php start.php reload ``` 508 | 509 | ## Documentation 510 | 511 | 中文主页:[http://www.workerman.net](http://www.workerman.net) 512 | 513 | 中文文档: [http://doc.workerman.net](http://doc.workerman.net) 514 | 515 | Documentation:[https://github.com/walkor/workerman-manual](https://github.com/walkor/workerman-manual/blob/master/english/src/SUMMARY.md) 516 | 517 | # Benchmarks 518 | ``` 519 | CPU: Intel(R) Core(TM) i3-3220 CPU @ 3.30GHz and 4 processors totally 520 | Memory: 8G 521 | OS: Ubuntu 14.04 LTS 522 | Software: ab 523 | PHP: 5.5.9 524 | ``` 525 | 526 | **Codes** 527 | ```php 528 | count=3; 532 | $worker->onMessage = function($connection, $data) 533 | { 534 | $connection->send("Workerman"); 535 | }; 536 | Worker::runAll(); 537 | ``` 538 | **Result** 539 | 540 | ```shell 541 | ab -n1000000 -c100 -k http://127.0.0.1:1234/ 542 | This is ApacheBench, Version 2.3 <$Revision: 1528965 $> 543 | Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ 544 | Licensed to The Apache Software Foundation, http://www.apache.org/ 545 | 546 | Benchmarking 127.0.0.1 (be patient) 547 | Completed 100000 requests 548 | Completed 200000 requests 549 | Completed 300000 requests 550 | Completed 400000 requests 551 | Completed 500000 requests 552 | Completed 600000 requests 553 | Completed 700000 requests 554 | Completed 800000 requests 555 | Completed 900000 requests 556 | Completed 1000000 requests 557 | Finished 1000000 requests 558 | 559 | 560 | Server Software: workerman/3.1.4 561 | Server Hostname: 127.0.0.1 562 | Server Port: 1234 563 | 564 | Document Path: / 565 | Document Length: 5 bytes 566 | 567 | Concurrency Level: 100 568 | Time taken for tests: 7.240 seconds 569 | Complete requests: 1000000 570 | Failed requests: 0 571 | Keep-Alive requests: 1000000 572 | Total transferred: 73000000 bytes 573 | HTML transferred: 5000000 bytes 574 | Requests per second: 138124.14 [#/sec] (mean) 575 | Time per request: 0.724 [ms] (mean) 576 | Time per request: 0.007 [ms] (mean, across all concurrent requests) 577 | Transfer rate: 9846.74 [Kbytes/sec] received 578 | 579 | Connection Times (ms) 580 | min mean[+/-sd] median max 581 | Connect: 0 0 0.0 0 5 582 | Processing: 0 1 0.2 1 9 583 | Waiting: 0 1 0.2 1 9 584 | Total: 0 1 0.2 1 9 585 | 586 | Percentage of the requests served within a certain time (ms) 587 | 50% 1 588 | 66% 1 589 | 75% 1 590 | 80% 1 591 | 90% 1 592 | 95% 1 593 | 98% 1 594 | 99% 1 595 | 100% 9 (longest request) 596 | 597 | ``` 598 | 599 | 600 | ## Other links with workerman 601 | 602 | [PHPSocket.IO](https://github.com/walkor/phpsocket.io) 603 | [php-socks5](https://github.com/walkor/php-socks5) 604 | [php-http-proxy](https://github.com/walkor/php-http-proxy) 605 | 606 | ## Donate 607 | 608 | 609 | ## LICENSE 610 | 611 | Workerman is released under the [MIT license](https://github.com/walkor/workerman/blob/master/MIT-LICENSE.txt). 612 | -------------------------------------------------------------------------------- /Workerman/Protocols/Websocket.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | namespace Workerman\Protocols; 15 | 16 | use Workerman\Connection\ConnectionInterface; 17 | use Workerman\Connection\TcpConnection; 18 | use Workerman\Worker; 19 | 20 | /** 21 | * WebSocket protocol. 22 | */ 23 | class Websocket implements \Workerman\Protocols\ProtocolInterface 24 | { 25 | /** 26 | * Websocket blob type. 27 | * 28 | * @var string 29 | */ 30 | const BINARY_TYPE_BLOB = "\x81"; 31 | 32 | /** 33 | * Websocket arraybuffer type. 34 | * 35 | * @var string 36 | */ 37 | const BINARY_TYPE_ARRAYBUFFER = "\x82"; 38 | 39 | /** 40 | * Check the integrity of the package. 41 | * 42 | * @param string $buffer 43 | * @param ConnectionInterface $connection 44 | * @return int 45 | */ 46 | public static function input($buffer, ConnectionInterface $connection) 47 | { 48 | // Receive length. 49 | $recv_len = strlen($buffer); 50 | // We need more data. 51 | if ($recv_len < 2) { 52 | return 0; 53 | } 54 | 55 | // Has not yet completed the handshake. 56 | if (empty($connection->websocketHandshake)) { 57 | return static::dealHandshake($buffer, $connection); 58 | } 59 | 60 | // Buffer websocket frame data. 61 | if ($connection->websocketCurrentFrameLength) { 62 | // We need more frame data. 63 | if ($connection->websocketCurrentFrameLength > $recv_len) { 64 | // Return 0, because it is not clear the full packet length, waiting for the frame of fin=1. 65 | return 0; 66 | } 67 | } else { 68 | $firstbyte = ord($buffer[0]); 69 | $secondbyte = ord($buffer[1]); 70 | $data_len = $secondbyte & 127; 71 | $is_fin_frame = $firstbyte >> 7; 72 | $masked = $secondbyte >> 7; 73 | $opcode = $firstbyte & 0xf; 74 | switch ($opcode) { 75 | case 0x0: 76 | break; 77 | // Blob type. 78 | case 0x1: 79 | break; 80 | // Arraybuffer type. 81 | case 0x2: 82 | break; 83 | // Close package. 84 | case 0x8: 85 | // Try to emit onWebSocketClose callback. 86 | if (isset($connection->onWebSocketClose) || isset($connection->worker->onWebSocketClose)) { 87 | try { 88 | call_user_func(isset($connection->onWebSocketClose)?$connection->onWebSocketClose:$connection->worker->onWebSocketClose, $connection); 89 | } catch (\Exception $e) { 90 | Worker::log($e); 91 | exit(250); 92 | } catch (\Error $e) { 93 | Worker::log($e); 94 | exit(250); 95 | } 96 | } // Close connection. 97 | else { 98 | $connection->close(); 99 | } 100 | return 0; 101 | // Ping package. 102 | case 0x9: 103 | // Try to emit onWebSocketPing callback. 104 | if (isset($connection->onWebSocketPing) || isset($connection->worker->onWebSocketPing)) { 105 | try { 106 | call_user_func(isset($connection->onWebSocketPing)?$connection->onWebSocketPing:$connection->worker->onWebSocketPing, $connection); 107 | } catch (\Exception $e) { 108 | Worker::log($e); 109 | exit(250); 110 | } catch (\Error $e) { 111 | Worker::log($e); 112 | exit(250); 113 | } 114 | } // Send pong package to client. 115 | else { 116 | $connection->send(pack('H*', '8a00'), true); 117 | } 118 | 119 | // Consume data from receive buffer. 120 | if (!$data_len) { 121 | $head_len = $masked ? 6 : 2; 122 | $connection->consumeRecvBuffer($head_len); 123 | if ($recv_len > $head_len) { 124 | return static::input(substr($buffer, $head_len), $connection); 125 | } 126 | return 0; 127 | } 128 | break; 129 | // Pong package. 130 | case 0xa: 131 | // Try to emit onWebSocketPong callback. 132 | if (isset($connection->onWebSocketPong) || isset($connection->worker->onWebSocketPong)) { 133 | try { 134 | call_user_func(isset($connection->onWebSocketPong)?$connection->onWebSocketPong:$connection->worker->onWebSocketPong, $connection); 135 | } catch (\Exception $e) { 136 | Worker::log($e); 137 | exit(250); 138 | } catch (\Error $e) { 139 | Worker::log($e); 140 | exit(250); 141 | } 142 | } 143 | // Consume data from receive buffer. 144 | if (!$data_len) { 145 | $head_len = $masked ? 6 : 2; 146 | $connection->consumeRecvBuffer($head_len); 147 | if ($recv_len > $head_len) { 148 | return static::input(substr($buffer, $head_len), $connection); 149 | } 150 | return 0; 151 | } 152 | break; 153 | // Wrong opcode. 154 | default : 155 | echo "error opcode $opcode and close websocket connection. Buffer:" . bin2hex($buffer) . "\n"; 156 | $connection->close(); 157 | return 0; 158 | } 159 | 160 | // Calculate packet length. 161 | $head_len = 6; 162 | if ($data_len === 126) { 163 | $head_len = 8; 164 | if ($head_len > $recv_len) { 165 | return 0; 166 | } 167 | $pack = unpack('nn/ntotal_len', $buffer); 168 | $data_len = $pack['total_len']; 169 | } else { 170 | if ($data_len === 127) { 171 | $head_len = 14; 172 | if ($head_len > $recv_len) { 173 | return 0; 174 | } 175 | $arr = unpack('n/N2c', $buffer); 176 | $data_len = $arr['c1']*4294967296 + $arr['c2']; 177 | } 178 | } 179 | $current_frame_length = $head_len + $data_len; 180 | 181 | $total_package_size = strlen($connection->websocketDataBuffer) + $current_frame_length; 182 | if ($total_package_size > TcpConnection::$maxPackageSize) { 183 | echo "error package. package_length=$total_package_size\n"; 184 | $connection->close(); 185 | return 0; 186 | } 187 | 188 | if ($is_fin_frame) { 189 | return $current_frame_length; 190 | } else { 191 | $connection->websocketCurrentFrameLength = $current_frame_length; 192 | } 193 | } 194 | 195 | // Received just a frame length data. 196 | if ($connection->websocketCurrentFrameLength === $recv_len) { 197 | static::decode($buffer, $connection); 198 | $connection->consumeRecvBuffer($connection->websocketCurrentFrameLength); 199 | $connection->websocketCurrentFrameLength = 0; 200 | return 0; 201 | } // The length of the received data is greater than the length of a frame. 202 | elseif ($connection->websocketCurrentFrameLength < $recv_len) { 203 | static::decode(substr($buffer, 0, $connection->websocketCurrentFrameLength), $connection); 204 | $connection->consumeRecvBuffer($connection->websocketCurrentFrameLength); 205 | $current_frame_length = $connection->websocketCurrentFrameLength; 206 | $connection->websocketCurrentFrameLength = 0; 207 | // Continue to read next frame. 208 | return static::input(substr($buffer, $current_frame_length), $connection); 209 | } // The length of the received data is less than the length of a frame. 210 | else { 211 | return 0; 212 | } 213 | } 214 | 215 | /** 216 | * Websocket encode. 217 | * 218 | * @param string $buffer 219 | * @param ConnectionInterface $connection 220 | * @return string 221 | */ 222 | public static function encode($buffer, ConnectionInterface $connection) 223 | { 224 | if (!is_scalar($buffer)) { 225 | throw new \Exception("You can't send(" . gettype($buffer) . ") to client, you need to convert it to a string. "); 226 | } 227 | $len = strlen($buffer); 228 | if (empty($connection->websocketType)) { 229 | $connection->websocketType = static::BINARY_TYPE_BLOB; 230 | } 231 | 232 | $first_byte = $connection->websocketType; 233 | 234 | if ($len <= 125) { 235 | $encode_buffer = $first_byte . chr($len) . $buffer; 236 | } else { 237 | if ($len <= 65535) { 238 | $encode_buffer = $first_byte . chr(126) . pack("n", $len) . $buffer; 239 | } else { 240 | $encode_buffer = $first_byte . chr(127) . pack("xxxxN", $len) . $buffer; 241 | } 242 | } 243 | 244 | // Handshake not completed so temporary buffer websocket data waiting for send. 245 | if (empty($connection->websocketHandshake)) { 246 | if (empty($connection->tmpWebsocketData)) { 247 | $connection->tmpWebsocketData = ''; 248 | } 249 | // If buffer has already full then discard the current package. 250 | if (strlen($connection->tmpWebsocketData) > $connection->maxSendBufferSize) { 251 | if ($connection->onError) { 252 | try { 253 | call_user_func($connection->onError, $connection, WORKERMAN_SEND_FAIL, 'send buffer full and drop package'); 254 | } catch (\Exception $e) { 255 | Worker::log($e); 256 | exit(250); 257 | } catch (\Error $e) { 258 | Worker::log($e); 259 | exit(250); 260 | } 261 | } 262 | return ''; 263 | } 264 | $connection->tmpWebsocketData .= $encode_buffer; 265 | // Check buffer is full. 266 | if ($connection->maxSendBufferSize <= strlen($connection->tmpWebsocketData)) { 267 | if ($connection->onBufferFull) { 268 | try { 269 | call_user_func($connection->onBufferFull, $connection); 270 | } catch (\Exception $e) { 271 | Worker::log($e); 272 | exit(250); 273 | } catch (\Error $e) { 274 | Worker::log($e); 275 | exit(250); 276 | } 277 | } 278 | } 279 | 280 | // Return empty string. 281 | return ''; 282 | } 283 | 284 | return $encode_buffer; 285 | } 286 | 287 | /** 288 | * Websocket decode. 289 | * 290 | * @param string $buffer 291 | * @param ConnectionInterface $connection 292 | * @return string 293 | */ 294 | public static function decode($buffer, ConnectionInterface $connection) 295 | { 296 | $masks = $data = $decoded = null; 297 | $len = ord($buffer[1]) & 127; 298 | if ($len === 126) { 299 | $masks = substr($buffer, 4, 4); 300 | $data = substr($buffer, 8); 301 | } else { 302 | if ($len === 127) { 303 | $masks = substr($buffer, 10, 4); 304 | $data = substr($buffer, 14); 305 | } else { 306 | $masks = substr($buffer, 2, 4); 307 | $data = substr($buffer, 6); 308 | } 309 | } 310 | for ($index = 0; $index < strlen($data); $index++) { 311 | $decoded .= $data[$index] ^ $masks[$index % 4]; 312 | } 313 | if ($connection->websocketCurrentFrameLength) { 314 | $connection->websocketDataBuffer .= $decoded; 315 | return $connection->websocketDataBuffer; 316 | } else { 317 | if ($connection->websocketDataBuffer !== '') { 318 | $decoded = $connection->websocketDataBuffer . $decoded; 319 | $connection->websocketDataBuffer = ''; 320 | } 321 | return $decoded; 322 | } 323 | } 324 | 325 | /** 326 | * Websocket handshake. 327 | * 328 | * @param string $buffer 329 | * @param \Workerman\Connection\TcpConnection $connection 330 | * @return int 331 | */ 332 | protected static function dealHandshake($buffer, $connection) 333 | { 334 | // HTTP protocol. 335 | if (0 === strpos($buffer, 'GET')) { 336 | // Find \r\n\r\n. 337 | $heder_end_pos = strpos($buffer, "\r\n\r\n"); 338 | if (!$heder_end_pos) { 339 | return 0; 340 | } 341 | $header_length = $heder_end_pos + 4; 342 | 343 | // Get Sec-WebSocket-Key. 344 | $Sec_WebSocket_Key = ''; 345 | if (preg_match("/Sec-WebSocket-Key: *(.*?)\r\n/i", $buffer, $match)) { 346 | $Sec_WebSocket_Key = $match[1]; 347 | } else { 348 | $connection->send("HTTP/1.1 400 Bad Request\r\n\r\n400 Bad Request
Sec-WebSocket-Key not found.
This is a WebSocket service and can not be accessed via HTTP.
See http://wiki.Workerman.net/Error1 for detail.", 349 | true); 350 | $connection->close(); 351 | return 0; 352 | } 353 | // Calculation websocket key. 354 | $new_key = base64_encode(sha1($Sec_WebSocket_Key . "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", true)); 355 | // Handshake response data. 356 | $handshake_message = "HTTP/1.1 101 Switching Protocols\r\n"; 357 | $handshake_message .= "Upgrade: websocket\r\n"; 358 | $handshake_message .= "Sec-WebSocket-Version: 13\r\n"; 359 | $handshake_message .= "Connection: Upgrade\r\n"; 360 | $handshake_message .= "Server: Workerman/".Worker::VERSION."\r\n"; 361 | $handshake_message .= "Sec-WebSocket-Accept: " . $new_key . "\r\n\r\n"; 362 | // Mark handshake complete.. 363 | $connection->websocketHandshake = true; 364 | // Websocket data buffer. 365 | $connection->websocketDataBuffer = ''; 366 | // Current websocket frame length. 367 | $connection->websocketCurrentFrameLength = 0; 368 | // Current websocket frame data. 369 | $connection->websocketCurrentFrameBuffer = ''; 370 | // Consume handshake data. 371 | $connection->consumeRecvBuffer($header_length); 372 | // Send handshake response. 373 | $connection->send($handshake_message, true); 374 | 375 | // There are data waiting to be sent. 376 | if (!empty($connection->tmpWebsocketData)) { 377 | $connection->send($connection->tmpWebsocketData, true); 378 | $connection->tmpWebsocketData = ''; 379 | } 380 | // blob or arraybuffer 381 | if (empty($connection->websocketType)) { 382 | $connection->websocketType = static::BINARY_TYPE_BLOB; 383 | } 384 | // Try to emit onWebSocketConnect callback. 385 | if (isset($connection->onWebSocketConnect) || isset($connection->worker->onWebSocketConnect)) { 386 | static::parseHttpHeader($buffer); 387 | try { 388 | call_user_func(isset($connection->onWebSocketConnect)?$connection->onWebSocketConnect:$connection->worker->onWebSocketConnect, $connection, $buffer); 389 | } catch (\Exception $e) { 390 | Worker::log($e); 391 | exit(250); 392 | } catch (\Error $e) { 393 | Worker::log($e); 394 | exit(250); 395 | } 396 | if (!empty($_SESSION) && class_exists('\GatewayWorker\Lib\Context')) { 397 | $connection->session = \GatewayWorker\Lib\Context::sessionEncode($_SESSION); 398 | } 399 | $_GET = $_SERVER = $_SESSION = $_COOKIE = array(); 400 | } 401 | if (strlen($buffer) > $header_length) { 402 | return static::input(substr($buffer, $header_length), $connection); 403 | } 404 | return 0; 405 | } // Is flash policy-file-request. 406 | elseif (0 === strpos($buffer, 'send($policy_xml, true); 409 | $connection->consumeRecvBuffer(strlen($buffer)); 410 | return 0; 411 | } 412 | // Bad websocket handshake request. 413 | $connection->send("HTTP/1.1 400 Bad Request\r\n\r\n400 Bad Request
Invalid handshake data for websocket.
See http://wiki.Workerman.net/Error1 for detail.", 414 | true); 415 | $connection->close(); 416 | return 0; 417 | } 418 | 419 | /** 420 | * Parse http header. 421 | * 422 | * @param string $buffer 423 | * @return void 424 | */ 425 | protected static function parseHttpHeader($buffer) 426 | { 427 | // Parse headers. 428 | list($http_header, ) = explode("\r\n\r\n", $buffer, 2); 429 | $header_data = explode("\r\n", $http_header); 430 | 431 | if ($_SERVER) { 432 | $_SERVER = array(); 433 | } 434 | 435 | list($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI'], $_SERVER['SERVER_PROTOCOL']) = explode(' ', 436 | $header_data[0]); 437 | 438 | unset($header_data[0]); 439 | foreach ($header_data as $content) { 440 | // \r\n\r\n 441 | if (empty($content)) { 442 | continue; 443 | } 444 | list($key, $value) = explode(':', $content, 2); 445 | $key = str_replace('-', '_', strtoupper($key)); 446 | $value = trim($value); 447 | $_SERVER['HTTP_' . $key] = $value; 448 | switch ($key) { 449 | // HTTP_HOST 450 | case 'HOST': 451 | $tmp = explode(':', $value); 452 | $_SERVER['SERVER_NAME'] = $tmp[0]; 453 | if (isset($tmp[1])) { 454 | $_SERVER['SERVER_PORT'] = $tmp[1]; 455 | } 456 | break; 457 | // cookie 458 | case 'COOKIE': 459 | parse_str(str_replace('; ', '&', $_SERVER['HTTP_COOKIE']), $_COOKIE); 460 | break; 461 | } 462 | } 463 | 464 | // QUERY_STRING 465 | $_SERVER['QUERY_STRING'] = parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY); 466 | if ($_SERVER['QUERY_STRING']) { 467 | // $GET 468 | parse_str($_SERVER['QUERY_STRING'], $_GET); 469 | } else { 470 | $_SERVER['QUERY_STRING'] = ''; 471 | } 472 | } 473 | } 474 | -------------------------------------------------------------------------------- /Workerman/Protocols/Http.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | namespace Workerman\Protocols; 15 | 16 | use Workerman\Connection\TcpConnection; 17 | use Workerman\Worker; 18 | 19 | /** 20 | * http protocol 21 | */ 22 | class Http 23 | { 24 | /** 25 | * The supported HTTP methods 26 | * @var array 27 | */ 28 | public static $methods = array('GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'OPTIONS'); 29 | 30 | /** 31 | * Check the integrity of the package. 32 | * 33 | * @param string $recv_buffer 34 | * @param TcpConnection $connection 35 | * @return int 36 | */ 37 | public static function input($recv_buffer, TcpConnection $connection) 38 | { 39 | if (!strpos($recv_buffer, "\r\n\r\n")) { 40 | // Judge whether the package length exceeds the limit. 41 | if (strlen($recv_buffer) >= TcpConnection::$maxPackageSize) { 42 | $connection->close(); 43 | return 0; 44 | } 45 | return 0; 46 | } 47 | 48 | list($header,) = explode("\r\n\r\n", $recv_buffer, 2); 49 | $method = substr($header, 0, strpos($header, ' ')); 50 | 51 | if(in_array($method, static::$methods)) { 52 | return static::getRequestSize($header, $method); 53 | }else{ 54 | $connection->send("HTTP/1.1 400 Bad Request\r\n\r\n", true); 55 | return 0; 56 | } 57 | } 58 | 59 | /** 60 | * Get whole size of the request 61 | * includes the request headers and request body. 62 | * @param string $header The request headers 63 | * @param string $method The request method 64 | * @return integer 65 | */ 66 | protected static function getRequestSize($header, $method) 67 | { 68 | if($method === 'GET' || $method === 'OPTIONS' || $method === 'HEAD') { 69 | return strlen($header) + 4; 70 | } 71 | $match = array(); 72 | if (preg_match("/\r\nContent-Length: ?(\d+)/i", $header, $match)) { 73 | $content_length = isset($match[1]) ? $match[1] : 0; 74 | return $content_length + strlen($header) + 4; 75 | } 76 | return 0; 77 | } 78 | 79 | /** 80 | * Parse $_POST、$_GET、$_COOKIE. 81 | * 82 | * @param string $recv_buffer 83 | * @param TcpConnection $connection 84 | * @return array 85 | */ 86 | public static function decode($recv_buffer, TcpConnection $connection) 87 | { 88 | // Init. 89 | $_POST = $_GET = $_COOKIE = $_REQUEST = $_SESSION = $_FILES = array(); 90 | $GLOBALS['HTTP_RAW_POST_DATA'] = ''; 91 | // Clear cache. 92 | HttpCache::$header = array('Connection' => 'Connection: keep-alive'); 93 | HttpCache::$instance = new HttpCache(); 94 | // $_SERVER 95 | $_SERVER = array( 96 | 'QUERY_STRING' => '', 97 | 'REQUEST_METHOD' => '', 98 | 'REQUEST_URI' => '', 99 | 'SERVER_PROTOCOL' => '', 100 | 'SERVER_SOFTWARE' => 'Workerman/'.Worker::VERSION, 101 | 'SERVER_NAME' => '', 102 | 'HTTP_HOST' => '', 103 | 'HTTP_USER_AGENT' => '', 104 | 'HTTP_ACCEPT' => '', 105 | 'HTTP_ACCEPT_LANGUAGE' => '', 106 | 'HTTP_ACCEPT_ENCODING' => '', 107 | 'HTTP_COOKIE' => '', 108 | 'HTTP_CONNECTION' => '', 109 | 'REMOTE_ADDR' => '', 110 | 'REMOTE_PORT' => '0', 111 | 'REQUEST_TIME' => time() 112 | ); 113 | 114 | // Parse headers. 115 | list($http_header, $http_body) = explode("\r\n\r\n", $recv_buffer, 2); 116 | $header_data = explode("\r\n", $http_header); 117 | 118 | list($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI'], $_SERVER['SERVER_PROTOCOL']) = explode(' ', 119 | $header_data[0]); 120 | 121 | $http_post_boundary = ''; 122 | unset($header_data[0]); 123 | foreach ($header_data as $content) { 124 | // \r\n\r\n 125 | if (empty($content)) { 126 | continue; 127 | } 128 | list($key, $value) = explode(':', $content, 2); 129 | $key = str_replace('-', '_', strtoupper($key)); 130 | $value = trim($value); 131 | $_SERVER['HTTP_' . $key] = $value; 132 | switch ($key) { 133 | // HTTP_HOST 134 | case 'HOST': 135 | $tmp = explode(':', $value); 136 | $_SERVER['SERVER_NAME'] = $tmp[0]; 137 | if (isset($tmp[1])) { 138 | $_SERVER['SERVER_PORT'] = $tmp[1]; 139 | } 140 | break; 141 | // cookie 142 | case 'COOKIE': 143 | parse_str(str_replace('; ', '&', $_SERVER['HTTP_COOKIE']), $_COOKIE); 144 | break; 145 | // content-type 146 | case 'CONTENT_TYPE': 147 | if (!preg_match('/boundary="?(\S+)"?/', $value, $match)) { 148 | if ($pos = strpos($value, ';')) { 149 | $_SERVER['CONTENT_TYPE'] = substr($value, 0, $pos); 150 | } else { 151 | $_SERVER['CONTENT_TYPE'] = $value; 152 | } 153 | } else { 154 | $_SERVER['CONTENT_TYPE'] = 'multipart/form-data'; 155 | $http_post_boundary = '--' . $match[1]; 156 | } 157 | break; 158 | case 'CONTENT_LENGTH': 159 | $_SERVER['CONTENT_LENGTH'] = $value; 160 | break; 161 | } 162 | } 163 | 164 | // Parse $_POST. 165 | if ($_SERVER['REQUEST_METHOD'] === 'POST') { 166 | if (isset($_SERVER['CONTENT_TYPE'])) { 167 | switch ($_SERVER['CONTENT_TYPE']) { 168 | case 'multipart/form-data': 169 | self::parseUploadFiles($http_body, $http_post_boundary); 170 | break; 171 | case 'application/x-www-form-urlencoded': 172 | parse_str($http_body, $_POST); 173 | break; 174 | } 175 | } 176 | } 177 | 178 | // HTTP_RAW_REQUEST_DATA HTTP_RAW_POST_DATA 179 | $GLOBALS['HTTP_RAW_REQUEST_DATA'] = $GLOBALS['HTTP_RAW_POST_DATA'] = $http_body; 180 | 181 | // QUERY_STRING 182 | $_SERVER['QUERY_STRING'] = parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY); 183 | if ($_SERVER['QUERY_STRING']) { 184 | // $GET 185 | parse_str($_SERVER['QUERY_STRING'], $_GET); 186 | } else { 187 | $_SERVER['QUERY_STRING'] = ''; 188 | } 189 | 190 | // REQUEST 191 | $_REQUEST = array_merge($_GET, $_POST); 192 | 193 | // REMOTE_ADDR REMOTE_PORT 194 | $_SERVER['REMOTE_ADDR'] = $connection->getRemoteIp(); 195 | $_SERVER['REMOTE_PORT'] = $connection->getRemotePort(); 196 | 197 | return array('get' => $_GET, 'post' => $_POST, 'cookie' => $_COOKIE, 'server' => $_SERVER, 'files' => $_FILES); 198 | } 199 | 200 | /** 201 | * Http encode. 202 | * 203 | * @param string $content 204 | * @param TcpConnection $connection 205 | * @return string 206 | */ 207 | public static function encode($content, TcpConnection $connection) 208 | { 209 | // Default http-code. 210 | if (!isset(HttpCache::$header['Http-Code'])) { 211 | $header = "HTTP/1.1 200 OK\r\n"; 212 | } else { 213 | $header = HttpCache::$header['Http-Code'] . "\r\n"; 214 | unset(HttpCache::$header['Http-Code']); 215 | } 216 | 217 | // Content-Type 218 | if (!isset(HttpCache::$header['Content-Type'])) { 219 | $header .= "Content-Type: text/html;charset=utf-8\r\n"; 220 | } 221 | 222 | // other headers 223 | foreach (HttpCache::$header as $key => $item) { 224 | if ('Set-Cookie' === $key && is_array($item)) { 225 | foreach ($item as $it) { 226 | $header .= $it . "\r\n"; 227 | } 228 | } else { 229 | $header .= $item . "\r\n"; 230 | } 231 | } 232 | 233 | // header 234 | $header .= "Server: Workerman/" . Worker::VERSION . "\r\nContent-Length: " . strlen($content) . "\r\n\r\n"; 235 | 236 | // save session 237 | self::sessionWriteClose(); 238 | 239 | // the whole http package 240 | return $header . $content; 241 | } 242 | 243 | /** 244 | * 设置http头 245 | * 246 | * @return bool|void 247 | */ 248 | public static function header($content, $replace = true, $http_response_code = 0) 249 | { 250 | if (PHP_SAPI != 'cli') { 251 | return $http_response_code ? header($content, $replace, $http_response_code) : header($content, $replace); 252 | } 253 | if (strpos($content, 'HTTP') === 0) { 254 | $key = 'Http-Code'; 255 | } else { 256 | $key = strstr($content, ":", true); 257 | if (empty($key)) { 258 | return false; 259 | } 260 | } 261 | 262 | if ('location' === strtolower($key) && !$http_response_code) { 263 | return self::header($content, true, 302); 264 | } 265 | 266 | if (isset(HttpCache::$codes[$http_response_code])) { 267 | HttpCache::$header['Http-Code'] = "HTTP/1.1 $http_response_code " . HttpCache::$codes[$http_response_code]; 268 | if ($key === 'Http-Code') { 269 | return true; 270 | } 271 | } 272 | 273 | if ($key === 'Set-Cookie') { 274 | HttpCache::$header[$key][] = $content; 275 | } else { 276 | HttpCache::$header[$key] = $content; 277 | } 278 | 279 | return true; 280 | } 281 | 282 | /** 283 | * Remove header. 284 | * 285 | * @param string $name 286 | * @return void 287 | */ 288 | public static function headerRemove($name) 289 | { 290 | if (PHP_SAPI != 'cli') { 291 | header_remove($name); 292 | return; 293 | } 294 | unset(HttpCache::$header[$name]); 295 | } 296 | 297 | /** 298 | * Set cookie. 299 | * 300 | * @param string $name 301 | * @param string $value 302 | * @param integer $maxage 303 | * @param string $path 304 | * @param string $domain 305 | * @param bool $secure 306 | * @param bool $HTTPOnly 307 | * @return bool|void 308 | */ 309 | public static function setcookie( 310 | $name, 311 | $value = '', 312 | $maxage = 0, 313 | $path = '', 314 | $domain = '', 315 | $secure = false, 316 | $HTTPOnly = false 317 | ) { 318 | if (PHP_SAPI != 'cli') { 319 | return setcookie($name, $value, $maxage, $path, $domain, $secure, $HTTPOnly); 320 | } 321 | return self::header( 322 | 'Set-Cookie: ' . $name . '=' . rawurlencode($value) 323 | . (empty($domain) ? '' : '; Domain=' . $domain) 324 | . (empty($maxage) ? '' : '; Max-Age=' . $maxage) 325 | . (empty($path) ? '' : '; Path=' . $path) 326 | . (!$secure ? '' : '; Secure') 327 | . (!$HTTPOnly ? '' : '; HttpOnly'), false); 328 | } 329 | 330 | /** 331 | * sessionStart 332 | * 333 | * @return bool 334 | */ 335 | public static function sessionStart() 336 | { 337 | if (PHP_SAPI != 'cli') { 338 | return session_start(); 339 | } 340 | 341 | self::tryGcSessions(); 342 | 343 | if (HttpCache::$instance->sessionStarted) { 344 | echo "already sessionStarted\n"; 345 | return true; 346 | } 347 | HttpCache::$instance->sessionStarted = true; 348 | // Generate a SID. 349 | if (!isset($_COOKIE[HttpCache::$sessionName]) || !is_file(HttpCache::$sessionPath . '/ses' . $_COOKIE[HttpCache::$sessionName])) { 350 | $file_name = tempnam(HttpCache::$sessionPath, 'ses'); 351 | if (!$file_name) { 352 | return false; 353 | } 354 | HttpCache::$instance->sessionFile = $file_name; 355 | $session_id = substr(basename($file_name), strlen('ses')); 356 | return self::setcookie( 357 | HttpCache::$sessionName 358 | , $session_id 359 | , ini_get('session.cookie_lifetime') 360 | , ini_get('session.cookie_path') 361 | , ini_get('session.cookie_domain') 362 | , ini_get('session.cookie_secure') 363 | , ini_get('session.cookie_httponly') 364 | ); 365 | } 366 | if (!HttpCache::$instance->sessionFile) { 367 | HttpCache::$instance->sessionFile = HttpCache::$sessionPath . '/ses' . $_COOKIE[HttpCache::$sessionName]; 368 | } 369 | // Read session from session file. 370 | if (HttpCache::$instance->sessionFile) { 371 | $raw = file_get_contents(HttpCache::$instance->sessionFile); 372 | if ($raw) { 373 | $_SESSION = unserialize($raw); 374 | } 375 | } 376 | return true; 377 | } 378 | 379 | /** 380 | * Save session. 381 | * 382 | * @return bool 383 | */ 384 | public static function sessionWriteClose() 385 | { 386 | if (PHP_SAPI != 'cli') { 387 | return session_write_close(); 388 | } 389 | if (!empty(HttpCache::$instance->sessionStarted) && !empty($_SESSION)) { 390 | $session_str = serialize($_SESSION); 391 | if ($session_str && HttpCache::$instance->sessionFile) { 392 | return file_put_contents(HttpCache::$instance->sessionFile, $session_str); 393 | } 394 | } 395 | return empty($_SESSION); 396 | } 397 | 398 | /** 399 | * End, like call exit in php-fpm. 400 | * 401 | * @param string $msg 402 | * @throws \Exception 403 | */ 404 | public static function end($msg = '') 405 | { 406 | if (PHP_SAPI != 'cli') { 407 | exit($msg); 408 | } 409 | if ($msg) { 410 | echo $msg; 411 | } 412 | throw new \Exception('jump_exit'); 413 | } 414 | 415 | /** 416 | * Get mime types. 417 | * 418 | * @return string 419 | */ 420 | public static function getMimeTypesFile() 421 | { 422 | return __DIR__ . '/Http/mime.types'; 423 | } 424 | 425 | /** 426 | * Parse $_FILES. 427 | * 428 | * @param string $http_body 429 | * @param string $http_post_boundary 430 | * @return void 431 | */ 432 | protected static function parseUploadFiles($http_body, $http_post_boundary) 433 | { 434 | $http_body = substr($http_body, 0, strlen($http_body) - (strlen($http_post_boundary) + 4)); 435 | $boundary_data_array = explode($http_post_boundary . "\r\n", $http_body); 436 | if ($boundary_data_array[0] === '') { 437 | unset($boundary_data_array[0]); 438 | } 439 | $key = -1; 440 | foreach ($boundary_data_array as $boundary_data_buffer) { 441 | list($boundary_header_buffer, $boundary_value) = explode("\r\n\r\n", $boundary_data_buffer, 2); 442 | // Remove \r\n from the end of buffer. 443 | $boundary_value = substr($boundary_value, 0, -2); 444 | $key ++; 445 | foreach (explode("\r\n", $boundary_header_buffer) as $item) { 446 | list($header_key, $header_value) = explode(": ", $item); 447 | $header_key = strtolower($header_key); 448 | switch ($header_key) { 449 | case "content-disposition": 450 | // Is file data. 451 | if (preg_match('/name="(.*?)"; filename="(.*?)"$/', $header_value, $match)) { 452 | // Parse $_FILES. 453 | $_FILES[$key] = array( 454 | 'name' => $match[1], 455 | 'file_name' => $match[2], 456 | 'file_data' => $boundary_value, 457 | 'file_size' => strlen($boundary_value), 458 | ); 459 | continue; 460 | } // Is post field. 461 | else { 462 | // Parse $_POST. 463 | if (preg_match('/name="(.*?)"$/', $header_value, $match)) { 464 | $_POST[$match[1]] = $boundary_value; 465 | } 466 | } 467 | break; 468 | case "content-type": 469 | // add file_type 470 | $_FILES[$key]['file_type'] = trim($header_value); 471 | break; 472 | } 473 | } 474 | } 475 | } 476 | 477 | /** 478 | * Try GC sessions. 479 | * 480 | * @return void 481 | */ 482 | public static function tryGcSessions() 483 | { 484 | if (HttpCache::$sessionGcProbability <= 0 || 485 | HttpCache::$sessionGcDivisor <= 0 || 486 | rand(1, HttpCache::$sessionGcDivisor) > HttpCache::$sessionGcProbability) { 487 | return; 488 | } 489 | 490 | $time_now = time(); 491 | foreach(glob(HttpCache::$sessionPath.'/ses*') as $file) { 492 | if(is_file($file) && $time_now - filemtime($file) > HttpCache::$sessionGcMaxLifeTime) { 493 | unlink($file); 494 | } 495 | } 496 | } 497 | } 498 | 499 | /** 500 | * Http cache for the current http response. 501 | */ 502 | class HttpCache 503 | { 504 | public static $codes = array( 505 | 100 => 'Continue', 506 | 101 => 'Switching Protocols', 507 | 200 => 'OK', 508 | 201 => 'Created', 509 | 202 => 'Accepted', 510 | 203 => 'Non-Authoritative Information', 511 | 204 => 'No Content', 512 | 205 => 'Reset Content', 513 | 206 => 'Partial Content', 514 | 300 => 'Multiple Choices', 515 | 301 => 'Moved Permanently', 516 | 302 => 'Found', 517 | 303 => 'See Other', 518 | 304 => 'Not Modified', 519 | 305 => 'Use Proxy', 520 | 306 => '(Unused)', 521 | 307 => 'Temporary Redirect', 522 | 400 => 'Bad Request', 523 | 401 => 'Unauthorized', 524 | 402 => 'Payment Required', 525 | 403 => 'Forbidden', 526 | 404 => 'Not Found', 527 | 405 => 'Method Not Allowed', 528 | 406 => 'Not Acceptable', 529 | 407 => 'Proxy Authentication Required', 530 | 408 => 'Request Timeout', 531 | 409 => 'Conflict', 532 | 410 => 'Gone', 533 | 411 => 'Length Required', 534 | 412 => 'Precondition Failed', 535 | 413 => 'Request Entity Too Large', 536 | 414 => 'Request-URI Too Long', 537 | 415 => 'Unsupported Media Type', 538 | 416 => 'Requested Range Not Satisfiable', 539 | 417 => 'Expectation Failed', 540 | 422 => 'Unprocessable Entity', 541 | 423 => 'Locked', 542 | 500 => 'Internal Server Error', 543 | 501 => 'Not Implemented', 544 | 502 => 'Bad Gateway', 545 | 503 => 'Service Unavailable', 546 | 504 => 'Gateway Timeout', 547 | 505 => 'HTTP Version Not Supported', 548 | ); 549 | 550 | /** 551 | * @var HttpCache 552 | */ 553 | public static $instance = null; 554 | public static $header = array(); 555 | public static $sessionPath = ''; 556 | public static $sessionName = ''; 557 | public static $sessionGcProbability = 1; 558 | public static $sessionGcDivisor = 1000; 559 | public static $sessionGcMaxLifeTime = 1440; 560 | public $sessionStarted = false; 561 | public $sessionFile = ''; 562 | 563 | public static function init() 564 | { 565 | self::$sessionName = ini_get('session.name'); 566 | self::$sessionPath = @session_save_path(); 567 | if (!self::$sessionPath || strpos(self::$sessionPath, 'tcp://') === 0) { 568 | self::$sessionPath = sys_get_temp_dir(); 569 | } 570 | 571 | if ($gc_probability = ini_get('session.gc_probability')) { 572 | self::$sessionGcProbability = $gc_probability; 573 | } 574 | 575 | if ($gc_divisor = ini_get('session.gc_divisor')) { 576 | self::$sessionGcDivisor = $gc_divisor; 577 | } 578 | 579 | if ($gc_max_life_time = ini_get('session.gc_maxlifetime')) { 580 | self::$sessionGcMaxLifeTime = $gc_max_life_time; 581 | } 582 | } 583 | } 584 | 585 | HttpCache::init(); 586 | --------------------------------------------------------------------------------