├── .DS_Store
├── Workerman
├── .gitignore
├── Lib
│ ├── Constants.php
│ └── Timer.php
├── MIT-LICENSE.txt
├── composer.json
├── Protocols
│ ├── Frame.php
│ ├── ProtocolInterface.php
│ ├── Text.php
│ ├── Http
│ │ └── mime.types
│ ├── Ws.php
│ ├── Websocket.php
│ └── Http.php
├── Autoloader.php
├── Events
│ ├── EventInterface.php
│ ├── React
│ │ ├── ExtEventLoop.php
│ │ ├── LibEventLoop.php
│ │ └── StreamSelectLoop.php
│ ├── Ev.php
│ ├── Event.php
│ ├── Libevent.php
│ └── Select.php
├── Connection
│ ├── ConnectionInterface.php
│ ├── UdpConnection.php
│ └── AsyncTcpConnection.php
├── WebServer.php
└── README.md
├── .gitattributes
├── Log
└── .DS_Store
├── .idea
├── dictionaries
│ └── zhaodanyang.xml
├── php.xml
├── vcs.xml
├── modules.xml
├── workerman_cor_ape.iml
└── misc.xml
├── Cor
├── Net
│ ├── SystemCall.php
│ ├── Task.php
│ ├── CpuConnection.php
│ ├── Cpu.php
│ └── Events
│ │ └── Select.php
├── CorThread.php
├── Extend
│ └── MySql.php
└── CorWorker.php
├── Events.php
├── start.php
└── README.md
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zyfei/workerman_cor_ape/HEAD/.DS_Store
--------------------------------------------------------------------------------
/Workerman/.gitignore:
--------------------------------------------------------------------------------
1 | logs
2 | .buildpath
3 | .project
4 | .settings
5 | .idea
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
--------------------------------------------------------------------------------
/Log/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zyfei/workerman_cor_ape/HEAD/Log/.DS_Store
--------------------------------------------------------------------------------
/.idea/dictionaries/zhaodanyang.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/.idea/php.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/workerman_cor_ape.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Cor/Net/SystemCall.php:
--------------------------------------------------------------------------------
1 | callback = $callback;
13 | }
14 |
15 | //可以将对象当做方法使用,默认就调用这个
16 | public function __invoke(Task $task, Cpu $cpu) {
17 | $callback = $this->callback;
18 | return $callback($task, $cpu);
19 | }
20 |
21 | //创建新任务
22 | public static function newTask(\Generator $coroutine) {
23 | return new SystemCall ( function (Task $task, Cpu $cpu) use ($coroutine) {
24 | $task->setSendValue ( $cpu->newTask ( $coroutine ) );
25 | $cpu->schedule ( $task );
26 | } );
27 | }
28 |
29 | }
--------------------------------------------------------------------------------
/Events.php:
--------------------------------------------------------------------------------
1 | async_query("select * from t_admin");
11 | $mysql->async_query("select * from t_admin");
12 | $mysql->async_query("select * from t_admin");
13 | //注意这里采用协程方式访问mysql里面的协程方法
14 | $res = yield from $mysql->async_result();
15 | return $res;
16 | }
17 |
18 | public static function helloworld($data){
19 | return "helloworld cor";
20 | yield;
21 | }
22 |
23 | }
--------------------------------------------------------------------------------
/start.php:
--------------------------------------------------------------------------------
1 | count = 1;
8 | //设置执行任务的类
9 | $worker->eventHandler = "Events";
10 |
11 | //以前怎么使用workerman,现在还可以怎么使用,毫无区别
12 | $worker->onMessage = function ($connection, $data) use ($worker) {
13 | //你也可以选择这样的方式,也就是workerman的方式,我们先注释掉,使用任务线程的send方法返回数据
14 | //$connection->send("hello workerman_cor_ape");
15 |
16 | //这段代码会异步任务线程Evnets类里面的testMysql方法
17 | $worker->ajax("testMysql", $data, function ($body) use ($connection){
18 | $connection->send(json_encode($body));
19 | });
20 |
21 | };
22 |
23 | // 日志
24 | CorWorker::$logFile = __DIR__ . "/Log/log.log";
25 | // 访问日志
26 | CorWorker::$stdoutFile = __DIR__ . "/Log/stadout.log";
27 |
28 | CorWorker::runAll();
--------------------------------------------------------------------------------
/Cor/Net/Task.php:
--------------------------------------------------------------------------------
1 | taskId = $taskId;
15 | $this->coroutine = $coroutine;
16 | }
17 |
18 | public function getTaskId() {
19 | return $this->taskId;
20 | }
21 |
22 | public function setSendValue($sendValue) {
23 | $this->sendValue = $sendValue;
24 | }
25 |
26 | public function run() {
27 | if ($this->beforeFirstYield) {
28 | $this->beforeFirstYield = false;
29 | return $this->coroutine->current ();
30 | } else {
31 | ////向生成器中传入一个值,当前yield接收值,然后继续执行下一个yield
32 | $retval = $this->coroutine->send ( $this->sendValue );
33 | $this->sendValue = null;
34 | return $retval;
35 | }
36 | }
37 |
38 | public function isFinished() {
39 | return ! $this->coroutine->valid ();
40 | }
41 | }
--------------------------------------------------------------------------------
/Workerman/Lib/Constants.php:
--------------------------------------------------------------------------------
1 |
10 | * @copyright walkor
11 | * @link http://www.workerman.net/
12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License
13 | */
14 |
15 | // Date.timezone
16 | if (!ini_get('date.timezone')) {
17 | date_default_timezone_set('Asia/Shanghai');
18 | }
19 | // Display errors.
20 | ini_set('display_errors', 'on');
21 | // Reporting all.
22 | error_reporting(E_ALL);
23 |
24 | // Reset opcache.
25 | if (function_exists('opcache_reset')) {
26 | opcache_reset();
27 | }
28 |
29 | // For onError callback.
30 | define('WORKERMAN_CONNECT_FAIL', 1);
31 | // For onError callback.
32 | define('WORKERMAN_SEND_FAIL', 2);
33 |
34 | // Compatible with php7
35 | if(!class_exists('Error'))
36 | {
37 | class Error extends Exception
38 | {
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Workerman/MIT-LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License
2 |
3 | Copyright (c) 2009-2015 walkor and contributors (see https://github.com/walkor/workerman/contributors)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Workerman/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "workerman/workerman",
3 | "type": "library",
4 | "keywords": [
5 | "event-loop",
6 | "asynchronous"
7 | ],
8 | "homepage": "http://www.workerman.net",
9 | "license": "MIT",
10 | "description": "An asynchronous event driven PHP framework for easily building fast, scalable network applications.",
11 | "authors": [
12 | {
13 | "name": "walkor",
14 | "email": "walkor@workerman.net",
15 | "homepage": "http://www.workerman.net",
16 | "role": "Developer"
17 | }
18 | ],
19 | "support": {
20 | "email": "walkor@workerman.net",
21 | "issues": "https://github.com/walkor/workerman/issues",
22 | "forum": "http://wenda.workerman.net/",
23 | "wiki": "http://doc3.workerman.net/index.html",
24 | "source": "https://github.com/walkor/workerman"
25 | },
26 | "require": {
27 | "php": ">=5.3",
28 | "ext-pcntl": "*",
29 | "ext-posix": "*"
30 | },
31 | "suggest": {
32 | "ext-event": "For better performance. "
33 | },
34 | "autoload": {
35 | "psr-4": {
36 | "Workerman\\": "./"
37 | }
38 | },
39 | "minimum-stability": "dev"
40 | }
41 |
--------------------------------------------------------------------------------
/Cor/CorThread.php:
--------------------------------------------------------------------------------
1 | is_exit = false;
21 | $this->cpu_unix_name = $cpu_unix_name;
22 | $this->eventHandler = $eventHandler;
23 | }
24 |
25 | public function run()
26 | {
27 | //分离子线程和父线程
28 | spl_autoload_register(function ($name) {
29 | $class_path = str_replace('\\', DIRECTORY_SEPARATOR, $name);
30 | $class_file = __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . "$class_path.php";
31 | if (is_file($class_file)) {
32 | require_once($class_file);
33 | if (class_exists($name, false)) {
34 | return true;
35 | }
36 | }
37 | return false;
38 | });
39 | global $cpu;
40 | $cpu = new \Cor\Net\Cpu($this->cpu_unix_name,$this);
41 | $cpu->run();
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | AngularJS
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/Cor/Extend/MySql.php:
--------------------------------------------------------------------------------
1 | host = $host;
27 | $this->user = $user;
28 | $this->password = $password;
29 | $this->database = $database;
30 | }
31 |
32 | function async_query($sql)
33 | {
34 | $con = new \mysqli($this->host, $this->user, $this->password, $this->database);
35 | $con->query($sql, MYSQLI_ASYNC);
36 | $this->conns[$con->thread_id] = $con;
37 | }
38 |
39 | function async_result()
40 | {
41 | $res = array();
42 | $e = array();
43 | $e2 = array();
44 | while (count($this->conns)>0) {
45 | $reads = $this->conns;
46 |
47 | if (!mysqli_poll($reads, $e, $e2, 10)) {
48 | yield;
49 | continue;
50 | }
51 | foreach ($reads as $obj) {
52 | unset($this->conns[$obj->thread_id]);
53 | $sql_result = $obj->reap_async_query();
54 | $sql_result_array = $sql_result->fetch_all();
55 | $sql_result->free();
56 | $res[] = $sql_result_array;
57 | }
58 | yield;
59 | }
60 | return $res;
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/Workerman/Protocols/Frame.php:
--------------------------------------------------------------------------------
1 |
10 | * @copyright walkor
11 | * @link http://www.workerman.net/
12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License
13 | */
14 | namespace Workerman\Protocols;
15 |
16 | use Workerman\Connection\TcpConnection;
17 |
18 | /**
19 | * Frame Protocol.
20 | */
21 | class Frame
22 | {
23 | /**
24 | * Check the integrity of the package.
25 | *
26 | * @param string $buffer
27 | * @param TcpConnection $connection
28 | * @return int
29 | */
30 | public static function input($buffer, TcpConnection $connection)
31 | {
32 | if (strlen($buffer) < 4) {
33 | return 0;
34 | }
35 | $unpack_data = unpack('Ntotal_length', $buffer);
36 | return $unpack_data['total_length'];
37 | }
38 |
39 | /**
40 | * Decode.
41 | *
42 | * @param string $buffer
43 | * @return string
44 | */
45 | public static function decode($buffer)
46 | {
47 | return substr($buffer, 4);
48 | }
49 |
50 | /**
51 | * Encode.
52 | *
53 | * @param string $buffer
54 | * @return string
55 | */
56 | public static function encode($buffer)
57 | {
58 | $total_length = 4 + strlen($buffer);
59 | return pack('N', $total_length) . $buffer;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/Workerman/Protocols/ProtocolInterface.php:
--------------------------------------------------------------------------------
1 |
10 | * @copyright walkor
11 | * @link http://www.workerman.net/
12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License
13 | */
14 | namespace Workerman\Protocols;
15 |
16 | use Workerman\Connection\ConnectionInterface;
17 |
18 | /**
19 | * Protocol interface
20 | */
21 | interface ProtocolInterface
22 | {
23 | /**
24 | * Check the integrity of the package.
25 | * Please return the length of package.
26 | * If length is unknow please return 0 that mean wating more data.
27 | * If the package has something wrong please return false the connection will be closed.
28 | *
29 | * @param ConnectionInterface $connection
30 | * @param string $recv_buffer
31 | * @return int|false
32 | */
33 | public static function input($recv_buffer, ConnectionInterface $connection);
34 |
35 | /**
36 | * Decode package and emit onMessage($message) callback, $message is the result that decode returned.
37 | *
38 | * @param ConnectionInterface $connection
39 | * @param string $recv_buffer
40 | * @return mixed
41 | */
42 | public static function decode($recv_buffer, ConnectionInterface $connection);
43 |
44 | /**
45 | * Encode package brefore sending to client.
46 | *
47 | * @param ConnectionInterface $connection
48 | * @param mixed $data
49 | * @return string
50 | */
51 | public static function encode($data, ConnectionInterface $connection);
52 | }
53 |
--------------------------------------------------------------------------------
/Workerman/Protocols/Text.php:
--------------------------------------------------------------------------------
1 |
10 | * @copyright walkor
11 | * @link http://www.workerman.net/
12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License
13 | */
14 | namespace Workerman\Protocols;
15 |
16 | use Workerman\Connection\TcpConnection;
17 |
18 | /**
19 | * Text Protocol.
20 | */
21 | class Text
22 | {
23 | /**
24 | * Check the integrity of the package.
25 | *
26 | * @param string $buffer
27 | * @param TcpConnection $connection
28 | * @return int
29 | */
30 | public static function input($buffer, TcpConnection $connection)
31 | {
32 | // Judge whether the package length exceeds the limit.
33 | if (strlen($buffer) >= TcpConnection::$maxPackageSize) {
34 | $connection->close();
35 | return 0;
36 | }
37 | // Find the position of "\n".
38 | $pos = strpos($buffer, "\n");
39 | // No "\n", packet length is unknown, continue to wait for the data so return 0.
40 | if ($pos === false) {
41 | return 0;
42 | }
43 | // Return the current package length.
44 | return $pos + 1;
45 | }
46 |
47 | /**
48 | * Encode.
49 | *
50 | * @param string $buffer
51 | * @return string
52 | */
53 | public static function encode($buffer)
54 | {
55 | // Add "\n"
56 | return $buffer . "\n";
57 | }
58 |
59 | /**
60 | * Decode.
61 | *
62 | * @param string $buffer
63 | * @return string
64 | */
65 | public static function decode($buffer)
66 | {
67 | // Remove "\n"
68 | return trim($buffer);
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/Workerman/Autoloader.php:
--------------------------------------------------------------------------------
1 |
10 | * @copyright walkor
11 | * @link http://www.workerman.net/
12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License
13 | */
14 | namespace Workerman;
15 |
16 | /**
17 | * Autoload.
18 | */
19 | class Autoloader
20 | {
21 | /**
22 | * Autoload root path.
23 | *
24 | * @var string
25 | */
26 | protected static $_autoloadRootPath = '';
27 |
28 | /**
29 | * Set autoload root path.
30 | *
31 | * @param string $root_path
32 | * @return void
33 | */
34 | public static function setRootPath($root_path)
35 | {
36 | self::$_autoloadRootPath = $root_path;
37 | }
38 |
39 | /**
40 | * Load files by namespace.
41 | *
42 | * @param string $name
43 | * @return boolean
44 | */
45 | public static function loadByNamespace($name)
46 | {
47 | $class_path = str_replace('\\', DIRECTORY_SEPARATOR, $name);
48 | if (strpos($name, 'Workerman\\') === 0) {
49 | $class_file = __DIR__ . substr($class_path, strlen('Workerman')) . '.php';
50 | } else {
51 | if (self::$_autoloadRootPath) {
52 | $class_file = self::$_autoloadRootPath . DIRECTORY_SEPARATOR . $class_path . '.php';
53 | }
54 | if (empty($class_file) || !is_file($class_file)) {
55 | $class_file = __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . "$class_path.php";
56 | }
57 | }
58 | if (is_file($class_file)) {
59 | require_once($class_file);
60 | if (class_exists($name, false)) {
61 | return true;
62 | }
63 | }
64 | return false;
65 | }
66 | }
67 |
68 | spl_autoload_register('\Workerman\Autoloader::loadByNamespace');
--------------------------------------------------------------------------------
/Workerman/Events/EventInterface.php:
--------------------------------------------------------------------------------
1 |
10 | * @copyright walkor
11 | * @link http://www.workerman.net/
12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License
13 | */
14 | namespace Workerman\Events;
15 |
16 | interface EventInterface
17 | {
18 | /**
19 | * Read event.
20 | *
21 | * @var int
22 | */
23 | const EV_READ = 1;
24 |
25 | /**
26 | * Write event.
27 | *
28 | * @var int
29 | */
30 | const EV_WRITE = 2;
31 |
32 | /**
33 | * Except event
34 | *
35 | * @var int
36 | */
37 | const EV_EXCEPT = 3;
38 |
39 | /**
40 | * Signal event.
41 | *
42 | * @var int
43 | */
44 | const EV_SIGNAL = 4;
45 |
46 | /**
47 | * Timer event.
48 | *
49 | * @var int
50 | */
51 | const EV_TIMER = 8;
52 |
53 | /**
54 | * Timer once event.
55 | *
56 | * @var int
57 | */
58 | const EV_TIMER_ONCE = 16;
59 |
60 | /**
61 | * Add event listener to event loop.
62 | *
63 | * @param mixed $fd
64 | * @param int $flag
65 | * @param callable $func
66 | * @param mixed $args
67 | * @return bool
68 | */
69 | public function add($fd, $flag, $func, $args = null);
70 |
71 | /**
72 | * Remove event listener from event loop.
73 | *
74 | * @param mixed $fd
75 | * @param int $flag
76 | * @return bool
77 | */
78 | public function del($fd, $flag);
79 |
80 | /**
81 | * Remove all timers.
82 | *
83 | * @return void
84 | */
85 | public function clearAllTimer();
86 |
87 | /**
88 | * Main loop.
89 | *
90 | * @return void
91 | */
92 | public function loop();
93 |
94 | /**
95 | * Destroy loop.
96 | *
97 | * @return mixed
98 | */
99 | public function destroy();
100 |
101 | /**
102 | * Get Timer count.
103 | *
104 | * @return mixed
105 | */
106 | public function getTimerCount();
107 | }
108 |
--------------------------------------------------------------------------------
/Workerman/Connection/ConnectionInterface.php:
--------------------------------------------------------------------------------
1 |
10 | * @copyright walkor
11 | * @link http://www.workerman.net/
12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License
13 | */
14 | namespace Workerman\Connection;
15 |
16 | /**
17 | * ConnectionInterface.
18 | */
19 | abstract class ConnectionInterface
20 | {
21 | /**
22 | * Statistics for status command.
23 | *
24 | * @var array
25 | */
26 | public static $statistics = array(
27 | 'connection_count' => 0,
28 | 'total_request' => 0,
29 | 'throw_exception' => 0,
30 | 'send_fail' => 0,
31 | );
32 |
33 | /**
34 | * Emitted when data is received.
35 | *
36 | * @var callback
37 | */
38 | public $onMessage = null;
39 |
40 | /**
41 | * Emitted when the other end of the socket sends a FIN packet.
42 | *
43 | * @var callback
44 | */
45 | public $onClose = null;
46 |
47 | /**
48 | * Emitted when an error occurs with connection.
49 | *
50 | * @var callback
51 | */
52 | public $onError = null;
53 |
54 | /**
55 | * Sends data on the connection.
56 | *
57 | * @param string $send_buffer
58 | * @return void|boolean
59 | */
60 | abstract public function send($send_buffer);
61 |
62 | /**
63 | * Get remote IP.
64 | *
65 | * @return string
66 | */
67 | abstract public function getRemoteIp();
68 |
69 | /**
70 | * Get remote port.
71 | *
72 | * @return int
73 | */
74 | abstract public function getRemotePort();
75 |
76 | /**
77 | * Get remote address.
78 | *
79 | * @return string
80 | */
81 | abstract public function getRemoteAddress();
82 |
83 | /**
84 | * Get remote IP.
85 | *
86 | * @return string
87 | */
88 | abstract public function getLocalIp();
89 |
90 | /**
91 | * Get remote port.
92 | *
93 | * @return int
94 | */
95 | abstract public function getLocalPort();
96 |
97 | /**
98 | * Get remote address.
99 | *
100 | * @return string
101 | */
102 | abstract public function getLocalAddress();
103 |
104 | /**
105 | * Is ipv4.
106 | *
107 | * @return bool
108 | */
109 | abstract public function isIPv4();
110 |
111 | /**
112 | * Is ipv6.
113 | *
114 | * @return bool
115 | */
116 | abstract public function isIPv6();
117 |
118 | /**
119 | * Close connection.
120 | *
121 | * @param $data
122 | * @return void
123 | */
124 | abstract public function close($data = null);
125 | }
126 |
--------------------------------------------------------------------------------
/Cor/Net/CpuConnection.php:
--------------------------------------------------------------------------------
1 | id = self::$max_id;
26 | $conn = stream_socket_accept($socket, 0);
27 | $this->worker = $worker;
28 | $this->conn = $conn;
29 | }
30 |
31 | // 动起来,跟着我的世界一起喝彩
32 | public function read()
33 | {
34 | //尝试读取
35 | $buffer = @fread($this->conn, self::READ_BUFFER_SIZE);
36 | if ($buffer === '' || $buffer === false) {
37 | if (feof($this->conn) || !is_resource($this->conn) || $buffer === false) {
38 | $this->destroy();
39 | return;
40 | }
41 | }
42 | //拼接上一次遗留的字符串
43 | $this->old_buffer = $this->old_buffer . $buffer;
44 | while(strlen($this->old_buffer)>2){
45 | $len = substr($this->old_buffer, 0, 2);
46 | $body_len = unpack('Sbin_len', $len)['bin_len'];
47 | if(strlen($this->old_buffer)<($body_len+2)){
48 | break;
49 | }
50 | $data = substr($this->old_buffer, 2, $body_len);
51 | $data = json_decode($data,true);
52 | $this->old_buffer = substr($this->old_buffer,($body_len+2),strlen($this->old_buffer)-($body_len+2));
53 |
54 | if (is_callable($this->worker->thread->eventHandler . '::' . $data[0])) {
55 | $gen = $this->worker->thread->eventHandler . '::' . $data[0];
56 | $response = yield from $gen ($data[2]);
57 | //如果等于0不需要返回,否则相反
58 | if($data[1]!=0){
59 | $this->worker->_event->add($this->conn, Select::EV_WRITE, array($this, '_write'), array(json_encode(array($data[1],$response))));
60 | }
61 | }else{
62 | /** 如果没找到对应方法,那么也返回 */
63 | var_dump("can't find ".$this->worker->thread->eventHandler . '::' . $data[0]);
64 | }
65 | }
66 | yield;
67 | }
68 |
69 |
70 | public function destroy()
71 | {
72 | if ($this->worker) {
73 | unset ($this->worker->connections [(int)$this->conn]);
74 | }
75 | $this->worker->_event->del($this->conn,Select::EV_READ);
76 | $this->worker->_event->del($this->conn,Select::EV_WRITE);
77 | $this->worker->thread->is_exit = false;
78 | }
79 |
80 | /**
81 | * 读回调
82 | */
83 | public function _read($socket){
84 | //将读取操作添加进任务队列中
85 | //尽量缩小不必要的任务切换,将任务切换使用在阻塞操作当中
86 | $this->worker->newTask ( $this->read() );
87 | }
88 |
89 | /**
90 | * 写回调
91 | */
92 | public function _write($key,$socket,$data){
93 | $body_len = strlen($data);
94 | $bin_head = pack('S*', $body_len);
95 | $len = @fwrite($socket, $bin_head . $data);
96 | if ($len === strlen($bin_head . $data)) {
97 | $this->worker->_event->del($socket, Select::EV_WRITE,$key);
98 | }else{
99 | $this->destroy();
100 | }
101 |
102 | }
103 | }
--------------------------------------------------------------------------------
/Workerman/Protocols/Http/mime.types:
--------------------------------------------------------------------------------
1 |
2 | types {
3 | text/html html htm shtml;
4 | text/css css;
5 | text/xml xml;
6 | image/gif gif;
7 | image/jpeg jpeg jpg;
8 | application/x-javascript js;
9 | application/atom+xml atom;
10 | application/rss+xml rss;
11 |
12 | text/mathml mml;
13 | text/plain txt;
14 | text/vnd.sun.j2me.app-descriptor jad;
15 | text/vnd.wap.wml wml;
16 | text/x-component htc;
17 |
18 | image/png png;
19 | image/tiff tif tiff;
20 | image/vnd.wap.wbmp wbmp;
21 | image/x-icon ico;
22 | image/x-jng jng;
23 | image/x-ms-bmp bmp;
24 | image/svg+xml svg svgz;
25 | image/webp webp;
26 |
27 | application/java-archive jar war ear;
28 | application/mac-binhex40 hqx;
29 | application/msword doc;
30 | application/pdf pdf;
31 | application/postscript ps eps ai;
32 | application/rtf rtf;
33 | application/vnd.ms-excel xls;
34 | application/vnd.ms-powerpoint ppt;
35 | application/vnd.wap.wmlc wmlc;
36 | application/vnd.google-earth.kml+xml kml;
37 | application/vnd.google-earth.kmz kmz;
38 | application/x-7z-compressed 7z;
39 | application/x-cocoa cco;
40 | application/x-java-archive-diff jardiff;
41 | application/x-java-jnlp-file jnlp;
42 | application/x-makeself run;
43 | application/x-perl pl pm;
44 | application/x-pilot prc pdb;
45 | application/x-rar-compressed rar;
46 | application/x-redhat-package-manager rpm;
47 | application/x-sea sea;
48 | application/x-shockwave-flash swf;
49 | application/x-stuffit sit;
50 | application/x-tcl tcl tk;
51 | application/x-x509-ca-cert der pem crt;
52 | application/x-xpinstall xpi;
53 | application/xhtml+xml xhtml;
54 | application/zip zip;
55 |
56 | application/octet-stream bin exe dll;
57 | application/octet-stream deb;
58 | application/octet-stream dmg;
59 | application/octet-stream eot;
60 | application/octet-stream iso img;
61 | application/octet-stream msi msp msm;
62 |
63 | audio/midi mid midi kar;
64 | audio/mpeg mp3;
65 | audio/ogg ogg;
66 | audio/x-m4a m4a;
67 | audio/x-realaudio ra;
68 |
69 | video/3gpp 3gpp 3gp;
70 | video/mp4 mp4;
71 | video/mpeg mpeg mpg;
72 | video/quicktime mov;
73 | video/webm webm;
74 | video/x-flv flv;
75 | video/x-m4v m4v;
76 | video/x-mng mng;
77 | video/x-ms-asf asx asf;
78 | video/x-ms-wmv wmv;
79 | video/x-msvideo avi;
80 | }
81 |
--------------------------------------------------------------------------------
/Cor/Net/Cpu.php:
--------------------------------------------------------------------------------
1 | task
26 |
27 | // 监听端口的socket
28 | public $_mainSocket = null;
29 |
30 |
31 | public function __construct($host, $thread)
32 | {
33 | // 设置监听路径
34 | $this->host = $host;
35 | $this->thread = $thread;
36 | // 初始化唯一ID
37 | if (PHP_INT_MAX === self::$_idRecorder) {
38 | self::$_idRecorder = 0;
39 | }
40 | $this->id = self::$_idRecorder++;
41 | // 实例化数据结构队列
42 | $this->taskQueue = new \SplQueue ();
43 | // 初始化系统
44 | $this->init();
45 | $this->_event = new Select($this);
46 | //添加端口socket监听
47 | $this->_event->add($this->_mainSocket, Select::EV_READ, array($this, '_accept'));
48 | //将核心监听任务加入队列中
49 | $this->newTask($this->_event->loop());
50 | }
51 |
52 |
53 | /**
54 | * 传入协程对象,创建新任务,并且把任务放入任务队列
55 | */
56 | public function newTask(\Generator $coroutine)
57 | {
58 | if (PHP_INT_MAX === $this->_taskId) {
59 | self::$_idRecorder = 0;
60 | }
61 | $tid = $this->_taskId;
62 |
63 | // 创建一个任务
64 | $task = new Task ($tid, $coroutine);
65 | // 将任务放入任务表中
66 | $this->taskMap [$tid] = $task;
67 | $this->schedule($task);
68 | return $tid;
69 | }
70 |
71 | /**
72 | * 将任务放入任务队列底部
73 | */
74 | public function schedule(Task $task)
75 | {
76 | $this->taskQueue->enqueue($task);
77 | }
78 |
79 | /**
80 | * 任务队列是否为空
81 | */
82 | public function taskQueueIsEmpty(){
83 | return $this->taskQueue->isEmpty();
84 | }
85 |
86 | /**
87 | * 开始调度任务,类似于cpu
88 | */
89 | public function run()
90 | {
91 | // 如果有任务队列中还有任务,那么继续执行
92 | while (!$this->taskQueue->isEmpty() && !$this->thread->is_exit) {
93 | // 取出队列头部的成员
94 | $task = $this->taskQueue->dequeue();
95 | // 执行一个刻度的任务
96 | $retval = $task->run();
97 |
98 | // 如果任务返回的是一个系统调用,那么执行这个调用,并且把这个任务移除,这是一次性任务
99 | if ($retval instanceof SystemCall) {
100 | $retval ($task, $this);
101 | continue;
102 | }
103 |
104 | // 如果任务运行完毕了
105 | if ($task->isFinished()) {
106 | // 那么把这个任务从任务表中移除
107 | unset ($this->taskMap [$task->getTaskId()]);
108 | } else {
109 | // 如果没完成,将他放入任务链表最后
110 | $this->schedule($task);
111 | }
112 | }
113 | }
114 |
115 | /**
116 | * 有新链接回调
117 | */
118 | public function _accept($socket){
119 | $connection = new CpuConnection ($this, $socket);
120 | $this->connections [( int )$connection->conn] = $connection;
121 | $this->_event->add($connection->conn, Select::EV_READ, array($connection, '_read'));
122 | }
123 |
124 | /**
125 | * 初始化相关数据
126 | */
127 | protected function init()
128 | {
129 | //如果是unix类型,那么启动之前先把之前的删除了
130 | if (strpos($this->host, "unix://") === 0) {
131 | $unix_file = str_replace("unix://", "", $this->host);
132 | if (file_exists($unix_file)) {
133 | unlink($unix_file);
134 | }
135 | }
136 | // 创建socket套接字,用来监听这个端口
137 | $socket = stream_socket_server($this->host, $errNo, $errStr);
138 | // 如果创建失败抛出异常
139 | if (!$socket) {
140 | throw new \Exception ($errStr, $errNo);
141 | exit ();
142 | }
143 | // 设置监听连接socket
144 | $this->_mainSocket = $socket;
145 | // 设置为非阻塞IO,当尝试读一个网络流,并且未读取字节的时候,立即告诉调用者结构
146 | stream_set_blocking($socket, 0);
147 | }
148 |
149 | }
150 |
151 |
--------------------------------------------------------------------------------
/Workerman/Connection/UdpConnection.php:
--------------------------------------------------------------------------------
1 |
10 | * @copyright walkor
11 | * @link http://www.workerman.net/
12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License
13 | */
14 | namespace Workerman\Connection;
15 |
16 | /**
17 | * UdpConnection.
18 | */
19 | class UdpConnection extends ConnectionInterface
20 | {
21 | /**
22 | * Application layer protocol.
23 | * The format is like this Workerman\\Protocols\\Http.
24 | *
25 | * @var \Workerman\Protocols\ProtocolInterface
26 | */
27 | public $protocol = null;
28 |
29 | /**
30 | * Udp socket.
31 | *
32 | * @var resource
33 | */
34 | protected $_socket = null;
35 |
36 | /**
37 | * Remote address.
38 | *
39 | * @var string
40 | */
41 | protected $_remoteAddress = '';
42 |
43 | /**
44 | * Construct.
45 | *
46 | * @param resource $socket
47 | * @param string $remote_address
48 | */
49 | public function __construct($socket, $remote_address)
50 | {
51 | $this->_socket = $socket;
52 | $this->_remoteAddress = $remote_address;
53 | }
54 |
55 | /**
56 | * Sends data on the connection.
57 | *
58 | * @param string $send_buffer
59 | * @param bool $raw
60 | * @return void|boolean
61 | */
62 | public function send($send_buffer, $raw = false)
63 | {
64 | if (false === $raw && $this->protocol) {
65 | $parser = $this->protocol;
66 | $send_buffer = $parser::encode($send_buffer, $this);
67 | if ($send_buffer === '') {
68 | return null;
69 | }
70 | }
71 | return strlen($send_buffer) === stream_socket_sendto($this->_socket, $send_buffer, 0, $this->_remoteAddress);
72 | }
73 |
74 | /**
75 | * Get remote IP.
76 | *
77 | * @return string
78 | */
79 | public function getRemoteIp()
80 | {
81 | $pos = strrpos($this->_remoteAddress, ':');
82 | if ($pos) {
83 | return trim(substr($this->_remoteAddress, 0, $pos), '[]');
84 | }
85 | return '';
86 | }
87 |
88 | /**
89 | * Get remote port.
90 | *
91 | * @return int
92 | */
93 | public function getRemotePort()
94 | {
95 | if ($this->_remoteAddress) {
96 | return (int)substr(strrchr($this->_remoteAddress, ':'), 1);
97 | }
98 | return 0;
99 | }
100 |
101 | /**
102 | * Get remote address.
103 | *
104 | * @return string
105 | */
106 | public function getRemoteAddress()
107 | {
108 | return $this->_remoteAddress;
109 | }
110 |
111 | /**
112 | * Get local IP.
113 | *
114 | * @return string
115 | */
116 | public function getLocalIp()
117 | {
118 | $address = $this->getLocalAddress();
119 | $pos = strrpos($address, ':');
120 | if (!$pos) {
121 | return '';
122 | }
123 | return substr($address, 0, $pos);
124 | }
125 |
126 | /**
127 | * Get local port.
128 | *
129 | * @return int
130 | */
131 | public function getLocalPort()
132 | {
133 | $address = $this->getLocalAddress();
134 | $pos = strrpos($address, ':');
135 | if (!$pos) {
136 | return 0;
137 | }
138 | return (int)substr(strrchr($address, ':'), 1);
139 | }
140 |
141 | /**
142 | * Get local address.
143 | *
144 | * @return string
145 | */
146 | public function getLocalAddress()
147 | {
148 | return (string)@stream_socket_get_name($this->_socket, false);
149 | }
150 |
151 | /**
152 | * Is ipv4.
153 | *
154 | * return bool.
155 | */
156 | public function isIpV4()
157 | {
158 | if ($this->transport === 'unix') {
159 | return false;
160 | }
161 | return strpos($this->getRemoteIp(), ':') === false;
162 | }
163 |
164 | /**
165 | * Is ipv6.
166 | *
167 | * return bool.
168 | */
169 | public function isIpV6()
170 | {
171 | if ($this->transport === 'unix') {
172 | return false;
173 | }
174 | return strpos($this->getRemoteIp(), ':') !== false;
175 | }
176 |
177 | /**
178 | * Close connection.
179 | *
180 | * @param mixed $data
181 | * @param bool $raw
182 | * @return bool
183 | */
184 | public function close($data = null, $raw = false)
185 | {
186 | if ($data !== null) {
187 | $this->send($data, $raw);
188 | }
189 | return true;
190 | }
191 | }
192 |
--------------------------------------------------------------------------------
/Workerman/Lib/Timer.php:
--------------------------------------------------------------------------------
1 |
10 | * @copyright walkor
11 | * @link http://www.workerman.net/
12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License
13 | */
14 | namespace Workerman\Lib;
15 |
16 | use Workerman\Events\EventInterface;
17 | use Exception;
18 |
19 | /**
20 | * Timer.
21 | *
22 | * example:
23 | * Workerman\Lib\Timer::add($time_interval, callback, array($arg1, $arg2..));
24 | */
25 | class Timer
26 | {
27 | /**
28 | * Tasks that based on ALARM signal.
29 | * [
30 | * run_time => [[$func, $args, $persistent, time_interval],[$func, $args, $persistent, time_interval],..]],
31 | * run_time => [[$func, $args, $persistent, time_interval],[$func, $args, $persistent, time_interval],..]],
32 | * ..
33 | * ]
34 | *
35 | * @var array
36 | */
37 | protected static $_tasks = array();
38 |
39 | /**
40 | * event
41 | *
42 | * @var \Workerman\Events\EventInterface
43 | */
44 | protected static $_event = null;
45 |
46 | /**
47 | * Init.
48 | *
49 | * @param \Workerman\Events\EventInterface $event
50 | * @return void
51 | */
52 | public static function init($event = null)
53 | {
54 | if ($event) {
55 | self::$_event = $event;
56 | } else {
57 | pcntl_signal(SIGALRM, array('\Workerman\Lib\Timer', 'signalHandle'), false);
58 | }
59 | }
60 |
61 | /**
62 | * ALARM signal handler.
63 | *
64 | * @return void
65 | */
66 | public static function signalHandle()
67 | {
68 | if (!self::$_event) {
69 | pcntl_alarm(1);
70 | self::tick();
71 | }
72 | }
73 |
74 | /**
75 | * Add a timer.
76 | *
77 | * @param int $time_interval
78 | * @param callback $func
79 | * @param mixed $args
80 | * @param bool $persistent
81 | * @return int/false
82 | */
83 | public static function add($time_interval, $func, $args = array(), $persistent = true)
84 | {
85 | if ($time_interval <= 0) {
86 | echo new Exception("bad time_interval");
87 | return false;
88 | }
89 |
90 | if (self::$_event) {
91 | return self::$_event->add($time_interval,
92 | $persistent ? EventInterface::EV_TIMER : EventInterface::EV_TIMER_ONCE, $func, $args);
93 | }
94 |
95 | if (!is_callable($func)) {
96 | echo new Exception("not callable");
97 | return false;
98 | }
99 |
100 | if (empty(self::$_tasks)) {
101 | pcntl_alarm(1);
102 | }
103 |
104 | $time_now = time();
105 | $run_time = $time_now + $time_interval;
106 | if (!isset(self::$_tasks[$run_time])) {
107 | self::$_tasks[$run_time] = array();
108 | }
109 | self::$_tasks[$run_time][] = array($func, (array)$args, $persistent, $time_interval);
110 | return 1;
111 | }
112 |
113 |
114 | /**
115 | * Tick.
116 | *
117 | * @return void
118 | */
119 | public static function tick()
120 | {
121 | if (empty(self::$_tasks)) {
122 | pcntl_alarm(0);
123 | return;
124 | }
125 |
126 | $time_now = time();
127 | foreach (self::$_tasks as $run_time => $task_data) {
128 | if ($time_now >= $run_time) {
129 | foreach ($task_data as $index => $one_task) {
130 | $task_func = $one_task[0];
131 | $task_args = $one_task[1];
132 | $persistent = $one_task[2];
133 | $time_interval = $one_task[3];
134 | try {
135 | call_user_func_array($task_func, $task_args);
136 | } catch (\Exception $e) {
137 | echo $e;
138 | }
139 | if ($persistent) {
140 | self::add($time_interval, $task_func, $task_args);
141 | }
142 | }
143 | unset(self::$_tasks[$run_time]);
144 | }
145 | }
146 | }
147 |
148 | /**
149 | * Remove a timer.
150 | *
151 | * @param mixed $timer_id
152 | * @return bool
153 | */
154 | public static function del($timer_id)
155 | {
156 | if (self::$_event) {
157 | return self::$_event->del($timer_id, EventInterface::EV_TIMER);
158 | }
159 |
160 | return false;
161 | }
162 |
163 | /**
164 | * Remove all timers.
165 | *
166 | * @return void
167 | */
168 | public static function delAll()
169 | {
170 | self::$_tasks = array();
171 | pcntl_alarm(0);
172 | if (self::$_event) {
173 | self::$_event->clearAllTimer();
174 | }
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Workerman_cor_ape
2 |
3 |
4 | ## 这是什么
5 | Workerman_cor_ape 是知名php框架 [Workerman](https://github.com/walkor/Workerman) 的强化版,在不影响任何使用方式,稳定性,性能前提下,增加了异步任务组件。
6 | ## 原理是什么
7 | Workerman每个工作进程只有一个线程,这个线程既负责收发网络消息,又负责处理业务,在业务阻塞比较多的情况下,比较浪费性能。
8 |
9 | Workerman_cor_ape框架将Workerman每个进程扩展为两个线程,分别是workerman线程,以及任务线程。
10 | `workerman线程:和原有的单线程workerman线程是相同的,无论是使用方式,还是性能。`
11 | `任务线程:负责接收workerman线程异步提交的任务,执行结束后可以将执行结果主动推送给workerman线程`
12 |
13 |
14 | 另外任务线程采用协程调度方式实现,自己控制线程等待/执行时机,最大限度压榨性能。并且方便自己扩展异步组件
15 |
16 |
17 | ## Requires
18 | 线程安全版本的 PHP7 or 更高
19 | A POSIX compatible operating system (Linux, OSX, BSD)
20 | POSIX PCNTL and **PTHREDS** extensions for PHP
21 |
22 | ## Workerman的使用方法
23 |
24 | 中文主页:[http://www.workerman.net](http://www.workerman.net)
25 |
26 | 中文文档: [http://doc.workerman.net](http://doc.workerman.net)
27 |
28 | Documentation:[https://github.com/walkor/workerman-manual](https://github.com/walkor/workerman-manual/blob/master/english/src/SUMMARY.md)
29 |
30 |
31 | ## 使用异步任务功能
32 |
33 | ### 核心方法
34 | ```php
35 | $worker->ajax("任务名字", 传递的数据, 回调的方法,超时时间);
36 | ```
37 | ### workerman线程
38 | ```php
39 | use Cor\CorWorker;
40 |
41 | require_once __DIR__ . '/Workerman/Autoloader.php';
42 |
43 | $worker = new CorWorker("http://0.0.0.0:1236");
44 | $worker->count = 1;
45 | //设置任务线程执行的位置,不设置默认根目录下Events
46 | $worker->eventHandler = "Events";
47 |
48 | //以前怎么使用workerman,现在还可以怎么使用,毫无区别
49 | $worker->onMessage = function ($connection, $data) use ($worker) {
50 | //你也可以选择这样的方式,也就是workerman的方式,我们先注释掉,使用任务线程的send方法返回数据
51 | //$connection->send("hello workerman_cor_ape");
52 |
53 | //这段代码会异步任务线程Evnets类里面的helloworld方法
54 | $worker->ajax("testMysql", $data, function ($body) use ($connection){
55 | $connection->send(json_encode($body));
56 | });
57 |
58 | };
59 |
60 | CorWorker::runAll();
61 | ```
62 |
63 | ### 任务线程
64 | ```php
65 | class Events{
66 |
67 |
68 | //任务线程任务
69 | public static function testMysql($connection,$data){
70 | //异步执行mysql操作,这只是随手写的例子,各位老爷能了解到可以很简单实现异步mysql就好
71 | $mysql = new \Cor\Extend\MySql("127.0.0.1","账号","密码","数据库");
72 | //查询三次
73 | $mysql->async_query("select * from t_admin");
74 | $mysql->async_query("select * from t_admin");
75 | $mysql->async_query("select * from t_admin");
76 | //注意这里采用协程方式访问mysql里面的协程方法
77 | $res = yield from $mysql->async_result();
78 | //方法内最少包含一个yield字段
79 | return $res;
80 | }
81 |
82 | }
83 | ```
84 |
85 |
86 | ## 如何启动
87 | ```php start.php start ```
88 | ```php start.php start -d ```
89 | ```php start.php status ```
90 | ```php start.php connections```
91 | ```php start.php stop ```
92 | ```php start.php restart ```
93 | ```php start.php reload ```
94 |
95 | # Workerman性能测试
96 | ```
97 | CPU: Intel(R) Core(TM) i3-3220 CPU @ 3.30GHz and 4 processors totally
98 | Memory: 8G
99 | OS: Ubuntu 14.04 LTS
100 | Software: ab
101 | PHP: 5.5.9
102 | ```
103 |
104 | **Codes**
105 | ```php
106 | count=3;
110 | $worker->onMessage = function($connection, $data)
111 | {
112 | $connection->send("HTTP/1.1 200 OK\r\nConnection: keep-alive\r\nServer: workerman\r\nContent-Length: 5\r\n\r\nhello");
113 | };
114 | Worker::runAll();
115 | ```
116 | **Result**
117 |
118 | ```shell
119 | ab -n1000000 -c100 -k http://127.0.0.1:1234/
120 | This is ApacheBench, Version 2.3 <$Revision: 1528965 $>
121 | Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
122 | Licensed to The Apache Software Foundation, http://www.apache.org/
123 |
124 | Benchmarking 127.0.0.1 (be patient)
125 | Completed 100000 requests
126 | Completed 200000 requests
127 | Completed 300000 requests
128 | Completed 400000 requests
129 | Completed 500000 requests
130 | Completed 600000 requests
131 | Completed 700000 requests
132 | Completed 800000 requests
133 | Completed 900000 requests
134 | Completed 1000000 requests
135 | Finished 1000000 requests
136 |
137 |
138 | Server Software: workerman/3.1.4
139 | Server Hostname: 127.0.0.1
140 | Server Port: 1234
141 |
142 | Document Path: /
143 | Document Length: 5 bytes
144 |
145 | Concurrency Level: 100
146 | Time taken for tests: 7.240 seconds
147 | Complete requests: 1000000
148 | Failed requests: 0
149 | Keep-Alive requests: 1000000
150 | Total transferred: 73000000 bytes
151 | HTML transferred: 5000000 bytes
152 | Requests per second: 138124.14 [#/sec] (mean)
153 | Time per request: 0.724 [ms] (mean)
154 | Time per request: 0.007 [ms] (mean, across all concurrent requests)
155 | Transfer rate: 9846.74 [Kbytes/sec] received
156 |
157 | Connection Times (ms)
158 | min mean[+/-sd] median max
159 | Connect: 0 0 0.0 0 5
160 | Processing: 0 1 0.2 1 9
161 | Waiting: 0 1 0.2 1 9
162 | Total: 0 1 0.2 1 9
163 |
164 | Percentage of the requests served within a certain time (ms)
165 | 50% 1
166 | 66% 1
167 | 75% 1
168 | 80% 1
169 | 90% 1
170 | 95% 1
171 | 98% 1
172 | 99% 1
173 | 100% 9 (longest request)
174 |
175 | ```
176 |
177 | ## 联系我
178 |
179 | QQ群: 1098698769
180 | 任何人都可以通过QQ群联系到我。
181 |
--------------------------------------------------------------------------------
/Workerman/Events/React/ExtEventLoop.php:
--------------------------------------------------------------------------------
1 |
10 | * @copyright walkor
11 | * @link http://www.workerman.net/
12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License
13 | */
14 | namespace Workerman\Events\React;
15 | use Workerman\Events\EventInterface;
16 |
17 | /**
18 | * Class ExtEventLoop
19 | * @package Workerman\Events\React
20 | */
21 | class ExtEventLoop extends \React\EventLoop\ExtEventLoop
22 | {
23 | /**
24 | * Event base.
25 | *
26 | * @var EventBase
27 | */
28 | protected $_eventBase = null;
29 |
30 | /**
31 | * All signal Event instances.
32 | *
33 | * @var array
34 | */
35 | protected $_signalEvents = array();
36 |
37 | /**
38 | * @var array
39 | */
40 | protected $_timerIdMap = array();
41 |
42 | /**
43 | * @var int
44 | */
45 | protected $_timerIdIndex = 0;
46 |
47 | /**
48 | * Add event listener to event loop.
49 | *
50 | * @param $fd
51 | * @param $flag
52 | * @param $func
53 | * @param array $args
54 | * @return bool
55 | */
56 | public function add($fd, $flag, $func, $args = array())
57 | {
58 | $args = (array)$args;
59 | switch ($flag) {
60 | case EventInterface::EV_READ:
61 | return $this->addReadStream($fd, $func);
62 | case EventInterface::EV_WRITE:
63 | return $this->addWriteStream($fd, $func);
64 | case EventInterface::EV_SIGNAL:
65 | return $this->addSignal($fd, $func);
66 | case EventInterface::EV_TIMER:
67 | $timer_id = ++$this->_timerIdIndex;
68 | $timer_obj = $this->addPeriodicTimer($fd, function() use ($func, $args) {
69 | call_user_func_array($func, $args);
70 | });
71 | $this->_timerIdMap[$timer_id] = $timer_obj;
72 | return $timer_id;
73 | case EventInterface::EV_TIMER_ONCE:
74 | $timer_id = ++$this->_timerIdIndex;
75 | $timer_obj = $this->addTimer($fd, function() use ($func, $args, $timer_id) {
76 | unset($this->_timerIdMap[$timer_id]);
77 | call_user_func_array($func, $args);
78 | });
79 | $this->_timerIdMap[$timer_id] = $timer_obj;
80 | return $timer_id;
81 | }
82 | return false;
83 | }
84 |
85 | /**
86 | * Remove event listener from event loop.
87 | *
88 | * @param mixed $fd
89 | * @param int $flag
90 | * @return bool
91 | */
92 | public function del($fd, $flag)
93 | {
94 | switch ($flag) {
95 | case EventInterface::EV_READ:
96 | return $this->removeReadStream($fd);
97 | case EventInterface::EV_WRITE:
98 | return $this->removeWriteStream($fd);
99 | case EventInterface::EV_SIGNAL:
100 | return $this->removeSignal($fd);
101 | case EventInterface::EV_TIMER:
102 | case EventInterface::EV_TIMER_ONCE;
103 | if (isset($this->_timerIdMap[$fd])){
104 | $timer_obj = $this->_timerIdMap[$fd];
105 | unset($this->_timerIdMap[$fd]);
106 | $this->cancelTimer($timer_obj);
107 | return true;
108 | }
109 | }
110 | return false;
111 | }
112 |
113 |
114 | /**
115 | * Main loop.
116 | *
117 | * @return void
118 | */
119 | public function loop()
120 | {
121 | $this->run();
122 | }
123 |
124 | /**
125 | * Construct
126 | */
127 | public function __construct()
128 | {
129 | parent::__construct();
130 | $class = new \ReflectionClass('\React\EventLoop\ExtEventLoop');
131 | $property = $class->getProperty('eventBase');
132 | $property->setAccessible(true);
133 | $this->_eventBase = $property->getValue($this);
134 | }
135 |
136 | /**
137 | * Add signal handler.
138 | *
139 | * @param $signal
140 | * @param $callback
141 | * @return bool
142 | */
143 | public function addSignal($signal, $callback)
144 | {
145 | $event = \Event::signal($this->_eventBase, $signal, $callback);
146 | if (!$event||!$event->add()) {
147 | return false;
148 | }
149 | $this->_signalEvents[$signal] = $event;
150 | }
151 |
152 | /**
153 | * Remove signal handler.
154 | *
155 | * @param $signal
156 | */
157 | public function removeSignal($signal)
158 | {
159 | if (isset($this->_signalEvents[$signal])) {
160 | $this->_signalEvents[$signal]->del();
161 | unset($this->_signalEvents[$signal]);
162 | }
163 | }
164 |
165 | /**
166 | * Destroy loop.
167 | *
168 | * @return void
169 | */
170 | public function destroy()
171 | {
172 | foreach ($this->_signalEvents as $event) {
173 | $event->del();
174 | }
175 | }
176 |
177 | /**
178 | * Get timer count.
179 | *
180 | * @return integer
181 | */
182 | public function getTimerCount()
183 | {
184 | return count($this->_timerIdMap);
185 | }
186 | }
187 |
--------------------------------------------------------------------------------
/Workerman/Events/React/LibEventLoop.php:
--------------------------------------------------------------------------------
1 |
10 | * @copyright walkor
11 | * @link http://www.workerman.net/
12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License
13 | */
14 | namespace Workerman\Events\React;
15 | use Workerman\Events\EventInterface;
16 |
17 | /**
18 | * Class LibEventLoop
19 | * @package Workerman\Events\React
20 | */
21 | class LibEventLoop extends \React\EventLoop\LibEventLoop
22 | {
23 | /**
24 | * Event base.
25 | *
26 | * @var event_base resource
27 | */
28 | protected $_eventBase = null;
29 |
30 | /**
31 | * All signal Event instances.
32 | *
33 | * @var array
34 | */
35 | protected $_signalEvents = array();
36 |
37 | /**
38 | * @var array
39 | */
40 | protected $_timerIdMap = array();
41 |
42 | /**
43 | * @var int
44 | */
45 | protected $_timerIdIndex = 0;
46 |
47 | /**
48 | * Add event listener to event loop.
49 | *
50 | * @param $fd
51 | * @param $flag
52 | * @param $func
53 | * @param array $args
54 | * @return bool
55 | */
56 | public function add($fd, $flag, $func, $args = array())
57 | {
58 | $args = (array)$args;
59 | switch ($flag) {
60 | case EventInterface::EV_READ:
61 | return $this->addReadStream($fd, $func);
62 | case EventInterface::EV_WRITE:
63 | return $this->addWriteStream($fd, $func);
64 | case EventInterface::EV_SIGNAL:
65 | return $this->addSignal($fd, $func);
66 | case EventInterface::EV_TIMER:
67 | $timer_id = ++$this->_timerIdIndex;
68 | $timer_obj = $this->addPeriodicTimer($fd, function() use ($func, $args) {
69 | call_user_func_array($func, $args);
70 | });
71 | $this->_timerIdMap[$timer_id] = $timer_obj;
72 | return $timer_id;
73 | case EventInterface::EV_TIMER_ONCE:
74 | $timer_id = ++$this->_timerIdIndex;
75 | $timer_obj = $this->addTimer($fd, function() use ($func, $args, $timer_id) {
76 | unset($this->_timerIdMap[$timer_id]);
77 | call_user_func_array($func, $args);
78 | });
79 | $this->_timerIdMap[$timer_id] = $timer_obj;
80 | return $timer_id;
81 | }
82 | return false;
83 | }
84 |
85 | /**
86 | * Remove event listener from event loop.
87 | *
88 | * @param mixed $fd
89 | * @param int $flag
90 | * @return bool
91 | */
92 | public function del($fd, $flag)
93 | {
94 | switch ($flag) {
95 | case EventInterface::EV_READ:
96 | return $this->removeReadStream($fd);
97 | case EventInterface::EV_WRITE:
98 | return $this->removeWriteStream($fd);
99 | case EventInterface::EV_SIGNAL:
100 | return $this->removeSignal($fd);
101 | case EventInterface::EV_TIMER:
102 | case EventInterface::EV_TIMER_ONCE;
103 | if (isset($this->_timerIdMap[$fd])){
104 | $timer_obj = $this->_timerIdMap[$fd];
105 | unset($this->_timerIdMap[$fd]);
106 | $this->cancelTimer($timer_obj);
107 | return true;
108 | }
109 | }
110 | return false;
111 | }
112 |
113 |
114 | /**
115 | * Main loop.
116 | *
117 | * @return void
118 | */
119 | public function loop()
120 | {
121 | $this->run();
122 | }
123 |
124 | /**
125 | * Construct.
126 | */
127 | public function __construct()
128 | {
129 | parent::__construct();
130 | $class = new \ReflectionClass('\React\EventLoop\LibEventLoop');
131 | $property = $class->getProperty('eventBase');
132 | $property->setAccessible(true);
133 | $this->_eventBase = $property->getValue($this);
134 | }
135 |
136 | /**
137 | * Add signal handler.
138 | *
139 | * @param $signal
140 | * @param $callback
141 | * @return bool
142 | */
143 | public function addSignal($signal, $callback)
144 | {
145 | $event = event_new();
146 | $this->_signalEvents[$signal] = $event;
147 | event_set($event, $signal, EV_SIGNAL | EV_PERSIST, $callback);
148 | event_base_set($event, $this->_eventBase);
149 | event_add($event);
150 | }
151 |
152 | /**
153 | * Remove signal handler.
154 | *
155 | * @param $signal
156 | */
157 | public function removeSignal($signal)
158 | {
159 | if (isset($this->_signalEvents[$signal])) {
160 | $event = $this->_signalEvents[$signal];
161 | event_del($event);
162 | unset($this->_signalEvents[$signal]);
163 | }
164 | }
165 |
166 | /**
167 | * Destroy loop.
168 | *
169 | * @return void
170 | */
171 | public function destroy()
172 | {
173 | foreach ($this->_signalEvents as $event) {
174 | event_del($event);
175 | }
176 | }
177 |
178 | /**
179 | * Get timer count.
180 | *
181 | * @return integer
182 | */
183 | public function getTimerCount()
184 | {
185 | return count($this->_timerIdMap);
186 | }
187 | }
188 |
--------------------------------------------------------------------------------
/Workerman/Events/Ev.php:
--------------------------------------------------------------------------------
1 |
10 | * @link http://www.workerman.net/
11 | * @license http://www.opensource.org/licenses/mit-license.php MIT License
12 | */
13 | namespace Workerman\Events;
14 |
15 | use Workerman\Worker;
16 |
17 | /**
18 | * ev eventloop
19 | */
20 | class Ev implements EventInterface
21 | {
22 | /**
23 | * All listeners for read/write event.
24 | *
25 | * @var array
26 | */
27 | protected $_allEvents = array();
28 |
29 | /**
30 | * Event listeners of signal.
31 | *
32 | * @var array
33 | */
34 | protected $_eventSignal = array();
35 |
36 | /**
37 | * All timer event listeners.
38 | * [func, args, event, flag, time_interval]
39 | *
40 | * @var array
41 | */
42 | protected $_eventTimer = array();
43 |
44 | /**
45 | * Timer id.
46 | *
47 | * @var int
48 | */
49 | protected static $_timerId = 1;
50 |
51 | /**
52 | * Add a timer.
53 | * {@inheritdoc}
54 | */
55 | public function add($fd, $flag, $func, $args = null)
56 | {
57 | $callback = function ($event, $socket) use ($fd, $func) {
58 | try {
59 | call_user_func($func, $fd);
60 | } catch (\Exception $e) {
61 | Worker::log($e);
62 | exit(250);
63 | } catch (\Error $e) {
64 | Worker::log($e);
65 | exit(250);
66 | }
67 | };
68 | switch ($flag) {
69 | case self::EV_SIGNAL:
70 | $event = new \EvSignal($fd, $callback);
71 | $this->_eventSignal[$fd] = $event;
72 | return true;
73 | case self::EV_TIMER:
74 | case self::EV_TIMER_ONCE:
75 | $repeat = $flag == self::EV_TIMER_ONCE ? 0 : $fd;
76 | $param = array($func, (array)$args, $flag, $fd, self::$_timerId);
77 | $event = new \EvTimer($fd, $repeat, array($this, 'timerCallback'), $param);
78 | $this->_eventTimer[self::$_timerId] = $event;
79 | return self::$_timerId++;
80 | default :
81 | $fd_key = (int)$fd;
82 | $real_flag = $flag === self::EV_READ ? \Ev::READ : \Ev::WRITE;
83 | $event = new \EvIo($fd, $real_flag, $callback);
84 | $this->_allEvents[$fd_key][$flag] = $event;
85 | return true;
86 | }
87 |
88 | }
89 |
90 | /**
91 | * Remove a timer.
92 | * {@inheritdoc}
93 | */
94 | public function del($fd, $flag)
95 | {
96 | switch ($flag) {
97 | case self::EV_READ:
98 | case self::EV_WRITE:
99 | $fd_key = (int)$fd;
100 | if (isset($this->_allEvents[$fd_key][$flag])) {
101 | $this->_allEvents[$fd_key][$flag]->stop();
102 | unset($this->_allEvents[$fd_key][$flag]);
103 | }
104 | if (empty($this->_allEvents[$fd_key])) {
105 | unset($this->_allEvents[$fd_key]);
106 | }
107 | break;
108 | case self::EV_SIGNAL:
109 | $fd_key = (int)$fd;
110 | if (isset($this->_eventSignal[$fd_key])) {
111 | $this->_eventSignal[$fd_key]->stop();
112 | unset($this->_eventSignal[$fd_key]);
113 | }
114 | break;
115 | case self::EV_TIMER:
116 | case self::EV_TIMER_ONCE:
117 | if (isset($this->_eventTimer[$fd])) {
118 | $this->_eventTimer[$fd]->stop();
119 | unset($this->_eventTimer[$fd]);
120 | }
121 | break;
122 | }
123 | return true;
124 | }
125 |
126 | /**
127 | * Timer callback.
128 | *
129 | * @param \EvWatcher $event
130 | */
131 | public function timerCallback($event)
132 | {
133 | $param = $event->data;
134 | $timer_id = $param[4];
135 | if ($param[2] === self::EV_TIMER_ONCE) {
136 | $this->_eventTimer[$timer_id]->stop();
137 | unset($this->_eventTimer[$timer_id]);
138 | }
139 | try {
140 | call_user_func_array($param[0], $param[1]);
141 | } catch (\Exception $e) {
142 | Worker::log($e);
143 | exit(250);
144 | } catch (\Error $e) {
145 | Worker::log($e);
146 | exit(250);
147 | }
148 | }
149 |
150 | /**
151 | * Remove all timers.
152 | *
153 | * @return void
154 | */
155 | public function clearAllTimer()
156 | {
157 | foreach ($this->_eventTimer as $event) {
158 | $event->stop();
159 | }
160 | $this->_eventTimer = array();
161 | }
162 |
163 | /**
164 | * Main loop.
165 | *
166 | * @see EventInterface::loop()
167 | */
168 | public function loop()
169 | {
170 | \Ev::run();
171 | }
172 |
173 | /**
174 | * Destroy loop.
175 | *
176 | * @return void
177 | */
178 | public function destroy()
179 | {
180 | foreach ($this->_allEvents as $event) {
181 | $event->stop();
182 | }
183 | }
184 |
185 | /**
186 | * Get timer count.
187 | *
188 | * @return integer
189 | */
190 | public function getTimerCount()
191 | {
192 | return count($this->_eventTimer);
193 | }
194 | }
195 |
--------------------------------------------------------------------------------
/Workerman/Events/React/StreamSelectLoop.php:
--------------------------------------------------------------------------------
1 |
10 | * @copyright walkor
11 | * @link http://www.workerman.net/
12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License
13 | */
14 | namespace Workerman\Events\React;
15 | use Workerman\Events\EventInterface;
16 |
17 | /**
18 | * Class StreamSelectLoop
19 | * @package Workerman\Events\React
20 | */
21 | class StreamSelectLoop extends \React\EventLoop\StreamSelectLoop
22 | {
23 | /**
24 | * @var array
25 | */
26 | protected $_timerIdMap = array();
27 |
28 | /**
29 | * @var int
30 | */
31 | protected $_timerIdIndex = 0;
32 |
33 | /**
34 | * Add event listener to event loop.
35 | *
36 | * @param $fd
37 | * @param $flag
38 | * @param $func
39 | * @param array $args
40 | * @return bool
41 | */
42 | public function add($fd, $flag, $func, $args = array())
43 | {
44 | $args = (array)$args;
45 | switch ($flag) {
46 | case EventInterface::EV_READ:
47 | return $this->addReadStream($fd, $func);
48 | case EventInterface::EV_WRITE:
49 | return $this->addWriteStream($fd, $func);
50 | case EventInterface::EV_SIGNAL:
51 | return $this->addSignal($fd, $func);
52 | case EventInterface::EV_TIMER:
53 | $timer_obj = $this->addPeriodicTimer($fd, function() use ($func, $args) {
54 | call_user_func_array($func, $args);
55 | });
56 | $this->_timerIdMap[++$this->_timerIdIndex] = $timer_obj;
57 | return $this->_timerIdIndex;
58 | case EventInterface::EV_TIMER_ONCE:
59 | $index = ++$this->_timerIdIndex;
60 | $timer_obj = $this->addTimer($fd, function() use ($func, $args, $index) {
61 | $this->del($index,EventInterface::EV_TIMER_ONCE);
62 | call_user_func_array($func, $args);
63 | });
64 | $this->_timerIdMap[$index] = $timer_obj;
65 | return $this->_timerIdIndex;
66 | }
67 | return false;
68 | }
69 |
70 | /**
71 | * Remove event listener from event loop.
72 | *
73 | * @param mixed $fd
74 | * @param int $flag
75 | * @return bool
76 | */
77 | public function del($fd, $flag)
78 | {
79 | switch ($flag) {
80 | case EventInterface::EV_READ:
81 | return $this->removeReadStream($fd);
82 | case EventInterface::EV_WRITE:
83 | return $this->removeWriteStream($fd);
84 | case EventInterface::EV_SIGNAL:
85 | return $this->removeSignal($fd);
86 | case EventInterface::EV_TIMER:
87 | case EventInterface::EV_TIMER_ONCE;
88 | if (isset($this->_timerIdMap[$fd])){
89 | $timer_obj = $this->_timerIdMap[$fd];
90 | unset($this->_timerIdMap[$fd]);
91 | $this->cancelTimer($timer_obj);
92 | return true;
93 | }
94 | }
95 | return false;
96 | }
97 |
98 |
99 | /**
100 | * Main loop.
101 | *
102 | * @return void
103 | */
104 | public function loop()
105 | {
106 | $this->run();
107 | }
108 |
109 | /**
110 | * Add signal handler.
111 | *
112 | * @param $signal
113 | * @param $callback
114 | * @return bool
115 | */
116 | public function addSignal($signal, $callback)
117 | {
118 | if(DIRECTORY_SEPARATOR === '/') {
119 | pcntl_signal($signal, $callback);
120 | }
121 | }
122 |
123 | /**
124 | * Remove signal handler.
125 | *
126 | * @param $signal
127 | */
128 | public function removeSignal($signal)
129 | {
130 | if(DIRECTORY_SEPARATOR === '/') {
131 | pcntl_signal($signal, SIG_IGN);
132 | }
133 | }
134 |
135 | /**
136 | * Emulate a stream_select() implementation that does not break when passed
137 | * empty stream arrays.
138 | *
139 | * @param array &$read An array of read streams to select upon.
140 | * @param array &$write An array of write streams to select upon.
141 | * @param integer|null $timeout Activity timeout in microseconds, or null to wait forever.
142 | *
143 | * @return integer|false The total number of streams that are ready for read/write.
144 | * Can return false if stream_select() is interrupted by a signal.
145 | */
146 | protected function streamSelect(array &$read, array &$write, $timeout)
147 | {
148 | if ($read || $write) {
149 | $except = null;
150 | // Calls signal handlers for pending signals
151 | if(DIRECTORY_SEPARATOR === '/') {
152 | pcntl_signal_dispatch();
153 | }
154 | // suppress warnings that occur, when stream_select is interrupted by a signal
155 | return @stream_select($read, $write, $except, $timeout === null ? null : 0, $timeout);
156 | }
157 |
158 | // Calls signal handlers for pending signals
159 | if(DIRECTORY_SEPARATOR === '/') {
160 | pcntl_signal_dispatch();
161 | }
162 | $timeout && usleep($timeout);
163 |
164 | return 0;
165 | }
166 |
167 | /**
168 | * Destroy loop.
169 | *
170 | * @return void
171 | */
172 | public function destroy()
173 | {
174 |
175 | }
176 |
177 | /**
178 | * Get timer count.
179 | *
180 | * @return integer
181 | */
182 | public function getTimerCount()
183 | {
184 | return count($this->_timerIdMap);
185 | }
186 | }
187 |
--------------------------------------------------------------------------------
/Cor/CorWorker.php:
--------------------------------------------------------------------------------
1 | 7");
37 | exit ();
38 | }
39 |
40 | if (!extension_loaded('pthreads')) {
41 | exit ("Please install pthreads extension. See http://doc3.workerman.net/install/install.html\n");
42 | }
43 | // 运行父方法
44 | parent::runAll();
45 | }
46 |
47 | public function run()
48 | {
49 | //设置工作类路径
50 | if (!$this->eventHandler) {
51 | $this->eventHandler = "Events";
52 | }
53 | //管道位置
54 | $this->cpu_unix_name = 'unix:///tmp/cor_' . posix_getpid() . ".sock";
55 | //创建任务线程
56 | $this->taskThread = new CorThread($this->cpu_unix_name, $this->eventHandler);
57 | $this->taskThread->start();
58 |
59 | //重新设置方法
60 | $this->_onWorkerStart = $this->onWorkerStart;
61 | $this->onWorkerStart = array($this, "onWorkerStart");
62 | $this->_onWorkerStop = $this->onWorkerStop;
63 | $this->onWorkerStop = array($this, "onWorkerStop");
64 |
65 | // 运行父方法
66 | parent::run();
67 | }
68 |
69 | /**
70 | * 进程start
71 | */
72 | public function onWorkerStart($worker)
73 | {
74 | Timer::add(0.3, function () use (&$worker) {
75 | $worker->taskAsyncConn = new AsyncTcpConnection($worker->taskThread->cpu_unix_name);
76 | $worker->taskAsyncConn->onMessage = function ($conn, $data) use ($worker) {
77 | //游标
78 | $i = 0;
79 | while (true) {
80 | $len = substr($data, $i, $i + 2);
81 | $body_len = unpack('Sbin_len', $len)['bin_len'];
82 | $body = substr($data, $i + 2, $body_len);
83 | $body = json_decode($body, true);
84 | if($body[0]!=1){
85 | if (array_key_exists($body[0], $worker->_job_funs)) {
86 | call_user_func($worker->_job_funs[$body[0]][1], $body[1]);
87 | }
88 | }
89 | $i = $i + 2 + $body_len;
90 | if ($i >= strlen($data)) {
91 | break;
92 | }
93 | }
94 | };
95 | $worker->taskAsyncConn->onConnect = function ($con) {
96 | var_dump("job thread is ready!");
97 | };
98 | $worker->taskAsyncConn->onClose = function ($con)use($worker) {
99 | $this->taskThread->is_exit = true;
100 | posix_kill(posix_getppid(), SIGUSR1);
101 | };
102 | $worker->taskAsyncConn->onError = function ($con, $code, $msg) {
103 | $this->taskThread->is_exit = true;
104 | posix_kill(posix_getppid(), SIGUSR1);
105 | };
106 | $worker->taskAsyncConn->connect();
107 |
108 | if ($worker->_onWorkerStart) {
109 | call_user_func($worker->_onWorkerStart, $worker);
110 | }
111 | }, array(), false);
112 |
113 | /**
114 | * 10秒钟检查一次是否有任务超时
115 | */
116 | Timer::add(10, function () use (&$worker) {
117 | $time = time();
118 | foreach ($worker->_job_funs as $k => $n) {
119 | if ($time >= $n[0]) {
120 | unset($worker->_job_funs[$k]);
121 | }
122 | }
123 | });
124 | }
125 |
126 | public function onWorkerStop($worker)
127 | {
128 | $this->taskThread->is_exit = true;
129 | /**
130 | * 退出的时候删除unix文件
131 | */
132 | if (strpos($worker->cpu_unix_name, "unix://") === 0) {
133 | $unix_file = str_replace("unix://", "", $worker->cpu_unix_name);
134 | if (file_exists($unix_file)) {
135 | unlink($unix_file);
136 | }
137 | }
138 | if ($worker->_onWorkerStop) {
139 | call_user_func($worker->_onWorkerStop, $worker);
140 | }
141 | }
142 |
143 | /**
144 | * @param $job 任务名称
145 | * @param $data 传递到任务中的数据
146 | * @param null $function 回调方法
147 | * @param int $timeout 超时时间,超过这个时间,主线程不会收到返回通知
148 | */
149 | public function ajax($job, $data, $function = null, $timeout = 10000)
150 | {
151 | /** @var 当key等于0的时候,代表不需要异步返回 $key */
152 | if ($function == null) {
153 | $key = 0;
154 | } else {
155 | if (PHP_INT_MAX === self::$_job_key_recorder) {
156 | self::$_job_key_recorder = 100;
157 | }
158 | self::$_job_key_recorder = self::$_job_key_recorder + 1;
159 | $key = self::$_job_key_recorder;
160 | //保存当前回调函数
161 | $this->_job_funs[$key] = array(time() + $timeout, $function);
162 | }
163 |
164 | /** 创建访问线程的消息体 */
165 | $arr = array();
166 | $arr[0] = $job;//任务名
167 | $arr[1] = $key;//任务唯一key
168 | $arr[2] = $data;//传递消息体
169 | $buffer = json_encode($arr);
170 | $body_len = strlen($buffer);
171 | $bin_head = pack('S*', $body_len);
172 | $this->taskAsyncConn->send($bin_head . $buffer);
173 | }
174 |
175 | /**
176 | * 重启整个进程
177 | */
178 | public function reload_cor(){
179 | $this->taskThread->is_exit = true;
180 | posix_kill(posix_getppid(), SIGUSR1);
181 | }
182 | }
--------------------------------------------------------------------------------
/Workerman/Events/Event.php:
--------------------------------------------------------------------------------
1 |
10 | * @copyright 有个鬼<42765633@qq.com>
11 | * @link http://www.workerman.net/
12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License
13 | */
14 | namespace Workerman\Events;
15 |
16 | use Workerman\Worker;
17 |
18 | /**
19 | * libevent eventloop
20 | */
21 | class Event implements EventInterface
22 | {
23 | /**
24 | * Event base.
25 | * @var object
26 | */
27 | protected $_eventBase = null;
28 |
29 | /**
30 | * All listeners for read/write event.
31 | * @var array
32 | */
33 | protected $_allEvents = array();
34 |
35 | /**
36 | * Event listeners of signal.
37 | * @var array
38 | */
39 | protected $_eventSignal = array();
40 |
41 | /**
42 | * All timer event listeners.
43 | * [func, args, event, flag, time_interval]
44 | * @var array
45 | */
46 | protected $_eventTimer = array();
47 |
48 | /**
49 | * Timer id.
50 | * @var int
51 | */
52 | protected static $_timerId = 1;
53 |
54 | /**
55 | * construct
56 | * @return void
57 | */
58 | public function __construct()
59 | {
60 | $this->_eventBase = new \EventBase();
61 | }
62 |
63 | /**
64 | * @see EventInterface::add()
65 | */
66 | public function add($fd, $flag, $func, $args=array())
67 | {
68 | switch ($flag) {
69 | case self::EV_SIGNAL:
70 |
71 | $fd_key = (int)$fd;
72 | $event = \Event::signal($this->_eventBase, $fd, $func);
73 | if (!$event||!$event->add()) {
74 | return false;
75 | }
76 | $this->_eventSignal[$fd_key] = $event;
77 | return true;
78 |
79 | case self::EV_TIMER:
80 | case self::EV_TIMER_ONCE:
81 |
82 | $param = array($func, (array)$args, $flag, $fd, self::$_timerId);
83 | $event = new \Event($this->_eventBase, -1, \Event::TIMEOUT|\Event::PERSIST, array($this, "timerCallback"), $param);
84 | if (!$event||!$event->addTimer($fd)) {
85 | return false;
86 | }
87 | $this->_eventTimer[self::$_timerId] = $event;
88 | return self::$_timerId++;
89 |
90 | default :
91 | $fd_key = (int)$fd;
92 | $real_flag = $flag === self::EV_READ ? \Event::READ | \Event::PERSIST : \Event::WRITE | \Event::PERSIST;
93 | $event = new \Event($this->_eventBase, $fd, $real_flag, $func, $fd);
94 | if (!$event||!$event->add()) {
95 | return false;
96 | }
97 | $this->_allEvents[$fd_key][$flag] = $event;
98 | return true;
99 | }
100 | }
101 |
102 | /**
103 | * @see Events\EventInterface::del()
104 | */
105 | public function del($fd, $flag)
106 | {
107 | switch ($flag) {
108 |
109 | case self::EV_READ:
110 | case self::EV_WRITE:
111 |
112 | $fd_key = (int)$fd;
113 | if (isset($this->_allEvents[$fd_key][$flag])) {
114 | $this->_allEvents[$fd_key][$flag]->del();
115 | unset($this->_allEvents[$fd_key][$flag]);
116 | }
117 | if (empty($this->_allEvents[$fd_key])) {
118 | unset($this->_allEvents[$fd_key]);
119 | }
120 | break;
121 |
122 | case self::EV_SIGNAL:
123 | $fd_key = (int)$fd;
124 | if (isset($this->_eventSignal[$fd_key])) {
125 | $this->_eventSignal[$fd_key]->del();
126 | unset($this->_eventSignal[$fd_key]);
127 | }
128 | break;
129 |
130 | case self::EV_TIMER:
131 | case self::EV_TIMER_ONCE:
132 | if (isset($this->_eventTimer[$fd])) {
133 | $this->_eventTimer[$fd]->del();
134 | unset($this->_eventTimer[$fd]);
135 | }
136 | break;
137 | }
138 | return true;
139 | }
140 |
141 | /**
142 | * Timer callback.
143 | * @param null $fd
144 | * @param int $what
145 | * @param int $timer_id
146 | */
147 | public function timerCallback($fd, $what, $param)
148 | {
149 | $timer_id = $param[4];
150 |
151 | if ($param[2] === self::EV_TIMER_ONCE) {
152 | $this->_eventTimer[$timer_id]->del();
153 | unset($this->_eventTimer[$timer_id]);
154 | }
155 |
156 | try {
157 | call_user_func_array($param[0], $param[1]);
158 | } catch (\Exception $e) {
159 | Worker::log($e);
160 | exit(250);
161 | } catch (\Error $e) {
162 | Worker::log($e);
163 | exit(250);
164 | }
165 | }
166 |
167 | /**
168 | * @see Events\EventInterface::clearAllTimer()
169 | * @return void
170 | */
171 | public function clearAllTimer()
172 | {
173 | foreach ($this->_eventTimer as $event) {
174 | $event->del();
175 | }
176 | $this->_eventTimer = array();
177 | }
178 |
179 |
180 | /**
181 | * @see EventInterface::loop()
182 | */
183 | public function loop()
184 | {
185 | $this->_eventBase->loop();
186 | }
187 |
188 | /**
189 | * Destroy loop.
190 | *
191 | * @return void
192 | */
193 | public function destroy()
194 | {
195 | foreach ($this->_eventSignal as $event) {
196 | $event->del();
197 | }
198 | }
199 |
200 | /**
201 | * Get timer count.
202 | *
203 | * @return integer
204 | */
205 | public function getTimerCount()
206 | {
207 | return count($this->_eventTimer);
208 | }
209 | }
210 |
--------------------------------------------------------------------------------
/Cor/Net/Events/Select.php:
--------------------------------------------------------------------------------
1 | cpu = $cup;
57 | }
58 |
59 |
60 | /**
61 | * 添加一个socket监听任务
62 | * @param $fd
63 | * @param $flag 什么类型的监听,是读监听还是写监听
64 | * @param $func
65 | * @param null $args
66 | * @param $key //写操作可能不一样,每次可能有好几个写操作挤压,因为是异步的,所以为没个操作设定唯一key,写操作可以不设定
67 | */
68 | public function add($fd, $flag, $func, $args = array())
69 | {
70 | array_unshift($args, $fd);
71 | switch ($flag) {
72 | case self::EV_READ:
73 | $fd_key = (int)$fd;
74 | $this->_allEvents[$fd_key][$flag] = array($func, $args);
75 | $this->_readFds[$fd_key] = $fd;
76 | break;
77 | case self::EV_WRITE:
78 | $fd_key = (int)$fd;
79 | if (empty(self::$write_key[$fd_key])){
80 | self::$write_key[$fd_key][0] = 1;//写操作唯一表示
81 | self::$write_key[$fd_key][1] = 0;//代表读取到了哪里
82 | }else{
83 | self::$write_key[$fd_key][0] = self::$write_key[$fd_key][0] + 1;
84 | }
85 | //写操作可能不一样,每次可能有好几个写操作挤压,因为是异步的
86 | array_unshift($args, self::$write_key[$fd_key][0]);
87 | $this->_allEvents[$fd_key][$flag][self::$write_key[$fd_key][0]] = array($func, $args);
88 | $this->_writeFds[$fd_key] = $fd;
89 | break;
90 | }
91 | return true;
92 | }
93 |
94 | /**
95 | * 从监听列表中移除一个监听任务
96 | * @param mixed $fd
97 | * @param int $flag
98 | * @return bool
99 | * $key 写入操作需要的唯一标识,指定哪个写入操作
100 | */
101 | public function del($fd, $flag , $key=null)
102 | {
103 | $fd_key = (int)$fd;
104 | switch ($flag) {
105 | case self::EV_READ:
106 | unset($this->_allEvents[$fd_key][$flag], $this->_readFds[$fd_key]);
107 | if (empty($this->_allEvents[$fd_key])) {
108 | unset($this->_allEvents[$fd_key]);
109 | }
110 | return true;
111 | case self::EV_WRITE:
112 | unset($this->_allEvents[$fd_key][$flag][$key]);
113 | if(count($this->_allEvents[$fd_key][$flag])==0){
114 | unset($this->_allEvents[$fd_key][$flag], $this->_writeFds[$fd_key]);
115 | }
116 | if (empty($this->_allEvents[$fd_key])) {
117 | unset($this->_allEvents[$fd_key]);
118 | }
119 | return true;
120 | }
121 | return false;
122 | }
123 |
124 | /**
125 | * Main loop.
126 | *
127 | * @return void
128 | */
129 | public function loop()
130 | {
131 | $e = null;
132 | while ($this->is_loop) {
133 | //如果没有任务,自身也会一秒循环一次
134 | $timeout = 0;
135 | if ($this->cpu->taskQueueIsEmpty()) {
136 | $this->_check_workerman_live();
137 | $timeout = 2;
138 | }
139 | $read = $this->_readFds;
140 | $write = $this->_writeFds;
141 | $except = null;
142 | // Waiting read/write/signal/timeout events.
143 | if (!@stream_select($read, $write, $except, $timeout)) {
144 | yield;
145 | continue;
146 | }
147 |
148 | if ($read) {
149 | foreach ($read as $fd) {
150 | $fd_key = (int)$fd;
151 | if (isset($this->_allEvents[$fd_key][self::EV_READ])) {
152 | $_e = reset($this->_allEvents[$fd_key][self::EV_READ]);
153 | call_user_func_array($this->_allEvents[$fd_key][self::EV_READ][0],
154 | $this->_allEvents[$fd_key][self::EV_READ][1]);
155 | }
156 | }
157 | }
158 |
159 | if ($write) {
160 | foreach ($write as $fd) {
161 | $fd_key = (int)$fd;
162 | //获取下一个写入key
163 | self::$write_key[$fd_key][1] = self::$write_key[$fd_key][1] + 1;
164 | if (isset($this->_allEvents[$fd_key][self::EV_WRITE][self::$write_key[$fd_key][1]])) {
165 |
166 | call_user_func_array($this->_allEvents[$fd_key][self::EV_WRITE][self::$write_key[$fd_key][1]][0],
167 | $this->_allEvents[$fd_key][self::EV_WRITE][self::$write_key[$fd_key][1]][1]);
168 | }
169 | }
170 | }
171 |
172 | /** 每次循环一次都将程序控制权交给调度器 */
173 | yield;
174 | }
175 | }
176 |
177 | /**
178 | * Destroy loop.
179 | *
180 | * @return mixed
181 | */
182 | public function destroy()
183 | {
184 | $this->is_loop = false;
185 | }
186 |
187 | public function _check_workerman_live(){
188 | $data = json_encode(array(1,""));
189 | $body_len = strlen($data);
190 | $bin_head = pack('S*', $body_len);
191 | $len = @fwrite($this->cpu->_mainSocket, $bin_head . $data);
192 | if ($len !== strlen($bin_head . $data)) {
193 | $this->cpu->thread->is_exit = false;
194 | }
195 |
196 | }
197 |
198 | }
--------------------------------------------------------------------------------
/Workerman/Events/Libevent.php:
--------------------------------------------------------------------------------
1 |
10 | * @copyright walkor
11 | * @link http://www.workerman.net/
12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License
13 | */
14 | namespace Workerman\Events;
15 |
16 | use Workerman\Worker;
17 |
18 | /**
19 | * libevent eventloop
20 | */
21 | class Libevent implements EventInterface
22 | {
23 | /**
24 | * Event base.
25 | *
26 | * @var resource
27 | */
28 | protected $_eventBase = null;
29 |
30 | /**
31 | * All listeners for read/write event.
32 | *
33 | * @var array
34 | */
35 | protected $_allEvents = array();
36 |
37 | /**
38 | * Event listeners of signal.
39 | *
40 | * @var array
41 | */
42 | protected $_eventSignal = array();
43 |
44 | /**
45 | * All timer event listeners.
46 | * [func, args, event, flag, time_interval]
47 | *
48 | * @var array
49 | */
50 | protected $_eventTimer = array();
51 |
52 | /**
53 | * construct
54 | */
55 | public function __construct()
56 | {
57 | $this->_eventBase = event_base_new();
58 | }
59 |
60 | /**
61 | * {@inheritdoc}
62 | */
63 | public function add($fd, $flag, $func, $args = array())
64 | {
65 | switch ($flag) {
66 | case self::EV_SIGNAL:
67 | $fd_key = (int)$fd;
68 | $real_flag = EV_SIGNAL | EV_PERSIST;
69 | $this->_eventSignal[$fd_key] = event_new();
70 | if (!event_set($this->_eventSignal[$fd_key], $fd, $real_flag, $func, null)) {
71 | return false;
72 | }
73 | if (!event_base_set($this->_eventSignal[$fd_key], $this->_eventBase)) {
74 | return false;
75 | }
76 | if (!event_add($this->_eventSignal[$fd_key])) {
77 | return false;
78 | }
79 | return true;
80 | case self::EV_TIMER:
81 | case self::EV_TIMER_ONCE:
82 | $event = event_new();
83 | $timer_id = (int)$event;
84 | if (!event_set($event, 0, EV_TIMEOUT, array($this, 'timerCallback'), $timer_id)) {
85 | return false;
86 | }
87 |
88 | if (!event_base_set($event, $this->_eventBase)) {
89 | return false;
90 | }
91 |
92 | $time_interval = $fd * 1000000;
93 | if (!event_add($event, $time_interval)) {
94 | return false;
95 | }
96 | $this->_eventTimer[$timer_id] = array($func, (array)$args, $event, $flag, $time_interval);
97 | return $timer_id;
98 |
99 | default :
100 | $fd_key = (int)$fd;
101 | $real_flag = $flag === self::EV_READ ? EV_READ | EV_PERSIST : EV_WRITE | EV_PERSIST;
102 |
103 | $event = event_new();
104 |
105 | if (!event_set($event, $fd, $real_flag, $func, null)) {
106 | return false;
107 | }
108 |
109 | if (!event_base_set($event, $this->_eventBase)) {
110 | return false;
111 | }
112 |
113 | if (!event_add($event)) {
114 | return false;
115 | }
116 |
117 | $this->_allEvents[$fd_key][$flag] = $event;
118 |
119 | return true;
120 | }
121 |
122 | }
123 |
124 | /**
125 | * {@inheritdoc}
126 | */
127 | public function del($fd, $flag)
128 | {
129 | switch ($flag) {
130 | case self::EV_READ:
131 | case self::EV_WRITE:
132 | $fd_key = (int)$fd;
133 | if (isset($this->_allEvents[$fd_key][$flag])) {
134 | event_del($this->_allEvents[$fd_key][$flag]);
135 | unset($this->_allEvents[$fd_key][$flag]);
136 | }
137 | if (empty($this->_allEvents[$fd_key])) {
138 | unset($this->_allEvents[$fd_key]);
139 | }
140 | break;
141 | case self::EV_SIGNAL:
142 | $fd_key = (int)$fd;
143 | if (isset($this->_eventSignal[$fd_key])) {
144 | event_del($this->_eventSignal[$fd_key]);
145 | unset($this->_eventSignal[$fd_key]);
146 | }
147 | break;
148 | case self::EV_TIMER:
149 | case self::EV_TIMER_ONCE:
150 | // 这里 fd 为timerid
151 | if (isset($this->_eventTimer[$fd])) {
152 | event_del($this->_eventTimer[$fd][2]);
153 | unset($this->_eventTimer[$fd]);
154 | }
155 | break;
156 | }
157 | return true;
158 | }
159 |
160 | /**
161 | * Timer callback.
162 | *
163 | * @param mixed $_null1
164 | * @param int $_null2
165 | * @param mixed $timer_id
166 | */
167 | protected function timerCallback($_null1, $_null2, $timer_id)
168 | {
169 | if ($this->_eventTimer[$timer_id][3] === self::EV_TIMER) {
170 | event_add($this->_eventTimer[$timer_id][2], $this->_eventTimer[$timer_id][4]);
171 | }
172 | try {
173 | call_user_func_array($this->_eventTimer[$timer_id][0], $this->_eventTimer[$timer_id][1]);
174 | } catch (\Exception $e) {
175 | Worker::log($e);
176 | exit(250);
177 | } catch (\Error $e) {
178 | Worker::log($e);
179 | exit(250);
180 | }
181 | if (isset($this->_eventTimer[$timer_id]) && $this->_eventTimer[$timer_id][3] === self::EV_TIMER_ONCE) {
182 | $this->del($timer_id, self::EV_TIMER_ONCE);
183 | }
184 | }
185 |
186 | /**
187 | * {@inheritdoc}
188 | */
189 | public function clearAllTimer()
190 | {
191 | foreach ($this->_eventTimer as $task_data) {
192 | event_del($task_data[2]);
193 | }
194 | $this->_eventTimer = array();
195 | }
196 |
197 | /**
198 | * {@inheritdoc}
199 | */
200 | public function loop()
201 | {
202 | event_base_loop($this->_eventBase);
203 | }
204 |
205 | /**
206 | * Destroy loop.
207 | *
208 | * @return void
209 | */
210 | public function destroy()
211 | {
212 | foreach ($this->_eventSignal as $event) {
213 | event_del($event);
214 | }
215 | }
216 |
217 | /**
218 | * Get timer count.
219 | *
220 | * @return integer
221 | */
222 | public function getTimerCount()
223 | {
224 | return count($this->_eventTimer);
225 | }
226 | }
227 |
228 |
--------------------------------------------------------------------------------
/Workerman/Events/Select.php:
--------------------------------------------------------------------------------
1 |
10 | * @copyright walkor
11 | * @link http://www.workerman.net/
12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License
13 | */
14 | namespace Workerman\Events;
15 |
16 | /**
17 | * select eventloop
18 | */
19 | class Select implements EventInterface
20 | {
21 | /**
22 | * All listeners for read/write event.
23 | *
24 | * @var array
25 | */
26 | public $_allEvents = array();
27 |
28 | /**
29 | * Event listeners of signal.
30 | *
31 | * @var array
32 | */
33 | public $_signalEvents = array();
34 |
35 | /**
36 | * Fds waiting for read event.
37 | *
38 | * @var array
39 | */
40 | protected $_readFds = array();
41 |
42 | /**
43 | * Fds waiting for write event.
44 | *
45 | * @var array
46 | */
47 | protected $_writeFds = array();
48 |
49 | /**
50 | * Fds waiting for except event.
51 | *
52 | * @var array
53 | */
54 | protected $_exceptFds = array();
55 |
56 | /**
57 | * Timer scheduler.
58 | * {['data':timer_id, 'priority':run_timestamp], ..}
59 | *
60 | * @var \SplPriorityQueue
61 | */
62 | protected $_scheduler = null;
63 |
64 | /**
65 | * All timer event listeners.
66 | * [[func, args, flag, timer_interval], ..]
67 | *
68 | * @var array
69 | */
70 | protected $_eventTimer = array();
71 |
72 | /**
73 | * Timer id.
74 | *
75 | * @var int
76 | */
77 | protected $_timerId = 1;
78 |
79 | /**
80 | * Select timeout.
81 | *
82 | * @var int
83 | */
84 | protected $_selectTimeout = 100000000;
85 |
86 | /**
87 | * Paired socket channels
88 | *
89 | * @var array
90 | */
91 | protected $channel = array();
92 |
93 | /**
94 | * Construct.
95 | */
96 | public function __construct()
97 | {
98 | // Create a pipeline and put into the collection of the read to read the descriptor to avoid empty polling.
99 | $this->channel = stream_socket_pair(DIRECTORY_SEPARATOR === '/' ? STREAM_PF_UNIX : STREAM_PF_INET,
100 | STREAM_SOCK_STREAM, STREAM_IPPROTO_IP);
101 | if($this->channel) {
102 | stream_set_blocking($this->channel[0], 0);
103 | $this->_readFds[0] = $this->channel[0];
104 | }
105 | // Init SplPriorityQueue.
106 | $this->_scheduler = new \SplPriorityQueue();
107 | $this->_scheduler->setExtractFlags(\SplPriorityQueue::EXTR_BOTH);
108 | }
109 |
110 | /**
111 | * {@inheritdoc}
112 | */
113 | public function add($fd, $flag, $func, $args = array())
114 | {
115 | switch ($flag) {
116 | case self::EV_READ:
117 | $fd_key = (int)$fd;
118 | $this->_allEvents[$fd_key][$flag] = array($func, $fd);
119 | $this->_readFds[$fd_key] = $fd;
120 | break;
121 | case self::EV_WRITE:
122 | $fd_key = (int)$fd;
123 | $this->_allEvents[$fd_key][$flag] = array($func, $fd);
124 | $this->_writeFds[$fd_key] = $fd;
125 | break;
126 | case self::EV_EXCEPT:
127 | $fd_key = (int)$fd;
128 | $this->_allEvents[$fd_key][$flag] = array($func, $fd);
129 | $this->_exceptFds[$fd_key] = $fd;
130 | break;
131 | case self::EV_SIGNAL:
132 | // Windows not support signal.
133 | if(DIRECTORY_SEPARATOR !== '/') {
134 | return false;
135 | }
136 | $fd_key = (int)$fd;
137 | $this->_signalEvents[$fd_key][$flag] = array($func, $fd);
138 | pcntl_signal($fd, array($this, 'signalHandler'));
139 | break;
140 | case self::EV_TIMER:
141 | case self::EV_TIMER_ONCE:
142 | $timer_id = $this->_timerId++;
143 | $run_time = microtime(true) + $fd;
144 | $this->_scheduler->insert($timer_id, -$run_time);
145 | $this->_eventTimer[$timer_id] = array($func, (array)$args, $flag, $fd);
146 | $select_timeout = ($run_time - microtime(true)) * 1000000;
147 | if( $this->_selectTimeout > $select_timeout ){
148 | $this->_selectTimeout = $select_timeout;
149 | }
150 | return $timer_id;
151 | }
152 |
153 | return true;
154 | }
155 |
156 | /**
157 | * Signal handler.
158 | *
159 | * @param int $signal
160 | */
161 | public function signalHandler($signal)
162 | {
163 | call_user_func_array($this->_signalEvents[$signal][self::EV_SIGNAL][0], array($signal));
164 | }
165 |
166 | /**
167 | * {@inheritdoc}
168 | */
169 | public function del($fd, $flag)
170 | {
171 | $fd_key = (int)$fd;
172 | switch ($flag) {
173 | case self::EV_READ:
174 | unset($this->_allEvents[$fd_key][$flag], $this->_readFds[$fd_key]);
175 | if (empty($this->_allEvents[$fd_key])) {
176 | unset($this->_allEvents[$fd_key]);
177 | }
178 | return true;
179 | case self::EV_WRITE:
180 | unset($this->_allEvents[$fd_key][$flag], $this->_writeFds[$fd_key]);
181 | if (empty($this->_allEvents[$fd_key])) {
182 | unset($this->_allEvents[$fd_key]);
183 | }
184 | return true;
185 | case self::EV_EXCEPT:
186 | unset($this->_allEvents[$fd_key][$flag], $this->_exceptFds[$fd_key]);
187 | if(empty($this->_allEvents[$fd_key]))
188 | {
189 | unset($this->_allEvents[$fd_key]);
190 | }
191 | return true;
192 | case self::EV_SIGNAL:
193 | if(DIRECTORY_SEPARATOR !== '/') {
194 | return false;
195 | }
196 | unset($this->_signalEvents[$fd_key]);
197 | pcntl_signal($fd, SIG_IGN);
198 | break;
199 | case self::EV_TIMER:
200 | case self::EV_TIMER_ONCE;
201 | unset($this->_eventTimer[$fd_key]);
202 | return true;
203 | }
204 | return false;
205 | }
206 |
207 | /**
208 | * Tick for timer.
209 | *
210 | * @return void
211 | */
212 | protected function tick()
213 | {
214 | while (!$this->_scheduler->isEmpty()) {
215 | $scheduler_data = $this->_scheduler->top();
216 | $timer_id = $scheduler_data['data'];
217 | $next_run_time = -$scheduler_data['priority'];
218 | $time_now = microtime(true);
219 | $this->_selectTimeout = ($next_run_time - $time_now) * 1000000;
220 | if ($this->_selectTimeout <= 0) {
221 | $this->_scheduler->extract();
222 |
223 | if (!isset($this->_eventTimer[$timer_id])) {
224 | continue;
225 | }
226 |
227 | // [func, args, flag, timer_interval]
228 | $task_data = $this->_eventTimer[$timer_id];
229 | if ($task_data[2] === self::EV_TIMER) {
230 | $next_run_time = $time_now + $task_data[3];
231 | $this->_scheduler->insert($timer_id, -$next_run_time);
232 | }
233 | call_user_func_array($task_data[0], $task_data[1]);
234 | if (isset($this->_eventTimer[$timer_id]) && $task_data[2] === self::EV_TIMER_ONCE) {
235 | $this->del($timer_id, self::EV_TIMER_ONCE);
236 | }
237 | continue;
238 | }
239 | return;
240 | }
241 | $this->_selectTimeout = 100000000;
242 | }
243 |
244 | /**
245 | * {@inheritdoc}
246 | */
247 | public function clearAllTimer()
248 | {
249 | $this->_scheduler = new \SplPriorityQueue();
250 | $this->_scheduler->setExtractFlags(\SplPriorityQueue::EXTR_BOTH);
251 | $this->_eventTimer = array();
252 | }
253 |
254 | /**
255 | * {@inheritdoc}
256 | */
257 | public function loop()
258 | {
259 | $e = null;
260 | while (1) {
261 | if(DIRECTORY_SEPARATOR === '/') {
262 | // Calls signal handlers for pending signals
263 | pcntl_signal_dispatch();
264 | }
265 |
266 | $read = $this->_readFds;
267 | $write = $this->_writeFds;
268 | $except = $this->_writeFds;
269 |
270 | // Waiting read/write/signal/timeout events.
271 | $ret = @stream_select($read, $write, $except, 0, $this->_selectTimeout);
272 | if (!$this->_scheduler->isEmpty()) {
273 | $this->tick();
274 | }
275 |
276 | if (!$ret) {
277 | continue;
278 | }
279 |
280 | if ($read) {
281 | foreach ($read as $fd) {
282 | $fd_key = (int)$fd;
283 | if (isset($this->_allEvents[$fd_key][self::EV_READ])) {
284 | call_user_func_array($this->_allEvents[$fd_key][self::EV_READ][0],
285 | array($this->_allEvents[$fd_key][self::EV_READ][1]));
286 | }
287 | }
288 | }
289 |
290 | if ($write) {
291 | foreach ($write as $fd) {
292 | $fd_key = (int)$fd;
293 | if (isset($this->_allEvents[$fd_key][self::EV_WRITE])) {
294 | call_user_func_array($this->_allEvents[$fd_key][self::EV_WRITE][0],
295 | array($this->_allEvents[$fd_key][self::EV_WRITE][1]));
296 | }
297 | }
298 | }
299 |
300 | if($except) {
301 | foreach($except as $fd) {
302 | $fd_key = (int) $fd;
303 | if(isset($this->_allEvents[$fd_key][self::EV_EXCEPT])) {
304 | call_user_func_array($this->_allEvents[$fd_key][self::EV_EXCEPT][0],
305 | array($this->_allEvents[$fd_key][self::EV_EXCEPT][1]));
306 | }
307 | }
308 | }
309 | }
310 | }
311 |
312 | /**
313 | * Destroy loop.
314 | *
315 | * @return void
316 | */
317 | public function destroy()
318 | {
319 |
320 | }
321 |
322 | /**
323 | * Get timer count.
324 | *
325 | * @return integer
326 | */
327 | public function getTimerCount()
328 | {
329 | return count($this->_eventTimer);
330 | }
331 | }
332 |
--------------------------------------------------------------------------------
/Workerman/WebServer.php:
--------------------------------------------------------------------------------
1 |
10 | * @copyright walkor
11 | * @link http://www.workerman.net/
12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License
13 | */
14 | namespace Workerman;
15 |
16 | use Workerman\Protocols\Http;
17 | use Workerman\Protocols\HttpCache;
18 |
19 | /**
20 | * WebServer.
21 | */
22 | class WebServer extends Worker
23 | {
24 | /**
25 | * Virtual host to path mapping.
26 | *
27 | * @var array ['Workerman.net'=>'/home', 'www.Workerman.net'=>'home/www']
28 | */
29 | protected $serverRoot = array();
30 |
31 | /**
32 | * Mime mapping.
33 | *
34 | * @var array
35 | */
36 | protected static $mimeTypeMap = array();
37 |
38 |
39 | /**
40 | * Used to save user OnWorkerStart callback settings.
41 | *
42 | * @var callback
43 | */
44 | protected $_onWorkerStart = null;
45 |
46 | /**
47 | * Add virtual host.
48 | *
49 | * @param string $domain
50 | * @param string $root_path
51 | * @return void
52 | */
53 | public function addRoot($domain, $root_path)
54 | {
55 | $this->serverRoot[$domain] = $root_path;
56 | }
57 |
58 | /**
59 | * Construct.
60 | *
61 | * @param string $socket_name
62 | * @param array $context_option
63 | */
64 | public function __construct($socket_name, $context_option = array())
65 | {
66 | list(, $address) = explode(':', $socket_name, 2);
67 | parent::__construct('http:' . $address, $context_option);
68 | $this->name = 'WebServer';
69 | }
70 |
71 | /**
72 | * Run webserver instance.
73 | *
74 | * @see Workerman.Worker::run()
75 | */
76 | public function run()
77 | {
78 | $this->_onWorkerStart = $this->onWorkerStart;
79 | $this->onWorkerStart = array($this, 'onWorkerStart');
80 | $this->onMessage = array($this, 'onMessage');
81 | parent::run();
82 | }
83 |
84 | /**
85 | * Emit when process start.
86 | *
87 | * @throws \Exception
88 | */
89 | public function onWorkerStart()
90 | {
91 | if (empty($this->serverRoot)) {
92 | echo new \Exception('server root not set, please use WebServer::addRoot($domain, $root_path) to set server root path');
93 | exit(250);
94 | }
95 |
96 | // Init mimeMap.
97 | $this->initMimeTypeMap();
98 |
99 | // Try to emit onWorkerStart callback.
100 | if ($this->_onWorkerStart) {
101 | try {
102 | call_user_func($this->_onWorkerStart, $this);
103 | } catch (\Exception $e) {
104 | self::log($e);
105 | exit(250);
106 | } catch (\Error $e) {
107 | self::log($e);
108 | exit(250);
109 | }
110 | }
111 | }
112 |
113 | /**
114 | * Init mime map.
115 | *
116 | * @return void
117 | */
118 | public function initMimeTypeMap()
119 | {
120 | $mime_file = Http::getMimeTypesFile();
121 | if (!is_file($mime_file)) {
122 | $this->log("$mime_file mime.type file not fond");
123 | return;
124 | }
125 | $items = file($mime_file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
126 | if (!is_array($items)) {
127 | $this->log("get $mime_file mime.type content fail");
128 | return;
129 | }
130 | foreach ($items as $content) {
131 | if (preg_match("/\s*(\S+)\s+(\S.+)/", $content, $match)) {
132 | $mime_type = $match[1];
133 | $workerman_file_extension_var = $match[2];
134 | $workerman_file_extension_array = explode(' ', substr($workerman_file_extension_var, 0, -1));
135 | foreach ($workerman_file_extension_array as $workerman_file_extension) {
136 | self::$mimeTypeMap[$workerman_file_extension] = $mime_type;
137 | }
138 | }
139 | }
140 | }
141 |
142 | /**
143 | * Emit when http message coming.
144 | *
145 | * @param Connection\TcpConnection $connection
146 | * @return void
147 | */
148 | public function onMessage($connection)
149 | {
150 | // REQUEST_URI.
151 | $workerman_url_info = parse_url($_SERVER['REQUEST_URI']);
152 | if (!$workerman_url_info) {
153 | Http::header('HTTP/1.1 400 Bad Request');
154 | $connection->close('400 Bad Request
');
155 | return;
156 | }
157 |
158 | $workerman_path = isset($workerman_url_info['path']) ? $workerman_url_info['path'] : '/';
159 |
160 | $workerman_path_info = pathinfo($workerman_path);
161 | $workerman_file_extension = isset($workerman_path_info['extension']) ? $workerman_path_info['extension'] : '';
162 | if ($workerman_file_extension === '') {
163 | $workerman_path = ($len = strlen($workerman_path)) && $workerman_path[$len - 1] === '/' ? $workerman_path . 'index.php' : $workerman_path . '/index.php';
164 | $workerman_file_extension = 'php';
165 | }
166 |
167 | $workerman_root_dir = isset($this->serverRoot[$_SERVER['SERVER_NAME']]) ? $this->serverRoot[$_SERVER['SERVER_NAME']] : current($this->serverRoot);
168 |
169 | $workerman_file = "$workerman_root_dir/$workerman_path";
170 |
171 | if ($workerman_file_extension === 'php' && !is_file($workerman_file)) {
172 | $workerman_file = "$workerman_root_dir/index.php";
173 | if (!is_file($workerman_file)) {
174 | $workerman_file = "$workerman_root_dir/index.html";
175 | $workerman_file_extension = 'html';
176 | }
177 | }
178 |
179 | // File exsits.
180 | if (is_file($workerman_file)) {
181 | // Security check.
182 | if ((!($workerman_request_realpath = realpath($workerman_file)) || !($workerman_root_dir_realpath = realpath($workerman_root_dir))) || 0 !== strpos($workerman_request_realpath,
183 | $workerman_root_dir_realpath)
184 | ) {
185 | Http::header('HTTP/1.1 400 Bad Request');
186 | $connection->close('400 Bad Request
');
187 | return;
188 | }
189 |
190 | $workerman_file = realpath($workerman_file);
191 |
192 | // Request php file.
193 | if ($workerman_file_extension === 'php') {
194 | $workerman_cwd = getcwd();
195 | chdir($workerman_root_dir);
196 | ini_set('display_errors', 'off');
197 | ob_start();
198 | // Try to include php file.
199 | try {
200 | // $_SERVER.
201 | $_SERVER['REMOTE_ADDR'] = $connection->getRemoteIp();
202 | $_SERVER['REMOTE_PORT'] = $connection->getRemotePort();
203 | include $workerman_file;
204 | } catch (\Exception $e) {
205 | // Jump_exit?
206 | if ($e->getMessage() != 'jump_exit') {
207 | echo $e;
208 | }
209 | }
210 | $content = ob_get_clean();
211 | ini_set('display_errors', 'on');
212 | if (strtolower($_SERVER['HTTP_CONNECTION']) === "keep-alive") {
213 | $connection->send($content);
214 | } else {
215 | $connection->close($content);
216 | }
217 | chdir($workerman_cwd);
218 | return;
219 | }
220 |
221 | // Send file to client.
222 | return self::sendFile($connection, $workerman_file);
223 | } else {
224 | // 404
225 | Http::header("HTTP/1.1 404 Not Found");
226 | $connection->close('404 File not found404 Not Found
');
227 | return;
228 | }
229 | }
230 |
231 | public static function sendFile($connection, $file_path)
232 | {
233 | // Check 304.
234 | $info = stat($file_path);
235 | $modified_time = $info ? date('D, d M Y H:i:s', $info['mtime']) . ' ' . date_default_timezone_get() : '';
236 | if (!empty($_SERVER['HTTP_IF_MODIFIED_SINCE']) && $info) {
237 | // Http 304.
238 | if ($modified_time === $_SERVER['HTTP_IF_MODIFIED_SINCE']) {
239 | // 304
240 | Http::header('HTTP/1.1 304 Not Modified');
241 | // Send nothing but http headers..
242 | $connection->close('');
243 | return;
244 | }
245 | }
246 |
247 | // Http header.
248 | if ($modified_time) {
249 | $modified_time = "Last-Modified: $modified_time\r\n";
250 | }
251 | $file_size = filesize($file_path);
252 | $file_info = pathinfo($file_path);
253 | $extension = isset($file_info['extension']) ? $file_info['extension'] : '';
254 | $file_name = isset($file_info['filename']) ? $file_info['filename'] : '';
255 | $header = "HTTP/1.1 200 OK\r\n";
256 | if (isset(self::$mimeTypeMap[$extension])) {
257 | $header .= "Content-Type: " . self::$mimeTypeMap[$extension] . "\r\n";
258 | } else {
259 | $header .= "Content-Type: application/octet-stream\r\n";
260 | $header .= "Content-Disposition: attachment; filename=\"$file_name\"\r\n";
261 | }
262 | $header .= "Connection: keep-alive\r\n";
263 | $header .= $modified_time;
264 | $header .= "Content-Length: $file_size\r\n\r\n";
265 | $trunk_limit_size = 1024*1024;
266 | if ($file_size < $trunk_limit_size) {
267 | return $connection->send($header.file_get_contents($file_path), true);
268 | }
269 | $connection->send($header, true);
270 |
271 | // Read file content from disk piece by piece and send to client.
272 | $connection->fileHandler = fopen($file_path, 'r');
273 | $do_write = function()use($connection)
274 | {
275 | // Send buffer not full.
276 | while(empty($connection->bufferFull))
277 | {
278 | // Read from disk.
279 | $buffer = fread($connection->fileHandler, 8192);
280 | // Read eof.
281 | if($buffer === '' || $buffer === false)
282 | {
283 | return;
284 | }
285 | $connection->send($buffer, true);
286 | }
287 | };
288 | // Send buffer full.
289 | $connection->onBufferFull = function($connection)
290 | {
291 | $connection->bufferFull = true;
292 | };
293 | // Send buffer drain.
294 | $connection->onBufferDrain = function($connection)use($do_write)
295 | {
296 | $connection->bufferFull = false;
297 | $do_write();
298 | };
299 | $do_write();
300 | }
301 | }
302 |
--------------------------------------------------------------------------------
/Workerman/Connection/AsyncTcpConnection.php:
--------------------------------------------------------------------------------
1 |
10 | * @copyright walkor
11 | * @link http://www.workerman.net/
12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License
13 | */
14 |
15 | namespace Workerman\Connection;
16 |
17 | use Workerman\Events\EventInterface;
18 | use Workerman\Lib\Timer;
19 | use Workerman\Worker;
20 | use Exception;
21 |
22 | /**
23 | * AsyncTcpConnection.
24 | */
25 | class AsyncTcpConnection extends TcpConnection
26 | {
27 | /**
28 | * Emitted when socket connection is successfully established.
29 | *
30 | * @var callback
31 | */
32 | public $onConnect = null;
33 |
34 | /**
35 | * Transport layer protocol.
36 | *
37 | * @var string
38 | */
39 | public $transport = 'tcp';
40 |
41 | /**
42 | * Status.
43 | *
44 | * @var int
45 | */
46 | protected $_status = self::STATUS_INITIAL;
47 |
48 | /**
49 | * Remote host.
50 | *
51 | * @var string
52 | */
53 | protected $_remoteHost = '';
54 |
55 | /**
56 | * Remote port.
57 | *
58 | * @var int
59 | */
60 | protected $_remotePort = 80;
61 |
62 | /**
63 | * Connect start time.
64 | *
65 | * @var string
66 | */
67 | protected $_connectStartTime = 0;
68 |
69 | /**
70 | * Remote URI.
71 | *
72 | * @var string
73 | */
74 | protected $_remoteURI = '';
75 |
76 | /**
77 | * Context option.
78 | *
79 | * @var resource
80 | */
81 | protected $_contextOption = null;
82 |
83 | /**
84 | * Reconnect timer.
85 | *
86 | * @var int
87 | */
88 | protected $_reconnectTimer = null;
89 |
90 | /**
91 | * PHP built-in protocols.
92 | *
93 | * @var array
94 | */
95 | protected static $_builtinTransports = array(
96 | 'tcp' => 'tcp',
97 | 'udp' => 'udp',
98 | 'unix' => 'unix',
99 | 'ssl' => 'ssl',
100 | 'sslv2' => 'sslv2',
101 | 'sslv3' => 'sslv3',
102 | 'tls' => 'tls'
103 | );
104 |
105 | /**
106 | * Construct.
107 | *
108 | * @param string $remote_address
109 | * @param array $context_option
110 | * @throws Exception
111 | */
112 | public function __construct($remote_address, $context_option = null)
113 | {
114 | $address_info = parse_url($remote_address);
115 | if (!$address_info) {
116 | list($scheme, $this->_remoteAddress) = explode(':', $remote_address, 2);
117 | if (!$this->_remoteAddress) {
118 | echo new \Exception('bad remote_address');
119 | }
120 | } else {
121 | if (!isset($address_info['port'])) {
122 | $address_info['port'] = 80;
123 | }
124 | if (!isset($address_info['path'])) {
125 | $address_info['path'] = '/';
126 | }
127 | if (!isset($address_info['query'])) {
128 | $address_info['query'] = '';
129 | } else {
130 | $address_info['query'] = '?' . $address_info['query'];
131 | }
132 | $this->_remoteAddress = "{$address_info['host']}:{$address_info['port']}";
133 | $this->_remoteHost = $address_info['host'];
134 | $this->_remotePort = $address_info['port'];
135 | $this->_remoteURI = "{$address_info['path']}{$address_info['query']}";
136 | $scheme = isset($address_info['scheme']) ? $address_info['scheme'] : 'tcp';
137 | }
138 |
139 | $this->id = $this->_id = self::$_idRecorder++;
140 | if (PHP_INT_MAX === self::$_idRecorder) {
141 | self::$_idRecorder = 0;
142 | }
143 | // Check application layer protocol class.
144 | if (!isset(self::$_builtinTransports[$scheme])) {
145 | $scheme = ucfirst($scheme);
146 | $this->protocol = '\\Protocols\\' . $scheme;
147 | if (!class_exists($this->protocol)) {
148 | $this->protocol = "\\Workerman\\Protocols\\$scheme";
149 | if (!class_exists($this->protocol)) {
150 | throw new Exception("class \\Protocols\\$scheme not exist");
151 | }
152 | }
153 | } else {
154 | $this->transport = self::$_builtinTransports[$scheme];
155 | }
156 |
157 | // For statistics.
158 | self::$statistics['connection_count']++;
159 | $this->maxSendBufferSize = self::$defaultMaxSendBufferSize;
160 | $this->_contextOption = $context_option;
161 | static::$connections[$this->id] = $this;
162 | }
163 |
164 | /**
165 | * Do connect.
166 | *
167 | * @return void
168 | */
169 | public function connect()
170 | {
171 | if ($this->_status !== self::STATUS_INITIAL && $this->_status !== self::STATUS_CLOSING &&
172 | $this->_status !== self::STATUS_CLOSED
173 | ) {
174 | return;
175 | }
176 | $this->_status = self::STATUS_CONNECTING;
177 | $this->_connectStartTime = microtime(true);
178 | // Open socket connection asynchronously.
179 | if ($this->_contextOption) {
180 | $context = stream_context_create($this->_contextOption);
181 | $this->_socket = stream_socket_client("{$this->transport}://{$this->_remoteHost}:{$this->_remotePort}", $errno, $errstr, 0,
182 | STREAM_CLIENT_ASYNC_CONNECT, $context);
183 | } else {
184 | if ($this->transport == "unix") {
185 | //zyf
186 | $this->_socket = stream_socket_client($this->transport . ":" . $this->_remoteAddress, $errno, $errstr, 0,
187 | STREAM_CLIENT_ASYNC_CONNECT);
188 | } else {
189 | $this->_socket = stream_socket_client("{$this->transport}://{$this->_remoteHost}:{$this->_remotePort}", $errno, $errstr, 0,
190 | STREAM_CLIENT_ASYNC_CONNECT);
191 | }
192 | }
193 | // If failed attempt to emit onError callback.
194 | if (!$this->_socket) {
195 | $this->emitError(WORKERMAN_CONNECT_FAIL, $errstr);
196 | if ($this->_status === self::STATUS_CLOSING) {
197 | $this->destroy();
198 | }
199 | if ($this->_status === self::STATUS_CLOSED) {
200 | $this->onConnect = null;
201 | }
202 | return;
203 | }
204 | // Add socket to global event loop waiting connection is successfully established or faild.
205 | Worker::$globalEvent->add($this->_socket, EventInterface::EV_WRITE, array($this, 'checkConnection'));
206 | // For windows.
207 | if (DIRECTORY_SEPARATOR === '\\') {
208 | Worker::$globalEvent->add($this->_socket, EventInterface::EV_EXCEPT, array($this, 'checkConnection'));
209 | }
210 | }
211 |
212 | /**
213 | * Reconnect.
214 | *
215 | * @param int $after
216 | * @return void
217 | */
218 | public function reConnect($after = 0)
219 | {
220 | $this->_status = self::STATUS_INITIAL;
221 | if ($this->_reconnectTimer) {
222 | Timer::del($this->_reconnectTimer);
223 | }
224 | if ($after > 0) {
225 | $this->_reconnectTimer = Timer::add($after, array($this, 'connect'), null, false);
226 | return;
227 | }
228 | $this->connect();
229 | }
230 |
231 | /**
232 | * Get remote address.
233 | *
234 | * @return string
235 | */
236 | public function getRemoteHost()
237 | {
238 | return $this->_remoteHost;
239 | }
240 |
241 | /**
242 | * Get remote URI.
243 | *
244 | * @return string
245 | */
246 | public function getRemoteURI()
247 | {
248 | return $this->_remoteURI;
249 | }
250 |
251 | /**
252 | * Try to emit onError callback.
253 | *
254 | * @param int $code
255 | * @param string $msg
256 | * @return void
257 | */
258 | protected function emitError($code, $msg)
259 | {
260 | $this->_status = self::STATUS_CLOSING;
261 | if ($this->onError) {
262 | try {
263 | call_user_func($this->onError, $this, $code, $msg);
264 | } catch (\Exception $e) {
265 | Worker::log($e);
266 | exit(250);
267 | } catch (\Error $e) {
268 | Worker::log($e);
269 | exit(250);
270 | }
271 | }
272 | }
273 |
274 | /**
275 | * Check connection is successfully established or faild.
276 | *
277 | * @param resource $socket
278 | * @return void
279 | */
280 | public function checkConnection($socket)
281 | {
282 | // Remove EV_EXPECT for windows.
283 | if (DIRECTORY_SEPARATOR === '\\') {
284 | Worker::$globalEvent->del($socket, EventInterface::EV_EXCEPT);
285 | }
286 | // Check socket state.
287 | if ($address = stream_socket_get_name($socket, true)) {
288 | // Remove write listener.
289 | Worker::$globalEvent->del($socket, EventInterface::EV_WRITE);
290 | // Nonblocking.
291 | stream_set_blocking($socket, 0);
292 | // Compatible with hhvm
293 | if (function_exists('stream_set_read_buffer')) {
294 | stream_set_read_buffer($socket, 0);
295 | }
296 | // Try to open keepalive for tcp and disable Nagle algorithm.
297 | if (function_exists('socket_import_stream') && $this->transport === 'tcp') {
298 | $raw_socket = socket_import_stream($socket);
299 | socket_set_option($raw_socket, SOL_SOCKET, SO_KEEPALIVE, 1);
300 | socket_set_option($raw_socket, SOL_TCP, TCP_NODELAY, 1);
301 | }
302 | // Register a listener waiting read event.
303 | Worker::$globalEvent->add($socket, EventInterface::EV_READ, array($this, 'baseRead'));
304 | // There are some data waiting to send.
305 | if ($this->_sendBuffer) {
306 | Worker::$globalEvent->add($socket, EventInterface::EV_WRITE, array($this, 'baseWrite'));
307 | }
308 | $this->_status = self::STATUS_ESTABLISHED;
309 | $this->_remoteAddress = $address;
310 | $this->_sslHandshakeCompleted = true;
311 |
312 | // Try to emit onConnect callback.
313 | if ($this->onConnect) {
314 | try {
315 | call_user_func($this->onConnect, $this);
316 | } catch (\Exception $e) {
317 | Worker::log($e);
318 | exit(250);
319 | } catch (\Error $e) {
320 | Worker::log($e);
321 | exit(250);
322 | }
323 | }
324 | // Try to emit protocol::onConnect
325 | if (method_exists($this->protocol, 'onConnect')) {
326 | try {
327 | call_user_func(array($this->protocol, 'onConnect'), $this);
328 | } catch (\Exception $e) {
329 | Worker::log($e);
330 | exit(250);
331 | } catch (\Error $e) {
332 | Worker::log($e);
333 | exit(250);
334 | }
335 | }
336 | } else {
337 | // Connection failed.
338 | $this->emitError(WORKERMAN_CONNECT_FAIL, 'connect ' . $this->_remoteAddress . ' fail after ' . round(microtime(true) - $this->_connectStartTime, 4) . ' seconds');
339 | if ($this->_status === self::STATUS_CLOSING) {
340 | $this->destroy();
341 | }
342 | if ($this->_status === self::STATUS_CLOSED) {
343 | $this->onConnect = null;
344 | }
345 | }
346 | }
347 | }
348 |
--------------------------------------------------------------------------------
/Workerman/Protocols/Ws.php:
--------------------------------------------------------------------------------
1 |
10 | * @copyright walkor
11 | * @link http://www.workerman.net/
12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License
13 | */
14 | namespace Workerman\Protocols;
15 |
16 | use Workerman\Worker;
17 | use Workerman\Lib\Timer;
18 | use Workerman\Connection\TcpConnection;
19 |
20 | /**
21 | * Websocket protocol for client.
22 | */
23 | class Ws
24 | {
25 | /**
26 | * Websocket blob type.
27 | *
28 | * @var string
29 | */
30 | const BINARY_TYPE_BLOB = "\x81";
31 |
32 | /**
33 | * Websocket arraybuffer type.
34 | *
35 | * @var string
36 | */
37 | const BINARY_TYPE_ARRAYBUFFER = "\x82";
38 |
39 | /**
40 | * Check the integrity of the package.
41 | *
42 | * @param string $buffer
43 | * @param ConnectionInterface $connection
44 | * @return int
45 | */
46 | public static function input($buffer, $connection)
47 | {
48 | if (empty($connection->handshakeStep)) {
49 | echo "recv data before handshake. Buffer:" . bin2hex($buffer) . "\n";
50 | return false;
51 | }
52 | // Recv handshake response
53 | if ($connection->handshakeStep === 1) {
54 | return self::dealHandshake($buffer, $connection);
55 | }
56 | $recv_len = strlen($buffer);
57 | if ($recv_len < 2) {
58 | return 0;
59 | }
60 | // Buffer websocket frame data.
61 | if ($connection->websocketCurrentFrameLength) {
62 | // We need more frame data.
63 | if ($connection->websocketCurrentFrameLength > $recv_len) {
64 | // Return 0, because it is not clear the full packet length, waiting for the frame of fin=1.
65 | return 0;
66 | }
67 | } else {
68 |
69 | $firstbyte = ord($buffer[0]);
70 | $secondbyte = ord($buffer[1]);
71 | $data_len = $secondbyte & 127;
72 | $is_fin_frame = $firstbyte >> 7;
73 | $masked = $secondbyte >> 7;
74 | $opcode = $firstbyte & 0xf;
75 |
76 | switch ($opcode) {
77 | case 0x0:
78 | break;
79 | // Blob type.
80 | case 0x1:
81 | break;
82 | // Arraybuffer type.
83 | case 0x2:
84 | break;
85 | // Close package.
86 | case 0x8:
87 | // Try to emit onWebSocketClose callback.
88 | if (isset($connection->onWebSocketClose)) {
89 | try {
90 | call_user_func($connection->onWebSocketClose, $connection);
91 | } catch (\Exception $e) {
92 | Worker::log($e);
93 | exit(250);
94 | } catch (\Error $e) {
95 | Worker::log($e);
96 | exit(250);
97 | }
98 | } // Close connection.
99 | else {
100 | $connection->close();
101 | }
102 | return 0;
103 | // Ping package.
104 | case 0x9:
105 | // Try to emit onWebSocketPing callback.
106 | if (isset($connection->onWebSocketPing)) {
107 | try {
108 | call_user_func($connection->onWebSocketPing, $connection);
109 | } catch (\Exception $e) {
110 | Worker::log($e);
111 | exit(250);
112 | } catch (\Error $e) {
113 | Worker::log($e);
114 | exit(250);
115 | }
116 | } // Send pong package to client.
117 | else {
118 | $connection->send(pack('H*', '8a00'), true);
119 | }
120 | // Consume data from receive buffer.
121 | if (!$data_len) {
122 | $head_len = $masked ? 6 : 2;
123 | $connection->consumeRecvBuffer($head_len);
124 | if ($recv_len > $head_len) {
125 | return self::input(substr($buffer, $head_len), $connection);
126 | }
127 | return 0;
128 | }
129 | break;
130 | // Pong package.
131 | case 0xa:
132 | // Try to emit onWebSocketPong callback.
133 | if (isset($connection->onWebSocketPong)) {
134 | try {
135 | call_user_func($connection->onWebSocketPong, $connection);
136 | } catch (\Exception $e) {
137 | Worker::log($e);
138 | exit(250);
139 | } catch (\Error $e) {
140 | Worker::log($e);
141 | exit(250);
142 | }
143 | }
144 | // Consume data from receive buffer.
145 | if (!$data_len) {
146 | $head_len = $masked ? 6 : 2;
147 | $connection->consumeRecvBuffer($head_len);
148 | if ($recv_len > $head_len) {
149 | return self::input(substr($buffer, $head_len), $connection);
150 | }
151 | return 0;
152 | }
153 | break;
154 | // Wrong opcode.
155 | default :
156 | echo "error opcode $opcode and close websocket connection. Buffer:" . $buffer . "\n";
157 | $connection->close();
158 | return 0;
159 | }
160 | // Calculate packet length.
161 | if ($data_len === 126) {
162 | if (strlen($buffer) < 6) {
163 | return 0;
164 | }
165 | $pack = unpack('nn/ntotal_len', $buffer);
166 | $current_frame_length = $pack['total_len'] + 4;
167 | } else if ($data_len === 127) {
168 | if (strlen($buffer) < 10) {
169 | return 0;
170 | }
171 | $arr = unpack('n/N2c', $buffer);
172 | $current_frame_length = $arr['c1']*4294967296 + $arr['c2'] + 10;
173 | } else {
174 | $current_frame_length = $data_len + 2;
175 | }
176 |
177 | $total_package_size = strlen($connection->websocketDataBuffer) + $current_frame_length;
178 | if ($total_package_size > TcpConnection::$maxPackageSize) {
179 | echo "error package. package_length=$total_package_size\n";
180 | $connection->close();
181 | return 0;
182 | }
183 |
184 | if ($is_fin_frame) {
185 | return $current_frame_length;
186 | } else {
187 | $connection->websocketCurrentFrameLength = $current_frame_length;
188 | }
189 | }
190 | // Received just a frame length data.
191 | if ($connection->websocketCurrentFrameLength === $recv_len) {
192 | self::decode($buffer, $connection);
193 | $connection->consumeRecvBuffer($connection->websocketCurrentFrameLength);
194 | $connection->websocketCurrentFrameLength = 0;
195 | return 0;
196 | } // The length of the received data is greater than the length of a frame.
197 | elseif ($connection->websocketCurrentFrameLength < $recv_len) {
198 | self::decode(substr($buffer, 0, $connection->websocketCurrentFrameLength), $connection);
199 | $connection->consumeRecvBuffer($connection->websocketCurrentFrameLength);
200 | $current_frame_length = $connection->websocketCurrentFrameLength;
201 | $connection->websocketCurrentFrameLength = 0;
202 | // Continue to read next frame.
203 | return self::input(substr($buffer, $current_frame_length), $connection);
204 | } // The length of the received data is less than the length of a frame.
205 | else {
206 | return 0;
207 | }
208 | }
209 |
210 | /**
211 | * Websocket encode.
212 | *
213 | * @param string $buffer
214 | * @param ConnectionInterface $connection
215 | * @return string
216 | */
217 | public static function encode($payload, $connection)
218 | {
219 | if (empty($connection->websocketType)) {
220 | $connection->websocketType = self::BINARY_TYPE_BLOB;
221 | }
222 | $payload = (string)$payload;
223 | if (empty($connection->handshakeStep)) {
224 | self::sendHandshake($connection);
225 | }
226 | $mask = 1;
227 | $mask_key = "\x00\x00\x00\x00";
228 |
229 | $pack = '';
230 | $length = $length_flag = strlen($payload);
231 | if (65535 < $length) {
232 | $pack = pack('NN', ($length & 0xFFFFFFFF00000000) >> 32, $length & 0x00000000FFFFFFFF);
233 | $length_flag = 127;
234 | } else if (125 < $length) {
235 | $pack = pack('n*', $length);
236 | $length_flag = 126;
237 | }
238 |
239 | $head = ($mask << 7) | $length_flag;
240 | $head = $connection->websocketType . chr($head) . $pack;
241 |
242 | $frame = $head . $mask_key;
243 | // append payload to frame:
244 | for ($i = 0; $i < $length; $i++) {
245 | $frame .= $payload[$i] ^ $mask_key[$i % 4];
246 | }
247 | if ($connection->handshakeStep === 1) {
248 | // If buffer has already full then discard the current package.
249 | if (strlen($connection->tmpWebsocketData) > $connection->maxSendBufferSize) {
250 | if ($connection->onError) {
251 | try {
252 | call_user_func($connection->onError, $connection, WORKERMAN_SEND_FAIL, 'send buffer full and drop package');
253 | } catch (\Exception $e) {
254 | Worker::log($e);
255 | exit(250);
256 | } catch (\Error $e) {
257 | Worker::log($e);
258 | exit(250);
259 | }
260 | }
261 | return '';
262 | }
263 | $connection->tmpWebsocketData = $connection->tmpWebsocketData . $frame;
264 | // Check buffer is full.
265 | if ($connection->maxSendBufferSize <= strlen($connection->tmpWebsocketData)) {
266 | if ($connection->onBufferFull) {
267 | try {
268 | call_user_func($connection->onBufferFull, $connection);
269 | } catch (\Exception $e) {
270 | Worker::log($e);
271 | exit(250);
272 | } catch (\Error $e) {
273 | Worker::log($e);
274 | exit(250);
275 | }
276 | }
277 | }
278 | return '';
279 | }
280 | return $frame;
281 | }
282 |
283 | /**
284 | * Websocket decode.
285 | *
286 | * @param string $buffer
287 | * @param ConnectionInterface $connection
288 | * @return string
289 | */
290 | public static function decode($bytes, $connection)
291 | {
292 | $masked = ord($bytes[1]) >> 7;
293 | $data_length = $masked ? ord($bytes[1]) & 127 : ord($bytes[1]);
294 | $decoded_data = '';
295 | if ($masked === true) {
296 | if ($data_length === 126) {
297 | $mask = substr($bytes, 4, 4);
298 | $coded_data = substr($bytes, 8);
299 | } else if ($data_length === 127) {
300 | $mask = substr($bytes, 10, 4);
301 | $coded_data = substr($bytes, 14);
302 | } else {
303 | $mask = substr($bytes, 2, 4);
304 | $coded_data = substr($bytes, 6);
305 | }
306 | for ($i = 0; $i < strlen($coded_data); $i++) {
307 | $decoded_data .= $coded_data[$i] ^ $mask[$i % 4];
308 | }
309 | } else {
310 | if ($data_length === 126) {
311 | $decoded_data = substr($bytes, 4);
312 | } else if ($data_length === 127) {
313 | $decoded_data = substr($bytes, 10);
314 | } else {
315 | $decoded_data = substr($bytes, 2);
316 | }
317 | }
318 | if ($connection->websocketCurrentFrameLength) {
319 | $connection->websocketDataBuffer .= $decoded_data;
320 | return $connection->websocketDataBuffer;
321 | } else {
322 | if ($connection->websocketDataBuffer !== '') {
323 | $decoded_data = $connection->websocketDataBuffer . $decoded_data;
324 | $connection->websocketDataBuffer = '';
325 | }
326 | return $decoded_data;
327 | }
328 | }
329 |
330 | /**
331 | * Send websocket handshake data.
332 | *
333 | * @return void
334 | */
335 | public static function onConnect($connection)
336 | {
337 | self::sendHandshake($connection);
338 | }
339 |
340 | /**
341 | * Clean
342 | *
343 | * @param $connection
344 | */
345 | public static function onClose($connection)
346 | {
347 | $connection->handshakeStep = null;
348 | $connection->websocketCurrentFrameLength = 0;
349 | $connection->tmpWebsocketData = '';
350 | $connection->websocketDataBuffer = '';
351 | if (!empty($connection->websocketPingTimer)) {
352 | Timer::del($connection->websocketPingTimer);
353 | $connection->websocketPingTimer = null;
354 | }
355 | }
356 |
357 | /**
358 | * Send websocket handshake.
359 | *
360 | * @param \Workerman\Connection\TcpConnection $connection
361 | * @return void
362 | */
363 | public static function sendHandshake($connection)
364 | {
365 | if (!empty($connection->handshakeStep)) {
366 | return;
367 | }
368 | // Get Host.
369 | $port = $connection->getRemotePort();
370 | $host = $port === 80 ? $connection->getRemoteHost() : $connection->getRemoteHost() . ':' . $port;
371 | // Handshake header.
372 | $header = 'GET ' . $connection->getRemoteURI() . " HTTP/1.1\r\n".
373 | "Host: $host\r\n".
374 | "Connection: Upgrade\r\n".
375 | "Upgrade: websocket\r\n".
376 | "Origin: ". (isset($connection->websocketOrigin) ? $connection->websocketOrigin : '*') ."\r\n".
377 | "Sec-WebSocket-Version: 13\r\n".
378 | "Sec-WebSocket-Key: " . base64_encode(md5(mt_rand(), true)) . "\r\n\r\n";
379 | $connection->send($header, true);
380 | $connection->handshakeStep = 1;
381 | $connection->websocketCurrentFrameLength = 0;
382 | $connection->websocketDataBuffer = '';
383 | $connection->tmpWebsocketData = '';
384 | }
385 |
386 | /**
387 | * Websocket handshake.
388 | *
389 | * @param string $buffer
390 | * @param \Workerman\Connection\TcpConnection $connection
391 | * @return int
392 | */
393 | public static function dealHandshake($buffer, $connection)
394 | {
395 | $pos = strpos($buffer, "\r\n\r\n");
396 | if ($pos) {
397 | // handshake complete
398 | $connection->handshakeStep = 2;
399 | $handshake_response_length = $pos + 4;
400 | // Try to emit onWebSocketConnect callback.
401 | if (isset($connection->onWebSocketConnect)) {
402 | try {
403 | call_user_func($connection->onWebSocketConnect, $connection, substr($buffer, 0, $handshake_response_length));
404 | } catch (\Exception $e) {
405 | Worker::log($e);
406 | exit(250);
407 | } catch (\Error $e) {
408 | Worker::log($e);
409 | exit(250);
410 | }
411 | }
412 | // Headbeat.
413 | if (!empty($connection->websocketPingInterval)) {
414 | $connection->websocketPingTimer = Timer::add($connection->websocketPingInterval, function() use ($connection){
415 | if (false === $connection->send(pack('H*', '898000000000'), true)) {
416 | Timer::del($connection->websocketPingTimer);
417 | $connection->websocketPingTimer = null;
418 | }
419 | });
420 | }
421 |
422 | $connection->consumeRecvBuffer($handshake_response_length);
423 | if (!empty($connection->tmpWebsocketData)) {
424 | $connection->send($connection->tmpWebsocketData, true);
425 | $connection->tmpWebsocketData = '';
426 | }
427 | if (strlen($buffer) > $handshake_response_length) {
428 | return self::input(substr($buffer, $handshake_response_length), $connection);
429 | }
430 | }
431 | return 0;
432 | }
433 | }
434 |
--------------------------------------------------------------------------------
/Workerman/README.md:
--------------------------------------------------------------------------------
1 | # Workerman
2 | [](https://gitter.im/walkor/Workerman?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=body_badge)
3 | [](https://packagist.org/packages/workerman/workerman)
4 | [](https://packagist.org/packages/workerman/workerman)
5 | [](https://packagist.org/packages/workerman/workerman)
6 | [](https://packagist.org/packages/workerman/workerman)
7 | [](https://packagist.org/packages/workerman/workerman)
8 |
9 | ## What is it
10 | Workerman is an asynchronous event driven PHP framework with high performance for easily building fast, scalable network applications. Supports HTTP, Websocket, SSL and other custom protocols. Supports libevent, [HHVM](https://github.com/facebook/hhvm) , [ReactPHP](https://github.com/reactphp/react).
11 |
12 | ## Requires
13 | PHP 5.3 or Higher
14 | A POSIX compatible operating system (Linux, OSX, BSD)
15 | POSIX and PCNTL extensions for PHP
16 |
17 | ## Installation
18 |
19 | ```
20 | composer require workerman/workerman
21 | ```
22 |
23 | ## Basic Usage
24 |
25 | ### A websocket server
26 | ```php
27 | count = 4;
36 |
37 | // Emitted when new connection come
38 | $ws_worker->onConnect = function($connection)
39 | {
40 | echo "New connection\n";
41 | };
42 |
43 | // Emitted when data received
44 | $ws_worker->onMessage = function($connection, $data)
45 | {
46 | // Send hello $data
47 | $connection->send('hello ' . $data);
48 | };
49 |
50 | // Emitted when connection closed
51 | $ws_worker->onClose = function($connection)
52 | {
53 | echo "Connection closed\n";
54 | };
55 |
56 | // Run worker
57 | Worker::runAll();
58 | ```
59 |
60 | ### An http server
61 | ```php
62 | require_once __DIR__ . '/vendor/autoload.php';
63 | use Workerman\Worker;
64 |
65 | // #### http worker ####
66 | $http_worker = new Worker("http://0.0.0.0:2345");
67 |
68 | // 4 processes
69 | $http_worker->count = 4;
70 |
71 | // Emitted when data received
72 | $http_worker->onMessage = function($connection, $data)
73 | {
74 | // $_GET, $_POST, $_COOKIE, $_SESSION, $_SERVER, $_FILES are available
75 | var_dump($_GET, $_POST, $_COOKIE, $_SESSION, $_SERVER, $_FILES);
76 | // send data to client
77 | $connection->send("hello world \n");
78 | };
79 |
80 | // run all workers
81 | Worker::runAll();
82 | ```
83 |
84 | ### A WebServer
85 | ```php
86 | require_once __DIR__ . '/vendor/autoload.php';
87 | use Workerman\WebServer;
88 | use Workerman\Worker;
89 |
90 | // WebServer
91 | $web = new WebServer("http://0.0.0.0:80");
92 |
93 | // 4 processes
94 | $web->count = 4;
95 |
96 | // Set the root of domains
97 | $web->addRoot('www.your_domain.com', '/your/path/Web');
98 | $web->addRoot('www.another_domain.com', '/another/path/Web');
99 | // run all workers
100 | Worker::runAll();
101 | ```
102 |
103 | ### A tcp server
104 | ```php
105 | require_once __DIR__ . '/vendor/autoload.php';
106 | use Workerman\Worker;
107 |
108 | // #### create socket and listen 1234 port ####
109 | $tcp_worker = new Worker("tcp://0.0.0.0:1234");
110 |
111 | // 4 processes
112 | $tcp_worker->count = 4;
113 |
114 | // Emitted when new connection come
115 | $tcp_worker->onConnect = function($connection)
116 | {
117 | echo "New Connection\n";
118 | };
119 |
120 | // Emitted when data received
121 | $tcp_worker->onMessage = function($connection, $data)
122 | {
123 | // send data to client
124 | $connection->send("hello $data \n");
125 | };
126 |
127 | // Emitted when new connection come
128 | $tcp_worker->onClose = function($connection)
129 | {
130 | echo "Connection closed\n";
131 | };
132 |
133 | Worker::runAll();
134 | ```
135 |
136 | ### Enable SSL
137 | ```php
138 | array(
145 | 'local_cert' => '/your/path/of/server.pem',
146 | 'local_pk' => '/your/path/of/server.key',
147 | 'verify_peer' => false,
148 | )
149 | );
150 |
151 | // Create a Websocket server with ssl context.
152 | $ws_worker = new Worker("websocket://0.0.0.0:2346", $context);
153 |
154 | // Enable SSL. WebSocket+SSL means that Secure WebSocket (wss://).
155 | // The similar approaches for Https etc.
156 | $ws_worker->transport = 'ssl';
157 |
158 | $ws_worker->onMessage = function($connection, $data)
159 | {
160 | // Send hello $data
161 | $connection->send('hello ' . $data);
162 | };
163 |
164 | Worker::runAll();
165 | ```
166 |
167 | ### Custom protocol
168 | Protocols/MyTextProtocol.php
169 | ```php
170 | namespace Protocols;
171 | /**
172 | * User defined protocol
173 | * Format Text+"\n"
174 | */
175 | class MyTextProtocol
176 | {
177 | public static function input($recv_buffer)
178 | {
179 | // Find the position of the first occurrence of "\n"
180 | $pos = strpos($recv_buffer, "\n");
181 | // Not a complete package. Return 0 because the length of package can not be calculated
182 | if($pos === false)
183 | {
184 | return 0;
185 | }
186 | // Return length of the package
187 | return $pos+1;
188 | }
189 |
190 | public static function decode($recv_buffer)
191 | {
192 | return trim($recv_buffer);
193 | }
194 |
195 | public static function encode($data)
196 | {
197 | return $data."\n";
198 | }
199 | }
200 | ```
201 |
202 | ```php
203 | require_once __DIR__ . '/vendor/autoload.php';
204 | use Workerman\Worker;
205 |
206 | // #### MyTextProtocol worker ####
207 | $text_worker = new Worker("MyTextProtocol://0.0.0.0:5678");
208 |
209 | $text_worker->onConnect = function($connection)
210 | {
211 | echo "New connection\n";
212 | };
213 |
214 | $text_worker->onMessage = function($connection, $data)
215 | {
216 | // send data to client
217 | $connection->send("hello world \n");
218 | };
219 |
220 | $text_worker->onClose = function($connection)
221 | {
222 | echo "Connection closed\n";
223 | };
224 |
225 | // run all workers
226 | Worker::runAll();
227 | ```
228 |
229 | ### Timer
230 | ```php
231 | require_once __DIR__ . '/vendor/autoload.php';
232 | use Workerman\Worker;
233 | use Workerman\Lib\Timer;
234 |
235 | $task = new Worker();
236 | $task->onWorkerStart = function($task)
237 | {
238 | // 2.5 seconds
239 | $time_interval = 2.5;
240 | $timer_id = Timer::add($time_interval,
241 | function()
242 | {
243 | echo "Timer run\n";
244 | }
245 | );
246 | };
247 |
248 | // run all workers
249 | Worker::runAll();
250 | ```
251 |
252 | ### AsyncTcpConnection (tcp/ws/text/frame etc...)
253 | ```php
254 | require_once __DIR__ . '/vendor/autoload.php';
255 | use Workerman\Worker;
256 | use Workerman\Connection\AsyncTcpConnection;
257 |
258 | $worker = new Worker();
259 | $worker->onWorkerStart = function()
260 | {
261 | // Websocket protocol for client.
262 | $ws_connection = new AsyncTcpConnection("ws://echo.websocket.org:80");
263 | $ws_connection->onConnect = function($connection){
264 | $connection->send('hello');
265 | };
266 | $ws_connection->onMessage = function($connection, $data){
267 | echo "recv: $data\n";
268 | };
269 | $ws_connection->onError = function($connection, $code, $msg){
270 | echo "error: $msg\n";
271 | };
272 | $ws_connection->onClose = function($connection){
273 | echo "connection closed\n";
274 | };
275 | $ws_connection->connect();
276 | };
277 | Worker::runAll();
278 | ```
279 |
280 | ### Async Mysql of ReactPHP
281 | ```
282 | composer require react/mysql
283 | ```
284 |
285 | ```php
286 | onWorkerStart = function() {
292 | global $mysql;
293 | $loop = Worker::getEventLoop();
294 | $mysql = new React\MySQL\Connection($loop, array(
295 | 'host' => '127.0.0.1',
296 | 'dbname' => 'dbname',
297 | 'user' => 'user',
298 | 'passwd' => 'passwd',
299 | ));
300 | $mysql->on('error', function($e){
301 | echo $e;
302 | });
303 | $mysql->connect(function ($e) {
304 | if($e) {
305 | echo $e;
306 | } else {
307 | echo "connect success\n";
308 | }
309 | });
310 | };
311 | $worker->onMessage = function($connection, $data) {
312 | global $mysql;
313 | $mysql->query('show databases' /*trim($data)*/, function ($command, $mysql) use ($connection) {
314 | if ($command->hasError()) {
315 | $error = $command->getError();
316 | } else {
317 | $results = $command->resultRows;
318 | $fields = $command->resultFields;
319 | $connection->send(json_encode($results));
320 | }
321 | });
322 | };
323 | Worker::runAll();
324 | ```
325 |
326 | ### Async Redis of ReactPHP
327 | ```
328 | composer require clue/redis-react
329 | ```
330 |
331 | ```php
332 | onWorkerStart = function() {
341 | global $factory;
342 | $loop = Worker::getEventLoop();
343 | $factory = new Factory($loop);
344 | };
345 |
346 | $worker->onMessage = function($connection, $data) {
347 | global $factory;
348 | $factory->createClient('localhost:6379')->then(function (Client $client) use ($connection) {
349 | $client->set('greeting', 'Hello world');
350 | $client->append('greeting', '!');
351 |
352 | $client->get('greeting')->then(function ($greeting) use ($connection){
353 | // Hello world!
354 | echo $greeting . PHP_EOL;
355 | $connection->send($greeting);
356 | });
357 |
358 | $client->incr('invocation')->then(function ($n) use ($connection){
359 | echo 'This is invocation #' . $n . PHP_EOL;
360 | $connection->send($n);
361 | });
362 | });
363 | };
364 |
365 | Worker::runAll();
366 | ```
367 |
368 | ### Aysnc dns of ReactPHP
369 | ```
370 | composer require react/dns
371 | ```
372 |
373 | ```php
374 | require_once __DIR__ . '/vendor/autoload.php';
375 | use Workerman\Worker;
376 | $worker = new Worker('tcp://0.0.0.0:6161');
377 | $worker->onWorkerStart = function() {
378 | global $dns;
379 | // Get event-loop.
380 | $loop = Worker::getEventLoop();
381 | $factory = new React\Dns\Resolver\Factory();
382 | $dns = $factory->create('8.8.8.8', $loop);
383 | };
384 | $worker->onMessage = function($connection, $host) {
385 | global $dns;
386 | $host = trim($host);
387 | $dns->resolve($host)->then(function($ip) use($host, $connection) {
388 | $connection->send("$host: $ip");
389 | },function($e) use($host, $connection){
390 | $connection->send("$host: {$e->getMessage()}");
391 | });
392 | };
393 |
394 | Worker::runAll();
395 | ```
396 |
397 | ### Http client of ReactPHP
398 | ```
399 | composer require react/http-client
400 | ```
401 |
402 | ```php
403 | onWorkerStart = function() {
410 | global $client;
411 | $loop = Worker::getEventLoop();
412 | $factory = new React\Dns\Resolver\Factory();
413 | $dns = $factory->createCached('8.8.8.8', $loop);
414 | $factory = new React\HttpClient\Factory();
415 | $client = $factory->create($loop, $dns);
416 | };
417 |
418 | $worker->onMessage = function($connection, $host) {
419 | global $client;
420 | $request = $client->request('GET', trim($host));
421 | $request->on('error', function(Exception $e) use ($connection) {
422 | $connection->send($e);
423 | });
424 | $request->on('response', function ($response) use ($connection) {
425 | $response->on('data', function ($data, $response) use ($connection) {
426 | $connection->send($data);
427 | });
428 | });
429 | $request->end();
430 | };
431 |
432 | Worker::runAll();
433 | ```
434 |
435 | ### ZMQ of ReactPHP
436 | ```
437 | composer require react/zmq
438 | ```
439 |
440 | ```php
441 | onWorkerStart = function() {
448 | global $pull;
449 | $loop = Worker::getEventLoop();
450 | $context = new React\ZMQ\Context($loop);
451 | $pull = $context->getSocket(ZMQ::SOCKET_PULL);
452 | $pull->bind('tcp://127.0.0.1:5555');
453 |
454 | $pull->on('error', function ($e) {
455 | var_dump($e->getMessage());
456 | });
457 |
458 | $pull->on('message', function ($msg) {
459 | echo "Received: $msg\n";
460 | });
461 | };
462 |
463 | Worker::runAll();
464 | ```
465 |
466 | ### STOMP of ReactPHP
467 | ```
468 | composer require react/stomp
469 | ```
470 |
471 | ```php
472 | onWorkerStart = function() {
479 | global $client;
480 | $loop = Worker::getEventLoop();
481 | $factory = new React\Stomp\Factory($loop);
482 | $client = $factory->createClient(array('vhost' => '/', 'login' => 'guest', 'passcode' => 'guest'));
483 |
484 | $client
485 | ->connect()
486 | ->then(function ($client) use ($loop) {
487 | $client->subscribe('/topic/foo', function ($frame) {
488 | echo "Message received: {$frame->body}\n";
489 | });
490 | });
491 | };
492 |
493 | Worker::runAll();
494 | ```
495 |
496 |
497 |
498 | ## Available commands
499 | ```php start.php start ```
500 | ```php start.php start -d ```
501 | 
502 | ```php start.php status ```
503 | 
504 | ```php start.php connections```
505 | ```php start.php stop ```
506 | ```php start.php restart ```
507 | ```php start.php reload ```
508 |
509 | ## Documentation
510 |
511 | 中文主页:[http://www.workerman.net](http://www.workerman.net)
512 |
513 | 中文文档: [http://doc.workerman.net](http://doc.workerman.net)
514 |
515 | Documentation:[https://github.com/walkor/workerman-manual](https://github.com/walkor/workerman-manual/blob/master/english/src/SUMMARY.md)
516 |
517 | # Benchmarks
518 | ```
519 | CPU: Intel(R) Core(TM) i3-3220 CPU @ 3.30GHz and 4 processors totally
520 | Memory: 8G
521 | OS: Ubuntu 14.04 LTS
522 | Software: ab
523 | PHP: 5.5.9
524 | ```
525 |
526 | **Codes**
527 | ```php
528 | count=3;
532 | $worker->onMessage = function($connection, $data)
533 | {
534 | $connection->send("Workerman");
535 | };
536 | Worker::runAll();
537 | ```
538 | **Result**
539 |
540 | ```shell
541 | ab -n1000000 -c100 -k http://127.0.0.1:1234/
542 | This is ApacheBench, Version 2.3 <$Revision: 1528965 $>
543 | Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
544 | Licensed to The Apache Software Foundation, http://www.apache.org/
545 |
546 | Benchmarking 127.0.0.1 (be patient)
547 | Completed 100000 requests
548 | Completed 200000 requests
549 | Completed 300000 requests
550 | Completed 400000 requests
551 | Completed 500000 requests
552 | Completed 600000 requests
553 | Completed 700000 requests
554 | Completed 800000 requests
555 | Completed 900000 requests
556 | Completed 1000000 requests
557 | Finished 1000000 requests
558 |
559 |
560 | Server Software: workerman/3.1.4
561 | Server Hostname: 127.0.0.1
562 | Server Port: 1234
563 |
564 | Document Path: /
565 | Document Length: 5 bytes
566 |
567 | Concurrency Level: 100
568 | Time taken for tests: 7.240 seconds
569 | Complete requests: 1000000
570 | Failed requests: 0
571 | Keep-Alive requests: 1000000
572 | Total transferred: 73000000 bytes
573 | HTML transferred: 5000000 bytes
574 | Requests per second: 138124.14 [#/sec] (mean)
575 | Time per request: 0.724 [ms] (mean)
576 | Time per request: 0.007 [ms] (mean, across all concurrent requests)
577 | Transfer rate: 9846.74 [Kbytes/sec] received
578 |
579 | Connection Times (ms)
580 | min mean[+/-sd] median max
581 | Connect: 0 0 0.0 0 5
582 | Processing: 0 1 0.2 1 9
583 | Waiting: 0 1 0.2 1 9
584 | Total: 0 1 0.2 1 9
585 |
586 | Percentage of the requests served within a certain time (ms)
587 | 50% 1
588 | 66% 1
589 | 75% 1
590 | 80% 1
591 | 90% 1
592 | 95% 1
593 | 98% 1
594 | 99% 1
595 | 100% 9 (longest request)
596 |
597 | ```
598 |
599 |
600 | ## Other links with workerman
601 |
602 | [PHPSocket.IO](https://github.com/walkor/phpsocket.io)
603 | [php-socks5](https://github.com/walkor/php-socks5)
604 | [php-http-proxy](https://github.com/walkor/php-http-proxy)
605 |
606 | ## Donate
607 |
608 |
609 | ## LICENSE
610 |
611 | Workerman is released under the [MIT license](https://github.com/walkor/workerman/blob/master/MIT-LICENSE.txt).
612 |
--------------------------------------------------------------------------------
/Workerman/Protocols/Websocket.php:
--------------------------------------------------------------------------------
1 |
10 | * @copyright walkor
11 | * @link http://www.workerman.net/
12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License
13 | */
14 | namespace Workerman\Protocols;
15 |
16 | use Workerman\Connection\ConnectionInterface;
17 | use Workerman\Connection\TcpConnection;
18 | use Workerman\Worker;
19 |
20 | /**
21 | * WebSocket protocol.
22 | */
23 | class Websocket implements \Workerman\Protocols\ProtocolInterface
24 | {
25 | /**
26 | * Websocket blob type.
27 | *
28 | * @var string
29 | */
30 | const BINARY_TYPE_BLOB = "\x81";
31 |
32 | /**
33 | * Websocket arraybuffer type.
34 | *
35 | * @var string
36 | */
37 | const BINARY_TYPE_ARRAYBUFFER = "\x82";
38 |
39 | /**
40 | * Check the integrity of the package.
41 | *
42 | * @param string $buffer
43 | * @param ConnectionInterface $connection
44 | * @return int
45 | */
46 | public static function input($buffer, ConnectionInterface $connection)
47 | {
48 | // Receive length.
49 | $recv_len = strlen($buffer);
50 | // We need more data.
51 | if ($recv_len < 2) {
52 | return 0;
53 | }
54 |
55 | // Has not yet completed the handshake.
56 | if (empty($connection->websocketHandshake)) {
57 | return static::dealHandshake($buffer, $connection);
58 | }
59 |
60 | // Buffer websocket frame data.
61 | if ($connection->websocketCurrentFrameLength) {
62 | // We need more frame data.
63 | if ($connection->websocketCurrentFrameLength > $recv_len) {
64 | // Return 0, because it is not clear the full packet length, waiting for the frame of fin=1.
65 | return 0;
66 | }
67 | } else {
68 | $firstbyte = ord($buffer[0]);
69 | $secondbyte = ord($buffer[1]);
70 | $data_len = $secondbyte & 127;
71 | $is_fin_frame = $firstbyte >> 7;
72 | $masked = $secondbyte >> 7;
73 | $opcode = $firstbyte & 0xf;
74 | switch ($opcode) {
75 | case 0x0:
76 | break;
77 | // Blob type.
78 | case 0x1:
79 | break;
80 | // Arraybuffer type.
81 | case 0x2:
82 | break;
83 | // Close package.
84 | case 0x8:
85 | // Try to emit onWebSocketClose callback.
86 | if (isset($connection->onWebSocketClose) || isset($connection->worker->onWebSocketClose)) {
87 | try {
88 | call_user_func(isset($connection->onWebSocketClose)?$connection->onWebSocketClose:$connection->worker->onWebSocketClose, $connection);
89 | } catch (\Exception $e) {
90 | Worker::log($e);
91 | exit(250);
92 | } catch (\Error $e) {
93 | Worker::log($e);
94 | exit(250);
95 | }
96 | } // Close connection.
97 | else {
98 | $connection->close();
99 | }
100 | return 0;
101 | // Ping package.
102 | case 0x9:
103 | // Try to emit onWebSocketPing callback.
104 | if (isset($connection->onWebSocketPing) || isset($connection->worker->onWebSocketPing)) {
105 | try {
106 | call_user_func(isset($connection->onWebSocketPing)?$connection->onWebSocketPing:$connection->worker->onWebSocketPing, $connection);
107 | } catch (\Exception $e) {
108 | Worker::log($e);
109 | exit(250);
110 | } catch (\Error $e) {
111 | Worker::log($e);
112 | exit(250);
113 | }
114 | } // Send pong package to client.
115 | else {
116 | $connection->send(pack('H*', '8a00'), true);
117 | }
118 |
119 | // Consume data from receive buffer.
120 | if (!$data_len) {
121 | $head_len = $masked ? 6 : 2;
122 | $connection->consumeRecvBuffer($head_len);
123 | if ($recv_len > $head_len) {
124 | return static::input(substr($buffer, $head_len), $connection);
125 | }
126 | return 0;
127 | }
128 | break;
129 | // Pong package.
130 | case 0xa:
131 | // Try to emit onWebSocketPong callback.
132 | if (isset($connection->onWebSocketPong) || isset($connection->worker->onWebSocketPong)) {
133 | try {
134 | call_user_func(isset($connection->onWebSocketPong)?$connection->onWebSocketPong:$connection->worker->onWebSocketPong, $connection);
135 | } catch (\Exception $e) {
136 | Worker::log($e);
137 | exit(250);
138 | } catch (\Error $e) {
139 | Worker::log($e);
140 | exit(250);
141 | }
142 | }
143 | // Consume data from receive buffer.
144 | if (!$data_len) {
145 | $head_len = $masked ? 6 : 2;
146 | $connection->consumeRecvBuffer($head_len);
147 | if ($recv_len > $head_len) {
148 | return static::input(substr($buffer, $head_len), $connection);
149 | }
150 | return 0;
151 | }
152 | break;
153 | // Wrong opcode.
154 | default :
155 | echo "error opcode $opcode and close websocket connection. Buffer:" . bin2hex($buffer) . "\n";
156 | $connection->close();
157 | return 0;
158 | }
159 |
160 | // Calculate packet length.
161 | $head_len = 6;
162 | if ($data_len === 126) {
163 | $head_len = 8;
164 | if ($head_len > $recv_len) {
165 | return 0;
166 | }
167 | $pack = unpack('nn/ntotal_len', $buffer);
168 | $data_len = $pack['total_len'];
169 | } else {
170 | if ($data_len === 127) {
171 | $head_len = 14;
172 | if ($head_len > $recv_len) {
173 | return 0;
174 | }
175 | $arr = unpack('n/N2c', $buffer);
176 | $data_len = $arr['c1']*4294967296 + $arr['c2'];
177 | }
178 | }
179 | $current_frame_length = $head_len + $data_len;
180 |
181 | $total_package_size = strlen($connection->websocketDataBuffer) + $current_frame_length;
182 | if ($total_package_size > TcpConnection::$maxPackageSize) {
183 | echo "error package. package_length=$total_package_size\n";
184 | $connection->close();
185 | return 0;
186 | }
187 |
188 | if ($is_fin_frame) {
189 | return $current_frame_length;
190 | } else {
191 | $connection->websocketCurrentFrameLength = $current_frame_length;
192 | }
193 | }
194 |
195 | // Received just a frame length data.
196 | if ($connection->websocketCurrentFrameLength === $recv_len) {
197 | static::decode($buffer, $connection);
198 | $connection->consumeRecvBuffer($connection->websocketCurrentFrameLength);
199 | $connection->websocketCurrentFrameLength = 0;
200 | return 0;
201 | } // The length of the received data is greater than the length of a frame.
202 | elseif ($connection->websocketCurrentFrameLength < $recv_len) {
203 | static::decode(substr($buffer, 0, $connection->websocketCurrentFrameLength), $connection);
204 | $connection->consumeRecvBuffer($connection->websocketCurrentFrameLength);
205 | $current_frame_length = $connection->websocketCurrentFrameLength;
206 | $connection->websocketCurrentFrameLength = 0;
207 | // Continue to read next frame.
208 | return static::input(substr($buffer, $current_frame_length), $connection);
209 | } // The length of the received data is less than the length of a frame.
210 | else {
211 | return 0;
212 | }
213 | }
214 |
215 | /**
216 | * Websocket encode.
217 | *
218 | * @param string $buffer
219 | * @param ConnectionInterface $connection
220 | * @return string
221 | */
222 | public static function encode($buffer, ConnectionInterface $connection)
223 | {
224 | if (!is_scalar($buffer)) {
225 | throw new \Exception("You can't send(" . gettype($buffer) . ") to client, you need to convert it to a string. ");
226 | }
227 | $len = strlen($buffer);
228 | if (empty($connection->websocketType)) {
229 | $connection->websocketType = static::BINARY_TYPE_BLOB;
230 | }
231 |
232 | $first_byte = $connection->websocketType;
233 |
234 | if ($len <= 125) {
235 | $encode_buffer = $first_byte . chr($len) . $buffer;
236 | } else {
237 | if ($len <= 65535) {
238 | $encode_buffer = $first_byte . chr(126) . pack("n", $len) . $buffer;
239 | } else {
240 | $encode_buffer = $first_byte . chr(127) . pack("xxxxN", $len) . $buffer;
241 | }
242 | }
243 |
244 | // Handshake not completed so temporary buffer websocket data waiting for send.
245 | if (empty($connection->websocketHandshake)) {
246 | if (empty($connection->tmpWebsocketData)) {
247 | $connection->tmpWebsocketData = '';
248 | }
249 | // If buffer has already full then discard the current package.
250 | if (strlen($connection->tmpWebsocketData) > $connection->maxSendBufferSize) {
251 | if ($connection->onError) {
252 | try {
253 | call_user_func($connection->onError, $connection, WORKERMAN_SEND_FAIL, 'send buffer full and drop package');
254 | } catch (\Exception $e) {
255 | Worker::log($e);
256 | exit(250);
257 | } catch (\Error $e) {
258 | Worker::log($e);
259 | exit(250);
260 | }
261 | }
262 | return '';
263 | }
264 | $connection->tmpWebsocketData .= $encode_buffer;
265 | // Check buffer is full.
266 | if ($connection->maxSendBufferSize <= strlen($connection->tmpWebsocketData)) {
267 | if ($connection->onBufferFull) {
268 | try {
269 | call_user_func($connection->onBufferFull, $connection);
270 | } catch (\Exception $e) {
271 | Worker::log($e);
272 | exit(250);
273 | } catch (\Error $e) {
274 | Worker::log($e);
275 | exit(250);
276 | }
277 | }
278 | }
279 |
280 | // Return empty string.
281 | return '';
282 | }
283 |
284 | return $encode_buffer;
285 | }
286 |
287 | /**
288 | * Websocket decode.
289 | *
290 | * @param string $buffer
291 | * @param ConnectionInterface $connection
292 | * @return string
293 | */
294 | public static function decode($buffer, ConnectionInterface $connection)
295 | {
296 | $masks = $data = $decoded = null;
297 | $len = ord($buffer[1]) & 127;
298 | if ($len === 126) {
299 | $masks = substr($buffer, 4, 4);
300 | $data = substr($buffer, 8);
301 | } else {
302 | if ($len === 127) {
303 | $masks = substr($buffer, 10, 4);
304 | $data = substr($buffer, 14);
305 | } else {
306 | $masks = substr($buffer, 2, 4);
307 | $data = substr($buffer, 6);
308 | }
309 | }
310 | for ($index = 0; $index < strlen($data); $index++) {
311 | $decoded .= $data[$index] ^ $masks[$index % 4];
312 | }
313 | if ($connection->websocketCurrentFrameLength) {
314 | $connection->websocketDataBuffer .= $decoded;
315 | return $connection->websocketDataBuffer;
316 | } else {
317 | if ($connection->websocketDataBuffer !== '') {
318 | $decoded = $connection->websocketDataBuffer . $decoded;
319 | $connection->websocketDataBuffer = '';
320 | }
321 | return $decoded;
322 | }
323 | }
324 |
325 | /**
326 | * Websocket handshake.
327 | *
328 | * @param string $buffer
329 | * @param \Workerman\Connection\TcpConnection $connection
330 | * @return int
331 | */
332 | protected static function dealHandshake($buffer, $connection)
333 | {
334 | // HTTP protocol.
335 | if (0 === strpos($buffer, 'GET')) {
336 | // Find \r\n\r\n.
337 | $heder_end_pos = strpos($buffer, "\r\n\r\n");
338 | if (!$heder_end_pos) {
339 | return 0;
340 | }
341 | $header_length = $heder_end_pos + 4;
342 |
343 | // Get Sec-WebSocket-Key.
344 | $Sec_WebSocket_Key = '';
345 | if (preg_match("/Sec-WebSocket-Key: *(.*?)\r\n/i", $buffer, $match)) {
346 | $Sec_WebSocket_Key = $match[1];
347 | } else {
348 | $connection->send("HTTP/1.1 400 Bad Request\r\n\r\n400 Bad Request
Sec-WebSocket-Key not found.
This is a WebSocket service and can not be accessed via HTTP.
See http://wiki.Workerman.net/Error1 for detail.",
349 | true);
350 | $connection->close();
351 | return 0;
352 | }
353 | // Calculation websocket key.
354 | $new_key = base64_encode(sha1($Sec_WebSocket_Key . "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", true));
355 | // Handshake response data.
356 | $handshake_message = "HTTP/1.1 101 Switching Protocols\r\n";
357 | $handshake_message .= "Upgrade: websocket\r\n";
358 | $handshake_message .= "Sec-WebSocket-Version: 13\r\n";
359 | $handshake_message .= "Connection: Upgrade\r\n";
360 | $handshake_message .= "Server: Workerman/".Worker::VERSION."\r\n";
361 | $handshake_message .= "Sec-WebSocket-Accept: " . $new_key . "\r\n\r\n";
362 | // Mark handshake complete..
363 | $connection->websocketHandshake = true;
364 | // Websocket data buffer.
365 | $connection->websocketDataBuffer = '';
366 | // Current websocket frame length.
367 | $connection->websocketCurrentFrameLength = 0;
368 | // Current websocket frame data.
369 | $connection->websocketCurrentFrameBuffer = '';
370 | // Consume handshake data.
371 | $connection->consumeRecvBuffer($header_length);
372 | // Send handshake response.
373 | $connection->send($handshake_message, true);
374 |
375 | // There are data waiting to be sent.
376 | if (!empty($connection->tmpWebsocketData)) {
377 | $connection->send($connection->tmpWebsocketData, true);
378 | $connection->tmpWebsocketData = '';
379 | }
380 | // blob or arraybuffer
381 | if (empty($connection->websocketType)) {
382 | $connection->websocketType = static::BINARY_TYPE_BLOB;
383 | }
384 | // Try to emit onWebSocketConnect callback.
385 | if (isset($connection->onWebSocketConnect) || isset($connection->worker->onWebSocketConnect)) {
386 | static::parseHttpHeader($buffer);
387 | try {
388 | call_user_func(isset($connection->onWebSocketConnect)?$connection->onWebSocketConnect:$connection->worker->onWebSocketConnect, $connection, $buffer);
389 | } catch (\Exception $e) {
390 | Worker::log($e);
391 | exit(250);
392 | } catch (\Error $e) {
393 | Worker::log($e);
394 | exit(250);
395 | }
396 | if (!empty($_SESSION) && class_exists('\GatewayWorker\Lib\Context')) {
397 | $connection->session = \GatewayWorker\Lib\Context::sessionEncode($_SESSION);
398 | }
399 | $_GET = $_SERVER = $_SESSION = $_COOKIE = array();
400 | }
401 | if (strlen($buffer) > $header_length) {
402 | return static::input(substr($buffer, $header_length), $connection);
403 | }
404 | return 0;
405 | } // Is flash policy-file-request.
406 | elseif (0 === strpos($buffer, '' . "\0";
408 | $connection->send($policy_xml, true);
409 | $connection->consumeRecvBuffer(strlen($buffer));
410 | return 0;
411 | }
412 | // Bad websocket handshake request.
413 | $connection->send("HTTP/1.1 400 Bad Request\r\n\r\n400 Bad Request
Invalid handshake data for websocket.
See http://wiki.Workerman.net/Error1 for detail.",
414 | true);
415 | $connection->close();
416 | return 0;
417 | }
418 |
419 | /**
420 | * Parse http header.
421 | *
422 | * @param string $buffer
423 | * @return void
424 | */
425 | protected static function parseHttpHeader($buffer)
426 | {
427 | // Parse headers.
428 | list($http_header, ) = explode("\r\n\r\n", $buffer, 2);
429 | $header_data = explode("\r\n", $http_header);
430 |
431 | if ($_SERVER) {
432 | $_SERVER = array();
433 | }
434 |
435 | list($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI'], $_SERVER['SERVER_PROTOCOL']) = explode(' ',
436 | $header_data[0]);
437 |
438 | unset($header_data[0]);
439 | foreach ($header_data as $content) {
440 | // \r\n\r\n
441 | if (empty($content)) {
442 | continue;
443 | }
444 | list($key, $value) = explode(':', $content, 2);
445 | $key = str_replace('-', '_', strtoupper($key));
446 | $value = trim($value);
447 | $_SERVER['HTTP_' . $key] = $value;
448 | switch ($key) {
449 | // HTTP_HOST
450 | case 'HOST':
451 | $tmp = explode(':', $value);
452 | $_SERVER['SERVER_NAME'] = $tmp[0];
453 | if (isset($tmp[1])) {
454 | $_SERVER['SERVER_PORT'] = $tmp[1];
455 | }
456 | break;
457 | // cookie
458 | case 'COOKIE':
459 | parse_str(str_replace('; ', '&', $_SERVER['HTTP_COOKIE']), $_COOKIE);
460 | break;
461 | }
462 | }
463 |
464 | // QUERY_STRING
465 | $_SERVER['QUERY_STRING'] = parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY);
466 | if ($_SERVER['QUERY_STRING']) {
467 | // $GET
468 | parse_str($_SERVER['QUERY_STRING'], $_GET);
469 | } else {
470 | $_SERVER['QUERY_STRING'] = '';
471 | }
472 | }
473 | }
474 |
--------------------------------------------------------------------------------
/Workerman/Protocols/Http.php:
--------------------------------------------------------------------------------
1 |
10 | * @copyright walkor
11 | * @link http://www.workerman.net/
12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License
13 | */
14 | namespace Workerman\Protocols;
15 |
16 | use Workerman\Connection\TcpConnection;
17 | use Workerman\Worker;
18 |
19 | /**
20 | * http protocol
21 | */
22 | class Http
23 | {
24 | /**
25 | * The supported HTTP methods
26 | * @var array
27 | */
28 | public static $methods = array('GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'OPTIONS');
29 |
30 | /**
31 | * Check the integrity of the package.
32 | *
33 | * @param string $recv_buffer
34 | * @param TcpConnection $connection
35 | * @return int
36 | */
37 | public static function input($recv_buffer, TcpConnection $connection)
38 | {
39 | if (!strpos($recv_buffer, "\r\n\r\n")) {
40 | // Judge whether the package length exceeds the limit.
41 | if (strlen($recv_buffer) >= TcpConnection::$maxPackageSize) {
42 | $connection->close();
43 | return 0;
44 | }
45 | return 0;
46 | }
47 |
48 | list($header,) = explode("\r\n\r\n", $recv_buffer, 2);
49 | $method = substr($header, 0, strpos($header, ' '));
50 |
51 | if(in_array($method, static::$methods)) {
52 | return static::getRequestSize($header, $method);
53 | }else{
54 | $connection->send("HTTP/1.1 400 Bad Request\r\n\r\n", true);
55 | return 0;
56 | }
57 | }
58 |
59 | /**
60 | * Get whole size of the request
61 | * includes the request headers and request body.
62 | * @param string $header The request headers
63 | * @param string $method The request method
64 | * @return integer
65 | */
66 | protected static function getRequestSize($header, $method)
67 | {
68 | if($method === 'GET' || $method === 'OPTIONS' || $method === 'HEAD') {
69 | return strlen($header) + 4;
70 | }
71 | $match = array();
72 | if (preg_match("/\r\nContent-Length: ?(\d+)/i", $header, $match)) {
73 | $content_length = isset($match[1]) ? $match[1] : 0;
74 | return $content_length + strlen($header) + 4;
75 | }
76 | return 0;
77 | }
78 |
79 | /**
80 | * Parse $_POST、$_GET、$_COOKIE.
81 | *
82 | * @param string $recv_buffer
83 | * @param TcpConnection $connection
84 | * @return array
85 | */
86 | public static function decode($recv_buffer, TcpConnection $connection)
87 | {
88 | // Init.
89 | $_POST = $_GET = $_COOKIE = $_REQUEST = $_SESSION = $_FILES = array();
90 | $GLOBALS['HTTP_RAW_POST_DATA'] = '';
91 | // Clear cache.
92 | HttpCache::$header = array('Connection' => 'Connection: keep-alive');
93 | HttpCache::$instance = new HttpCache();
94 | // $_SERVER
95 | $_SERVER = array(
96 | 'QUERY_STRING' => '',
97 | 'REQUEST_METHOD' => '',
98 | 'REQUEST_URI' => '',
99 | 'SERVER_PROTOCOL' => '',
100 | 'SERVER_SOFTWARE' => 'Workerman/'.Worker::VERSION,
101 | 'SERVER_NAME' => '',
102 | 'HTTP_HOST' => '',
103 | 'HTTP_USER_AGENT' => '',
104 | 'HTTP_ACCEPT' => '',
105 | 'HTTP_ACCEPT_LANGUAGE' => '',
106 | 'HTTP_ACCEPT_ENCODING' => '',
107 | 'HTTP_COOKIE' => '',
108 | 'HTTP_CONNECTION' => '',
109 | 'REMOTE_ADDR' => '',
110 | 'REMOTE_PORT' => '0',
111 | 'REQUEST_TIME' => time()
112 | );
113 |
114 | // Parse headers.
115 | list($http_header, $http_body) = explode("\r\n\r\n", $recv_buffer, 2);
116 | $header_data = explode("\r\n", $http_header);
117 |
118 | list($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI'], $_SERVER['SERVER_PROTOCOL']) = explode(' ',
119 | $header_data[0]);
120 |
121 | $http_post_boundary = '';
122 | unset($header_data[0]);
123 | foreach ($header_data as $content) {
124 | // \r\n\r\n
125 | if (empty($content)) {
126 | continue;
127 | }
128 | list($key, $value) = explode(':', $content, 2);
129 | $key = str_replace('-', '_', strtoupper($key));
130 | $value = trim($value);
131 | $_SERVER['HTTP_' . $key] = $value;
132 | switch ($key) {
133 | // HTTP_HOST
134 | case 'HOST':
135 | $tmp = explode(':', $value);
136 | $_SERVER['SERVER_NAME'] = $tmp[0];
137 | if (isset($tmp[1])) {
138 | $_SERVER['SERVER_PORT'] = $tmp[1];
139 | }
140 | break;
141 | // cookie
142 | case 'COOKIE':
143 | parse_str(str_replace('; ', '&', $_SERVER['HTTP_COOKIE']), $_COOKIE);
144 | break;
145 | // content-type
146 | case 'CONTENT_TYPE':
147 | if (!preg_match('/boundary="?(\S+)"?/', $value, $match)) {
148 | if ($pos = strpos($value, ';')) {
149 | $_SERVER['CONTENT_TYPE'] = substr($value, 0, $pos);
150 | } else {
151 | $_SERVER['CONTENT_TYPE'] = $value;
152 | }
153 | } else {
154 | $_SERVER['CONTENT_TYPE'] = 'multipart/form-data';
155 | $http_post_boundary = '--' . $match[1];
156 | }
157 | break;
158 | case 'CONTENT_LENGTH':
159 | $_SERVER['CONTENT_LENGTH'] = $value;
160 | break;
161 | }
162 | }
163 |
164 | // Parse $_POST.
165 | if ($_SERVER['REQUEST_METHOD'] === 'POST') {
166 | if (isset($_SERVER['CONTENT_TYPE'])) {
167 | switch ($_SERVER['CONTENT_TYPE']) {
168 | case 'multipart/form-data':
169 | self::parseUploadFiles($http_body, $http_post_boundary);
170 | break;
171 | case 'application/x-www-form-urlencoded':
172 | parse_str($http_body, $_POST);
173 | break;
174 | }
175 | }
176 | }
177 |
178 | // HTTP_RAW_REQUEST_DATA HTTP_RAW_POST_DATA
179 | $GLOBALS['HTTP_RAW_REQUEST_DATA'] = $GLOBALS['HTTP_RAW_POST_DATA'] = $http_body;
180 |
181 | // QUERY_STRING
182 | $_SERVER['QUERY_STRING'] = parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY);
183 | if ($_SERVER['QUERY_STRING']) {
184 | // $GET
185 | parse_str($_SERVER['QUERY_STRING'], $_GET);
186 | } else {
187 | $_SERVER['QUERY_STRING'] = '';
188 | }
189 |
190 | // REQUEST
191 | $_REQUEST = array_merge($_GET, $_POST);
192 |
193 | // REMOTE_ADDR REMOTE_PORT
194 | $_SERVER['REMOTE_ADDR'] = $connection->getRemoteIp();
195 | $_SERVER['REMOTE_PORT'] = $connection->getRemotePort();
196 |
197 | return array('get' => $_GET, 'post' => $_POST, 'cookie' => $_COOKIE, 'server' => $_SERVER, 'files' => $_FILES);
198 | }
199 |
200 | /**
201 | * Http encode.
202 | *
203 | * @param string $content
204 | * @param TcpConnection $connection
205 | * @return string
206 | */
207 | public static function encode($content, TcpConnection $connection)
208 | {
209 | // Default http-code.
210 | if (!isset(HttpCache::$header['Http-Code'])) {
211 | $header = "HTTP/1.1 200 OK\r\n";
212 | } else {
213 | $header = HttpCache::$header['Http-Code'] . "\r\n";
214 | unset(HttpCache::$header['Http-Code']);
215 | }
216 |
217 | // Content-Type
218 | if (!isset(HttpCache::$header['Content-Type'])) {
219 | $header .= "Content-Type: text/html;charset=utf-8\r\n";
220 | }
221 |
222 | // other headers
223 | foreach (HttpCache::$header as $key => $item) {
224 | if ('Set-Cookie' === $key && is_array($item)) {
225 | foreach ($item as $it) {
226 | $header .= $it . "\r\n";
227 | }
228 | } else {
229 | $header .= $item . "\r\n";
230 | }
231 | }
232 |
233 | // header
234 | $header .= "Server: Workerman/" . Worker::VERSION . "\r\nContent-Length: " . strlen($content) . "\r\n\r\n";
235 |
236 | // save session
237 | self::sessionWriteClose();
238 |
239 | // the whole http package
240 | return $header . $content;
241 | }
242 |
243 | /**
244 | * 设置http头
245 | *
246 | * @return bool|void
247 | */
248 | public static function header($content, $replace = true, $http_response_code = 0)
249 | {
250 | if (PHP_SAPI != 'cli') {
251 | return $http_response_code ? header($content, $replace, $http_response_code) : header($content, $replace);
252 | }
253 | if (strpos($content, 'HTTP') === 0) {
254 | $key = 'Http-Code';
255 | } else {
256 | $key = strstr($content, ":", true);
257 | if (empty($key)) {
258 | return false;
259 | }
260 | }
261 |
262 | if ('location' === strtolower($key) && !$http_response_code) {
263 | return self::header($content, true, 302);
264 | }
265 |
266 | if (isset(HttpCache::$codes[$http_response_code])) {
267 | HttpCache::$header['Http-Code'] = "HTTP/1.1 $http_response_code " . HttpCache::$codes[$http_response_code];
268 | if ($key === 'Http-Code') {
269 | return true;
270 | }
271 | }
272 |
273 | if ($key === 'Set-Cookie') {
274 | HttpCache::$header[$key][] = $content;
275 | } else {
276 | HttpCache::$header[$key] = $content;
277 | }
278 |
279 | return true;
280 | }
281 |
282 | /**
283 | * Remove header.
284 | *
285 | * @param string $name
286 | * @return void
287 | */
288 | public static function headerRemove($name)
289 | {
290 | if (PHP_SAPI != 'cli') {
291 | header_remove($name);
292 | return;
293 | }
294 | unset(HttpCache::$header[$name]);
295 | }
296 |
297 | /**
298 | * Set cookie.
299 | *
300 | * @param string $name
301 | * @param string $value
302 | * @param integer $maxage
303 | * @param string $path
304 | * @param string $domain
305 | * @param bool $secure
306 | * @param bool $HTTPOnly
307 | * @return bool|void
308 | */
309 | public static function setcookie(
310 | $name,
311 | $value = '',
312 | $maxage = 0,
313 | $path = '',
314 | $domain = '',
315 | $secure = false,
316 | $HTTPOnly = false
317 | ) {
318 | if (PHP_SAPI != 'cli') {
319 | return setcookie($name, $value, $maxage, $path, $domain, $secure, $HTTPOnly);
320 | }
321 | return self::header(
322 | 'Set-Cookie: ' . $name . '=' . rawurlencode($value)
323 | . (empty($domain) ? '' : '; Domain=' . $domain)
324 | . (empty($maxage) ? '' : '; Max-Age=' . $maxage)
325 | . (empty($path) ? '' : '; Path=' . $path)
326 | . (!$secure ? '' : '; Secure')
327 | . (!$HTTPOnly ? '' : '; HttpOnly'), false);
328 | }
329 |
330 | /**
331 | * sessionStart
332 | *
333 | * @return bool
334 | */
335 | public static function sessionStart()
336 | {
337 | if (PHP_SAPI != 'cli') {
338 | return session_start();
339 | }
340 |
341 | self::tryGcSessions();
342 |
343 | if (HttpCache::$instance->sessionStarted) {
344 | echo "already sessionStarted\n";
345 | return true;
346 | }
347 | HttpCache::$instance->sessionStarted = true;
348 | // Generate a SID.
349 | if (!isset($_COOKIE[HttpCache::$sessionName]) || !is_file(HttpCache::$sessionPath . '/ses' . $_COOKIE[HttpCache::$sessionName])) {
350 | $file_name = tempnam(HttpCache::$sessionPath, 'ses');
351 | if (!$file_name) {
352 | return false;
353 | }
354 | HttpCache::$instance->sessionFile = $file_name;
355 | $session_id = substr(basename($file_name), strlen('ses'));
356 | return self::setcookie(
357 | HttpCache::$sessionName
358 | , $session_id
359 | , ini_get('session.cookie_lifetime')
360 | , ini_get('session.cookie_path')
361 | , ini_get('session.cookie_domain')
362 | , ini_get('session.cookie_secure')
363 | , ini_get('session.cookie_httponly')
364 | );
365 | }
366 | if (!HttpCache::$instance->sessionFile) {
367 | HttpCache::$instance->sessionFile = HttpCache::$sessionPath . '/ses' . $_COOKIE[HttpCache::$sessionName];
368 | }
369 | // Read session from session file.
370 | if (HttpCache::$instance->sessionFile) {
371 | $raw = file_get_contents(HttpCache::$instance->sessionFile);
372 | if ($raw) {
373 | $_SESSION = unserialize($raw);
374 | }
375 | }
376 | return true;
377 | }
378 |
379 | /**
380 | * Save session.
381 | *
382 | * @return bool
383 | */
384 | public static function sessionWriteClose()
385 | {
386 | if (PHP_SAPI != 'cli') {
387 | return session_write_close();
388 | }
389 | if (!empty(HttpCache::$instance->sessionStarted) && !empty($_SESSION)) {
390 | $session_str = serialize($_SESSION);
391 | if ($session_str && HttpCache::$instance->sessionFile) {
392 | return file_put_contents(HttpCache::$instance->sessionFile, $session_str);
393 | }
394 | }
395 | return empty($_SESSION);
396 | }
397 |
398 | /**
399 | * End, like call exit in php-fpm.
400 | *
401 | * @param string $msg
402 | * @throws \Exception
403 | */
404 | public static function end($msg = '')
405 | {
406 | if (PHP_SAPI != 'cli') {
407 | exit($msg);
408 | }
409 | if ($msg) {
410 | echo $msg;
411 | }
412 | throw new \Exception('jump_exit');
413 | }
414 |
415 | /**
416 | * Get mime types.
417 | *
418 | * @return string
419 | */
420 | public static function getMimeTypesFile()
421 | {
422 | return __DIR__ . '/Http/mime.types';
423 | }
424 |
425 | /**
426 | * Parse $_FILES.
427 | *
428 | * @param string $http_body
429 | * @param string $http_post_boundary
430 | * @return void
431 | */
432 | protected static function parseUploadFiles($http_body, $http_post_boundary)
433 | {
434 | $http_body = substr($http_body, 0, strlen($http_body) - (strlen($http_post_boundary) + 4));
435 | $boundary_data_array = explode($http_post_boundary . "\r\n", $http_body);
436 | if ($boundary_data_array[0] === '') {
437 | unset($boundary_data_array[0]);
438 | }
439 | $key = -1;
440 | foreach ($boundary_data_array as $boundary_data_buffer) {
441 | list($boundary_header_buffer, $boundary_value) = explode("\r\n\r\n", $boundary_data_buffer, 2);
442 | // Remove \r\n from the end of buffer.
443 | $boundary_value = substr($boundary_value, 0, -2);
444 | $key ++;
445 | foreach (explode("\r\n", $boundary_header_buffer) as $item) {
446 | list($header_key, $header_value) = explode(": ", $item);
447 | $header_key = strtolower($header_key);
448 | switch ($header_key) {
449 | case "content-disposition":
450 | // Is file data.
451 | if (preg_match('/name="(.*?)"; filename="(.*?)"$/', $header_value, $match)) {
452 | // Parse $_FILES.
453 | $_FILES[$key] = array(
454 | 'name' => $match[1],
455 | 'file_name' => $match[2],
456 | 'file_data' => $boundary_value,
457 | 'file_size' => strlen($boundary_value),
458 | );
459 | continue;
460 | } // Is post field.
461 | else {
462 | // Parse $_POST.
463 | if (preg_match('/name="(.*?)"$/', $header_value, $match)) {
464 | $_POST[$match[1]] = $boundary_value;
465 | }
466 | }
467 | break;
468 | case "content-type":
469 | // add file_type
470 | $_FILES[$key]['file_type'] = trim($header_value);
471 | break;
472 | }
473 | }
474 | }
475 | }
476 |
477 | /**
478 | * Try GC sessions.
479 | *
480 | * @return void
481 | */
482 | public static function tryGcSessions()
483 | {
484 | if (HttpCache::$sessionGcProbability <= 0 ||
485 | HttpCache::$sessionGcDivisor <= 0 ||
486 | rand(1, HttpCache::$sessionGcDivisor) > HttpCache::$sessionGcProbability) {
487 | return;
488 | }
489 |
490 | $time_now = time();
491 | foreach(glob(HttpCache::$sessionPath.'/ses*') as $file) {
492 | if(is_file($file) && $time_now - filemtime($file) > HttpCache::$sessionGcMaxLifeTime) {
493 | unlink($file);
494 | }
495 | }
496 | }
497 | }
498 |
499 | /**
500 | * Http cache for the current http response.
501 | */
502 | class HttpCache
503 | {
504 | public static $codes = array(
505 | 100 => 'Continue',
506 | 101 => 'Switching Protocols',
507 | 200 => 'OK',
508 | 201 => 'Created',
509 | 202 => 'Accepted',
510 | 203 => 'Non-Authoritative Information',
511 | 204 => 'No Content',
512 | 205 => 'Reset Content',
513 | 206 => 'Partial Content',
514 | 300 => 'Multiple Choices',
515 | 301 => 'Moved Permanently',
516 | 302 => 'Found',
517 | 303 => 'See Other',
518 | 304 => 'Not Modified',
519 | 305 => 'Use Proxy',
520 | 306 => '(Unused)',
521 | 307 => 'Temporary Redirect',
522 | 400 => 'Bad Request',
523 | 401 => 'Unauthorized',
524 | 402 => 'Payment Required',
525 | 403 => 'Forbidden',
526 | 404 => 'Not Found',
527 | 405 => 'Method Not Allowed',
528 | 406 => 'Not Acceptable',
529 | 407 => 'Proxy Authentication Required',
530 | 408 => 'Request Timeout',
531 | 409 => 'Conflict',
532 | 410 => 'Gone',
533 | 411 => 'Length Required',
534 | 412 => 'Precondition Failed',
535 | 413 => 'Request Entity Too Large',
536 | 414 => 'Request-URI Too Long',
537 | 415 => 'Unsupported Media Type',
538 | 416 => 'Requested Range Not Satisfiable',
539 | 417 => 'Expectation Failed',
540 | 422 => 'Unprocessable Entity',
541 | 423 => 'Locked',
542 | 500 => 'Internal Server Error',
543 | 501 => 'Not Implemented',
544 | 502 => 'Bad Gateway',
545 | 503 => 'Service Unavailable',
546 | 504 => 'Gateway Timeout',
547 | 505 => 'HTTP Version Not Supported',
548 | );
549 |
550 | /**
551 | * @var HttpCache
552 | */
553 | public static $instance = null;
554 | public static $header = array();
555 | public static $sessionPath = '';
556 | public static $sessionName = '';
557 | public static $sessionGcProbability = 1;
558 | public static $sessionGcDivisor = 1000;
559 | public static $sessionGcMaxLifeTime = 1440;
560 | public $sessionStarted = false;
561 | public $sessionFile = '';
562 |
563 | public static function init()
564 | {
565 | self::$sessionName = ini_get('session.name');
566 | self::$sessionPath = @session_save_path();
567 | if (!self::$sessionPath || strpos(self::$sessionPath, 'tcp://') === 0) {
568 | self::$sessionPath = sys_get_temp_dir();
569 | }
570 |
571 | if ($gc_probability = ini_get('session.gc_probability')) {
572 | self::$sessionGcProbability = $gc_probability;
573 | }
574 |
575 | if ($gc_divisor = ini_get('session.gc_divisor')) {
576 | self::$sessionGcDivisor = $gc_divisor;
577 | }
578 |
579 | if ($gc_max_life_time = ini_get('session.gc_maxlifetime')) {
580 | self::$sessionGcMaxLifeTime = $gc_max_life_time;
581 | }
582 | }
583 | }
584 |
585 | HttpCache::init();
586 |
--------------------------------------------------------------------------------