├── .gitignore ├── README.md ├── start.php ├── Workerman ├── Lib │ ├── Constants.php │ └── Timer.php ├── composer.json ├── MIT-LICENSE.txt ├── Protocols │ ├── Frame.php │ ├── ProtocolInterface.php │ ├── Text.php │ ├── Http │ │ └── mime.types │ ├── Ws.php │ ├── Websocket.php │ └── Http.php ├── Events │ ├── EventInterface.php │ ├── Ev.php │ ├── Event.php │ ├── Libevent.php │ └── Select.php ├── Connection │ ├── ConnectionInterface.php │ ├── UdpConnection.php │ ├── AsyncTcpConnection.php │ └── TcpConnection.php ├── Autoloader.php ├── WebServer.php ├── README.md └── Worker.php ├── Applications └── Queue │ ├── client_demo.php │ ├── Consumer │ └── Mail.php │ └── start.php └── MIT-LICENSE.txt /.gitignore: -------------------------------------------------------------------------------- 1 | .buildpath 2 | .project 3 | .settings/org.eclipse.php.core.prefs -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | workerman-queue 2 | ========= 3 | 4 | 一个简单的消息队列,基于Linux sysv 队列实现。 5 | 6 | 需要安装 sysvmsg扩展 7 | 8 | ## 注意,默认sysv队列的存储空间比较小,可以通过设置内核参数加大sysv队列的空间 9 | 10 | // 此文件可以用于查看和修改在队列中排队消息的数量的上界 11 | 12 | /proc/sys/fs/mqueue/msg_max 13 | 14 | // 此文件可以用于查看和修改消息大小最大值的上界 15 | 16 | /proc/sys/fs/mqueue/msgsize_max 17 | 18 | // 此文件可以用于查看和修改整个系统上可创建的消息队列的数量的限制值 19 | 20 | /proc/sys/fs/mqueue/queues_max 21 | 22 | 23 | ## run 24 | 25 | 进入workerman根目录 26 | 27 | debug模式运行 28 | 29 | php start.php start 30 | 31 | daemon模式运行 32 | 33 | php start.php start -d 34 | 35 | 36 | 37 | ## test 38 | php Applications/Queue/client_demo.php -------------------------------------------------------------------------------- /start.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 | // For onError callback. 25 | define('WORKERMAN_CONNECT_FAIL', 1); 26 | // For onError callback. 27 | define('WORKERMAN_SEND_FAIL', 2); 28 | 29 | // Compatible with php7 30 | if(!class_exists('Error')) 31 | { 32 | class Error extends Exception 33 | { 34 | } 35 | } -------------------------------------------------------------------------------- /Applications/Queue/client_demo.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 | // 连接队列服务,ip地址为队列服务端ip,这里假设是本机部署ip为127.0.0.1 15 | $client = stream_socket_client("tcp://127.0.0.1:1236", $err_no, $err_msg, 5); 16 | if(!$client) 17 | { 18 | exit($err_msg); 19 | } 20 | // 一个邮件任务 21 | $message = array( 22 | 'class' => 'Mail', 23 | 'method' => 'send', 24 | 'args' => array('xiaoming', 'xiaowang', 'hello'), 25 | ); 26 | // 数据末尾加一个换行,使其符合Text协议。使用json编码 27 | $message = json_encode($message)."\n"; 28 | // 向队列发送任务,让队列慢慢去执行 29 | fwrite($client, $message); 30 | // 队列返回的结果,这个结果是立即返回的 31 | echo fread($client, 8192); 32 | -------------------------------------------------------------------------------- /Applications/Queue/Consumer/Mail.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 Consumer; 15 | 16 | /** 17 | * 消费者逻辑 18 | * @author walkor 19 | */ 20 | class Mail 21 | { 22 | /** 23 | * 模拟慢任务 24 | * 数据包格式: {"class":"Mail", "method":"send", "args":["xiaoming","xiaowang","hello"]} 25 | * @param string $from 26 | * @param string $to 27 | * @param string $content 28 | * @return void 29 | */ 30 | public function send($from, $to, $content) 31 | { 32 | // 作为例子,代码省略 33 | sleep(5); 34 | echo "from:$from to:$to content:$content mail send success\n"; 35 | } 36 | 37 | public function read() 38 | { 39 | // .. 40 | } 41 | } -------------------------------------------------------------------------------- /Workerman/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "workerman/workerman", 3 | "type" : "project", 4 | "keywords": ["event-loop", "asynchronous"], 5 | "homepage": "http://www.workerman.net", 6 | "license" : "MIT", 7 | "description": "An asynchronous event driven PHP framework for easily building fast, scalable network applications.", 8 | "authors" : [ 9 | { 10 | "name" : "walkor", 11 | "email" : "walkor@workerman.net", 12 | "homepage" : "http://www.workerman.net", 13 | "role": "Developer" 14 | } 15 | ], 16 | "support" : { 17 | "email" : "walkor@workerman.net", 18 | "issues": "https://github.com/walkor/workerman/issues", 19 | "forum" : "http://wenda.workerman.net/", 20 | "wiki" : "http://doc3.workerman.net/index.html", 21 | "source": "https://github.com/walkor/workerman" 22 | }, 23 | "require": { 24 | "php": ">=5.3", 25 | "ext-sockets": "*" 26 | }, 27 | "suggest": { 28 | "ext-libevent": "For better performance." 29 | }, 30 | "autoload": { 31 | "psr-4": {"Workerman\\": "./"} 32 | }, 33 | "minimum-stability":"dev" 34 | } 35 | -------------------------------------------------------------------------------- /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/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/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 | * Encode. 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 | * Decode. 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/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 | * Signal event. 34 | * 35 | * @var int 36 | */ 37 | const EV_SIGNAL = 4; 38 | 39 | /** 40 | * Timer event. 41 | * 42 | * @var int 43 | */ 44 | const EV_TIMER = 8; 45 | 46 | /** 47 | * Timer once event. 48 | * 49 | * @var int 50 | */ 51 | const EV_TIMER_ONCE = 16; 52 | 53 | /** 54 | * Add event listener to event loop. 55 | * 56 | * @param mixed $fd 57 | * @param int $flag 58 | * @param callable $func 59 | * @param mixed $args 60 | * @return bool 61 | */ 62 | public function add($fd, $flag, $func, $args = null); 63 | 64 | /** 65 | * Remove event listener from event loop. 66 | * 67 | * @param mixed $fd 68 | * @param int $flag 69 | * @return bool 70 | */ 71 | public function del($fd, $flag); 72 | 73 | /** 74 | * Remove all timers. 75 | * 76 | * @return void 77 | */ 78 | public function clearAllTimer(); 79 | 80 | /** 81 | * Main loop. 82 | * 83 | * @return void 84 | */ 85 | public function loop(); 86 | } 87 | -------------------------------------------------------------------------------- /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/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 | * Close connection. 78 | * 79 | * @param $data 80 | * @return void 81 | */ 82 | abstract public function close($data = null); 83 | } 84 | -------------------------------------------------------------------------------- /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 | 59 | if (is_file($class_file)) { 60 | require_once($class_file); 61 | if (class_exists($name, false)) { 62 | return true; 63 | } 64 | } 65 | return false; 66 | } 67 | } 68 | 69 | spl_autoload_register('\Workerman\Autoloader::loadByNamespace'); -------------------------------------------------------------------------------- /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 ip. 38 | * 39 | * @var string 40 | */ 41 | protected $_remoteIp = ''; 42 | 43 | /** 44 | * Remote port. 45 | * 46 | * @var int 47 | */ 48 | protected $_remotePort = 0; 49 | 50 | /** 51 | * Remote address. 52 | * 53 | * @var string 54 | */ 55 | protected $_remoteAddress = ''; 56 | 57 | /** 58 | * Construct. 59 | * 60 | * @param resource $socket 61 | * @param string $remote_address 62 | */ 63 | public function __construct($socket, $remote_address) 64 | { 65 | $this->_socket = $socket; 66 | $this->_remoteAddress = $remote_address; 67 | } 68 | 69 | /** 70 | * Sends data on the connection. 71 | * 72 | * @param string $send_buffer 73 | * @param bool $raw 74 | * @return void|boolean 75 | */ 76 | public function send($send_buffer, $raw = false) 77 | { 78 | if (false === $raw && $this->protocol) { 79 | $parser = $this->protocol; 80 | $send_buffer = $parser::encode($send_buffer, $this); 81 | if ($send_buffer === '') { 82 | return null; 83 | } 84 | } 85 | return strlen($send_buffer) === stream_socket_sendto($this->_socket, $send_buffer, 0, $this->_remoteAddress); 86 | } 87 | 88 | /** 89 | * Get remote IP. 90 | * 91 | * @return string 92 | */ 93 | public function getRemoteIp() 94 | { 95 | if (!$this->_remoteIp) { 96 | list($this->_remoteIp, $this->_remotePort) = explode(':', $this->_remoteAddress, 2); 97 | } 98 | return $this->_remoteIp; 99 | } 100 | 101 | /** 102 | * Get remote port. 103 | * 104 | * @return int 105 | */ 106 | public function getRemotePort() 107 | { 108 | if (!$this->_remotePort) { 109 | list($this->_remoteIp, $this->_remotePort) = explode(':', $this->_remoteAddress, 2); 110 | } 111 | return $this->_remotePort; 112 | } 113 | 114 | /** 115 | * Close connection. 116 | * 117 | * @param mixed $data 118 | * @return bool 119 | */ 120 | public function close($data = null) 121 | { 122 | if ($data !== null) { 123 | $this->send($data); 124 | } 125 | return true; 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Applications/Queue/start.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 | use Workerman\Worker; 15 | // 自动加载类 16 | require_once __DIR__ . '/../../Workerman/Autoloader.php'; 17 | 18 | 19 | /******************************************************************* 20 | * 基于Worker实现的一个简单的消息队列服务 21 | * 服务分为两组进程, 22 | * 一组监听端口并把发来的数据放到sysv消息队列中 23 | * 另外一组进程为消费者,负责从队列中读取数据并处理 24 | * 25 | * 注意: 26 | * 使用的是系统自带的 sysv 队列,即使队列服务重启数据也不会丢失 27 | * 但服务器重启后数据会丢失 28 | * 系统默认sysv队列容量比较小,可以根据需要配置Linux内核参数, 29 | * 增大队列容量 30 | *******************************************************************/ 31 | 32 | // 队列的id。为了避免混淆,可以和监听的端口相同 33 | $QUEUE_ID = 1236; 34 | 35 | // #######消息队列服务监听的端口########## 36 | $msg_recver = new Worker('Text://0.0.0.0:1236'); 37 | // 向哪个队列放数据 38 | $msg_recver->queueId = $QUEUE_ID; 39 | 40 | if(!extension_loaded('sysvmsg')) 41 | { 42 | echo "Please install sysvmsg extension.\n"; 43 | exit; 44 | } 45 | 46 | /** 47 | * 进程启动时,初始化sysv消息队列 48 | */ 49 | $msg_recver->onWorkerStart = function($msg_recver) 50 | { 51 | $msg_recver->queue = msg_get_queue($msg_recver->queueId); 52 | }; 53 | 54 | /** 55 | * 服务接收到消息时,将消息写入系统的sysv消息队列,消费者从该队列中读取 56 | */ 57 | $msg_recver->onMessage = function($connection, $message) use ($msg_recver) 58 | { 59 | $msgtype = 1; 60 | $errorcode = 500; 61 | // @see http://php.net/manual/zh/function.msg-send.php 62 | if(extension_loaded('sysvmsg') && msg_send( $msg_recver->queue , $msgtype , $message, true , true , $errorcode)) 63 | { 64 | return $connection->send('{"code":0, "msg":"success"}'); 65 | } 66 | else 67 | { 68 | return $connection->send('{"code":'.$errorcode.', "msg":"fail"}'); 69 | } 70 | }; 71 | 72 | 73 | // ######## 消息队列消费者 ######## 74 | $consumer = new Worker(); 75 | // 消费的队列的id 76 | $consumer->queueId = $QUEUE_ID; 77 | // 慢任务,消费者的进程数可以开多一些 78 | $consumer->count = 32; 79 | 80 | /** 81 | * 进程启动阻塞式的从队列中读取数据并处理 82 | */ 83 | $consumer->onWorkerStart = function($consumer) 84 | { 85 | // 获得队列资源 86 | $consumer->queue = msg_get_queue($consumer->queueId); 87 | \Workerman\Lib\Timer::add(0.5, function() use ($consumer){ 88 | if(extension_loaded('sysvmsg')) 89 | { 90 | // 循环取数据 91 | while(1) 92 | { 93 | $desiredmsgtype = 1; 94 | $msgtype = 0; 95 | $message = ''; 96 | $maxsize = 65535; 97 | // 从队列中获取消息 @see http://php.net/manual/zh/function.msg-receive.php 98 | @msg_receive($consumer->queue , $desiredmsgtype , $msgtype , $maxsize , $message, true, MSG_IPC_NOWAIT); 99 | if(!$message) 100 | { 101 | return; 102 | } 103 | // 假设消息数据为json,格式类似{"class":"class_name", "method":"method_name", "args":[]} 104 | $message = json_decode($message, true); 105 | // 格式如果是正确的,则尝试执行对应的类方法 106 | if(isset($message['class']) && isset($message['method']) && isset($message['args'])) 107 | { 108 | // 要调用的类名,加上Consumer命名空间 109 | $class_name = "\\Consumer\\".$message['class']; 110 | // 要调用的方法名 111 | $method = $message['method']; 112 | // 调用参数,是个数组 113 | $args = (array)$message['args']; 114 | 115 | // 类存在则尝试执行 116 | if(class_exists($class_name)) 117 | { 118 | $class = new $class_name; 119 | $callback = array($class, $method); 120 | if(is_callable($callback)) 121 | { 122 | call_user_func_array($callback, $args); 123 | } 124 | else 125 | { 126 | echo "$class_name::$method not exist\n"; 127 | } 128 | } 129 | else 130 | { 131 | echo "$class_name not exist\n"; 132 | } 133 | } 134 | else 135 | { 136 | echo "unknow message\n"; 137 | } 138 | } 139 | } 140 | }); 141 | }; 142 | 143 | // 如果不是在根目录启动,则运行runAll方法 144 | if(!defined('GLOBAL_START')) 145 | { 146 | Worker::runAll(); 147 | } 148 | -------------------------------------------------------------------------------- /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 bool 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 true; 111 | } 112 | 113 | 114 | /** 115 | * Tick. 116 | * 117 | * @return void 118 | */ 119 | public static function tick() 120 | { 121 | if (empty(self::$_tasks)) { 122 | pcntl_alarm(0); 123 | return; 124 | } 125 | 126 | $time_now = time(); 127 | foreach (self::$_tasks as $run_time => $task_data) { 128 | if ($time_now >= $run_time) { 129 | foreach ($task_data as $index => $one_task) { 130 | $task_func = $one_task[0]; 131 | $task_args = $one_task[1]; 132 | $persistent = $one_task[2]; 133 | $time_interval = $one_task[3]; 134 | try { 135 | call_user_func_array($task_func, $task_args); 136 | } catch (\Exception $e) { 137 | echo $e; 138 | } 139 | if ($persistent) { 140 | self::add($time_interval, $task_func, $task_args); 141 | } 142 | } 143 | unset(self::$_tasks[$run_time]); 144 | } 145 | } 146 | } 147 | 148 | /** 149 | * Remove a timer. 150 | * 151 | * @param mixed $timer_id 152 | * @return bool 153 | */ 154 | public static function del($timer_id) 155 | { 156 | if (self::$_event) { 157 | return self::$_event->del($timer_id, EventInterface::EV_TIMER); 158 | } 159 | 160 | return false; 161 | } 162 | 163 | /** 164 | * Remove all timers. 165 | * 166 | * @return void 167 | */ 168 | public static function delAll() 169 | { 170 | self::$_tasks = array(); 171 | pcntl_alarm(0); 172 | if (self::$_event) { 173 | self::$_event->clearAllTimer(); 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /Workerman/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 | /** 16 | * ev eventloop 17 | */ 18 | class Ev implements EventInterface 19 | { 20 | /** 21 | * All listeners for read/write event. 22 | * 23 | * @var array 24 | */ 25 | protected $_allEvents = array(); 26 | 27 | /** 28 | * Event listeners of signal. 29 | * 30 | * @var array 31 | */ 32 | protected $_eventSignal = array(); 33 | 34 | /** 35 | * All timer event listeners. 36 | * [func, args, event, flag, time_interval] 37 | * 38 | * @var array 39 | */ 40 | protected $_eventTimer = array(); 41 | 42 | /** 43 | * Timer id. 44 | * 45 | * @var int 46 | */ 47 | protected static $_timerId = 1; 48 | 49 | /** 50 | * Add a timer. 51 | * {@inheritdoc} 52 | */ 53 | public function add($fd, $flag, $func, $args = null) 54 | { 55 | $callback = function ($event, $socket) use ($fd, $func) { 56 | try { 57 | call_user_func($func, $fd); 58 | } catch (\Exception $e) { 59 | echo $e; 60 | exit(250); 61 | } catch (\Error $e) { 62 | echo $e; 63 | exit(250); 64 | } 65 | }; 66 | 67 | switch ($flag) { 68 | case self::EV_SIGNAL: 69 | $event = new \EvSignal($fd, $callback); 70 | $this->_eventSignal[$fd] = $event; 71 | return true; 72 | case self::EV_TIMER: 73 | case self::EV_TIMER_ONCE: 74 | $repeat = $flag == self::EV_TIMER_ONCE ? 0 : $fd; 75 | $param = array($func, (array)$args, $flag, $fd, self::$_timerId); 76 | $event = new \EvTimer($fd, $repeat, array($this, 'timerCallback'), $param); 77 | $this->_eventTimer[self::$_timerId] = $event; 78 | return self::$_timerId++; 79 | default : 80 | $fd_key = (int)$fd; 81 | $real_flag = $flag === self::EV_READ ? \Ev::READ : \Ev::WRITE; 82 | $event = new \EvIo($fd, $real_flag, $callback); 83 | $this->_allEvents[$fd_key][$flag] = $event; 84 | return true; 85 | } 86 | 87 | } 88 | 89 | /** 90 | * Remove a timer. 91 | * {@inheritdoc} 92 | */ 93 | public function del($fd, $flag) 94 | { 95 | switch ($flag) { 96 | case self::EV_READ: 97 | case self::EV_WRITE: 98 | $fd_key = (int)$fd; 99 | if (isset($this->_allEvents[$fd_key][$flag])) { 100 | $this->_allEvents[$fd_key][$flag]->stop(); 101 | unset($this->_allEvents[$fd_key][$flag]); 102 | } 103 | if (empty($this->_allEvents[$fd_key])) { 104 | unset($this->_allEvents[$fd_key]); 105 | } 106 | break; 107 | case self::EV_SIGNAL: 108 | $fd_key = (int)$fd; 109 | if (isset($this->_eventSignal[$fd_key])) { 110 | $this->_allEvents[$fd_key][$flag]->stop(); 111 | unset($this->_eventSignal[$fd_key]); 112 | } 113 | break; 114 | case self::EV_TIMER: 115 | case self::EV_TIMER_ONCE: 116 | if (isset($this->_eventTimer[$fd])) { 117 | $this->_eventTimer[$fd]->stop(); 118 | unset($this->_eventTimer[$fd]); 119 | } 120 | break; 121 | } 122 | return true; 123 | } 124 | 125 | /** 126 | * Timer callback. 127 | * 128 | * @param \EvWatcher $event 129 | */ 130 | public function timerCallback($event) 131 | { 132 | $param = $event->data; 133 | $timer_id = $param[4]; 134 | if ($param[2] === self::EV_TIMER_ONCE) { 135 | $this->_eventTimer[$timer_id]->stop(); 136 | unset($this->_eventTimer[$timer_id]); 137 | } 138 | try { 139 | call_user_func_array($param[0], $param[1]); 140 | } catch (\Exception $e) { 141 | echo $e; 142 | exit(250); 143 | } catch (\Error $e) { 144 | echo $e; 145 | exit(250); 146 | } 147 | } 148 | 149 | /** 150 | * Remove all timers. 151 | * 152 | * @return void 153 | */ 154 | public function clearAllTimer() 155 | { 156 | foreach ($this->_eventTimer as $event) { 157 | $event->stop(); 158 | } 159 | $this->_eventTimer = array(); 160 | } 161 | 162 | /** 163 | * Main loop. 164 | * 165 | * @see EventInterface::loop() 166 | */ 167 | public function loop() 168 | { 169 | \Ev::run(); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /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 | /** 17 | * libevent eventloop 18 | */ 19 | class Event implements EventInterface 20 | { 21 | /** 22 | * Event base. 23 | * @var object 24 | */ 25 | protected $_eventBase = null; 26 | 27 | /** 28 | * All listeners for read/write event. 29 | * @var array 30 | */ 31 | protected $_allEvents = array(); 32 | 33 | /** 34 | * Event listeners of signal. 35 | * @var array 36 | */ 37 | protected $_eventSignal = array(); 38 | 39 | /** 40 | * All timer event listeners. 41 | * [func, args, event, flag, time_interval] 42 | * @var array 43 | */ 44 | protected $_eventTimer = array(); 45 | 46 | /** 47 | * Timer id. 48 | * @var int 49 | */ 50 | protected static $_timerId = 1; 51 | 52 | /** 53 | * construct 54 | * @return void 55 | */ 56 | public function __construct() 57 | { 58 | $this->_eventBase = new \EventBase(); 59 | } 60 | 61 | /** 62 | * @see EventInterface::add() 63 | */ 64 | public function add($fd, $flag, $func, $args=array()) 65 | { 66 | switch ($flag) { 67 | case self::EV_SIGNAL: 68 | 69 | $fd_key = (int)$fd; 70 | $event = \Event::signal($this->_eventBase, $fd, $func); 71 | if (!$event||!$event->add()) { 72 | return false; 73 | } 74 | $this->_eventSignal[$fd_key] = $event; 75 | return true; 76 | 77 | case self::EV_TIMER: 78 | case self::EV_TIMER_ONCE: 79 | 80 | $param = array($func, (array)$args, $flag, $fd, self::$_timerId); 81 | $event = new \Event($this->_eventBase, -1, \Event::TIMEOUT|\Event::PERSIST, array($this, "timerCallback"), $param); 82 | if (!$event||!$event->addTimer($fd)) { 83 | return false; 84 | } 85 | $this->_eventTimer[self::$_timerId] = $event; 86 | return self::$_timerId++; 87 | 88 | default : 89 | $fd_key = (int)$fd; 90 | $real_flag = $flag === self::EV_READ ? \Event::READ | \Event::PERSIST : \Event::WRITE | \Event::PERSIST; 91 | $event = new \Event($this->_eventBase, $fd, $real_flag, $func, $fd); 92 | if (!$event||!$event->add()) { 93 | return false; 94 | } 95 | $this->_allEvents[$fd_key][$flag] = $event; 96 | return true; 97 | } 98 | } 99 | 100 | /** 101 | * @see Events\EventInterface::del() 102 | */ 103 | public function del($fd, $flag) 104 | { 105 | switch ($flag) { 106 | 107 | case self::EV_READ: 108 | case self::EV_WRITE: 109 | 110 | $fd_key = (int)$fd; 111 | if (isset($this->_allEvents[$fd_key][$flag])) { 112 | $this->_allEvents[$fd_key][$flag]->del(); 113 | unset($this->_allEvents[$fd_key][$flag]); 114 | } 115 | if (empty($this->_allEvents[$fd_key])) { 116 | unset($this->_allEvents[$fd_key]); 117 | } 118 | break; 119 | 120 | case self::EV_SIGNAL: 121 | 122 | $fd_key = (int)$fd; 123 | if (isset($this->_eventSignal[$fd_key])) { 124 | $this->_allEvents[$fd_key][$flag]->del(); 125 | unset($this->_eventSignal[$fd_key]); 126 | } 127 | break; 128 | 129 | case self::EV_TIMER: 130 | case self::EV_TIMER_ONCE: 131 | if (isset($this->_eventTimer[$fd])) { 132 | $this->_eventTimer[$fd]->del(); 133 | unset($this->_eventTimer[$fd]); 134 | } 135 | break; 136 | } 137 | return true; 138 | } 139 | 140 | /** 141 | * Timer callback. 142 | * @param null $fd 143 | * @param int $what 144 | * @param int $timer_id 145 | */ 146 | public function timerCallback($fd, $what, $param) 147 | { 148 | $timer_id = $param[4]; 149 | 150 | if ($param[2] === self::EV_TIMER_ONCE) { 151 | $this->_eventTimer[$timer_id]->del(); 152 | unset($this->_eventTimer[$timer_id]); 153 | } 154 | 155 | try { 156 | call_user_func_array($param[0], $param[1]); 157 | } catch (\Exception $e) { 158 | echo $e; 159 | exit(250); 160 | } catch (\Error $e) { 161 | echo $e; 162 | exit(250); 163 | } 164 | } 165 | 166 | /** 167 | * @see Events\EventInterface::clearAllTimer() 168 | * @return void 169 | */ 170 | public function clearAllTimer() 171 | { 172 | foreach ($this->_eventTimer as $event) { 173 | $event->del(); 174 | } 175 | $this->_eventTimer = array(); 176 | } 177 | 178 | 179 | /** 180 | * @see EventInterface::loop() 181 | */ 182 | public function loop() 183 | { 184 | $this->_eventBase->loop(); 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /Workerman/Connection/AsyncTcpConnection.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | namespace Workerman\Connection; 15 | 16 | use Workerman\Events\EventInterface; 17 | use Workerman\Worker; 18 | use Exception; 19 | 20 | /** 21 | * AsyncTcpConnection. 22 | */ 23 | class AsyncTcpConnection extends TcpConnection 24 | { 25 | /** 26 | * Emitted when socket connection is successfully established. 27 | * 28 | * @var callback 29 | */ 30 | public $onConnect = null; 31 | 32 | /** 33 | * Status. 34 | * 35 | * @var int 36 | */ 37 | protected $_status = self::STATUS_CONNECTING; 38 | 39 | /** 40 | * Remote host. 41 | * 42 | * @var string 43 | */ 44 | protected $_remoteHost = ''; 45 | 46 | /** 47 | * Construct. 48 | * 49 | * @param string $remote_address 50 | * @throws Exception 51 | */ 52 | public function __construct($remote_address) 53 | { 54 | list($scheme, $address) = explode(':', $remote_address, 2); 55 | if ($scheme != 'tcp') { 56 | // Get application layer protocol. 57 | $scheme = ucfirst($scheme); 58 | $this->protocol = '\\Protocols\\' . $scheme; 59 | if (!class_exists($this->protocol)) { 60 | $this->protocol = '\\Workerman\\Protocols\\' . $scheme; 61 | if (!class_exists($this->protocol)) { 62 | throw new Exception("class \\Protocols\\$scheme not exist"); 63 | } 64 | } 65 | } 66 | $this->_remoteAddress = substr($address, 2); 67 | $this->_remoteHost = substr($this->_remoteAddress, 0, strrpos($this->_remoteAddress, ':')); 68 | $this->id = self::$_idRecorder++; 69 | // For statistics. 70 | self::$statistics['connection_count']++; 71 | $this->maxSendBufferSize = self::$defaultMaxSendBufferSize; 72 | } 73 | 74 | public function connect() 75 | { 76 | // Open socket connection asynchronously. 77 | $this->_socket = stream_socket_client("tcp://{$this->_remoteAddress}", $errno, $errstr, 0, 78 | STREAM_CLIENT_ASYNC_CONNECT); 79 | // If failed attempt to emit onError callback. 80 | if (!$this->_socket) { 81 | $this->_status = self::STATUS_CLOSED; 82 | $this->emitError(WORKERMAN_CONNECT_FAIL, $errstr); 83 | return; 84 | } 85 | // Add socket to global event loop waiting connection is successfully established or faild. 86 | Worker::$globalEvent->add($this->_socket, EventInterface::EV_WRITE, array($this, 'checkConnection')); 87 | } 88 | 89 | /** 90 | * Get remote address. 91 | * 92 | * @return string 93 | */ 94 | public function getRemoteHost() 95 | { 96 | return $this->_remoteHost; 97 | } 98 | 99 | /** 100 | * Try to emit onError callback. 101 | * 102 | * @param int $code 103 | * @param string $msg 104 | * @return void 105 | */ 106 | protected function emitError($code, $msg) 107 | { 108 | if ($this->onError) { 109 | try { 110 | call_user_func($this->onError, $this, $code, $msg); 111 | } catch (\Exception $e) { 112 | echo $e; 113 | exit(250); 114 | } catch (\Error $e) { 115 | echo $e; 116 | exit(250); 117 | } 118 | } 119 | } 120 | 121 | /** 122 | * Check connection is successfully established or faild. 123 | * 124 | * @param resource $socket 125 | * @return void 126 | */ 127 | public function checkConnection($socket) 128 | { 129 | // Check socket state. 130 | if (stream_socket_get_name($socket, true)) { 131 | // Remove write listener. 132 | Worker::$globalEvent->del($socket, EventInterface::EV_WRITE); 133 | // Nonblocking. 134 | stream_set_blocking($socket, 0); 135 | // Try to open keepalive for tcp and disable Nagle algorithm. 136 | if (function_exists('socket_import_stream')) { 137 | $raw_socket = socket_import_stream($socket); 138 | socket_set_option($raw_socket, SOL_SOCKET, SO_KEEPALIVE, 1); 139 | socket_set_option($raw_socket, SOL_TCP, TCP_NODELAY, 1); 140 | } 141 | // Register a listener waiting read event. 142 | Worker::$globalEvent->add($socket, EventInterface::EV_READ, array($this, 'baseRead')); 143 | // There are some data waiting to send. 144 | if ($this->_sendBuffer) { 145 | Worker::$globalEvent->add($socket, EventInterface::EV_WRITE, array($this, 'baseWrite')); 146 | } 147 | $this->_status = self::STATUS_ESTABLISH; 148 | $this->_remoteAddress = stream_socket_get_name($socket, true); 149 | // Try to emit onConnect callback. 150 | if ($this->onConnect) { 151 | try { 152 | call_user_func($this->onConnect, $this); 153 | } catch (\Exception $e) { 154 | echo $e; 155 | exit(250); 156 | } catch (\Error $e) { 157 | echo $e; 158 | exit(250); 159 | } 160 | } 161 | } else { 162 | // Connection failed. 163 | $this->emitError(WORKERMAN_CONNECT_FAIL, 'connect fail'); 164 | $this->destroy(); 165 | $this->onConnect = null; 166 | } 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /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 | /** 17 | * libevent eventloop 18 | */ 19 | class Libevent implements EventInterface 20 | { 21 | /** 22 | * Event base. 23 | * 24 | * @var resource 25 | */ 26 | protected $_eventBase = null; 27 | 28 | /** 29 | * All listeners for read/write event. 30 | * 31 | * @var array 32 | */ 33 | protected $_allEvents = array(); 34 | 35 | /** 36 | * Event listeners of signal. 37 | * 38 | * @var array 39 | */ 40 | protected $_eventSignal = array(); 41 | 42 | /** 43 | * All timer event listeners. 44 | * [func, args, event, flag, time_interval] 45 | * 46 | * @var array 47 | */ 48 | protected $_eventTimer = array(); 49 | 50 | /** 51 | * construct 52 | */ 53 | public function __construct() 54 | { 55 | $this->_eventBase = event_base_new(); 56 | } 57 | 58 | /** 59 | * {@inheritdoc} 60 | */ 61 | public function add($fd, $flag, $func, $args = array()) 62 | { 63 | switch ($flag) { 64 | case self::EV_SIGNAL: 65 | $fd_key = (int)$fd; 66 | $real_flag = EV_SIGNAL | EV_PERSIST; 67 | $this->_eventSignal[$fd_key] = event_new(); 68 | if (!event_set($this->_eventSignal[$fd_key], $fd, $real_flag, $func, null)) { 69 | return false; 70 | } 71 | if (!event_base_set($this->_eventSignal[$fd_key], $this->_eventBase)) { 72 | return false; 73 | } 74 | if (!event_add($this->_eventSignal[$fd_key])) { 75 | return false; 76 | } 77 | return true; 78 | case self::EV_TIMER: 79 | case self::EV_TIMER_ONCE: 80 | $event = event_new(); 81 | $timer_id = (int)$event; 82 | if (!event_set($event, 0, EV_TIMEOUT, array($this, 'timerCallback'), $timer_id)) { 83 | return false; 84 | } 85 | 86 | if (!event_base_set($event, $this->_eventBase)) { 87 | return false; 88 | } 89 | 90 | $time_interval = $fd * 1000000; 91 | if (!event_add($event, $time_interval)) { 92 | return false; 93 | } 94 | $this->_eventTimer[$timer_id] = array($func, (array)$args, $event, $flag, $time_interval); 95 | return $timer_id; 96 | 97 | default : 98 | $fd_key = (int)$fd; 99 | $real_flag = $flag === self::EV_READ ? EV_READ | EV_PERSIST : EV_WRITE | EV_PERSIST; 100 | 101 | $event = event_new(); 102 | 103 | if (!event_set($event, $fd, $real_flag, $func, null)) { 104 | return false; 105 | } 106 | 107 | if (!event_base_set($event, $this->_eventBase)) { 108 | return false; 109 | } 110 | 111 | if (!event_add($event)) { 112 | return false; 113 | } 114 | 115 | $this->_allEvents[$fd_key][$flag] = $event; 116 | 117 | return true; 118 | } 119 | 120 | } 121 | 122 | /** 123 | * {@inheritdoc} 124 | */ 125 | public function del($fd, $flag) 126 | { 127 | switch ($flag) { 128 | case self::EV_READ: 129 | case self::EV_WRITE: 130 | $fd_key = (int)$fd; 131 | if (isset($this->_allEvents[$fd_key][$flag])) { 132 | event_del($this->_allEvents[$fd_key][$flag]); 133 | unset($this->_allEvents[$fd_key][$flag]); 134 | } 135 | if (empty($this->_allEvents[$fd_key])) { 136 | unset($this->_allEvents[$fd_key]); 137 | } 138 | break; 139 | case self::EV_SIGNAL: 140 | $fd_key = (int)$fd; 141 | if (isset($this->_eventSignal[$fd_key])) { 142 | event_del($this->_eventSignal[$fd_key]); 143 | unset($this->_eventSignal[$fd_key]); 144 | } 145 | break; 146 | case self::EV_TIMER: 147 | case self::EV_TIMER_ONCE: 148 | // 这里 fd 为timerid 149 | if (isset($this->_eventTimer[$fd])) { 150 | event_del($this->_eventTimer[$fd][2]); 151 | unset($this->_eventTimer[$fd]); 152 | } 153 | break; 154 | } 155 | return true; 156 | } 157 | 158 | /** 159 | * Timer callback. 160 | * 161 | * @param mixed $_null1 162 | * @param int $_null2 163 | * @param mixed $timer_id 164 | */ 165 | protected function timerCallback($_null1, $_null2, $timer_id) 166 | { 167 | if ($this->_eventTimer[$timer_id][3] === self::EV_TIMER) { 168 | event_add($this->_eventTimer[$timer_id][2], $this->_eventTimer[$timer_id][4]); 169 | } 170 | try { 171 | call_user_func_array($this->_eventTimer[$timer_id][0], $this->_eventTimer[$timer_id][1]); 172 | } catch (\Exception $e) { 173 | echo $e; 174 | exit(250); 175 | } catch (\Error $e) { 176 | echo $e; 177 | exit(250); 178 | } 179 | if (isset($this->_eventTimer[$timer_id]) && $this->_eventTimer[$timer_id][3] === self::EV_TIMER_ONCE) { 180 | $this->del($timer_id, self::EV_TIMER_ONCE); 181 | } 182 | } 183 | 184 | /** 185 | * {@inheritdoc} 186 | */ 187 | public function clearAllTimer() 188 | { 189 | foreach ($this->_eventTimer as $task_data) { 190 | event_del($task_data[2]); 191 | } 192 | $this->_eventTimer = array(); 193 | } 194 | 195 | /** 196 | * {@inheritdoc} 197 | */ 198 | public function loop() 199 | { 200 | event_base_loop($this->_eventBase); 201 | } 202 | } 203 | 204 | -------------------------------------------------------------------------------- /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 | * Timer scheduler. 51 | * {['data':timer_id, 'priority':run_timestamp], ..} 52 | * 53 | * @var \SplPriorityQueue 54 | */ 55 | protected $_scheduler = null; 56 | 57 | /** 58 | * All timer event listeners. 59 | * [[func, args, flag, timer_interval], ..] 60 | * 61 | * @var array 62 | */ 63 | protected $_task = array(); 64 | 65 | /** 66 | * Timer id. 67 | * 68 | * @var int 69 | */ 70 | protected $_timerId = 1; 71 | 72 | /** 73 | * Select timeout. 74 | * 75 | * @var int 76 | */ 77 | protected $_selectTimeout = 100000000; 78 | 79 | /** 80 | * Paired socket channels 81 | * 82 | * @var array 83 | */ 84 | protected $channel = array(); 85 | 86 | /** 87 | * Construct. 88 | */ 89 | public function __construct() 90 | { 91 | // Create a pipeline and put into the collection of the read to read the descriptor to avoid empty polling. 92 | $this->channel = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP); 93 | if ($this->channel) { 94 | stream_set_blocking($this->channel[0], 0); 95 | $this->_readFds[0] = $this->channel[0]; 96 | } 97 | // Init SplPriorityQueue. 98 | $this->_scheduler = new \SplPriorityQueue(); 99 | $this->_scheduler->setExtractFlags(\SplPriorityQueue::EXTR_BOTH); 100 | } 101 | 102 | /** 103 | * {@inheritdoc} 104 | */ 105 | public function add($fd, $flag, $func, $args = array()) 106 | { 107 | switch ($flag) { 108 | case self::EV_READ: 109 | $fd_key = (int)$fd; 110 | $this->_allEvents[$fd_key][$flag] = array($func, $fd); 111 | $this->_readFds[$fd_key] = $fd; 112 | break; 113 | case self::EV_WRITE: 114 | $fd_key = (int)$fd; 115 | $this->_allEvents[$fd_key][$flag] = array($func, $fd); 116 | $this->_writeFds[$fd_key] = $fd; 117 | break; 118 | case self::EV_SIGNAL: 119 | $fd_key = (int)$fd; 120 | $this->_signalEvents[$fd_key][$flag] = array($func, $fd); 121 | pcntl_signal($fd, array($this, 'signalHandler')); 122 | break; 123 | case self::EV_TIMER: 124 | case self::EV_TIMER_ONCE: 125 | $run_time = microtime(true) + $fd; 126 | $this->_scheduler->insert($this->_timerId, -$run_time); 127 | $this->_task[$this->_timerId] = array($func, (array)$args, $flag, $fd); 128 | $this->tick(); 129 | return $this->_timerId++; 130 | } 131 | 132 | return true; 133 | } 134 | 135 | /** 136 | * Signal handler. 137 | * 138 | * @param int $signal 139 | */ 140 | public function signalHandler($signal) 141 | { 142 | call_user_func_array($this->_signalEvents[$signal][self::EV_SIGNAL][0], array($signal)); 143 | } 144 | 145 | /** 146 | * {@inheritdoc} 147 | */ 148 | public function del($fd, $flag) 149 | { 150 | $fd_key = (int)$fd; 151 | switch ($flag) { 152 | case self::EV_READ: 153 | unset($this->_allEvents[$fd_key][$flag], $this->_readFds[$fd_key]); 154 | if (empty($this->_allEvents[$fd_key])) { 155 | unset($this->_allEvents[$fd_key]); 156 | } 157 | return true; 158 | case self::EV_WRITE: 159 | unset($this->_allEvents[$fd_key][$flag], $this->_writeFds[$fd_key]); 160 | if (empty($this->_allEvents[$fd_key])) { 161 | unset($this->_allEvents[$fd_key]); 162 | } 163 | return true; 164 | case self::EV_SIGNAL: 165 | unset($this->_signalEvents[$fd_key]); 166 | pcntl_signal($fd, SIG_IGN); 167 | break; 168 | case self::EV_TIMER: 169 | case self::EV_TIMER_ONCE; 170 | unset($this->_task[$fd_key]); 171 | return true; 172 | } 173 | return false; 174 | } 175 | 176 | /** 177 | * Tick for timer. 178 | * 179 | * @return void 180 | */ 181 | protected function tick() 182 | { 183 | while (!$this->_scheduler->isEmpty()) { 184 | $scheduler_data = $this->_scheduler->top(); 185 | $timer_id = $scheduler_data['data']; 186 | $next_run_time = -$scheduler_data['priority']; 187 | $time_now = microtime(true); 188 | $this->_selectTimeout = ($next_run_time - $time_now) * 1000000; 189 | if ($this->_selectTimeout <= 0) { 190 | $this->_scheduler->extract(); 191 | 192 | if (!isset($this->_task[$timer_id])) { 193 | continue; 194 | } 195 | 196 | // [func, args, flag, timer_interval] 197 | $task_data = $this->_task[$timer_id]; 198 | if ($task_data[2] === self::EV_TIMER) { 199 | $next_run_time = $time_now + $task_data[3]; 200 | $this->_scheduler->insert($timer_id, -$next_run_time); 201 | } 202 | call_user_func_array($task_data[0], $task_data[1]); 203 | if (isset($this->_task[$timer_id]) && $task_data[2] === self::EV_TIMER_ONCE) { 204 | $this->del($timer_id, self::EV_TIMER_ONCE); 205 | } 206 | continue; 207 | } 208 | return; 209 | } 210 | $this->_selectTimeout = 100000000; 211 | } 212 | 213 | /** 214 | * {@inheritdoc} 215 | */ 216 | public function clearAllTimer() 217 | { 218 | $this->_scheduler = new \SplPriorityQueue(); 219 | $this->_scheduler->setExtractFlags(\SplPriorityQueue::EXTR_BOTH); 220 | $this->_task = array(); 221 | } 222 | 223 | /** 224 | * {@inheritdoc} 225 | */ 226 | public function loop() 227 | { 228 | $e = null; 229 | while (1) { 230 | // Calls signal handlers for pending signals 231 | pcntl_signal_dispatch(); 232 | 233 | $read = $this->_readFds; 234 | $write = $this->_writeFds; 235 | // Waiting read/write/signal/timeout events. 236 | $ret = @stream_select($read, $write, $e, 0, $this->_selectTimeout); 237 | 238 | if (!$this->_scheduler->isEmpty()) { 239 | $this->tick(); 240 | } 241 | 242 | if (!$ret) { 243 | continue; 244 | } 245 | 246 | foreach ($read as $fd) { 247 | $fd_key = (int)$fd; 248 | if (isset($this->_allEvents[$fd_key][self::EV_READ])) { 249 | call_user_func_array($this->_allEvents[$fd_key][self::EV_READ][0], 250 | array($this->_allEvents[$fd_key][self::EV_READ][1])); 251 | } 252 | } 253 | 254 | foreach ($write as $fd) { 255 | $fd_key = (int)$fd; 256 | if (isset($this->_allEvents[$fd_key][self::EV_WRITE])) { 257 | call_user_func_array($this->_allEvents[$fd_key][self::EV_WRITE][0], 258 | array($this->_allEvents[$fd_key][self::EV_WRITE][1])); 259 | } 260 | } 261 | } 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /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 | * Mime. 26 | * 27 | * @var string 28 | */ 29 | protected static $defaultMimeType = 'text/html; charset=utf-8'; 30 | 31 | /** 32 | * Virtual host to path mapping. 33 | * 34 | * @var array ['workerman.net'=>'/home', 'www.workerman.net'=>'home/www'] 35 | */ 36 | protected $serverRoot = array(); 37 | 38 | /** 39 | * Mime mapping. 40 | * 41 | * @var array 42 | */ 43 | protected static $mimeTypeMap = array(); 44 | 45 | 46 | /** 47 | * Used to save user OnWorkerStart callback settings. 48 | * 49 | * @var callback 50 | */ 51 | protected $_onWorkerStart = null; 52 | 53 | /** 54 | * Add virtual host. 55 | * 56 | * @param string $domain 57 | * @param string $root_path 58 | * @return void 59 | */ 60 | public function addRoot($domain, $root_path) 61 | { 62 | $this->serverRoot[$domain] = $root_path; 63 | } 64 | 65 | /** 66 | * Construct. 67 | * 68 | * @param string $socket_name 69 | * @param array $context_option 70 | */ 71 | public function __construct($socket_name, $context_option = array()) 72 | { 73 | list(, $address) = explode(':', $socket_name, 2); 74 | parent::__construct('http:' . $address, $context_option); 75 | $this->name = 'WebServer'; 76 | } 77 | 78 | /** 79 | * Run webserver instance. 80 | * 81 | * @see Workerman.Worker::run() 82 | */ 83 | public function run() 84 | { 85 | $this->_onWorkerStart = $this->onWorkerStart; 86 | $this->onWorkerStart = array($this, 'onWorkerStart'); 87 | $this->onMessage = array($this, 'onMessage'); 88 | parent::run(); 89 | } 90 | 91 | /** 92 | * Emit when process start. 93 | * 94 | * @throws \Exception 95 | */ 96 | public function onWorkerStart() 97 | { 98 | if (empty($this->serverRoot)) { 99 | throw new \Exception('server root not set, please use WebServer::addRoot($domain, $root_path) to set server root path'); 100 | } 101 | // Init HttpCache. 102 | HttpCache::init(); 103 | // Init mimeMap. 104 | $this->initMimeTypeMap(); 105 | 106 | // Try to emit onWorkerStart callback. 107 | if ($this->_onWorkerStart) { 108 | try { 109 | call_user_func($this->_onWorkerStart, $this); 110 | } catch (\Exception $e) { 111 | echo $e; 112 | exit(250); 113 | } catch (\Error $e) { 114 | echo $e; 115 | exit(250); 116 | } 117 | } 118 | } 119 | 120 | /** 121 | * Init mime map. 122 | * 123 | * @return void 124 | */ 125 | public function initMimeTypeMap() 126 | { 127 | $mime_file = Http::getMimeTypesFile(); 128 | if (!is_file($mime_file)) { 129 | $this->log("$mime_file mime.type file not fond"); 130 | return; 131 | } 132 | $items = file($mime_file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); 133 | if (!is_array($items)) { 134 | $this->log("get $mime_file mime.type content fail"); 135 | return; 136 | } 137 | foreach ($items as $content) { 138 | if (preg_match("/\s*(\S+)\s+(\S.+)/", $content, $match)) { 139 | $mime_type = $match[1]; 140 | $workerman_file_extension_var = $match[2]; 141 | $workerman_file_extension_array = explode(' ', substr($workerman_file_extension_var, 0, -1)); 142 | foreach ($workerman_file_extension_array as $workerman_file_extension) { 143 | self::$mimeTypeMap[$workerman_file_extension] = $mime_type; 144 | } 145 | } 146 | } 147 | } 148 | 149 | /** 150 | * Emit when http message coming. 151 | * 152 | * @param Connection\TcpConnection $connection 153 | * @return void 154 | */ 155 | public function onMessage($connection) 156 | { 157 | // REQUEST_URI. 158 | $workerman_url_info = parse_url($_SERVER['REQUEST_URI']); 159 | if (!$workerman_url_info) { 160 | Http::header('HTTP/1.1 400 Bad Request'); 161 | $connection->close('

400 Bad Request

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

400 Bad Request

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

404 Not Found

'); 230 | return; 231 | } 232 | } 233 | 234 | public static function sendFile($connection, $file_name) 235 | { 236 | // Check 304. 237 | $info = stat($file_name); 238 | $modified_time = $info ? date('D, d M Y H:i:s', $info['mtime']) . ' GMT' : ''; 239 | if (!empty($_SERVER['HTTP_IF_MODIFIED_SINCE']) && $info) { 240 | // Http 304. 241 | if ($modified_time === $_SERVER['HTTP_IF_MODIFIED_SINCE']) { 242 | // 304 243 | Http::header('HTTP/1.1 304 Not Modified'); 244 | // Send nothing but http headers.. 245 | $connection->close(''); 246 | return; 247 | } 248 | } 249 | 250 | // Http header. 251 | if ($modified_time) { 252 | $modified_time = "Last-Modified: $modified_time\r\n"; 253 | } 254 | $file_size = filesize($file_name); 255 | $extension = pathinfo($file_name, PATHINFO_EXTENSION); 256 | $content_type = isset(self::$mimeTypeMap[$extension]) ? self::$mimeTypeMap[$extension] : self::$defaultMimeType; 257 | $header = "HTTP/1.1 200 OK\r\n"; 258 | $header .= "Content-Type: $content_type\r\n"; 259 | $header .= "Connection: keep-alive\r\n"; 260 | $header .= $modified_time; 261 | $header .= "Content-Length: $file_size\r\n\r\n"; 262 | $trunk_limit_size = 1024*1024; 263 | if ($file_size < $trunk_limit_size) { 264 | return $connection->send($header.file_get_contents($file_name), true); 265 | } 266 | $connection->send($header, true); 267 | 268 | // Read file content from disk piece by piece and send to client. 269 | $connection->fileHandler = fopen($file_name, 'r'); 270 | $do_write = function()use($connection) 271 | { 272 | // Send buffer not full. 273 | while(empty($connection->bufferFull)) 274 | { 275 | // Read from disk. 276 | $buffer = fread($connection->fileHandler, 8192); 277 | // Read eof. 278 | if($buffer === '' || $buffer === false) 279 | { 280 | return; 281 | } 282 | $connection->send($buffer, true); 283 | } 284 | }; 285 | // Send buffer full. 286 | $connection->onBufferFull = function($connection) 287 | { 288 | $connection->bufferFull = true; 289 | }; 290 | // Send buffer drain. 291 | $connection->onBufferDrain = function($connection)use($do_write) 292 | { 293 | $connection->bufferFull = false; 294 | $do_write(); 295 | }; 296 | $do_write(); 297 | } 298 | } 299 | -------------------------------------------------------------------------------- /Workerman/README.md: -------------------------------------------------------------------------------- 1 | # Workerman 2 | 3 | ## What is it 4 | Workerman is a library for event-driven programming in PHP. It has a huge number of features. Each worker is able to handle thousands of connections. 5 | 6 | ## Requires 7 | 8 | PHP 5.3 or Higher 9 | A POSIX compatible operating system (Linux, OSX, BSD) 10 | POSIX and PCNTL extensions for PHP 11 | 12 | ## Installation 13 | 14 | ``` 15 | composer require workerman/workerman 16 | ``` 17 | 18 | ## Basic Usage 19 | 20 | ### A websocket server 21 | test.php 22 | ```php 23 | count = 4; 32 | 33 | // Emitted when new connection come 34 | $ws_worker->onConnect = function($connection) 35 | { 36 | echo "New connection\n"; 37 | }; 38 | 39 | // Emitted when data received 40 | $ws_worker->onMessage = function($connection, $data) 41 | { 42 | // Send hello $data 43 | $connection->send('hello ' . $data); 44 | }; 45 | 46 | // Emitted when connection closed 47 | $ws_worker->onClose = function($connection) 48 | { 49 | echo "Connection closed\n"; 50 | }; 51 | 52 | // Run worker 53 | Worker::runAll(); 54 | ``` 55 | 56 | ### A http server 57 | test.php 58 | ```php 59 | require_once './Workerman/Autoloader.php'; 60 | use Workerman\Worker; 61 | 62 | // #### http worker #### 63 | $http_worker = new Worker("http://0.0.0.0:2345"); 64 | 65 | // 4 processes 66 | $http_worker->count = 4; 67 | 68 | // Emitted when data received 69 | $http_worker->onMessage = function($connection, $data) 70 | { 71 | // $_GET, $_POST, $_COOKIE, $_SESSION, $_SERVER, $_FILES are available 72 | var_dump($_GET, $_POST, $_COOKIE, $_SESSION, $_SERVER, $_FILES); 73 | // send data to client 74 | $connection->send("hello world \n"); 75 | }; 76 | 77 | // run all workers 78 | Worker::runAll(); 79 | ``` 80 | 81 | ### A WebServer 82 | test.php 83 | ```php 84 | require_once './Workerman/Autoloader.php'; 85 | use \Workerman\WebServer; 86 | 87 | // WebServer 88 | $web = new WebServer("http://0.0.0.0:80"); 89 | 90 | // 4 processes 91 | $web->count = 4; 92 | 93 | // Set the root of domains 94 | $web->addRoot('www.your_domain.com', '/your/path/Web'); 95 | $web->addRoot('www.another_domain.com', '/another/path/Web'); 96 | // run all workers 97 | Worker::runAll(); 98 | ``` 99 | 100 | ### A tcp server 101 | test.php 102 | ```php 103 | require_once './Workerman/Autoloader.php'; 104 | use Workerman\Worker; 105 | 106 | // #### create socket and listen 1234 port #### 107 | $tcp_worker = new Worker("tcp://0.0.0.0:1234"); 108 | 109 | // 4 processes 110 | $tcp_worker->count = 4; 111 | 112 | // Emitted when new connection come 113 | $tcp_worker->onConnect = function($connection) 114 | { 115 | echo "New Connection\n"; 116 | }; 117 | 118 | // Emitted when data received 119 | $tcp_worker->onMessage = function($connection, $data) 120 | { 121 | // send data to client 122 | $connection->send("hello $data \n"); 123 | }; 124 | 125 | // Emitted when new connection come 126 | $tcp_worker->onClose($connection) 127 | { 128 | echo "Connection closed\n"; 129 | }; 130 | 131 | Worker::runAll(); 132 | ``` 133 | 134 | ### Custom protocol 135 | Protocols/MyTextProtocol.php 136 | ```php 137 | namespace Protocols; 138 | /** 139 | * User defined protocol 140 | * Format Text+"\n" 141 | */ 142 | class MyTextProtocol 143 | { 144 | public static function input($recv_buffer) 145 | { 146 | // Find the position of the first occurrence of "\n" 147 | $pos = strpos($recv_buffer, "\n"); 148 | // Not a complete package. Return 0 because the length of package can not be calculated 149 | if($pos === false) 150 | { 151 | return 0; 152 | } 153 | // Return length of the package 154 | return $pos+1; 155 | } 156 | 157 | public static function decode($recv_buffer) 158 | { 159 | return trim($recv_buffer); 160 | } 161 | 162 | public static function encode($data) 163 | { 164 | return $data."\n"; 165 | } 166 | } 167 | ``` 168 | 169 | test.php 170 | ```php 171 | require_once './Workerman/Autoloader.php'; 172 | use Workerman\Worker 173 | 174 | // #### MyTextProtocol worker #### 175 | $text_worker = new Worker("MyTextProtocol://0.0.0.0:5678"); 176 | 177 | $text_worker->onConnect = function($connection) 178 | { 179 | echo "New connection\n"; 180 | }; 181 | 182 | $text_worker->onMessage = function($connection, $data) 183 | { 184 | // send data to client 185 | $connection->send("hello world \n"); 186 | }; 187 | 188 | $text_worker->onClose = function($connection) 189 | { 190 | echo "Connection closed\n"; 191 | }; 192 | 193 | // run all workers 194 | Worker::runAll(); 195 | ``` 196 | 197 | ### Timer 198 | test.php 199 | ```php 200 | require_once './Workerman/Autoloader.php'; 201 | use Workerman\Worker; 202 | use Workerman\Lib\Timer; 203 | 204 | $task = new Worker(); 205 | $task->onWorkerStart = function($task) 206 | { 207 | // 2.5 seconds 208 | $time_interval = 2.5; 209 | $timer_id = Timer::add($time_interval, 210 | function() 211 | { 212 | echo "Timer run\n"; 213 | } 214 | ); 215 | }; 216 | 217 | // run all workers 218 | Worker::runAll(); 219 | ``` 220 | 221 | run width 222 | 223 | ```php test.php start``` 224 | 225 | ## Available commands 226 | ```php test.php start ``` 227 | ```php test.php start -d ``` 228 | ![workerman start](http://www.workerman.net/img/workerman-start.png) 229 | ```php test.php status ``` 230 | ![workerman satus](http://www.workerman.net/img/workerman-status.png?a=123) 231 | ```php test.php stop ``` 232 | ```php test.php restart ``` 233 | ```php test.php reload ``` 234 | 235 | ## Documentation 236 | 237 | 中文主页:[http://www.workerman.net](http://www.workerman.net) 238 | 239 | 中文文档: [http://doc3.workerman.net](http://doc3.workerman.net) 240 | 241 | Documentation:[https://github.com/walkor/workerman-manual](https://github.com/walkor/workerman-manual/blob/master/english/src/SUMMARY.md) 242 | 243 | # Benchmarks 244 | ``` 245 | CPU: Intel(R) Core(TM) i3-3220 CPU @ 3.30GHz and 4 processors totally 246 | Memory: 8G 247 | OS: Ubuntu 14.04 LTS 248 | Software: ab 249 | PHP: 5.5.9 250 | ``` 251 | 252 | **Codes** 253 | ```php 254 | count=3; 258 | $worker->onMessage = function($connection, $data) 259 | { 260 | $connection->send("HTTP/1.1 200 OK\r\nConnection: keep-alive\r\nServer: workerman\1.1.4\r\n\r\nhello"); 261 | }; 262 | Worker::runAll(); 263 | ``` 264 | **Result** 265 | 266 | ```shell 267 | ab -n1000000 -c100 -k http://127.0.0.1:1234/ 268 | This is ApacheBench, Version 2.3 <$Revision: 1528965 $> 269 | Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ 270 | Licensed to The Apache Software Foundation, http://www.apache.org/ 271 | 272 | Benchmarking 127.0.0.1 (be patient) 273 | Completed 100000 requests 274 | Completed 200000 requests 275 | Completed 300000 requests 276 | Completed 400000 requests 277 | Completed 500000 requests 278 | Completed 600000 requests 279 | Completed 700000 requests 280 | Completed 800000 requests 281 | Completed 900000 requests 282 | Completed 1000000 requests 283 | Finished 1000000 requests 284 | 285 | 286 | Server Software: workerman/3.1.4 287 | Server Hostname: 127.0.0.1 288 | Server Port: 1234 289 | 290 | Document Path: / 291 | Document Length: 5 bytes 292 | 293 | Concurrency Level: 100 294 | Time taken for tests: 7.240 seconds 295 | Complete requests: 1000000 296 | Failed requests: 0 297 | Keep-Alive requests: 1000000 298 | Total transferred: 73000000 bytes 299 | HTML transferred: 5000000 bytes 300 | Requests per second: 138124.14 [#/sec] (mean) 301 | Time per request: 0.724 [ms] (mean) 302 | Time per request: 0.007 [ms] (mean, across all concurrent requests) 303 | Transfer rate: 9846.74 [Kbytes/sec] received 304 | 305 | Connection Times (ms) 306 | min mean[+/-sd] median max 307 | Connect: 0 0 0.0 0 5 308 | Processing: 0 1 0.2 1 9 309 | Waiting: 0 1 0.2 1 9 310 | Total: 0 1 0.2 1 9 311 | 312 | Percentage of the requests served within a certain time (ms) 313 | 50% 1 314 | 66% 1 315 | 75% 1 316 | 80% 1 317 | 90% 1 318 | 95% 1 319 | 98% 1 320 | 99% 1 321 | 100% 9 (longest request) 322 | 323 | ``` 324 | 325 | 326 | # Demos 327 | 328 | ## [tadpole](http://kedou.workerman.net/) 329 | [Live demo](http://kedou.workerman.net/) 330 | [Source code](https://github.com/walkor/workerman) 331 | ![workerman todpole](http://www.workerman.net/img/workerman-todpole.png) 332 | 333 | ## [BrowserQuest](http://www.workerman.net/demos/browserquest/) 334 | [Live demo](http://www.workerman.net/demos/browserquest/) 335 | [Source code](https://github.com/walkor/BrowserQuest-PHP) 336 | ![BrowserQuest width workerman](http://www.workerman.net/img/browserquest.jpg) 337 | 338 | ## [web vmstat](http://www.workerman.net/demos/vmstat/) 339 | [Live demo](http://www.workerman.net/demos/vmstat/) 340 | [Source code](https://github.com/walkor/workerman-vmstat) 341 | ![web vmstat](http://www.workerman.net/img/workerman-vmstat.png) 342 | 343 | ## [live-ascii-camera](https://github.com/walkor/live-ascii-camera) 344 | [Live demo camera page](http://www.workerman.net/demos/live-ascii-camera/camera.html) 345 | [Live demo receive page](http://www.workerman.net/demos/live-ascii-camera/) 346 | [Source code](https://github.com/walkor/live-ascii-camera) 347 | ![live-ascii-camera](http://www.workerman.net/img/live-ascii-camera.png) 348 | 349 | ## [live-camera](https://github.com/walkor/live-camera) 350 | [Live demo camera page](http://www.workerman.net/demos/live-camera/camera.html) 351 | [Live demo receive page](http://www.workerman.net/demos/live-camera/) 352 | [Source code](https://github.com/walkor/live-camera) 353 | ![live-camera](http://www.workerman.net/img/live-camera.jpg) 354 | 355 | ## [chat room](http://chat.workerman.net/) 356 | [Live demo](http://chat.workerman.net/) 357 | [Source code](https://github.com/walkor/workerman-chat) 358 | ![workerman-chat](http://www.workerman.net/img/workerman-chat.png) 359 | 360 | ## [PHPSocket.IO](https://github.com/walkor/phpsocket.io) 361 | [Live demo](http://www.workerman.net/demos/phpsocketio-chat/) 362 | [Source code](https://github.com/walkor/phpsocket.io) 363 | ![phpsocket.io](http://www.workerman.net/img/socket.io.png) 364 | 365 | ## [statistics](http://www.workerman.net:55757/) 366 | [Live demo](http://www.workerman.net:55757/) 367 | [Source code](https://github.com/walkor/workerman-statistics) 368 | ![workerman-statistics](http://www.workerman.net/img/workerman-statistics.png) 369 | 370 | ## [flappybird](http://workerman.net/demos/flappy-bird/) 371 | [Live demo](http://workerman.net/demos/flappy-bird/) 372 | [Source code](https://github.com/walkor/workerman-flappy-bird) 373 | ![workerman-statistics](http://www.workerman.net/img/workerman-flappy-bird.png) 374 | 375 | ## [jsonRpc](https://github.com/walkor/workerman-JsonRpc) 376 | [Source code](https://github.com/walkor/workerman-JsonRpc) 377 | ![workerman-jsonRpc](http://www.workerman.net/img/workerman-json-rpc.png) 378 | 379 | ## [thriftRpc](https://github.com/walkor/workerman-thrift) 380 | [Source code](https://github.com/walkor/workerman-thrift) 381 | ![workerman-thriftRpc](http://www.workerman.net/img/workerman-thrift.png) 382 | 383 | ## [web-msg-sender](https://github.com/walkor/web-msg-sender) 384 | [Live demo send page](http://workerman.net:3333/) 385 | [Live demo receive page](http://workerman.net/web-msg-sender.html) 386 | [Source code](https://github.com/walkor/web-msg-sender) 387 | ![web-msg-sender](http://www.workerman.net/img/web-msg-sender.png) 388 | 389 | ## [shadowsocks-php](https://github.com/walkor/shadowsocks-php) 390 | [Source code](https://github.com/walkor/shadowsocks-php) 391 | ![shadowsocks-php](http://www.workerman.net/img/shadowsocks-php.png) 392 | 393 | ## [queue](https://github.com/walkor/workerman-queue) 394 | [Source code](https://github.com/walkor/workerman-queue) 395 | 396 | ## LICENSE 397 | 398 | Workerman is released under the [MIT license](https://github.com/walkor/workerman/blob/master/MIT-LICENSE.txt). 399 | -------------------------------------------------------------------------------- /Workerman/Protocols/Ws.php: -------------------------------------------------------------------------------- 1 | handshakeStep)) { 40 | echo "recv data before handshake\n"; 41 | return false; 42 | } 43 | // Recv handshake response 44 | if ($connection->handshakeStep === 1) { 45 | return self::dealHandshake($buffer, $connection); 46 | } 47 | $recv_len = strlen($buffer); 48 | if ($recv_len < self::MIN_HEAD_LEN) { 49 | return 0; 50 | } 51 | // Buffer websocket frame data. 52 | if ($connection->websocketCurrentFrameLength) { 53 | // We need more frame data. 54 | if ($connection->websocketCurrentFrameLength > $recv_len) { 55 | // Return 0, because it is not clear the full packet length, waiting for the frame of fin=1. 56 | return 0; 57 | } 58 | } else { 59 | $data_len = ord($buffer[1]) & 127; 60 | $firstbyte = ord($buffer[0]); 61 | $is_fin_frame = $firstbyte >> 7; 62 | $opcode = $firstbyte & 0xf; 63 | switch ($opcode) { 64 | case 0x0: 65 | break; 66 | // Blob type. 67 | case 0x1: 68 | break; 69 | // Arraybuffer type. 70 | case 0x2: 71 | break; 72 | // Close package. 73 | case 0x8: 74 | // Try to emit onWebSocketClose callback. 75 | if (isset($connection->onWebSocketClose)) { 76 | try { 77 | call_user_func($connection->onWebSocketClose, $connection); 78 | } catch (\Exception $e) { 79 | echo $e; 80 | exit(250); 81 | } catch (\Error $e) { 82 | echo $e; 83 | exit(250); 84 | } 85 | } // Close connection. 86 | else { 87 | $connection->close(); 88 | } 89 | return 0; 90 | // Ping package. 91 | case 0x9: 92 | // Try to emit onWebSocketPing callback. 93 | if (isset($connection->onWebSocketPing)) { 94 | try { 95 | call_user_func($connection->onWebSocketPing, $connection); 96 | } catch (\Exception $e) { 97 | echo $e; 98 | exit(250); 99 | } catch (\Error $e) { 100 | echo $e; 101 | exit(250); 102 | } 103 | } // Send pong package to client. 104 | else { 105 | $connection->send(pack('H*', '8a00'), true); 106 | } 107 | // Consume data from receive buffer. 108 | if (!$data_len) { 109 | $connection->consumeRecvBuffer(self::MIN_HEAD_LEN); 110 | if ($recv_len > self::MIN_HEAD_LEN) { 111 | return self::input(substr($buffer, self::MIN_HEAD_LEN), $connection); 112 | } 113 | return 0; 114 | } 115 | break; 116 | // Pong package. 117 | case 0xa: 118 | // Try to emit onWebSocketPong callback. 119 | if (isset($connection->onWebSocketPong)) { 120 | try { 121 | call_user_func($connection->onWebSocketPong, $connection); 122 | } catch (\Exception $e) { 123 | echo $e; 124 | exit(250); 125 | } catch (\Error $e) { 126 | echo $e; 127 | exit(250); 128 | } 129 | } 130 | // Consume data from receive buffer. 131 | if (!$data_len) { 132 | $connection->consumeRecvBuffer(self::MIN_HEAD_LEN); 133 | if ($recv_len > self::MIN_HEAD_LEN) { 134 | return self::input(substr($buffer, self::MIN_HEAD_LEN), $connection); 135 | } 136 | return 0; 137 | } 138 | break; 139 | // Wrong opcode. 140 | default : 141 | echo "error opcode $opcode and close websocket connection\n"; 142 | $connection->close(); 143 | return 0; 144 | } 145 | // Calculate packet length. 146 | if ($data_len === 126) { 147 | if (strlen($buffer) < 6) { 148 | return 0; 149 | } 150 | $pack = unpack('nn/ntotal_len', $buffer); 151 | $current_frame_length = $pack['total_len'] + 4; 152 | } else if ($data_len === 127) { 153 | if (strlen($buffer) < 10) { 154 | return 0; 155 | } 156 | $arr = unpack('n/N2c', $buffer); 157 | $current_frame_length = $arr['c1']*4294967296 + $arr['c2'] + 10; 158 | } else { 159 | $current_frame_length = $data_len + 2; 160 | } 161 | if ($is_fin_frame) { 162 | return $current_frame_length; 163 | } else { 164 | $connection->websocketCurrentFrameLength = $current_frame_length; 165 | } 166 | } 167 | // Received just a frame length data. 168 | if ($connection->websocketCurrentFrameLength === $recv_len) { 169 | self::decode($buffer, $connection); 170 | $connection->consumeRecvBuffer($connection->websocketCurrentFrameLength); 171 | $connection->websocketCurrentFrameLength = 0; 172 | return 0; 173 | } // The length of the received data is greater than the length of a frame. 174 | elseif ($connection->websocketCurrentFrameLength < $recv_len) { 175 | self::decode(substr($buffer, 0, $connection->websocketCurrentFrameLength), $connection); 176 | $connection->consumeRecvBuffer($connection->websocketCurrentFrameLength); 177 | $current_frame_length = $connection->websocketCurrentFrameLength; 178 | $connection->websocketCurrentFrameLength = 0; 179 | // Continue to read next frame. 180 | return self::input(substr($buffer, $current_frame_length), $connection); 181 | } // The length of the received data is less than the length of a frame. 182 | else { 183 | return 0; 184 | } 185 | } 186 | 187 | /** 188 | * Websocket encode. 189 | * 190 | * @param string $buffer 191 | * @param ConnectionInterface $connection 192 | * @return string 193 | */ 194 | public static function encode($payload, $connection) 195 | { 196 | $payload = (string)$payload; 197 | if (empty($connection->handshakeStep)) { 198 | self::sendHandshake($connection); 199 | } 200 | $mask = 1; 201 | $mask_key = "\x00\x00\x00\x00"; 202 | 203 | $pack = ''; 204 | $length = $length_flag = strlen($payload); 205 | if (65535 < $length) { 206 | $pack = pack('NN', ($length & 0xFFFFFFFF00000000) >> 32, $length & 0x00000000FFFFFFFF); 207 | $length_flag = 127; 208 | } else if (125 < $length) { 209 | $pack = pack('n*', $length); 210 | $length_flag = 126; 211 | } 212 | 213 | $head = ($mask << 7) | $length_flag; 214 | $head = $connection->websocketType . chr($head) . $pack; 215 | 216 | $frame = $head . $mask_key; 217 | // append payload to frame: 218 | for ($i = 0; $i < $length; $i++) { 219 | $frame .= $payload[$i] ^ $mask_key[$i % 4]; 220 | } 221 | if ($connection->handshakeStep === 1) { 222 | $connection->tmpWebsocketData = isset($connection->tmpWebsocketData) ? $connection->tmpWebsocketData . $frame : $frame; 223 | return ''; 224 | } 225 | return $frame; 226 | } 227 | 228 | /** 229 | * Websocket decode. 230 | * 231 | * @param string $buffer 232 | * @param ConnectionInterface $connection 233 | * @return string 234 | */ 235 | public static function decode($bytes, $connection) 236 | { 237 | $masked = $bytes[1] >> 7; 238 | $data_length = $masked ? ord($bytes[1]) & 127 : ord($bytes[1]); 239 | $decoded_data = ''; 240 | if ($masked === true) { 241 | if ($data_length === 126) { 242 | $mask = substr($bytes, 4, 4); 243 | $coded_data = substr($bytes, 8); 244 | } else if ($data_length === 127) { 245 | $mask = substr($bytes, 10, 4); 246 | $coded_data = substr($bytes, 14); 247 | } else { 248 | $mask = substr($bytes, 2, 4); 249 | $coded_data = substr($bytes, 6); 250 | } 251 | for ($i = 0; $i < strlen($coded_data); $i++) { 252 | $decoded_data .= $coded_data[$i] ^ $mask[$i % 4]; 253 | } 254 | } else { 255 | if ($data_length === 126) { 256 | $decoded_data = substr($bytes, 4); 257 | } else if ($data_length === 127) { 258 | $decoded_data = substr($bytes, 10); 259 | } else { 260 | $decoded_data = substr($bytes, 2); 261 | } 262 | } 263 | if ($connection->websocketCurrentFrameLength) { 264 | $connection->websocketDataBuffer .= $decoded_data; 265 | return $connection->websocketDataBuffer; 266 | } else { 267 | if ($connection->websocketDataBuffer !== '') { 268 | $decoded_data = $connection->websocketDataBuffer . $decoded_data; 269 | $connection->websocketDataBuffer = ''; 270 | } 271 | return $decoded_data; 272 | } 273 | } 274 | 275 | /** 276 | * Send websocket handshake. 277 | * 278 | * @param \Workerman\Connection\TcpConnection $connection 279 | * @return void 280 | */ 281 | public static function sendHandshake($connection) 282 | { 283 | // Get Host. 284 | $port = $connection->getRemotePort(); 285 | $host = $port === 80 ? $connection->getRemoteHost() : $connection->getRemoteHost() . ':' . $port; 286 | // Handshake header. 287 | $header = "GET / HTTP/1.1\r\n". 288 | "Host: $host\r\n". 289 | "Connection: Upgrade\r\n". 290 | "Upgrade: websocket\r\n". 291 | "Origin: ". (isset($connection->websocketOrigin) ? $connection->websocketOrigin : '*') ."\r\n". 292 | "Sec-WebSocket-Version: 13\r\n". 293 | "Sec-WebSocket-Key: ".base64_encode(sha1(uniqid(mt_rand(), true), true))."\r\n\r\n"; 294 | $connection->send($header, true); 295 | $connection->handshakeStep = 1; 296 | $connection->websocketCurrentFrameLength = 0; 297 | $connection->websocketDataBuffer = ''; 298 | if (empty($connection->websocketType)) { 299 | $connection->websocketType = self::BINARY_TYPE_BLOB; 300 | } 301 | } 302 | 303 | /** 304 | * Websocket handshake. 305 | * 306 | * @param string $buffer 307 | * @param \Workerman\Connection\TcpConnection $connection 308 | * @return int 309 | */ 310 | public static function dealHandshake($buffer, $connection) 311 | { 312 | $pos = strpos($buffer, "\r\n\r\n"); 313 | if ($pos) { 314 | // handshake complete 315 | $connection->handshakeStep = 2; 316 | $handshake_respnse_length = $pos + 4; 317 | // Try to emit onWebSocketConnect callback. 318 | if (isset($connection->onWebSocketConnect)) { 319 | try { 320 | call_user_func($connection->onWebSocketConnect, $connection, substr($buffer, 0, $handshake_respnse_length)); 321 | } catch (\Exception $e) { 322 | echo $e; 323 | exit(250); 324 | } catch (\Error $e) { 325 | echo $e; 326 | exit(250); 327 | } 328 | } 329 | // Headbeat. 330 | if (!empty($connection->websocketPingInterval)) { 331 | $connection->websocketPingTimer = \Workerman\Lib\Timer::add($connection->websocketPingInterval, function() use ($connection){ 332 | if (false === $connection->send(pack('H*', '8900'), true)) { 333 | \Workerman\Lib\Timer::del($connection->websocketPingTimer); 334 | } 335 | }); 336 | } 337 | 338 | $connection->consumeRecvBuffer($handshake_respnse_length); 339 | if (!empty($connection->tmpWebsocketData)) { 340 | $connection->send($connection->tmpWebsocketData, true); 341 | } 342 | if (strlen($buffer > $handshake_respnse_length)) { 343 | return self::input(substr($buffer, $handshake_respnse_length)); 344 | } 345 | } 346 | return 0; 347 | } 348 | } 349 | -------------------------------------------------------------------------------- /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 | 18 | /** 19 | * WebSocket protocol. 20 | */ 21 | class Websocket implements \Workerman\Protocols\ProtocolInterface 22 | { 23 | /** 24 | * Minimum head length of websocket protocol. 25 | * 26 | * @var int 27 | */ 28 | const MIN_HEAD_LEN = 2; 29 | 30 | /** 31 | * Websocket blob type. 32 | * 33 | * @var string 34 | */ 35 | const BINARY_TYPE_BLOB = "\x81"; 36 | 37 | /** 38 | * Websocket arraybuffer type. 39 | * 40 | * @var string 41 | */ 42 | const BINARY_TYPE_ARRAYBUFFER = "\x82"; 43 | 44 | /** 45 | * Check the integrity of the package. 46 | * 47 | * @param string $buffer 48 | * @param ConnectionInterface $connection 49 | * @return int 50 | */ 51 | public static function input($buffer, ConnectionInterface $connection) 52 | { 53 | // Receive length. 54 | $recv_len = strlen($buffer); 55 | // We need more data. 56 | if ($recv_len < self::MIN_HEAD_LEN) { 57 | return 0; 58 | } 59 | 60 | // Has not yet completed the handshake. 61 | if (empty($connection->websocketHandshake)) { 62 | return self::dealHandshake($buffer, $connection); 63 | } 64 | 65 | // Buffer websocket frame data. 66 | if ($connection->websocketCurrentFrameLength) { 67 | // We need more frame data. 68 | if ($connection->websocketCurrentFrameLength > $recv_len) { 69 | // Return 0, because it is not clear the full packet length, waiting for the frame of fin=1. 70 | return 0; 71 | } 72 | } else { 73 | $data_len = ord($buffer[1]) & 127; 74 | $firstbyte = ord($buffer[0]); 75 | $is_fin_frame = $firstbyte >> 7; 76 | $opcode = $firstbyte & 0xf; 77 | switch ($opcode) { 78 | case 0x0: 79 | break; 80 | // Blob type. 81 | case 0x1: 82 | break; 83 | // Arraybuffer type. 84 | case 0x2: 85 | break; 86 | // Close package. 87 | case 0x8: 88 | // Try to emit onWebSocketClose callback. 89 | if (isset($connection->onWebSocketClose)) { 90 | try { 91 | call_user_func($connection->onWebSocketClose, $connection); 92 | } catch (\Exception $e) { 93 | echo $e; 94 | exit(250); 95 | } catch (\Error $e) { 96 | echo $e; 97 | exit(250); 98 | } 99 | } // Close connection. 100 | else { 101 | $connection->close(); 102 | } 103 | return 0; 104 | // Ping package. 105 | case 0x9: 106 | // Try to emit onWebSocketPing callback. 107 | if (isset($connection->onWebSocketPing)) { 108 | try { 109 | call_user_func($connection->onWebSocketPing, $connection); 110 | } catch (\Exception $e) { 111 | echo $e; 112 | exit(250); 113 | } catch (\Error $e) { 114 | echo $e; 115 | exit(250); 116 | } 117 | } // Send pong package to client. 118 | else { 119 | $connection->send(pack('H*', '8a00'), true); 120 | } 121 | 122 | // Consume data from receive buffer. 123 | if (!$data_len) { 124 | $connection->consumeRecvBuffer(self::MIN_HEAD_LEN); 125 | if ($recv_len > self::MIN_HEAD_LEN) { 126 | return self::input(substr($buffer, self::MIN_HEAD_LEN), $connection); 127 | } 128 | return 0; 129 | } 130 | break; 131 | // Pong package. 132 | case 0xa: 133 | // Try to emit onWebSocketPong callback. 134 | if (isset($connection->onWebSocketPong)) { 135 | try { 136 | call_user_func($connection->onWebSocketPong, $connection); 137 | } catch (\Exception $e) { 138 | echo $e; 139 | exit(250); 140 | } catch (\Error $e) { 141 | echo $e; 142 | exit(250); 143 | } 144 | } 145 | // Consume data from receive buffer. 146 | if (!$data_len) { 147 | $connection->consumeRecvBuffer(self::MIN_HEAD_LEN); 148 | if ($recv_len > self::MIN_HEAD_LEN) { 149 | return self::input(substr($buffer, self::MIN_HEAD_LEN), $connection); 150 | } 151 | return 0; 152 | } 153 | break; 154 | // Wrong opcode. 155 | default : 156 | echo "error opcode $opcode and close websocket connection\n"; 157 | $connection->close(); 158 | return 0; 159 | } 160 | 161 | // Calculate packet length. 162 | $head_len = 6; 163 | if ($data_len === 126) { 164 | $head_len = 8; 165 | if ($head_len > $recv_len) { 166 | return 0; 167 | } 168 | $pack = unpack('nn/ntotal_len', $buffer); 169 | $data_len = $pack['total_len']; 170 | } else { 171 | if ($data_len === 127) { 172 | $head_len = 14; 173 | if ($head_len > $recv_len) { 174 | return 0; 175 | } 176 | $arr = unpack('n/N2c', $buffer); 177 | $data_len = $arr['c1']*4294967296 + $arr['c2']; 178 | } 179 | } 180 | $current_frame_length = $head_len + $data_len; 181 | if ($is_fin_frame) { 182 | return $current_frame_length; 183 | } else { 184 | $connection->websocketCurrentFrameLength = $current_frame_length; 185 | } 186 | } 187 | 188 | // Received just a frame length data. 189 | if ($connection->websocketCurrentFrameLength === $recv_len) { 190 | self::decode($buffer, $connection); 191 | $connection->consumeRecvBuffer($connection->websocketCurrentFrameLength); 192 | $connection->websocketCurrentFrameLength = 0; 193 | return 0; 194 | } // The length of the received data is greater than the length of a frame. 195 | elseif ($connection->websocketCurrentFrameLength < $recv_len) { 196 | self::decode(substr($buffer, 0, $connection->websocketCurrentFrameLength), $connection); 197 | $connection->consumeRecvBuffer($connection->websocketCurrentFrameLength); 198 | $current_frame_length = $connection->websocketCurrentFrameLength; 199 | $connection->websocketCurrentFrameLength = 0; 200 | // Continue to read next frame. 201 | return self::input(substr($buffer, $current_frame_length), $connection); 202 | } // The length of the received data is less than the length of a frame. 203 | else { 204 | return 0; 205 | } 206 | } 207 | 208 | /** 209 | * Websocket encode. 210 | * 211 | * @param string $buffer 212 | * @param ConnectionInterface $connection 213 | * @return string 214 | */ 215 | public static function encode($buffer, ConnectionInterface $connection) 216 | { 217 | $len = strlen($buffer); 218 | if (empty($connection->websocketType)) { 219 | $connection->websocketType = self::BINARY_TYPE_BLOB; 220 | } 221 | 222 | $first_byte = $connection->websocketType; 223 | 224 | if ($len <= 125) { 225 | $encode_buffer = $first_byte . chr($len) . $buffer; 226 | } else { 227 | if ($len <= 65535) { 228 | $encode_buffer = $first_byte . chr(126) . pack("n", $len) . $buffer; 229 | } else { 230 | $encode_buffer = $first_byte . chr(127) . pack("xxxxN", $len) . $buffer; 231 | } 232 | } 233 | 234 | // Handshake not completed so temporary buffer websocket data waiting for send. 235 | if (empty($connection->websocketHandshake)) { 236 | if (empty($connection->tmpWebsocketData)) { 237 | $connection->tmpWebsocketData = ''; 238 | } 239 | $connection->tmpWebsocketData .= $encode_buffer; 240 | // Return empty string. 241 | return ''; 242 | } 243 | 244 | return $encode_buffer; 245 | } 246 | 247 | /** 248 | * Websocket decode. 249 | * 250 | * @param string $buffer 251 | * @param ConnectionInterface $connection 252 | * @return string 253 | */ 254 | public static function decode($buffer, ConnectionInterface $connection) 255 | { 256 | $len = $masks = $data = $decoded = null; 257 | $len = ord($buffer[1]) & 127; 258 | if ($len === 126) { 259 | $masks = substr($buffer, 4, 4); 260 | $data = substr($buffer, 8); 261 | } else { 262 | if ($len === 127) { 263 | $masks = substr($buffer, 10, 4); 264 | $data = substr($buffer, 14); 265 | } else { 266 | $masks = substr($buffer, 2, 4); 267 | $data = substr($buffer, 6); 268 | } 269 | } 270 | for ($index = 0; $index < strlen($data); $index++) { 271 | $decoded .= $data[$index] ^ $masks[$index % 4]; 272 | } 273 | if ($connection->websocketCurrentFrameLength) { 274 | $connection->websocketDataBuffer .= $decoded; 275 | return $connection->websocketDataBuffer; 276 | } else { 277 | if ($connection->websocketDataBuffer !== '') { 278 | $decoded = $connection->websocketDataBuffer . $decoded; 279 | $connection->websocketDataBuffer = ''; 280 | } 281 | return $decoded; 282 | } 283 | } 284 | 285 | /** 286 | * Websocket handshake. 287 | * 288 | * @param string $buffer 289 | * @param \Workerman\Connection\TcpConnection $connection 290 | * @return int 291 | */ 292 | protected static function dealHandshake($buffer, $connection) 293 | { 294 | // HTTP protocol. 295 | if (0 === strpos($buffer, 'GET')) { 296 | // Find \r\n\r\n. 297 | $heder_end_pos = strpos($buffer, "\r\n\r\n"); 298 | if (!$heder_end_pos) { 299 | return 0; 300 | } 301 | $header_length = $heder_end_pos + 4; 302 | 303 | // Get Sec-WebSocket-Key. 304 | $Sec_WebSocket_Key = ''; 305 | if (preg_match("/Sec-WebSocket-Key: *(.*?)\r\n/i", $buffer, $match)) { 306 | $Sec_WebSocket_Key = $match[1]; 307 | } else { 308 | $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.", 309 | true); 310 | $connection->close(); 311 | return 0; 312 | } 313 | // Calculation websocket key. 314 | $new_key = base64_encode(sha1($Sec_WebSocket_Key . "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", true)); 315 | // Handshake response data. 316 | $handshake_message = "HTTP/1.1 101 Switching Protocols\r\n"; 317 | $handshake_message .= "Upgrade: websocket\r\n"; 318 | $handshake_message .= "Sec-WebSocket-Version: 13\r\n"; 319 | $handshake_message .= "Connection: Upgrade\r\n"; 320 | $handshake_message .= "Sec-WebSocket-Accept: " . $new_key . "\r\n\r\n"; 321 | // Mark handshake complete.. 322 | $connection->websocketHandshake = true; 323 | // Websocket data buffer. 324 | $connection->websocketDataBuffer = ''; 325 | // Current websocket frame length. 326 | $connection->websocketCurrentFrameLength = 0; 327 | // Current websocket frame data. 328 | $connection->websocketCurrentFrameBuffer = ''; 329 | // Consume handshake data. 330 | $connection->consumeRecvBuffer($header_length); 331 | // Send handshake response. 332 | $connection->send($handshake_message, true); 333 | 334 | // There are data waiting to be sent. 335 | if (!empty($connection->tmpWebsocketData)) { 336 | $connection->send($connection->tmpWebsocketData, true); 337 | $connection->tmpWebsocketData = ''; 338 | } 339 | // blob or arraybuffer 340 | if (empty($connection->websocketType)) { 341 | $connection->websocketType = self::BINARY_TYPE_BLOB; 342 | } 343 | // Try to emit onWebSocketConnect callback. 344 | if (isset($connection->onWebSocketConnect)) { 345 | self::parseHttpHeader($buffer); 346 | try { 347 | call_user_func($connection->onWebSocketConnect, $connection, $buffer); 348 | } catch (\Exception $e) { 349 | echo $e; 350 | exit(250); 351 | } catch (\Error $e) { 352 | echo $e; 353 | exit(250); 354 | } 355 | $_GET = $_COOKIE = $_SERVER = array(); 356 | } 357 | if (strlen($buffer) > $header_length) { 358 | return self::input(substr($buffer, $header_length), $connection); 359 | } 360 | return 0; 361 | } // Is flash policy-file-request. 362 | elseif (0 === strpos($buffer, 'send($policy_xml, true); 365 | $connection->consumeRecvBuffer(strlen($buffer)); 366 | return 0; 367 | } 368 | // Bad websocket handshake request. 369 | $connection->send("HTTP/1.1 400 Bad Request\r\n\r\n400 Bad Request
Invalid handshake data for websocket. ", 370 | true); 371 | $connection->close(); 372 | return 0; 373 | } 374 | 375 | /** 376 | * Parse http header. 377 | * 378 | * @param string $buffer 379 | * @return void 380 | */ 381 | protected static function parseHttpHeader($buffer) 382 | { 383 | $header_data = explode("\r\n", $buffer); 384 | $_SERVER = array(); 385 | list($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI'], $_SERVER['SERVER_PROTOCOL']) = explode(' ', 386 | $header_data[0]); 387 | unset($header_data[0]); 388 | foreach ($header_data as $content) { 389 | // \r\n\r\n 390 | if (empty($content)) { 391 | continue; 392 | } 393 | list($key, $value) = explode(':', $content, 2); 394 | $key = strtolower($key); 395 | $value = trim($value); 396 | switch ($key) { 397 | // HTTP_HOST 398 | case 'host': 399 | $_SERVER['HTTP_HOST'] = $value; 400 | $tmp = explode(':', $value); 401 | $_SERVER['SERVER_NAME'] = $tmp[0]; 402 | if (isset($tmp[1])) { 403 | $_SERVER['SERVER_PORT'] = $tmp[1]; 404 | } 405 | break; 406 | // HTTP_COOKIE 407 | case 'cookie': 408 | $_SERVER['HTTP_COOKIE'] = $value; 409 | parse_str(str_replace('; ', '&', $_SERVER['HTTP_COOKIE']), $_COOKIE); 410 | break; 411 | // HTTP_USER_AGENT 412 | case 'user-agent': 413 | $_SERVER['HTTP_USER_AGENT'] = $value; 414 | break; 415 | // HTTP_REFERER 416 | case 'referer': 417 | $_SERVER['HTTP_REFERER'] = $value; 418 | break; 419 | case 'origin': 420 | $_SERVER['HTTP_ORIGIN'] = $value; 421 | break; 422 | } 423 | } 424 | 425 | // QUERY_STRING 426 | $_SERVER['QUERY_STRING'] = parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY); 427 | if ($_SERVER['QUERY_STRING']) { 428 | // $GET 429 | parse_str($_SERVER['QUERY_STRING'], $_GET); 430 | } else { 431 | $_SERVER['QUERY_STRING'] = ''; 432 | } 433 | } 434 | } 435 | -------------------------------------------------------------------------------- /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 | 18 | /** 19 | * http protocol 20 | */ 21 | class Http 22 | { 23 | /** 24 | * Check the integrity of the package. 25 | * 26 | * @param string $recv_buffer 27 | * @param TcpConnection $connection 28 | * @return int 29 | */ 30 | public static function input($recv_buffer, TcpConnection $connection) 31 | { 32 | if (!strpos($recv_buffer, "\r\n\r\n")) { 33 | // Judge whether the package length exceeds the limit. 34 | if (strlen($recv_buffer) >= TcpConnection::$maxPackageSize) { 35 | $connection->close(); 36 | return 0; 37 | } 38 | return 0; 39 | } 40 | 41 | list($header,) = explode("\r\n\r\n", $recv_buffer, 2); 42 | if (0 === strpos($recv_buffer, "POST")) { 43 | // find Content-Length 44 | $match = array(); 45 | if (preg_match("/\r\nContent-Length: ?(\d+)/i", $header, $match)) { 46 | $content_length = $match[1]; 47 | return $content_length + strlen($header) + 4; 48 | } else { 49 | return 0; 50 | } 51 | } else { 52 | return strlen($header) + 4; 53 | } 54 | } 55 | 56 | /** 57 | * Parse $_POST、$_GET、$_COOKIE. 58 | * 59 | * @param string $recv_buffer 60 | * @param TcpConnection $connection 61 | * @return array 62 | */ 63 | public static function decode($recv_buffer, TcpConnection $connection) 64 | { 65 | // Init. 66 | $_POST = $_GET = $_COOKIE = $_REQUEST = $_SESSION = $_FILES = array(); 67 | $GLOBALS['HTTP_RAW_POST_DATA'] = ''; 68 | // Clear cache. 69 | HttpCache::$header = array('Connection' => 'Connection: keep-alive'); 70 | HttpCache::$instance = new HttpCache(); 71 | // $_SERVER 72 | $_SERVER = array( 73 | 'QUERY_STRING' => '', 74 | 'REQUEST_METHOD' => '', 75 | 'REQUEST_URI' => '', 76 | 'SERVER_PROTOCOL' => '', 77 | 'SERVER_SOFTWARE' => 'workerman/3.0', 78 | 'SERVER_NAME' => '', 79 | 'HTTP_HOST' => '', 80 | 'HTTP_USER_AGENT' => '', 81 | 'HTTP_ACCEPT' => '', 82 | 'HTTP_ACCEPT_LANGUAGE' => '', 83 | 'HTTP_ACCEPT_ENCODING' => '', 84 | 'HTTP_COOKIE' => '', 85 | 'HTTP_CONNECTION' => '', 86 | 'REMOTE_ADDR' => '', 87 | 'REMOTE_PORT' => '0', 88 | ); 89 | 90 | // Parse headers. 91 | list($http_header, $http_body) = explode("\r\n\r\n", $recv_buffer, 2); 92 | $header_data = explode("\r\n", $http_header); 93 | 94 | list($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI'], $_SERVER['SERVER_PROTOCOL']) = explode(' ', 95 | $header_data[0]); 96 | 97 | $http_post_boundary = ''; 98 | unset($header_data[0]); 99 | foreach ($header_data as $content) { 100 | // \r\n\r\n 101 | if (empty($content)) { 102 | continue; 103 | } 104 | list($key, $value) = explode(':', $content, 2); 105 | $key = strtolower($key); 106 | $value = trim($value); 107 | switch ($key) { 108 | // HTTP_HOST 109 | case 'host': 110 | $_SERVER['HTTP_HOST'] = $value; 111 | $tmp = explode(':', $value); 112 | $_SERVER['SERVER_NAME'] = $tmp[0]; 113 | if (isset($tmp[1])) { 114 | $_SERVER['SERVER_PORT'] = $tmp[1]; 115 | } 116 | break; 117 | // cookie 118 | case 'cookie': 119 | $_SERVER['HTTP_COOKIE'] = $value; 120 | parse_str(str_replace('; ', '&', $_SERVER['HTTP_COOKIE']), $_COOKIE); 121 | break; 122 | // user-agent 123 | case 'user-agent': 124 | $_SERVER['HTTP_USER_AGENT'] = $value; 125 | break; 126 | // accept 127 | case 'accept': 128 | $_SERVER['HTTP_ACCEPT'] = $value; 129 | break; 130 | // accept-language 131 | case 'accept-language': 132 | $_SERVER['HTTP_ACCEPT_LANGUAGE'] = $value; 133 | break; 134 | // accept-encoding 135 | case 'accept-encoding': 136 | $_SERVER['HTTP_ACCEPT_ENCODING'] = $value; 137 | break; 138 | // connection 139 | case 'connection': 140 | $_SERVER['HTTP_CONNECTION'] = $value; 141 | break; 142 | case 'referer': 143 | $_SERVER['HTTP_REFERER'] = $value; 144 | break; 145 | case 'if-modified-since': 146 | $_SERVER['HTTP_IF_MODIFIED_SINCE'] = $value; 147 | break; 148 | case 'if-none-match': 149 | $_SERVER['HTTP_IF_NONE_MATCH'] = $value; 150 | break; 151 | case 'content-type': 152 | if (!preg_match('/boundary="?(\S+)"?/', $value, $match)) { 153 | $_SERVER['CONTENT_TYPE'] = $value; 154 | } else { 155 | $_SERVER['CONTENT_TYPE'] = 'multipart/form-data'; 156 | $http_post_boundary = '--' . $match[1]; 157 | } 158 | break; 159 | } 160 | } 161 | 162 | // Parse $_POST. 163 | if ($_SERVER['REQUEST_METHOD'] === 'POST') { 164 | if (isset($_SERVER['CONTENT_TYPE']) && $_SERVER['CONTENT_TYPE'] === 'multipart/form-data') { 165 | self::parseUploadFiles($http_body, $http_post_boundary); 166 | } else { 167 | parse_str($http_body, $_POST); 168 | // $GLOBALS['HTTP_RAW_POST_DATA'] 169 | $GLOBALS['HTTP_RAW_POST_DATA'] = $http_body; 170 | } 171 | } 172 | 173 | // QUERY_STRING 174 | $_SERVER['QUERY_STRING'] = parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY); 175 | if ($_SERVER['QUERY_STRING']) { 176 | // $GET 177 | parse_str($_SERVER['QUERY_STRING'], $_GET); 178 | } else { 179 | $_SERVER['QUERY_STRING'] = ''; 180 | } 181 | 182 | // REQUEST 183 | $_REQUEST = array_merge($_GET, $_POST); 184 | 185 | // REMOTE_ADDR REMOTE_PORT 186 | $_SERVER['REMOTE_ADDR'] = $connection->getRemoteIp(); 187 | $_SERVER['REMOTE_PORT'] = $connection->getRemotePort(); 188 | 189 | return array('get' => $_GET, 'post' => $_POST, 'cookie' => $_COOKIE, 'server' => $_SERVER, 'files' => $_FILES); 190 | } 191 | 192 | /** 193 | * Http encode. 194 | * 195 | * @param string $content 196 | * @param TcpConnection $connection 197 | * @return string 198 | */ 199 | public static function encode($content, TcpConnection $connection) 200 | { 201 | // Default http-code. 202 | if (!isset(HttpCache::$header['Http-Code'])) { 203 | $header = "HTTP/1.1 200 OK\r\n"; 204 | } else { 205 | $header = HttpCache::$header['Http-Code'] . "\r\n"; 206 | unset(HttpCache::$header['Http-Code']); 207 | } 208 | 209 | // Content-Type 210 | if (!isset(HttpCache::$header['Content-Type'])) { 211 | $header .= "Content-Type: text/html;charset=utf-8\r\n"; 212 | } 213 | 214 | // other headers 215 | foreach (HttpCache::$header as $key => $item) { 216 | if ('Set-Cookie' === $key && is_array($item)) { 217 | foreach ($item as $it) { 218 | $header .= $it . "\r\n"; 219 | } 220 | } else { 221 | $header .= $item . "\r\n"; 222 | } 223 | } 224 | 225 | // header 226 | $header .= "Server: WorkerMan/3.0\r\nContent-Length: " . strlen($content) . "\r\n\r\n"; 227 | 228 | // save session 229 | self::sessionWriteClose(); 230 | 231 | // the whole http package 232 | return $header . $content; 233 | } 234 | 235 | /** 236 | * 设置http头 237 | * 238 | * @return bool|void 239 | */ 240 | public static function header($content, $replace = true, $http_response_code = 0) 241 | { 242 | if (PHP_SAPI != 'cli') { 243 | return $http_response_code ? header($content, $replace, $http_response_code) : header($content, $replace); 244 | } 245 | if (strpos($content, 'HTTP') === 0) { 246 | $key = 'Http-Code'; 247 | } else { 248 | $key = strstr($content, ":", true); 249 | if (empty($key)) { 250 | return false; 251 | } 252 | } 253 | 254 | if ('location' === strtolower($key) && !$http_response_code) { 255 | return self::header($content, true, 302); 256 | } 257 | 258 | if (isset(HttpCache::$codes[$http_response_code])) { 259 | HttpCache::$header['Http-Code'] = "HTTP/1.1 $http_response_code " . HttpCache::$codes[$http_response_code]; 260 | if ($key === 'Http-Code') { 261 | return true; 262 | } 263 | } 264 | 265 | if ($key === 'Set-Cookie') { 266 | HttpCache::$header[$key][] = $content; 267 | } else { 268 | HttpCache::$header[$key] = $content; 269 | } 270 | 271 | return true; 272 | } 273 | 274 | /** 275 | * Remove header. 276 | * 277 | * @param string $name 278 | * @return void 279 | */ 280 | public static function headerRemove($name) 281 | { 282 | if (PHP_SAPI != 'cli') { 283 | header_remove($name); 284 | return; 285 | } 286 | unset(HttpCache::$header[$name]); 287 | } 288 | 289 | /** 290 | * Set cookie. 291 | * 292 | * @param string $name 293 | * @param string $value 294 | * @param integer $maxage 295 | * @param string $path 296 | * @param string $domain 297 | * @param bool $secure 298 | * @param bool $HTTPOnly 299 | * @return bool|void 300 | */ 301 | public static function setcookie( 302 | $name, 303 | $value = '', 304 | $maxage = 0, 305 | $path = '', 306 | $domain = '', 307 | $secure = false, 308 | $HTTPOnly = false 309 | ) { 310 | if (PHP_SAPI != 'cli') { 311 | return setcookie($name, $value, $maxage, $path, $domain, $secure, $HTTPOnly); 312 | } 313 | return self::header( 314 | 'Set-Cookie: ' . $name . '=' . rawurlencode($value) 315 | . (empty($domain) ? '' : '; Domain=' . $domain) 316 | . (empty($maxage) ? '' : '; Max-Age=' . $maxage) 317 | . (empty($path) ? '' : '; Path=' . $path) 318 | . (!$secure ? '' : '; Secure') 319 | . (!$HTTPOnly ? '' : '; HttpOnly'), false); 320 | } 321 | 322 | /** 323 | * sessionStart 324 | * 325 | * @return bool 326 | */ 327 | public static function sessionStart() 328 | { 329 | if (PHP_SAPI != 'cli') { 330 | return session_start(); 331 | } 332 | if (HttpCache::$instance->sessionStarted) { 333 | echo "already sessionStarted\nn"; 334 | return true; 335 | } 336 | HttpCache::$instance->sessionStarted = true; 337 | // Generate a SID. 338 | if (!isset($_COOKIE[HttpCache::$sessionName]) || !is_file(HttpCache::$sessionPath . '/ses' . $_COOKIE[HttpCache::$sessionName])) { 339 | $file_name = tempnam(HttpCache::$sessionPath, 'ses'); 340 | if (!$file_name) { 341 | return false; 342 | } 343 | HttpCache::$instance->sessionFile = $file_name; 344 | $session_id = substr(basename($file_name), strlen('ses')); 345 | return self::setcookie( 346 | HttpCache::$sessionName 347 | , $session_id 348 | , ini_get('session.cookie_lifetime') 349 | , ini_get('session.cookie_path') 350 | , ini_get('session.cookie_domain') 351 | , ini_get('session.cookie_secure') 352 | , ini_get('session.cookie_httponly') 353 | ); 354 | } 355 | if (!HttpCache::$instance->sessionFile) { 356 | HttpCache::$instance->sessionFile = HttpCache::$sessionPath . '/ses' . $_COOKIE[HttpCache::$sessionName]; 357 | } 358 | // Read session from session file. 359 | if (HttpCache::$instance->sessionFile) { 360 | $raw = file_get_contents(HttpCache::$instance->sessionFile); 361 | if ($raw) { 362 | session_decode($raw); 363 | } 364 | } 365 | } 366 | 367 | /** 368 | * Save session. 369 | * 370 | * @return bool 371 | */ 372 | public static function sessionWriteClose() 373 | { 374 | if (PHP_SAPI != 'cli') { 375 | return session_write_close(); 376 | } 377 | if (!empty(HttpCache::$instance->sessionStarted) && !empty($_SESSION)) { 378 | $session_str = session_encode(); 379 | if ($session_str && HttpCache::$instance->sessionFile) { 380 | return file_put_contents(HttpCache::$instance->sessionFile, $session_str); 381 | } 382 | } 383 | return empty($_SESSION); 384 | } 385 | 386 | /** 387 | * End, like call exit in php-fpm. 388 | * 389 | * @param string $msg 390 | * @throws \Exception 391 | */ 392 | public static function end($msg = '') 393 | { 394 | if (PHP_SAPI != 'cli') { 395 | exit($msg); 396 | } 397 | if ($msg) { 398 | echo $msg; 399 | } 400 | throw new \Exception('jump_exit'); 401 | } 402 | 403 | /** 404 | * Get mime types. 405 | * 406 | * @return string 407 | */ 408 | public static function getMimeTypesFile() 409 | { 410 | return __DIR__ . '/Http/mime.types'; 411 | } 412 | 413 | /** 414 | * Parse $_FILES. 415 | * 416 | * @param string $http_body 417 | * @param string $http_post_boundary 418 | * @return void 419 | */ 420 | protected static function parseUploadFiles($http_body, $http_post_boundary) 421 | { 422 | $http_body = substr($http_body, 0, strlen($http_body) - (strlen($http_post_boundary) + 4)); 423 | $boundary_data_array = explode($http_post_boundary . "\r\n", $http_body); 424 | if ($boundary_data_array[0] === '') { 425 | unset($boundary_data_array[0]); 426 | } 427 | foreach ($boundary_data_array as $boundary_data_buffer) { 428 | list($boundary_header_buffer, $boundary_value) = explode("\r\n\r\n", $boundary_data_buffer, 2); 429 | // Remove \r\n from the end of buffer. 430 | $boundary_value = substr($boundary_value, 0, -2); 431 | foreach (explode("\r\n", $boundary_header_buffer) as $item) { 432 | list($header_key, $header_value) = explode(": ", $item); 433 | $header_key = strtolower($header_key); 434 | switch ($header_key) { 435 | case "content-disposition": 436 | // Is file data. 437 | if (preg_match('/name=".*?"; filename="(.*?)"$/', $header_value, $match)) { 438 | // Parse $_FILES. 439 | $_FILES[] = array( 440 | 'file_name' => $match[1], 441 | 'file_data' => $boundary_value, 442 | 'file_size' => strlen($boundary_value), 443 | ); 444 | continue; 445 | } // Is post field. 446 | else { 447 | // Parse $_POST. 448 | if (preg_match('/name="(.*?)"$/', $header_value, $match)) { 449 | $_POST[$match[1]] = $boundary_value; 450 | } 451 | } 452 | break; 453 | } 454 | } 455 | } 456 | } 457 | } 458 | 459 | /** 460 | * Http cache for the current http response. 461 | */ 462 | class HttpCache 463 | { 464 | public static $codes = array( 465 | 100 => 'Continue', 466 | 101 => 'Switching Protocols', 467 | 200 => 'OK', 468 | 201 => 'Created', 469 | 202 => 'Accepted', 470 | 203 => 'Non-Authoritative Information', 471 | 204 => 'No Content', 472 | 205 => 'Reset Content', 473 | 206 => 'Partial Content', 474 | 300 => 'Multiple Choices', 475 | 301 => 'Moved Permanently', 476 | 302 => 'Found', 477 | 303 => 'See Other', 478 | 304 => 'Not Modified', 479 | 305 => 'Use Proxy', 480 | 306 => '(Unused)', 481 | 307 => 'Temporary Redirect', 482 | 400 => 'Bad Request', 483 | 401 => 'Unauthorized', 484 | 402 => 'Payment Required', 485 | 403 => 'Forbidden', 486 | 404 => 'Not Found', 487 | 405 => 'Method Not Allowed', 488 | 406 => 'Not Acceptable', 489 | 407 => 'Proxy Authentication Required', 490 | 408 => 'Request Timeout', 491 | 409 => 'Conflict', 492 | 410 => 'Gone', 493 | 411 => 'Length Required', 494 | 412 => 'Precondition Failed', 495 | 413 => 'Request Entity Too Large', 496 | 414 => 'Request-URI Too Long', 497 | 415 => 'Unsupported Media Type', 498 | 416 => 'Requested Range Not Satisfiable', 499 | 417 => 'Expectation Failed', 500 | 422 => 'Unprocessable Entity', 501 | 423 => 'Locked', 502 | 500 => 'Internal Server Error', 503 | 501 => 'Not Implemented', 504 | 502 => 'Bad Gateway', 505 | 503 => 'Service Unavailable', 506 | 504 => 'Gateway Timeout', 507 | 505 => 'HTTP Version Not Supported', 508 | ); 509 | 510 | /** 511 | * @var HttpCache 512 | */ 513 | public static $instance = null; 514 | 515 | public static $header = array(); 516 | public static $sessionPath = ''; 517 | public static $sessionName = ''; 518 | public $sessionStarted = false; 519 | public $sessionFile = ''; 520 | 521 | public static function init() 522 | { 523 | self::$sessionName = ini_get('session.name'); 524 | self::$sessionPath = session_save_path(); 525 | if (!self::$sessionPath) { 526 | self::$sessionPath = sys_get_temp_dir(); 527 | } 528 | @\session_start(); 529 | } 530 | } 531 | -------------------------------------------------------------------------------- /Workerman/Connection/TcpConnection.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | namespace Workerman\Connection; 15 | 16 | use Workerman\Events\EventInterface; 17 | use Workerman\Worker; 18 | use Exception; 19 | 20 | /** 21 | * TcpConnection. 22 | */ 23 | class TcpConnection extends ConnectionInterface 24 | { 25 | /** 26 | * Read buffer size. 27 | * 28 | * @var int 29 | */ 30 | const READ_BUFFER_SIZE = 65535; 31 | 32 | /** 33 | * Status connecting. 34 | * 35 | * @var int 36 | */ 37 | const STATUS_CONNECTING = 1; 38 | 39 | /** 40 | * Status connection established. 41 | * 42 | * @var int 43 | */ 44 | const STATUS_ESTABLISH = 2; 45 | 46 | /** 47 | * Status closing. 48 | * 49 | * @var int 50 | */ 51 | const STATUS_CLOSING = 4; 52 | 53 | /** 54 | * Status closed. 55 | * 56 | * @var int 57 | */ 58 | const STATUS_CLOSED = 8; 59 | 60 | /** 61 | * Emitted when data is received. 62 | * 63 | * @var callback 64 | */ 65 | public $onMessage = null; 66 | 67 | /** 68 | * Emitted when the other end of the socket sends a FIN packet. 69 | * 70 | * @var callback 71 | */ 72 | public $onClose = null; 73 | 74 | /** 75 | * Emitted when an error occurs with connection. 76 | * 77 | * @var callback 78 | */ 79 | public $onError = null; 80 | 81 | /** 82 | * Emitted when the send buffer becomes full. 83 | * 84 | * @var callback 85 | */ 86 | public $onBufferFull = null; 87 | 88 | /** 89 | * Emitted when the send buffer becomes empty. 90 | * 91 | * @var callback 92 | */ 93 | public $onBufferDrain = null; 94 | 95 | /** 96 | * Application layer protocol. 97 | * The format is like this Workerman\\Protocols\\Http. 98 | * 99 | * @var \Workerman\Protocols\ProtocolInterface 100 | */ 101 | public $protocol = null; 102 | 103 | /** 104 | * Which worker belong to. 105 | * 106 | * @var Worker 107 | */ 108 | public $worker = null; 109 | 110 | /** 111 | * Connection->id. 112 | * 113 | * @var int 114 | */ 115 | public $id = 0; 116 | 117 | /** 118 | * A copy of $worker->id which used to clean up the connection in worker->connections 119 | * 120 | * @var int 121 | */ 122 | protected $_id = 0; 123 | 124 | /** 125 | * Sets the maximum send buffer size for the current connection. 126 | * OnBufferFull callback will be emited When the send buffer is full. 127 | * 128 | * @var int 129 | */ 130 | public $maxSendBufferSize = 1048576; 131 | 132 | /** 133 | * Default send buffer size. 134 | * 135 | * @var int 136 | */ 137 | public static $defaultMaxSendBufferSize = 1048576; 138 | 139 | /** 140 | * Maximum acceptable packet size. 141 | * 142 | * @var int 143 | */ 144 | public static $maxPackageSize = 10485760; 145 | 146 | /** 147 | * Id recorder. 148 | * 149 | * @var int 150 | */ 151 | protected static $_idRecorder = 1; 152 | 153 | /** 154 | * Socket 155 | * 156 | * @var resource 157 | */ 158 | protected $_socket = null; 159 | 160 | /** 161 | * Send buffer. 162 | * 163 | * @var string 164 | */ 165 | protected $_sendBuffer = ''; 166 | 167 | /** 168 | * Receive buffer. 169 | * 170 | * @var string 171 | */ 172 | protected $_recvBuffer = ''; 173 | 174 | /** 175 | * Current package length. 176 | * 177 | * @var int 178 | */ 179 | protected $_currentPackageLength = 0; 180 | 181 | /** 182 | * Connection status. 183 | * 184 | * @var int 185 | */ 186 | protected $_status = self::STATUS_ESTABLISH; 187 | 188 | /** 189 | * Remote address. 190 | * 191 | * @var string 192 | */ 193 | protected $_remoteAddress = ''; 194 | 195 | /** 196 | * Is paused. 197 | * 198 | * @var bool 199 | */ 200 | protected $_isPaused = false; 201 | 202 | /** 203 | * Construct. 204 | * 205 | * @param resource $socket 206 | * @param string $remote_address 207 | */ 208 | public function __construct($socket, $remote_address = '') 209 | { 210 | self::$statistics['connection_count']++; 211 | $this->id = $this->_id = self::$_idRecorder++; 212 | $this->_socket = $socket; 213 | stream_set_blocking($this->_socket, 0); 214 | Worker::$globalEvent->add($this->_socket, EventInterface::EV_READ, array($this, 'baseRead')); 215 | $this->maxSendBufferSize = self::$defaultMaxSendBufferSize; 216 | $this->_remoteAddress = $remote_address; 217 | } 218 | 219 | /** 220 | * Sends data on the connection. 221 | * 222 | * @param string $send_buffer 223 | * @param bool $raw 224 | * @return void|bool|null 225 | */ 226 | public function send($send_buffer, $raw = false) 227 | { 228 | // Try to call protocol::encode($send_buffer) before sending. 229 | if (false === $raw && $this->protocol) { 230 | $parser = $this->protocol; 231 | $send_buffer = $parser::encode($send_buffer, $this); 232 | if ($send_buffer === '') { 233 | return null; 234 | } 235 | } 236 | 237 | if ($this->_status === self::STATUS_CONNECTING) { 238 | $this->_sendBuffer .= $send_buffer; 239 | return null; 240 | } elseif ($this->_status === self::STATUS_CLOSING || $this->_status === self::STATUS_CLOSED) { 241 | return false; 242 | } 243 | 244 | // Attempt to send data directly. 245 | if ($this->_sendBuffer === '') { 246 | $len = @fwrite($this->_socket, $send_buffer); 247 | // send successful. 248 | if ($len === strlen($send_buffer)) { 249 | return true; 250 | } 251 | // Send only part of the data. 252 | if ($len > 0) { 253 | $this->_sendBuffer = substr($send_buffer, $len); 254 | } else { 255 | // Connection closed? 256 | if (!is_resource($this->_socket) || feof($this->_socket)) { 257 | self::$statistics['send_fail']++; 258 | if ($this->onError) { 259 | try { 260 | call_user_func($this->onError, $this, WORKERMAN_SEND_FAIL, 'client closed'); 261 | } catch (\Exception $e) { 262 | echo $e; 263 | exit(250); 264 | } catch (\Error $e) { 265 | echo $e; 266 | exit(250); 267 | } 268 | } 269 | $this->destroy(); 270 | return false; 271 | } 272 | $this->_sendBuffer = $send_buffer; 273 | } 274 | Worker::$globalEvent->add($this->_socket, EventInterface::EV_WRITE, array($this, 'baseWrite')); 275 | // Check if the send buffer is full. 276 | $this->checkBufferIsFull(); 277 | return null; 278 | } else { 279 | // Buffer has been marked as full but still has data to send the packet is discarded. 280 | if ($this->maxSendBufferSize <= strlen($this->_sendBuffer)) { 281 | self::$statistics['send_fail']++; 282 | if ($this->onError) { 283 | try { 284 | call_user_func($this->onError, $this, WORKERMAN_SEND_FAIL, 'send buffer full and drop package'); 285 | } catch (\Exception $e) { 286 | echo $e; 287 | exit(250); 288 | } 289 | } 290 | return false; 291 | } 292 | $this->_sendBuffer .= $send_buffer; 293 | // Check if the send buffer is full. 294 | $this->checkBufferIsFull(); 295 | } 296 | } 297 | 298 | /** 299 | * Get remote IP. 300 | * 301 | * @return string 302 | */ 303 | public function getRemoteIp() 304 | { 305 | $pos = strrpos($this->_remoteAddress, ':'); 306 | if ($pos) { 307 | return substr($this->_remoteAddress, 0, $pos); 308 | } 309 | return ''; 310 | } 311 | 312 | /** 313 | * Get remote port. 314 | * 315 | * @return int 316 | */ 317 | public function getRemotePort() 318 | { 319 | if ($this->_remoteAddress) { 320 | return (int)substr(strrchr($this->_remoteAddress, ':'), 1); 321 | } 322 | return 0; 323 | } 324 | 325 | /** 326 | * Pauses the reading of data. That is onMessage will not be emitted. Useful to throttle back an upload. 327 | * 328 | * @return void 329 | */ 330 | public function pauseRecv() 331 | { 332 | Worker::$globalEvent->del($this->_socket, EventInterface::EV_READ); 333 | $this->_isPaused = true; 334 | } 335 | 336 | /** 337 | * Resumes reading after a call to pauseRecv. 338 | * 339 | * @return void 340 | */ 341 | public function resumeRecv() 342 | { 343 | if ($this->_isPaused === true) { 344 | Worker::$globalEvent->add($this->_socket, EventInterface::EV_READ, array($this, 'baseRead')); 345 | $this->_isPaused = false; 346 | $this->baseRead($this->_socket, false); 347 | } 348 | } 349 | 350 | /** 351 | * Base read handler. 352 | * 353 | * @param resource $socket 354 | * @return void 355 | */ 356 | public function baseRead($socket, $check_eof = true) 357 | { 358 | $read_data = false; 359 | while (1) { 360 | $buffer = fread($socket, self::READ_BUFFER_SIZE); 361 | if ($buffer === '' || $buffer === false) { 362 | break; 363 | } 364 | $read_data = true; 365 | $this->_recvBuffer .= $buffer; 366 | } 367 | 368 | // Check connection closed. 369 | if (!$read_data && $check_eof) { 370 | $this->destroy(); 371 | return; 372 | } 373 | 374 | // If the application layer protocol has been set up. 375 | if ($this->protocol) { 376 | $parser = $this->protocol; 377 | while ($this->_recvBuffer !== '' && !$this->_isPaused) { 378 | // The current packet length is known. 379 | if ($this->_currentPackageLength) { 380 | // Data is not enough for a package. 381 | if ($this->_currentPackageLength > strlen($this->_recvBuffer)) { 382 | break; 383 | } 384 | } else { 385 | // Get current package length. 386 | $this->_currentPackageLength = $parser::input($this->_recvBuffer, $this); 387 | // The packet length is unknown. 388 | if ($this->_currentPackageLength === 0) { 389 | break; 390 | } elseif ($this->_currentPackageLength > 0 && $this->_currentPackageLength <= self::$maxPackageSize) { 391 | // Data is not enough for a package. 392 | if ($this->_currentPackageLength > strlen($this->_recvBuffer)) { 393 | break; 394 | } 395 | } // Wrong package. 396 | else { 397 | echo 'error package. package_length=' . var_export($this->_currentPackageLength, true); 398 | $this->destroy(); 399 | return; 400 | } 401 | } 402 | 403 | // The data is enough for a packet. 404 | self::$statistics['total_request']++; 405 | // The current packet length is equal to the length of the buffer. 406 | if (strlen($this->_recvBuffer) === $this->_currentPackageLength) { 407 | $one_request_buffer = $this->_recvBuffer; 408 | $this->_recvBuffer = ''; 409 | } else { 410 | // Get a full package from the buffer. 411 | $one_request_buffer = substr($this->_recvBuffer, 0, $this->_currentPackageLength); 412 | // Remove the current package from the receive buffer. 413 | $this->_recvBuffer = substr($this->_recvBuffer, $this->_currentPackageLength); 414 | } 415 | // Reset the current packet length to 0. 416 | $this->_currentPackageLength = 0; 417 | if (!$this->onMessage) { 418 | continue; 419 | } 420 | try { 421 | // Decode request buffer before Emiting onMessage callback. 422 | call_user_func($this->onMessage, $this, $parser::decode($one_request_buffer, $this)); 423 | } catch (\Exception $e) { 424 | echo $e; 425 | exit(250); 426 | } catch (\Error $e) { 427 | echo $e; 428 | exit(250); 429 | } 430 | } 431 | return; 432 | } 433 | 434 | if ($this->_recvBuffer === '' || $this->_isPaused) { 435 | return; 436 | } 437 | 438 | // Applications protocol is not set. 439 | self::$statistics['total_request']++; 440 | if (!$this->onMessage) { 441 | $this->_recvBuffer = ''; 442 | return; 443 | } 444 | try { 445 | call_user_func($this->onMessage, $this, $this->_recvBuffer); 446 | } catch (\Exception $e) { 447 | echo $e; 448 | exit(250); 449 | } catch (\Error $e) { 450 | echo $e; 451 | exit(250); 452 | } 453 | // Clean receive buffer. 454 | $this->_recvBuffer = ''; 455 | } 456 | 457 | /** 458 | * Base write handler. 459 | * 460 | * @return void|bool 461 | */ 462 | public function baseWrite() 463 | { 464 | $len = @fwrite($this->_socket, $this->_sendBuffer); 465 | if ($len === strlen($this->_sendBuffer)) { 466 | Worker::$globalEvent->del($this->_socket, EventInterface::EV_WRITE); 467 | $this->_sendBuffer = ''; 468 | // Try to emit onBufferDrain callback when the send buffer becomes empty. 469 | if ($this->onBufferDrain) { 470 | try { 471 | call_user_func($this->onBufferDrain, $this); 472 | } catch (\Exception $e) { 473 | echo $e; 474 | exit(250); 475 | } catch (\Error $e) { 476 | echo $e; 477 | exit(250); 478 | } 479 | } 480 | if ($this->_status === self::STATUS_CLOSING) { 481 | $this->destroy(); 482 | } 483 | return true; 484 | } 485 | if ($len > 0) { 486 | $this->_sendBuffer = substr($this->_sendBuffer, $len); 487 | } else { 488 | self::$statistics['send_fail']++; 489 | $this->destroy(); 490 | } 491 | } 492 | 493 | /** 494 | * This method pulls all the data out of a readable stream, and writes it to the supplied destination. 495 | * 496 | * @param TcpConnection $dest 497 | * @return void 498 | */ 499 | public function pipe($dest) 500 | { 501 | $source = $this; 502 | $this->onMessage = function ($source, $data) use ($dest) { 503 | $dest->send($data); 504 | }; 505 | $this->onClose = function ($source) use ($dest) { 506 | $dest->destroy(); 507 | }; 508 | $dest->onBufferFull = function ($dest) use ($source) { 509 | $source->pauseRecv(); 510 | }; 511 | $dest->onBufferDrain = function ($dest) use ($source) { 512 | $source->resumeRecv(); 513 | }; 514 | } 515 | 516 | /** 517 | * Remove $length of data from receive buffer. 518 | * 519 | * @param int $length 520 | * @return void 521 | */ 522 | public function consumeRecvBuffer($length) 523 | { 524 | $this->_recvBuffer = substr($this->_recvBuffer, $length); 525 | } 526 | 527 | /** 528 | * Close connection. 529 | * 530 | * @param mixed $data 531 | * @return void 532 | */ 533 | public function close($data = null) 534 | { 535 | if ($this->_status === self::STATUS_CLOSING || $this->_status === self::STATUS_CLOSED) { 536 | return; 537 | } else { 538 | if ($data !== null) { 539 | $this->send($data); 540 | } 541 | $this->_status = self::STATUS_CLOSING; 542 | } 543 | if ($this->_sendBuffer === '') { 544 | $this->destroy(); 545 | } 546 | } 547 | 548 | /** 549 | * Get the real socket. 550 | * 551 | * @return resource 552 | */ 553 | public function getSocket() 554 | { 555 | return $this->_socket; 556 | } 557 | 558 | /** 559 | * Check whether the send buffer is full. 560 | * 561 | * @return void 562 | */ 563 | protected function checkBufferIsFull() 564 | { 565 | if ($this->maxSendBufferSize <= strlen($this->_sendBuffer)) { 566 | if ($this->onBufferFull) { 567 | try { 568 | call_user_func($this->onBufferFull, $this); 569 | } catch (\Exception $e) { 570 | echo $e; 571 | exit(250); 572 | } catch (\Error $e) { 573 | echo $e; 574 | exit(250); 575 | } 576 | } 577 | } 578 | } 579 | 580 | /** 581 | * Destroy connection. 582 | * 583 | * @return void 584 | */ 585 | public function destroy() 586 | { 587 | // Avoid repeated calls. 588 | if ($this->_status === self::STATUS_CLOSED) { 589 | return; 590 | } 591 | // Remove event listener. 592 | Worker::$globalEvent->del($this->_socket, EventInterface::EV_READ); 593 | Worker::$globalEvent->del($this->_socket, EventInterface::EV_WRITE); 594 | // Close socket. 595 | @fclose($this->_socket); 596 | // Remove from worker->connections. 597 | if ($this->worker) { 598 | unset($this->worker->connections[$this->_id]); 599 | } 600 | $this->_status = self::STATUS_CLOSED; 601 | // Try to emit onClose callback. 602 | if ($this->onClose) { 603 | try { 604 | call_user_func($this->onClose, $this); 605 | } catch (\Exception $e) { 606 | echo $e; 607 | exit(250); 608 | } catch (\Error $e) { 609 | echo $e; 610 | exit(250); 611 | } 612 | } 613 | // Cleaning up the callback to avoid memory leaks. 614 | $this->onMessage = $this->onClose = $this->onError = $this->onBufferFull = $this->onBufferDrain = null; 615 | } 616 | 617 | /** 618 | * Destruct. 619 | * 620 | * @return void 621 | */ 622 | public function __destruct() 623 | { 624 | self::$statistics['connection_count']--; 625 | } 626 | } 627 | -------------------------------------------------------------------------------- /Workerman/Worker.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | namespace Workerman; 15 | 16 | require_once __DIR__ . '/Lib/Constants.php'; 17 | 18 | use Workerman\Events\EventInterface; 19 | use Workerman\Connection\ConnectionInterface; 20 | use Workerman\Connection\TcpConnection; 21 | use Workerman\Connection\UdpConnection; 22 | use Workerman\Lib\Timer; 23 | use Exception; 24 | 25 | /** 26 | * Worker class 27 | * A container for listening ports 28 | */ 29 | class Worker 30 | { 31 | /** 32 | * Version. 33 | * 34 | * @var string 35 | */ 36 | const VERSION = '3.3.2'; 37 | 38 | /** 39 | * Status starting. 40 | * 41 | * @var int 42 | */ 43 | const STATUS_STARTING = 1; 44 | 45 | /** 46 | * Status running. 47 | * 48 | * @var int 49 | */ 50 | const STATUS_RUNNING = 2; 51 | 52 | /** 53 | * Status shutdown. 54 | * 55 | * @var int 56 | */ 57 | const STATUS_SHUTDOWN = 4; 58 | 59 | /** 60 | * Status reloading. 61 | * 62 | * @var int 63 | */ 64 | const STATUS_RELOADING = 8; 65 | 66 | /** 67 | * After sending the restart command to the child process KILL_WORKER_TIMER_TIME seconds, 68 | * if the process is still living then forced to kill. 69 | * 70 | * @var int 71 | */ 72 | const KILL_WORKER_TIMER_TIME = 2; 73 | 74 | /** 75 | * Default backlog. Backlog is the maximum length of the queue of pending connections. 76 | * 77 | * @var int 78 | */ 79 | const DEFAUL_BACKLOG = 1024; 80 | 81 | /** 82 | * Max udp package size. 83 | * 84 | * @var int 85 | */ 86 | const MAX_UDP_PACKAGE_SIZE = 65535; 87 | 88 | /** 89 | * Worker id. 90 | * 91 | * @var int 92 | */ 93 | public $id = 0; 94 | 95 | /** 96 | * Name of the worker processes. 97 | * 98 | * @var string 99 | */ 100 | public $name = 'none'; 101 | 102 | /** 103 | * Number of worker processes. 104 | * 105 | * @var int 106 | */ 107 | public $count = 1; 108 | 109 | /** 110 | * Unix user of processes, needs appropriate privileges (usually root). 111 | * 112 | * @var string 113 | */ 114 | public $user = ''; 115 | 116 | /** 117 | * Unix group of processes, needs appropriate privileges (usually root). 118 | * 119 | * @var string 120 | */ 121 | public $group = ''; 122 | 123 | /** 124 | * reloadable. 125 | * 126 | * @var bool 127 | */ 128 | public $reloadable = true; 129 | 130 | /** 131 | * reuse port. 132 | * 133 | * @var bool 134 | */ 135 | public $reusePort = false; 136 | 137 | /** 138 | * Emitted when worker processes start. 139 | * 140 | * @var callback 141 | */ 142 | public $onWorkerStart = null; 143 | 144 | /** 145 | * Emitted when a socket connection is successfully established. 146 | * 147 | * @var callback 148 | */ 149 | public $onConnect = null; 150 | 151 | /** 152 | * Emitted when data is received. 153 | * 154 | * @var callback 155 | */ 156 | public $onMessage = null; 157 | 158 | /** 159 | * Emitted when the other end of the socket sends a FIN packet. 160 | * 161 | * @var callback 162 | */ 163 | public $onClose = null; 164 | 165 | /** 166 | * Emitted when an error occurs with connection. 167 | * 168 | * @var callback 169 | */ 170 | public $onError = null; 171 | 172 | /** 173 | * Emitted when the send buffer becomes full. 174 | * 175 | * @var callback 176 | */ 177 | public $onBufferFull = null; 178 | 179 | /** 180 | * Emitted when the send buffer becomes empty. 181 | * 182 | * @var callback 183 | */ 184 | public $onBufferDrain = null; 185 | 186 | /** 187 | * Emitted when worker processes stoped. 188 | * 189 | * @var callback 190 | */ 191 | public $onWorkerStop = null; 192 | 193 | /** 194 | * Emitted when worker processes get reload command. 195 | * 196 | * @var callback 197 | */ 198 | public $onWorkerReload = null; 199 | 200 | /** 201 | * Transport layer protocol. 202 | * 203 | * @var string 204 | */ 205 | public $transport = 'tcp'; 206 | 207 | /** 208 | * Store all connections of clients. 209 | * 210 | * @var array 211 | */ 212 | public $connections = array(); 213 | 214 | /** 215 | * Application layer protocol. 216 | * 217 | * @var Protocols\ProtocolInterface 218 | */ 219 | public $protocol = ''; 220 | 221 | /** 222 | * Root path for autoload. 223 | * 224 | * @var string 225 | */ 226 | protected $_autoloadRootPath = ''; 227 | 228 | /** 229 | * Daemonize. 230 | * 231 | * @var bool 232 | */ 233 | public static $daemonize = false; 234 | 235 | /** 236 | * Stdout file. 237 | * 238 | * @var string 239 | */ 240 | public static $stdoutFile = '/dev/null'; 241 | 242 | /** 243 | * The file to store master process PID. 244 | * 245 | * @var string 246 | */ 247 | public static $pidFile = ''; 248 | 249 | /** 250 | * Log file. 251 | * 252 | * @var mixed 253 | */ 254 | public static $logFile = ''; 255 | 256 | /** 257 | * Global event loop. 258 | * 259 | * @var Events\EventInterface 260 | */ 261 | public static $globalEvent = null; 262 | 263 | /** 264 | * The PID of master process. 265 | * 266 | * @var int 267 | */ 268 | protected static $_masterPid = 0; 269 | 270 | /** 271 | * Listening socket. 272 | * 273 | * @var resource 274 | */ 275 | protected $_mainSocket = null; 276 | 277 | /** 278 | * Socket name. The format is like this http://0.0.0.0:80 . 279 | * 280 | * @var string 281 | */ 282 | protected $_socketName = ''; 283 | 284 | /** 285 | * Context of socket. 286 | * 287 | * @var resource 288 | */ 289 | protected $_context = null; 290 | 291 | /** 292 | * All worker instances. 293 | * 294 | * @var array 295 | */ 296 | protected static $_workers = array(); 297 | 298 | /** 299 | * All worker porcesses pid. 300 | * The format is like this [worker_id=>[pid=>pid, pid=>pid, ..], ..] 301 | * 302 | * @var array 303 | */ 304 | protected static $_pidMap = array(); 305 | 306 | /** 307 | * All worker processes waiting for restart. 308 | * The format is like this [pid=>pid, pid=>pid]. 309 | * 310 | * @var array 311 | */ 312 | protected static $_pidsToRestart = array(); 313 | 314 | /** 315 | * Mapping from PID to worker process ID. 316 | * The format is like this [worker_id=>[0=>$pid, 1=>$pid, ..], ..]. 317 | * 318 | * @var array 319 | */ 320 | protected static $_idMap = array(); 321 | 322 | /** 323 | * Current status. 324 | * 325 | * @var int 326 | */ 327 | protected static $_status = self::STATUS_STARTING; 328 | 329 | /** 330 | * Maximum length of the worker names. 331 | * 332 | * @var int 333 | */ 334 | protected static $_maxWorkerNameLength = 12; 335 | 336 | /** 337 | * Maximum length of the socket names. 338 | * 339 | * @var int 340 | */ 341 | protected static $_maxSocketNameLength = 12; 342 | 343 | /** 344 | * Maximum length of the process user names. 345 | * 346 | * @var int 347 | */ 348 | protected static $_maxUserNameLength = 12; 349 | 350 | /** 351 | * The file to store status info of current worker process. 352 | * 353 | * @var string 354 | */ 355 | protected static $_statisticsFile = ''; 356 | 357 | /** 358 | * Start file. 359 | * 360 | * @var string 361 | */ 362 | protected static $_startFile = ''; 363 | 364 | /** 365 | * Status info of current worker process. 366 | * 367 | * @var array 368 | */ 369 | protected static $_globalStatistics = array( 370 | 'start_timestamp' => 0, 371 | 'worker_exit_info' => array() 372 | ); 373 | 374 | /** 375 | * Available event loops. 376 | * 377 | * @var array 378 | */ 379 | protected static $_availableEventLoops = array( 380 | 'libevent', 381 | 'event', 382 | 'ev' 383 | ); 384 | 385 | /** 386 | * Current eventLoop name. 387 | * 388 | * @var string 389 | */ 390 | protected static $_eventLoopName = 'select'; 391 | 392 | /** 393 | * PHP built-in protocols. 394 | * 395 | * @var array 396 | */ 397 | protected static $_builtinTransports = array( 398 | 'tcp' => 'tcp', 399 | 'udp' => 'udp', 400 | 'unix' => 'unix', 401 | 'ssl' => 'tcp', 402 | 'tsl' => 'tcp', 403 | 'sslv2' => 'tcp', 404 | 'sslv3' => 'tcp', 405 | 'tls' => 'tcp' 406 | ); 407 | 408 | /** 409 | * Run all worker instances. 410 | * 411 | * @return void 412 | */ 413 | public static function runAll() 414 | { 415 | self::checkSapiEnv(); 416 | self::init(); 417 | self::parseCommand(); 418 | self::daemonize(); 419 | self::initWorkers(); 420 | self::installSignal(); 421 | self::saveMasterPid(); 422 | self::forkWorkers(); 423 | self::displayUI(); 424 | self::resetStd(); 425 | self::monitorWorkers(); 426 | } 427 | 428 | /** 429 | * Check sapi. 430 | * 431 | * @return void 432 | */ 433 | protected static function checkSapiEnv() 434 | { 435 | // Only for cli. 436 | if (php_sapi_name() != "cli") { 437 | exit("only run in command line mode \n"); 438 | } 439 | } 440 | 441 | /** 442 | * Init. 443 | * 444 | * @return void 445 | */ 446 | protected static function init() 447 | { 448 | // Start file. 449 | $backtrace = debug_backtrace(); 450 | self::$_startFile = $backtrace[count($backtrace) - 1]['file']; 451 | 452 | // Pid file. 453 | if (empty(self::$pidFile)) { 454 | self::$pidFile = __DIR__ . "/../" . str_replace('/', '_', self::$_startFile) . ".pid"; 455 | } 456 | 457 | // Log file. 458 | if (empty(self::$logFile)) { 459 | self::$logFile = __DIR__ . '/../workerman.log'; 460 | } 461 | touch(self::$logFile); 462 | chmod(self::$logFile, 0622); 463 | 464 | // State. 465 | self::$_status = self::STATUS_STARTING; 466 | 467 | // For statistics. 468 | self::$_globalStatistics['start_timestamp'] = time(); 469 | self::$_statisticsFile = sys_get_temp_dir() . '/workerman.status'; 470 | 471 | // Process title. 472 | self::setProcessTitle('WorkerMan: master process start_file=' . self::$_startFile); 473 | 474 | // Init data for worker id. 475 | self::initId(); 476 | 477 | // Timer init. 478 | Timer::init(); 479 | } 480 | 481 | /** 482 | * Init All worker instances. 483 | * 484 | * @return void 485 | */ 486 | protected static function initWorkers() 487 | { 488 | foreach (self::$_workers as $worker) { 489 | // Worker name. 490 | if (empty($worker->name)) { 491 | $worker->name = 'none'; 492 | } 493 | 494 | // Get maximum length of worker name. 495 | $worker_name_length = strlen($worker->name); 496 | if (self::$_maxWorkerNameLength < $worker_name_length) { 497 | self::$_maxWorkerNameLength = $worker_name_length; 498 | } 499 | 500 | // Get maximum length of socket name. 501 | $socket_name_length = strlen($worker->getSocketName()); 502 | if (self::$_maxSocketNameLength < $socket_name_length) { 503 | self::$_maxSocketNameLength = $socket_name_length; 504 | } 505 | 506 | // Get unix user of the worker process. 507 | if (empty($worker->user)) { 508 | $worker->user = self::getCurrentUser(); 509 | } else { 510 | if (posix_getuid() !== 0 && $worker->user != self::getCurrentUser()) { 511 | self::log('Warning: You must have the root privileges to change uid and gid.'); 512 | } 513 | } 514 | 515 | // Get maximum length of unix user name. 516 | $user_name_length = strlen($worker->user); 517 | if (self::$_maxUserNameLength < $user_name_length) { 518 | self::$_maxUserNameLength = $user_name_length; 519 | } 520 | 521 | // Listen. 522 | if (!$worker->reusePort) { 523 | $worker->listen(); 524 | } 525 | } 526 | } 527 | 528 | /** 529 | * Init idMap. 530 | * return void 531 | */ 532 | protected static function initId() 533 | { 534 | foreach (self::$_workers as $worker_id => $worker) { 535 | self::$_idMap[$worker_id] = array_fill(0, $worker->count, 0); 536 | } 537 | } 538 | 539 | /** 540 | * Get unix user of current porcess. 541 | * 542 | * @return string 543 | */ 544 | protected static function getCurrentUser() 545 | { 546 | $user_info = posix_getpwuid(posix_getuid()); 547 | return $user_info['name']; 548 | } 549 | 550 | /** 551 | * Display staring UI. 552 | * 553 | * @return void 554 | */ 555 | protected static function displayUI() 556 | { 557 | echo "\033[1A\n\033[K-----------------------\033[47;30m WORKERMAN \033[0m-----------------------------\n\033[0m"; 558 | echo 'Workerman version:', Worker::VERSION, " PHP version:", PHP_VERSION, "\n"; 559 | echo "------------------------\033[47;30m WORKERS \033[0m-------------------------------\n"; 560 | echo "\033[47;30muser\033[0m", str_pad('', 561 | self::$_maxUserNameLength + 2 - strlen('user')), "\033[47;30mworker\033[0m", str_pad('', 562 | self::$_maxWorkerNameLength + 2 - strlen('worker')), "\033[47;30mlisten\033[0m", str_pad('', 563 | self::$_maxSocketNameLength + 2 - strlen('listen')), "\033[47;30mprocesses\033[0m \033[47;30m", "status\033[0m\n"; 564 | 565 | foreach (self::$_workers as $worker) { 566 | echo str_pad($worker->user, self::$_maxUserNameLength + 2), str_pad($worker->name, 567 | self::$_maxWorkerNameLength + 2), str_pad($worker->getSocketName(), 568 | self::$_maxSocketNameLength + 2), str_pad(' ' . $worker->count, 9), " \033[32;40m [OK] \033[0m\n";; 569 | } 570 | echo "----------------------------------------------------------------\n"; 571 | if (self::$daemonize) { 572 | global $argv; 573 | $start_file = $argv[0]; 574 | echo "Input \"php $start_file stop\" to quit. Start success.\n"; 575 | } else { 576 | echo "Press Ctrl-C to quit. Start success.\n"; 577 | } 578 | } 579 | 580 | /** 581 | * Parse command. 582 | * php yourfile.php start | stop | restart | reload | status 583 | * 584 | * @return void 585 | */ 586 | protected static function parseCommand() 587 | { 588 | global $argv; 589 | // Check argv; 590 | $start_file = $argv[0]; 591 | if (!isset($argv[1])) { 592 | exit("Usage: php yourfile.php {start|stop|restart|reload|status|kill}\n"); 593 | } 594 | 595 | // Get command. 596 | $command = trim($argv[1]); 597 | $command2 = isset($argv[2]) ? $argv[2] : ''; 598 | 599 | // Start command. 600 | $mode = ''; 601 | if ($command === 'start') { 602 | if ($command2 === '-d') { 603 | $mode = 'in DAEMON mode'; 604 | } else { 605 | $mode = 'in DEBUG mode'; 606 | } 607 | } 608 | self::log("Workerman[$start_file] $command $mode"); 609 | 610 | // Get master process PID. 611 | $master_pid = @file_get_contents(self::$pidFile); 612 | $master_is_alive = $master_pid && @posix_kill($master_pid, 0); 613 | // Master is still alive? 614 | if ($master_is_alive) { 615 | if ($command === 'start') { 616 | self::log("Workerman[$start_file] already running"); 617 | exit; 618 | } 619 | } elseif ($command !== 'start' && $command !== 'restart' && $command !== 'kill') { 620 | self::log("Workerman[$start_file] not run"); 621 | exit; 622 | } 623 | 624 | // execute command. 625 | switch ($command) { 626 | case 'kill': 627 | exec("ps aux | grep $start_file | grep -v grep | awk '{print $2}' |xargs kill -SIGINT"); 628 | usleep(100000); 629 | exec("ps aux | grep $start_file | grep -v grep | awk '{print $2}' |xargs kill -SIGKILL"); 630 | break; 631 | case 'start': 632 | if ($command2 === '-d') { 633 | Worker::$daemonize = true; 634 | } 635 | break; 636 | case 'status': 637 | if (is_file(self::$_statisticsFile)) { 638 | @unlink(self::$_statisticsFile); 639 | } 640 | // Master process will send status signal to all child processes. 641 | posix_kill($master_pid, SIGUSR2); 642 | // Waiting amoment. 643 | usleep(100000); 644 | // Display statisitcs data from a disk file. 645 | @readfile(self::$_statisticsFile); 646 | exit(0); 647 | case 'restart': 648 | case 'stop': 649 | self::log("Workerman[$start_file] is stoping ..."); 650 | // Send stop signal to master process. 651 | $master_pid && posix_kill($master_pid, SIGINT); 652 | // Timeout. 653 | $timeout = 5; 654 | $start_time = time(); 655 | // Check master process is still alive? 656 | while (1) { 657 | $master_is_alive = $master_pid && posix_kill($master_pid, 0); 658 | if ($master_is_alive) { 659 | // Timeout? 660 | if (time() - $start_time >= $timeout) { 661 | self::log("Workerman[$start_file] stop fail"); 662 | exit; 663 | } 664 | // Waiting amoment. 665 | usleep(10000); 666 | continue; 667 | } 668 | // Stop success. 669 | self::log("Workerman[$start_file] stop success"); 670 | if ($command === 'stop') { 671 | exit(0); 672 | } 673 | if ($command2 === '-d') { 674 | Worker::$daemonize = true; 675 | } 676 | break; 677 | } 678 | break; 679 | case 'reload': 680 | posix_kill($master_pid, SIGUSR1); 681 | self::log("Workerman[$start_file] reload"); 682 | exit; 683 | default : 684 | exit("Usage: php yourfile.php {start|stop|restart|reload|status|kill}\n"); 685 | } 686 | } 687 | 688 | /** 689 | * Install signal handler. 690 | * 691 | * @return void 692 | */ 693 | protected static function installSignal() 694 | { 695 | // stop 696 | pcntl_signal(SIGINT, array('\Workerman\Worker', 'signalHandler'), false); 697 | // reload 698 | pcntl_signal(SIGUSR1, array('\Workerman\Worker', 'signalHandler'), false); 699 | // status 700 | pcntl_signal(SIGUSR2, array('\Workerman\Worker', 'signalHandler'), false); 701 | // ignore 702 | pcntl_signal(SIGPIPE, SIG_IGN, false); 703 | } 704 | 705 | /** 706 | * Reinstall signal handler. 707 | * 708 | * @return void 709 | */ 710 | protected static function reinstallSignal() 711 | { 712 | // uninstall stop signal handler 713 | pcntl_signal(SIGINT, SIG_IGN, false); 714 | // uninstall reload signal handler 715 | pcntl_signal(SIGUSR1, SIG_IGN, false); 716 | // uninstall status signal handler 717 | pcntl_signal(SIGUSR2, SIG_IGN, false); 718 | // reinstall stop signal handler 719 | self::$globalEvent->add(SIGINT, EventInterface::EV_SIGNAL, array('\Workerman\Worker', 'signalHandler')); 720 | // uninstall reload signal handler 721 | self::$globalEvent->add(SIGUSR1, EventInterface::EV_SIGNAL, array('\Workerman\Worker', 'signalHandler')); 722 | // uninstall status signal handler 723 | self::$globalEvent->add(SIGUSR2, EventInterface::EV_SIGNAL, array('\Workerman\Worker', 'signalHandler')); 724 | } 725 | 726 | /** 727 | * Signal hander. 728 | * 729 | * @param int $signal 730 | */ 731 | public static function signalHandler($signal) 732 | { 733 | switch ($signal) { 734 | // Stop. 735 | case SIGINT: 736 | self::stopAll(); 737 | break; 738 | // Reload. 739 | case SIGUSR1: 740 | self::$_pidsToRestart = self::getAllWorkerPids(); 741 | self::reload(); 742 | break; 743 | // Show status. 744 | case SIGUSR2: 745 | self::writeStatisticsToStatusFile(); 746 | break; 747 | } 748 | } 749 | 750 | /** 751 | * Run as deamon mode. 752 | * 753 | * @throws Exception 754 | */ 755 | protected static function daemonize() 756 | { 757 | if (!self::$daemonize) { 758 | return; 759 | } 760 | umask(0); 761 | $pid = pcntl_fork(); 762 | if (-1 === $pid) { 763 | throw new Exception('fork fail'); 764 | } elseif ($pid > 0) { 765 | exit(0); 766 | } 767 | if (-1 === posix_setsid()) { 768 | throw new Exception("setsid fail"); 769 | } 770 | // Fork again avoid SVR4 system regain the control of terminal. 771 | $pid = pcntl_fork(); 772 | if (-1 === $pid) { 773 | throw new Exception("fork fail"); 774 | } elseif (0 !== $pid) { 775 | exit(0); 776 | } 777 | } 778 | 779 | /** 780 | * Redirect standard input and output. 781 | * 782 | * @throws Exception 783 | */ 784 | protected static function resetStd() 785 | { 786 | if (!self::$daemonize) { 787 | return; 788 | } 789 | global $STDOUT, $STDERR; 790 | $handle = fopen(self::$stdoutFile, "a"); 791 | if ($handle) { 792 | unset($handle); 793 | @fclose(STDOUT); 794 | @fclose(STDERR); 795 | $STDOUT = fopen(self::$stdoutFile, "a"); 796 | $STDERR = fopen(self::$stdoutFile, "a"); 797 | } else { 798 | throw new Exception('can not open stdoutFile ' . self::$stdoutFile); 799 | } 800 | } 801 | 802 | /** 803 | * Save pid. 804 | * 805 | * @throws Exception 806 | */ 807 | protected static function saveMasterPid() 808 | { 809 | self::$_masterPid = posix_getpid(); 810 | if (false === @file_put_contents(self::$pidFile, self::$_masterPid)) { 811 | throw new Exception('can not save pid to ' . self::$pidFile); 812 | } 813 | } 814 | 815 | /** 816 | * Get event loop name. 817 | * 818 | * @return string 819 | */ 820 | protected static function getEventLoopName() 821 | { 822 | foreach (self::$_availableEventLoops as $name) { 823 | if (extension_loaded($name)) { 824 | self::$_eventLoopName = $name; 825 | break; 826 | } 827 | } 828 | return self::$_eventLoopName; 829 | } 830 | 831 | /** 832 | * Get all pids of worker processes. 833 | * 834 | * @return array 835 | */ 836 | protected static function getAllWorkerPids() 837 | { 838 | $pid_array = array(); 839 | foreach (self::$_pidMap as $worker_pid_array) { 840 | foreach ($worker_pid_array as $worker_pid) { 841 | $pid_array[$worker_pid] = $worker_pid; 842 | } 843 | } 844 | return $pid_array; 845 | } 846 | 847 | /** 848 | * Fork some worker processes. 849 | * 850 | * @return void 851 | */ 852 | protected static function forkWorkers() 853 | { 854 | foreach (self::$_workers as $worker) { 855 | if (self::$_status === self::STATUS_STARTING) { 856 | if (empty($worker->name)) { 857 | $worker->name = $worker->getSocketName(); 858 | } 859 | $worker_name_length = strlen($worker->name); 860 | if (self::$_maxWorkerNameLength < $worker_name_length) { 861 | self::$_maxWorkerNameLength = $worker_name_length; 862 | } 863 | } 864 | 865 | while (count(self::$_pidMap[$worker->workerId]) < $worker->count) { 866 | static::forkOneWorker($worker); 867 | } 868 | } 869 | } 870 | 871 | /** 872 | * Fork one worker process. 873 | * 874 | * @param Worker $worker 875 | * @throws Exception 876 | */ 877 | protected static function forkOneWorker($worker) 878 | { 879 | $pid = pcntl_fork(); 880 | // Get available worker id. 881 | $id = self::getId($worker->workerId, 0); 882 | // For master process. 883 | if ($pid > 0) { 884 | self::$_pidMap[$worker->workerId][$pid] = $pid; 885 | self::$_idMap[$worker->workerId][$id] = $pid; 886 | } // For child processes. 887 | elseif (0 === $pid) { 888 | if ($worker->reusePort) { 889 | $worker->listen(); 890 | } 891 | if (self::$_status === self::STATUS_STARTING) { 892 | self::resetStd(); 893 | } 894 | self::$_pidMap = array(); 895 | self::$_workers = array($worker->workerId => $worker); 896 | Timer::delAll(); 897 | self::setProcessTitle('WorkerMan: worker process ' . $worker->name . ' ' . $worker->getSocketName()); 898 | $worker->setUserAndGroup(); 899 | $worker->id = $id; 900 | $worker->run(); 901 | exit(250); 902 | } else { 903 | throw new Exception("forkOneWorker fail"); 904 | } 905 | } 906 | 907 | /** 908 | * Get worker id. 909 | * 910 | * @param int $worker_id 911 | * @param int $pid 912 | */ 913 | protected static function getId($worker_id, $pid) 914 | { 915 | $id = array_search($pid, self::$_idMap[$worker_id]); 916 | if ($id === false) { 917 | echo "getId fail\n"; 918 | } 919 | return $id; 920 | } 921 | 922 | /** 923 | * Set unix user and group for current process. 924 | * 925 | * @return void 926 | */ 927 | public function setUserAndGroup() 928 | { 929 | // Get uid. 930 | $user_info = posix_getpwnam($this->user); 931 | if (!$user_info) { 932 | self::log("Warning: User {$this->user} not exsits"); 933 | return; 934 | } 935 | $uid = $user_info['uid']; 936 | // Get gid. 937 | if ($this->group) { 938 | $group_info = posix_getgrnam($this->group); 939 | if (!$group_info) { 940 | self::log("Warning: Group {$this->group} not exsits"); 941 | return; 942 | } 943 | $gid = $group_info['gid']; 944 | } else { 945 | $gid = $user_info['gid']; 946 | } 947 | 948 | // Set uid and gid. 949 | if ($uid != posix_getuid() || $gid != posix_getgid()) { 950 | if (!posix_setgid($gid) || !posix_initgroups($user_info['name'], $gid) || !posix_setuid($uid)) { 951 | self::log("Warning: change gid or uid fail."); 952 | } 953 | } 954 | } 955 | 956 | /** 957 | * Set process name. 958 | * 959 | * @param string $title 960 | * @return void 961 | */ 962 | protected static function setProcessTitle($title) 963 | { 964 | // >=php 5.5 965 | if (function_exists('cli_set_process_title')) { 966 | @cli_set_process_title($title); 967 | } // Need proctitle when php<=5.5 . 968 | elseif (extension_loaded('proctitle') && function_exists('setproctitle')) { 969 | @setproctitle($title); 970 | } 971 | } 972 | 973 | /** 974 | * Monitor all child processes. 975 | * 976 | * @return void 977 | */ 978 | protected static function monitorWorkers() 979 | { 980 | self::$_status = self::STATUS_RUNNING; 981 | while (1) { 982 | // Calls signal handlers for pending signals. 983 | pcntl_signal_dispatch(); 984 | // Suspends execution of the current process until a child has exited, or until a signal is delivered 985 | $status = 0; 986 | $pid = pcntl_wait($status, WUNTRACED); 987 | // Calls signal handlers for pending signals again. 988 | pcntl_signal_dispatch(); 989 | // If a child has already exited. 990 | if ($pid > 0) { 991 | // Find out witch worker process exited. 992 | foreach (self::$_pidMap as $worker_id => $worker_pid_array) { 993 | if (isset($worker_pid_array[$pid])) { 994 | $worker = self::$_workers[$worker_id]; 995 | // Exit status. 996 | if ($status !== 0) { 997 | self::log("worker[" . $worker->name . ":$pid] exit with status $status"); 998 | } 999 | 1000 | // For Statistics. 1001 | if (!isset(self::$_globalStatistics['worker_exit_info'][$worker_id][$status])) { 1002 | self::$_globalStatistics['worker_exit_info'][$worker_id][$status] = 0; 1003 | } 1004 | self::$_globalStatistics['worker_exit_info'][$worker_id][$status]++; 1005 | 1006 | // Clear process data. 1007 | unset(self::$_pidMap[$worker_id][$pid]); 1008 | 1009 | // Mark id is available. 1010 | $id = self::getId($worker_id, $pid); 1011 | self::$_idMap[$worker_id][$id] = 0; 1012 | 1013 | break; 1014 | } 1015 | } 1016 | // Is still running state then fork a new worker process. 1017 | if (self::$_status !== self::STATUS_SHUTDOWN) { 1018 | self::forkWorkers(); 1019 | // If reloading continue. 1020 | if (isset(self::$_pidsToRestart[$pid])) { 1021 | unset(self::$_pidsToRestart[$pid]); 1022 | self::reload(); 1023 | } 1024 | } else { 1025 | // If shutdown state and all child processes exited then master process exit. 1026 | if (!self::getAllWorkerPids()) { 1027 | self::exitAndClearAll(); 1028 | } 1029 | } 1030 | } else { 1031 | // If shutdown state and all child processes exited then master process exit. 1032 | if (self::$_status === self::STATUS_SHUTDOWN && !self::getAllWorkerPids()) { 1033 | self::exitAndClearAll(); 1034 | } 1035 | } 1036 | } 1037 | } 1038 | 1039 | /** 1040 | * Exit current process. 1041 | * 1042 | * @return void 1043 | */ 1044 | protected static function exitAndClearAll() 1045 | { 1046 | foreach (self::$_workers as $worker) { 1047 | $socket_name = $worker->getSocketName(); 1048 | if ($worker->transport === 'unix' && $socket_name) { 1049 | list(, $address) = explode(':', $socket_name, 2); 1050 | @unlink($address); 1051 | } 1052 | } 1053 | @unlink(self::$pidFile); 1054 | self::log("Workerman[" . basename(self::$_startFile) . "] has been stopped"); 1055 | exit(0); 1056 | } 1057 | 1058 | /** 1059 | * Execute reload. 1060 | * 1061 | * @return void 1062 | */ 1063 | protected static function reload() 1064 | { 1065 | // For master process. 1066 | if (self::$_masterPid === posix_getpid()) { 1067 | // Set reloading state. 1068 | if (self::$_status !== self::STATUS_RELOADING && self::$_status !== self::STATUS_SHUTDOWN) { 1069 | self::log("Workerman[" . basename(self::$_startFile) . "] reloading"); 1070 | self::$_status = self::STATUS_RELOADING; 1071 | } 1072 | 1073 | // Send reload signal to all child processes. 1074 | $reloadable_pid_array = array(); 1075 | foreach (self::$_pidMap as $worker_id => $worker_pid_array) { 1076 | $worker = self::$_workers[$worker_id]; 1077 | if ($worker->reloadable) { 1078 | foreach ($worker_pid_array as $pid) { 1079 | $reloadable_pid_array[$pid] = $pid; 1080 | } 1081 | } else { 1082 | foreach ($worker_pid_array as $pid) { 1083 | // Send reload signal to a worker process which reloadable is false. 1084 | posix_kill($pid, SIGUSR1); 1085 | } 1086 | } 1087 | } 1088 | 1089 | // Get all pids that are waiting reload. 1090 | self::$_pidsToRestart = array_intersect(self::$_pidsToRestart, $reloadable_pid_array); 1091 | 1092 | // Reload complete. 1093 | if (empty(self::$_pidsToRestart)) { 1094 | if (self::$_status !== self::STATUS_SHUTDOWN) { 1095 | self::$_status = self::STATUS_RUNNING; 1096 | } 1097 | return; 1098 | } 1099 | // Continue reload. 1100 | $one_worker_pid = current(self::$_pidsToRestart); 1101 | // Send reload signal to a worker process. 1102 | posix_kill($one_worker_pid, SIGUSR1); 1103 | // If the process does not exit after self::KILL_WORKER_TIMER_TIME seconds try to kill it. 1104 | Timer::add(self::KILL_WORKER_TIMER_TIME, 'posix_kill', array($one_worker_pid, SIGKILL), false); 1105 | } // For child processes. 1106 | else { 1107 | $worker = current(self::$_workers); 1108 | // Try to emit onWorkerReload callback. 1109 | if ($worker->onWorkerReload) { 1110 | try { 1111 | call_user_func($worker->onWorkerReload, $worker); 1112 | } catch (\Exception $e) { 1113 | echo $e; 1114 | exit(250); 1115 | } catch (\Error $e) { 1116 | echo $e; 1117 | exit(250); 1118 | } 1119 | } 1120 | 1121 | if ($worker->reloadable) { 1122 | self::stopAll(); 1123 | } 1124 | } 1125 | } 1126 | 1127 | /** 1128 | * Stop. 1129 | * 1130 | * @return void 1131 | */ 1132 | public static function stopAll() 1133 | { 1134 | self::$_status = self::STATUS_SHUTDOWN; 1135 | // For master process. 1136 | if (self::$_masterPid === posix_getpid()) { 1137 | self::log("Workerman[" . basename(self::$_startFile) . "] Stopping ..."); 1138 | $worker_pid_array = self::getAllWorkerPids(); 1139 | // Send stop signal to all child processes. 1140 | foreach ($worker_pid_array as $worker_pid) { 1141 | posix_kill($worker_pid, SIGINT); 1142 | Timer::add(self::KILL_WORKER_TIMER_TIME, 'posix_kill', array($worker_pid, SIGKILL), false); 1143 | } 1144 | } // For child processes. 1145 | else { 1146 | // Execute exit. 1147 | foreach (self::$_workers as $worker) { 1148 | $worker->stop(); 1149 | } 1150 | exit(0); 1151 | } 1152 | } 1153 | 1154 | /** 1155 | * Write statistics data to disk. 1156 | * 1157 | * @return void 1158 | */ 1159 | protected static function writeStatisticsToStatusFile() 1160 | { 1161 | // For master process. 1162 | if (self::$_masterPid === posix_getpid()) { 1163 | $loadavg = sys_getloadavg(); 1164 | file_put_contents(self::$_statisticsFile, 1165 | "---------------------------------------GLOBAL STATUS--------------------------------------------\n"); 1166 | file_put_contents(self::$_statisticsFile, 1167 | 'Workerman version:' . Worker::VERSION . " PHP version:" . PHP_VERSION . "\n", FILE_APPEND); 1168 | file_put_contents(self::$_statisticsFile, 'start time:' . date('Y-m-d H:i:s', 1169 | self::$_globalStatistics['start_timestamp']) . ' run ' . floor((time() - self::$_globalStatistics['start_timestamp']) / (24 * 60 * 60)) . ' days ' . floor(((time() - self::$_globalStatistics['start_timestamp']) % (24 * 60 * 60)) / (60 * 60)) . " hours \n", 1170 | FILE_APPEND); 1171 | $load_str = 'load average: ' . implode(", ", $loadavg); 1172 | file_put_contents(self::$_statisticsFile, 1173 | str_pad($load_str, 33) . 'event-loop:' . self::getEventLoopName() . "\n", FILE_APPEND); 1174 | file_put_contents(self::$_statisticsFile, 1175 | count(self::$_pidMap) . ' workers ' . count(self::getAllWorkerPids()) . " processes\n", 1176 | FILE_APPEND); 1177 | file_put_contents(self::$_statisticsFile, 1178 | str_pad('worker_name', self::$_maxWorkerNameLength) . " exit_status exit_count\n", FILE_APPEND); 1179 | foreach (self::$_pidMap as $worker_id => $worker_pid_array) { 1180 | $worker = self::$_workers[$worker_id]; 1181 | if (isset(self::$_globalStatistics['worker_exit_info'][$worker_id])) { 1182 | foreach (self::$_globalStatistics['worker_exit_info'][$worker_id] as $worker_exit_status => $worker_exit_count) { 1183 | file_put_contents(self::$_statisticsFile, 1184 | str_pad($worker->name, self::$_maxWorkerNameLength) . " " . str_pad($worker_exit_status, 1185 | 16) . " $worker_exit_count\n", FILE_APPEND); 1186 | } 1187 | } else { 1188 | file_put_contents(self::$_statisticsFile, 1189 | str_pad($worker->name, self::$_maxWorkerNameLength) . " " . str_pad(0, 16) . " 0\n", 1190 | FILE_APPEND); 1191 | } 1192 | } 1193 | file_put_contents(self::$_statisticsFile, 1194 | "---------------------------------------PROCESS STATUS-------------------------------------------\n", 1195 | FILE_APPEND); 1196 | file_put_contents(self::$_statisticsFile, 1197 | "pid\tmemory " . str_pad('listening', self::$_maxSocketNameLength) . " " . str_pad('worker_name', 1198 | self::$_maxWorkerNameLength) . " connections " . str_pad('total_request', 1199 | 13) . " " . str_pad('send_fail', 9) . " " . str_pad('throw_exception', 15) . "\n", FILE_APPEND); 1200 | 1201 | chmod(self::$_statisticsFile, 0722); 1202 | 1203 | foreach (self::getAllWorkerPids() as $worker_pid) { 1204 | posix_kill($worker_pid, SIGUSR2); 1205 | } 1206 | return; 1207 | } 1208 | 1209 | // For child processes. 1210 | /** @var Worker $worker */ 1211 | $worker = current(self::$_workers); 1212 | $wrker_status_str = posix_getpid() . "\t" . str_pad(round(memory_get_usage(true) / (1024 * 1024), 2) . "M", 1213 | 7) . " " . str_pad($worker->getSocketName(), 1214 | self::$_maxSocketNameLength) . " " . str_pad(($worker->name === $worker->getSocketName() ? 'none' : $worker->name), 1215 | self::$_maxWorkerNameLength) . " "; 1216 | $wrker_status_str .= str_pad(ConnectionInterface::$statistics['connection_count'], 1217 | 11) . " " . str_pad(ConnectionInterface::$statistics['total_request'], 1218 | 14) . " " . str_pad(ConnectionInterface::$statistics['send_fail'], 1219 | 9) . " " . str_pad(ConnectionInterface::$statistics['throw_exception'], 15) . "\n"; 1220 | file_put_contents(self::$_statisticsFile, $wrker_status_str, FILE_APPEND); 1221 | } 1222 | 1223 | /** 1224 | * Check errors when current process exited. 1225 | * 1226 | * @return void 1227 | */ 1228 | public static function checkErrors() 1229 | { 1230 | if (self::STATUS_SHUTDOWN != self::$_status) { 1231 | $error_msg = "WORKER EXIT UNEXPECTED "; 1232 | $errors = error_get_last(); 1233 | if ($errors && ($errors['type'] === E_ERROR || 1234 | $errors['type'] === E_PARSE || 1235 | $errors['type'] === E_CORE_ERROR || 1236 | $errors['type'] === E_COMPILE_ERROR || 1237 | $errors['type'] === E_RECOVERABLE_ERROR) 1238 | ) { 1239 | $error_msg .= self::getErrorType($errors['type']) . " {$errors['message']} in {$errors['file']} on line {$errors['line']}"; 1240 | } 1241 | self::log($error_msg); 1242 | } 1243 | } 1244 | 1245 | /** 1246 | * Get error message by error code. 1247 | * 1248 | * @param integer $type 1249 | * @return string 1250 | */ 1251 | protected static function getErrorType($type) 1252 | { 1253 | switch ($type) { 1254 | case E_ERROR: // 1 // 1255 | return 'E_ERROR'; 1256 | case E_WARNING: // 2 // 1257 | return 'E_WARNING'; 1258 | case E_PARSE: // 4 // 1259 | return 'E_PARSE'; 1260 | case E_NOTICE: // 8 // 1261 | return 'E_NOTICE'; 1262 | case E_CORE_ERROR: // 16 // 1263 | return 'E_CORE_ERROR'; 1264 | case E_CORE_WARNING: // 32 // 1265 | return 'E_CORE_WARNING'; 1266 | case E_COMPILE_ERROR: // 64 // 1267 | return 'E_COMPILE_ERROR'; 1268 | case E_COMPILE_WARNING: // 128 // 1269 | return 'E_COMPILE_WARNING'; 1270 | case E_USER_ERROR: // 256 // 1271 | return 'E_USER_ERROR'; 1272 | case E_USER_WARNING: // 512 // 1273 | return 'E_USER_WARNING'; 1274 | case E_USER_NOTICE: // 1024 // 1275 | return 'E_USER_NOTICE'; 1276 | case E_STRICT: // 2048 // 1277 | return 'E_STRICT'; 1278 | case E_RECOVERABLE_ERROR: // 4096 // 1279 | return 'E_RECOVERABLE_ERROR'; 1280 | case E_DEPRECATED: // 8192 // 1281 | return 'E_DEPRECATED'; 1282 | case E_USER_DEPRECATED: // 16384 // 1283 | return 'E_USER_DEPRECATED'; 1284 | } 1285 | return ""; 1286 | } 1287 | 1288 | /** 1289 | * Log. 1290 | * 1291 | * @param string $msg 1292 | * @return void 1293 | */ 1294 | protected static function log($msg) 1295 | { 1296 | $msg = $msg . "\n"; 1297 | if (!self::$daemonize) { 1298 | echo $msg; 1299 | } 1300 | file_put_contents(self::$logFile, date('Y-m-d H:i:s') . " " . $msg, FILE_APPEND | LOCK_EX); 1301 | } 1302 | 1303 | /** 1304 | * Construct. 1305 | * 1306 | * @param string $socket_name 1307 | * @param array $context_option 1308 | */ 1309 | public function __construct($socket_name = '', $context_option = array()) 1310 | { 1311 | // Save all worker instances. 1312 | $this->workerId = spl_object_hash($this); 1313 | self::$_workers[$this->workerId] = $this; 1314 | self::$_pidMap[$this->workerId] = array(); 1315 | 1316 | // Get autoload root path. 1317 | $backrace = debug_backtrace(); 1318 | $this->_autoloadRootPath = dirname($backrace[0]['file']); 1319 | 1320 | // Context for socket. 1321 | if ($socket_name) { 1322 | $this->_socketName = $socket_name; 1323 | if (!isset($context_option['socket']['backlog'])) { 1324 | $context_option['socket']['backlog'] = self::DEFAUL_BACKLOG; 1325 | } 1326 | $this->_context = stream_context_create($context_option); 1327 | } 1328 | 1329 | // Set an empty onMessage callback. 1330 | $this->onMessage = function () { 1331 | }; 1332 | } 1333 | 1334 | /** 1335 | * Listen port. 1336 | * 1337 | * @throws Exception 1338 | */ 1339 | public function listen() 1340 | { 1341 | if (!$this->_socketName || $this->_mainSocket) { 1342 | return; 1343 | } 1344 | 1345 | // Autoload. 1346 | Autoloader::setRootPath($this->_autoloadRootPath); 1347 | 1348 | $local_socket = $this->_socketName; 1349 | // Get the application layer communication protocol and listening address. 1350 | list($scheme, $address) = explode(':', $this->_socketName, 2); 1351 | // Check application layer protocol class. 1352 | if (!isset(self::$_builtinTransports[$scheme])) { 1353 | $scheme = ucfirst($scheme); 1354 | $this->protocol = '\\Protocols\\' . $scheme; 1355 | if (!class_exists($this->protocol)) { 1356 | $this->protocol = "\\Workerman\\Protocols\\$scheme"; 1357 | if (!class_exists($this->protocol)) { 1358 | throw new Exception("class \\Protocols\\$scheme not exist"); 1359 | } 1360 | } 1361 | $local_socket = $this->transport . ":" . $address; 1362 | } else { 1363 | $this->transport = self::$_builtinTransports[$scheme]; 1364 | } 1365 | 1366 | // Flag. 1367 | $flags = $this->transport === 'udp' ? STREAM_SERVER_BIND : STREAM_SERVER_BIND | STREAM_SERVER_LISTEN; 1368 | $errno = 0; 1369 | $errmsg = ''; 1370 | // SO_REUSEPORT. 1371 | if ($this->reusePort) { 1372 | stream_context_set_option($this->_context, 'socket', 'so_reuseport', 1); 1373 | } 1374 | if ($this->transport === 'unix') { 1375 | umask(0); 1376 | list(, $address) = explode(':', $this->_socketName, 2); 1377 | if (!is_file($address)) { 1378 | register_shutdown_function(function () use ($address) { 1379 | @unlink($address); 1380 | }); 1381 | } 1382 | } 1383 | // Create an Internet or Unix domain server socket. 1384 | $this->_mainSocket = stream_socket_server($local_socket, $errno, $errmsg, $flags, $this->_context); 1385 | if (!$this->_mainSocket) { 1386 | throw new Exception($errmsg); 1387 | } 1388 | 1389 | // Try to open keepalive for tcp and disable Nagle algorithm. 1390 | if (function_exists('socket_import_stream') && $this->transport === 'tcp') { 1391 | $socket = socket_import_stream($this->_mainSocket); 1392 | @socket_set_option($socket, SOL_SOCKET, SO_KEEPALIVE, 1); 1393 | @socket_set_option($socket, SOL_TCP, TCP_NODELAY, 1); 1394 | } 1395 | 1396 | // Non blocking. 1397 | stream_set_blocking($this->_mainSocket, 0); 1398 | 1399 | // Register a listener to be notified when server socket is ready to read. 1400 | if (self::$globalEvent) { 1401 | if ($this->transport !== 'udp') { 1402 | self::$globalEvent->add($this->_mainSocket, EventInterface::EV_READ, array($this, 'acceptConnection')); 1403 | } else { 1404 | self::$globalEvent->add($this->_mainSocket, EventInterface::EV_READ, 1405 | array($this, 'acceptUdpConnection')); 1406 | } 1407 | } 1408 | } 1409 | 1410 | /** 1411 | * Get socket name. 1412 | * 1413 | * @return string 1414 | */ 1415 | public function getSocketName() 1416 | { 1417 | return $this->_socketName ? lcfirst($this->_socketName) : 'none'; 1418 | } 1419 | 1420 | /** 1421 | * Run worker instance. 1422 | * 1423 | * @return void 1424 | */ 1425 | public function run() 1426 | { 1427 | //Update process state. 1428 | self::$_status = self::STATUS_RUNNING; 1429 | 1430 | // Eegister shutdown function for checking errors. 1431 | register_shutdown_function(array("\\Workerman\\Worker", 'checkErrors')); 1432 | 1433 | // Set autoload root path. 1434 | Autoloader::setRootPath($this->_autoloadRootPath); 1435 | 1436 | // Create a global event loop. 1437 | if (!self::$globalEvent) { 1438 | $eventLoopClass = "\\Workerman\\Events\\" . ucfirst(self::getEventLoopName()); 1439 | self::$globalEvent = new $eventLoopClass; 1440 | // Register a listener to be notified when server socket is ready to read. 1441 | if ($this->_socketName) { 1442 | if ($this->transport !== 'udp') { 1443 | self::$globalEvent->add($this->_mainSocket, EventInterface::EV_READ, 1444 | array($this, 'acceptConnection')); 1445 | } else { 1446 | self::$globalEvent->add($this->_mainSocket, EventInterface::EV_READ, 1447 | array($this, 'acceptUdpConnection')); 1448 | } 1449 | } 1450 | } 1451 | 1452 | // Reinstall signal. 1453 | self::reinstallSignal(); 1454 | 1455 | // Init Timer. 1456 | Timer::init(self::$globalEvent); 1457 | 1458 | // Try to emit onWorkerStart callback. 1459 | if ($this->onWorkerStart) { 1460 | try { 1461 | call_user_func($this->onWorkerStart, $this); 1462 | } catch (\Exception $e) { 1463 | echo $e; 1464 | exit(250); 1465 | } catch (\Error $e) { 1466 | echo $e; 1467 | exit(250); 1468 | } 1469 | } 1470 | 1471 | // Main loop. 1472 | self::$globalEvent->loop(); 1473 | } 1474 | 1475 | /** 1476 | * Stop current worker instance. 1477 | * 1478 | * @return void 1479 | */ 1480 | public function stop() 1481 | { 1482 | // Try to emit onWorkerStop callback. 1483 | if ($this->onWorkerStop) { 1484 | try { 1485 | call_user_func($this->onWorkerStop, $this); 1486 | } catch (\Exception $e) { 1487 | echo $e; 1488 | exit(250); 1489 | } catch (\Error $e) { 1490 | echo $e; 1491 | exit(250); 1492 | } 1493 | } 1494 | // Remove listener for server socket. 1495 | self::$globalEvent->del($this->_mainSocket, EventInterface::EV_READ); 1496 | @fclose($this->_mainSocket); 1497 | } 1498 | 1499 | /** 1500 | * Accept a connection. 1501 | * 1502 | * @param resource $socket 1503 | * @return void 1504 | */ 1505 | public function acceptConnection($socket) 1506 | { 1507 | // Accept a connection on server socket. 1508 | $new_socket = @stream_socket_accept($socket, 0, $remote_address); 1509 | // Thundering herd. 1510 | if (!$new_socket) { 1511 | return; 1512 | } 1513 | 1514 | // TcpConnection. 1515 | $connection = new TcpConnection($new_socket, $remote_address); 1516 | $this->connections[$connection->id] = $connection; 1517 | $connection->worker = $this; 1518 | $connection->protocol = $this->protocol; 1519 | $connection->onMessage = $this->onMessage; 1520 | $connection->onClose = $this->onClose; 1521 | $connection->onError = $this->onError; 1522 | $connection->onBufferDrain = $this->onBufferDrain; 1523 | $connection->onBufferFull = $this->onBufferFull; 1524 | 1525 | // Try to emit onConnect callback. 1526 | if ($this->onConnect) { 1527 | try { 1528 | call_user_func($this->onConnect, $connection); 1529 | } catch (\Exception $e) { 1530 | echo $e; 1531 | exit(250); 1532 | } catch (\Error $e) { 1533 | echo $e; 1534 | exit(250); 1535 | } 1536 | } 1537 | } 1538 | 1539 | /** 1540 | * For udp package. 1541 | * 1542 | * @param resource $socket 1543 | * @return bool 1544 | */ 1545 | public function acceptUdpConnection($socket) 1546 | { 1547 | $recv_buffer = stream_socket_recvfrom($socket, self::MAX_UDP_PACKAGE_SIZE, 0, $remote_address); 1548 | if (false === $recv_buffer || empty($remote_address)) { 1549 | return false; 1550 | } 1551 | // UdpConnection. 1552 | $connection = new UdpConnection($socket, $remote_address); 1553 | $connection->protocol = $this->protocol; 1554 | if ($this->onMessage) { 1555 | if ($this->protocol) { 1556 | $parser = $this->protocol; 1557 | $recv_buffer = $parser::decode($recv_buffer, $connection); 1558 | } 1559 | ConnectionInterface::$statistics['total_request']++; 1560 | try { 1561 | call_user_func($this->onMessage, $connection, $recv_buffer); 1562 | } catch (\Exception $e) { 1563 | echo $e; 1564 | exit(250); 1565 | } catch (\Error $e) { 1566 | echo $e; 1567 | exit(250); 1568 | } 1569 | } 1570 | return true; 1571 | } 1572 | } 1573 | --------------------------------------------------------------------------------