来源: thinkPHP3.2.3集成swoole扩展 – 林中侠客 – 博客园

swoole.php
1 #!/bin/env php
2 <?php
3 /**
4 * 默认时区定义
5 */
6 date_default_timezone_set('Asia/Shanghai');
7
8 /**
9 * 设置错误报告模式
10 */
11 error_reporting(0);
12
13 /**
14 * 设置默认区域
15 */
16 setlocale(LC_ALL, "zh_CN.utf-8");
17
18 /**
19 * 检测 PDO_MYSQL
20 */
21 if (!extension_loaded('pdo_mysql')) {
22 exit('PDO_MYSQL extension is not installed' . PHP_EOL);
23 }
24 /**
25 * 检查exec 函数是否启用
26 */
27 if (!function_exists('exec')) {
28 exit('exec function is disabled' . PHP_EOL);
29 }
30 /**
31 * 检查命令 lsof 命令是否存在
32 */
33 exec("whereis lsof", $out);
34 if ($out[0] == 'lsof:') {
35 exit('lsof is not found' . PHP_EOL);
36 }
37 /**
38 * 定义项目根目录&swoole-task pid
39 */
40 define('SWOOLE_PATH', __DIR__);
41 define('SWOOLE_TASK_PID_PATH', SWOOLE_PATH . DIRECTORY_SEPARATOR . 'Swoole' . DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR . 'swoole-task.pid');
42 define('SWOOLE_TASK_NAME_PRE', 'swooleServ');
43
44 /**
45 * 加载 swoole server
46 */
47 include SWOOLE_PATH . DIRECTORY_SEPARATOR . 'Swoole' . DIRECTORY_SEPARATOR . 'SwooleServer.php';
48
49 function portBind($port) {
50 $ret = [];
51 $cmd = "lsof -i :{$port}|awk '$1 != \"COMMAND\" {print $1, $2, $9}'";
52 exec($cmd, $out);
53 if ($out) {
54 foreach ($out as $v) {
55 $a = explode(' ', $v);
56 list($ip, $p) = explode(':', $a[2]);
57 $ret[$a[1]] = [
58 'cmd' => $a[0],
59 'ip' => $ip,
60 'port' => $p,
61 ];
62 }
63 }
64
65 return $ret;
66 }
67
68 function servStart($host, $port, $daemon, $name) {
69 echo "正在启动 swoole-task 服务" . PHP_EOL;
70 if (!is_writable(dirname(SWOOLE_TASK_PID_PATH))) {
71 exit("swoole-task-pid文件需要目录的写入权限:" . dirname(SWOOLE_TASK_PID_PATH) . PHP_EOL);
72 }
73 if (file_exists(SWOOLE_TASK_PID_PATH)) {
74 $pid = explode("\n", file_get_contents(SWOOLE_TASK_PID_PATH));
75 $cmd = "ps ax | awk '{ print $1 }' | grep -e \"^{$pid[0]}$\"";
76 exec($cmd, $out);
77 if (!empty($out)) {
78 exit("swoole-task pid文件 " . SWOOLE_TASK_PID_PATH . " 存在,swoole-task 服务器已经启动,进程pid为:{$pid[0]}" . PHP_EOL);
79 } else {
80 echo "警告:swoole-task pid文件 " . SWOOLE_TASK_PID_PATH . " 存在,可能swoole-task服务上次异常退出(非守护模式ctrl+c终止造成是最大可能)" . PHP_EOL;
81 unlink(SWOOLE_TASK_PID_PATH);
82 }
83 }
84 $bind = portBind($port);
85 if ($bind) {
86 foreach ($bind as $k => $v) {
87 if ($v['ip'] == '*' || $v['ip'] == $host) {
88 exit("端口已经被占用 {$host}:$port, 占用端口进程ID {$k}" . PHP_EOL);
89 }
90 }
91 }
92 unset($_SERVER['argv']);
93 $_SERVER['argc'] = 0;
94 echo "启动 swoole-task 服务成功" . PHP_EOL;
95 $server = new SwooleServer('127.0.0.1', 9501);
96 $server->run();
97 //确保服务器启动后swoole-task-pid文件必须生成
98 /*if (!empty(portBind($port)) && !file_exists(SWOOLE_TASK_PID_PATH)) {
99 exit("swoole-task pid文件生成失败( " . SWOOLE_TASK_PID_PATH . ") ,请手动关闭当前启动的swoole-task服务检查原因" . PHP_EOL);
100 }*/
101 }
102
103 function servStop($host, $port, $isRestart = false) {
104 echo "正在停止 swoole-task 服务" . PHP_EOL;
105 if (!file_exists(SWOOLE_TASK_PID_PATH)) {
106 exit('swoole-task-pid文件:' . SWOOLE_TASK_PID_PATH . '不存在' . PHP_EOL);
107 }
108 $pid = explode("\n", file_get_contents(SWOOLE_TASK_PID_PATH));
109 $bind = portBind($port);
110 if (empty($bind) || !isset($bind[$pid[0]])) {
111 exit("指定端口占用进程不存在 port:{$port}, pid:{$pid[0]}" . PHP_EOL);
112 }
113 $cmd = "kill {$pid[0]}";
114 exec($cmd);
115 do {
116 $out = [];
117 $c = "ps ax | awk '{ print $1 }' | grep -e \"^{$pid[0]}$\"";
118 exec($c, $out);
119 if (empty($out)) {
120 break;
121 }
122 } while (true);
123 //确保停止服务后swoole-task-pid文件被删除
124 if (file_exists(SWOOLE_TASK_PID_PATH)) {
125 unlink(SWOOLE_TASK_PID_PATH);
126 }
127 $msg = "执行命令 {$cmd} 成功,端口 {$host}:{$port} 进程结束" . PHP_EOL;
128 if ($isRestart) {
129 echo $msg;
130 } else {
131 exit($msg);
132 }
133 }
134
135 function servReload($host, $port, $isRestart = false) {
136 echo "正在平滑重启 swoole-task 服务" . PHP_EOL;
137 try {
138 $client = new \swoole_client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC);
139 $ret = $client->connect($host, $port);
140 if (empty($ret)) {
141 exit("{$host}:{$port} swoole-task服务不存在或者已经关闭" . PHP_EOL);
142 } else {
143 $client->send(json_encode(array('action' => 'reload')));
144 }
145 $msg = "执行命令reload成功,端口 {$host}:{$port} 进程重启" . PHP_EOL;
146 if ($isRestart) {
147 echo $msg;
148 } else {
149 exit($msg);
150 }
151 } catch (Exception $e) {
152 exit($e->getMessage() . PHP_EOL . $e->getTraceAsString());
153 }
154 }
155
156 function servClose($host, $port, $isRestart = false) {
157 echo "正在关闭 swoole-task 服务" . PHP_EOL;
158 try {
159 $client = new \swoole_client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC);
160 $ret = $client->connect($host, $port);
161 if (empty($ret)) {
162 exit("{$host}:{$port} swoole-task服务不存在或者已经关闭" . PHP_EOL);
163 } else {
164 $client->send(json_encode(array('action' => 'close')));
165 }
166 //确保停止服务后swoole-task-pid文件被删除
167 if (file_exists(SWOOLE_TASK_PID_PATH)) {
168 unlink(SWOOLE_TASK_PID_PATH);
169 }
170 $msg = "执行命令close成功,端口 {$host}:{$port} 进程结束" . PHP_EOL;
171 if ($isRestart) {
172 echo $msg;
173 } else {
174 exit($msg);
175 }
176 } catch (\Exception $e) {
177 exit($e->getMessage() . PHP_EOL . $e->getTraceAsString());
178 }
179 }
180
181 function servStatus($host, $port) {
182 echo "swoole-task {$host}:{$port} 运行状态" . PHP_EOL;
183 $pid = explode("\n", file_get_contents(SWOOLE_TASK_PID_PATH));
184 $bind = portBind($port);
185 if (empty($bind) || !isset($bind[$pid[0]])) {
186 exit("指定端口占用进程不存在 port:{$port}, pid:{$pid[0]}" . PHP_EOL);
187 }
188 $client = new \swoole_client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC);
189 $ret = $client->connect($host, $port);
190 if (empty($ret)) {
191 exit("{$host}:{$port} swoole-task服务不存在或者已经停止" . PHP_EOL);
192 } else {
193 $client->send(json_encode(array('action' => 'status')));
194 $out = $client->recv();
195 $a = json_decode($out);
196 $b = array(
197 'start_time' => '服务器启动的时间',
198 'connection_num' => '当前连接的数量',
199 'accept_count' => '接受的连接数量',
200 'close_count' => '关闭的连接数量',
201 'tasking_num' => '当前正在排队的任务数',
202 'request_count' => '请求的连接数量',
203 'worker_request_count' => 'worker连接数量',
204 'task_process_num' => '任务进程数量'
205 );
206 foreach ($a as $k1 => $v1) {
207 if ($k1 == 'start_time') {
208 $v1 = date("Y-m-d H:i:s", $v1);
209 }
210 echo $b[$k1] . ":\t$v1" . PHP_EOL;
211 }
212 }
213 exit();
214 }
215
216 function servList() {
217 echo "本机运行的swoole-task服务进程" . PHP_EOL;
218 $cmd = "ps aux|grep " . SWOOLE_TASK_NAME_PRE . "|grep -v grep|awk '{print $1, $2, $6, $8, $9, $11}'";
219 exec($cmd, $out);
220 if (empty($out)) {
221 exit("没有发现正在运行的swoole-task服务" . PHP_EOL);
222 }
223 echo "USER PID RSS(kb) STAT START COMMAND" . PHP_EOL;
224 foreach ($out as $v) {
225 echo $v . PHP_EOL;
226 }
227 exit();
228 }
229
230 //可执行命令
231 $cmds = [
232 'start',
233 'stop',
234 'restart',
235 'reload',
236 'close',
237 'status',
238 'list',
239 ];
240 $shortopts = "dDh:p:n:";
241 $longopts = [
242 'help',
243 'daemon',
244 'nondaemon',
245 'host:',
246 'port:',
247 'name:',
248 ];
249 $opts = getopt($shortopts, $longopts);
250
251 if (isset($opts['help']) || $argc < 2) {
252 echo <<<HELP
253 用法:php swoole.php 选项 ... 命令[start|stop|restart|reload|close|status|list]
254 管理swoole-task服务,确保系统 lsof 命令有效
255 如果不指定监听host或者port,使用配置参数
256
257 参数说明
258 --help 显示本帮助说明
259 -d, --daemon 指定此参数,以守护进程模式运行,不指定则读取配置文件值
260 -D, --nondaemon 指定此参数,以非守护进程模式运行,不指定则读取配置文件值
261 -h, --host 指定监听ip,例如 php swoole.php -h127.0.0.1
262 -p, --port 指定监听端口port, 例如 php swoole.php -h127.0.0.1 -p9520
263 -n, --name 指定服务进程名称,例如 php swoole.php -ntest start, 则进程名称为SWOOLE_TASK_NAME_PRE-name
264 启动swoole-task 如果不指定 host和port,读取默认配置
265 强制关闭swoole-task 必须指定port,没有指定host,关闭的监听端口是 *:port,指定了host,关闭 host:port端口
266 平滑关闭swoole-task 必须指定port,没有指定host,关闭的监听端口是 *:port,指定了host,关闭 host:port端口
267 强制重启swoole-task 必须指定端口
268 平滑重启swoole-task 必须指定端口
269 获取swoole-task 状态,必须指定port(不指定host默认127.0.0.1), tasking_num是正在处理的任务数量(0表示没有待处理任务)
270
271 HELP;
272 exit;
273 }
274 //参数检查
275 foreach ($opts as $k => $v) {
276 if (($k == 'h' || $k == 'host')) {
277 if (empty($v)) {
278 exit("参数 -h --host 必须指定值\n");
279 }
280 }
281 if (($k == 'p' || $k == 'port')) {
282 if (empty($v)) {
283 exit("参数 -p --port 必须指定值\n");
284 }
285 }
286 if (($k == 'n' || $k == 'name')) {
287 if (empty($v)) {
288 exit("参数 -n --name 必须指定值\n");
289 }
290 }
291 }
292
293 //命令检查
294 $cmd = $argv[$argc - 1];
295 if (!in_array($cmd, $cmds)) {
296 exit("输入命令有误 : {$cmd}, 请查看帮助文档\n");
297 }
298
299 //监听ip 127.0.0.1,空读取配置文件
300 $host = '127.0.0.1';
301 if (!empty($opts['h'])) {
302 $host = $opts['h'];
303 if (!filter_var($host, FILTER_VALIDATE_IP)) {
304 exit("输入host有误:{$host}");
305 }
306 }
307 if (!empty($opts['host'])) {
308 $host = $opts['host'];
309 if (!filter_var($host, FILTER_VALIDATE_IP)) {
310 exit("输入host有误:{$host}");
311 }
312 }
313 //监听端口,9501 读取配置文件
314 $port = 9501;
315 if (!empty($opts['p'])) {
316 $port = (int)$opts['p'];
317 if ($port <= 0) {
318 exit("输入port有误:{$port}");
319 }
320 }
321 if (!empty($opts['port'])) {
322 $port = (int)$opts['port'];
323 if ($port <= 0) {
324 exit("输入port有误:{$port}");
325 }
326 }
327 //进程名称 没有默认为 SWOOLE_TASK_NAME_PRE;
328 $name = SWOOLE_TASK_NAME_PRE;
329 if (!empty($opts['n'])) {
330 $name = $opts['n'];
331 }
332 if (!empty($opts['name'])) {
333 $name = $opts['n'];
334 }
335 //是否守护进程 -1 读取配置文件
336 $isdaemon = -1;
337 if (isset($opts['D']) || isset($opts['nondaemon'])) {
338 $isdaemon = 0;
339 }
340 if (isset($opts['d']) || isset($opts['daemon'])) {
341 $isdaemon = 1;
342 }
343 //启动swoole-task服务
344 if ($cmd == 'start') {
345 servStart($host, $port, $isdaemon, $name);
346 }
347 //强制停止swoole-task服务
348 if ($cmd == 'stop') {
349 if (empty($port)) {
350 exit("停止swoole-task服务必须指定port" . PHP_EOL);
351 }
352 servStop($host, $port);
353 }
354 //关闭swoole-task服务
355 if ($cmd == 'close') {
356 if (empty($port)) {
357 exit("停止swoole-task服务必须指定port" . PHP_EOL);
358 }
359 servClose($host, $port);
360 }
361 //强制重启swoole-task服务
362 if ($cmd == 'restart') {
363 if (empty($port)) {
364 exit("重启swoole-task服务必须指定port" . PHP_EOL);
365 }
366 echo "重启swoole-task服务" . PHP_EOL;
367 servStop($host, $port, true);
368 servStart($host, $port, $isdaemon, $name);
369 }
370 //平滑重启swoole-task服务
371 if ($cmd == 'reload') {
372 if (empty($port)) {
373 exit("平滑重启swoole-task服务必须指定port" . PHP_EOL);
374 }
375 echo "平滑重启swoole-task服务" . PHP_EOL;
376 servReload($host, $port, true);
377 }
378 //查看swoole-task服务状态
379 if ($cmd == 'status') {
380 if (empty($host)) {
381 $host = '127.0.0.1';
382 }
383 if (empty($port)) {
384 exit("查看swoole-task服务必须指定port(host不指定默认使用127.0.0.1)" . PHP_EOL);
385 }
386 servStatus($host, $port);
387 }
388 //查看swoole-task服务进程列表
389 if ($cmd == 'list') {
390 servList();
391 }
SwooleServer.php
1 <?php
2
3 /**
4 * Swoole服务端
5 */
6 class SwooleServer {
7
8 private $_serv = null;
9 private $_setting = array();
10
11 public function __construct($host = '0.0.0.0', $port = 9501) {
12 $this->_setting = array(
13 'host' => $host,
14 'port' => $port,
15 'env' => 'dev', //环境 dev|test|prod
16 'process_name' => SWOOLE_TASK_NAME_PRE, //swoole 进程名称
17 'worker_num' => 4, //一般设置为服务器CPU数的1-4倍
18 'task_worker_num' => 4, //task进程的数量
19 'task_ipc_mode' => 3, //使用消息队列通信,并设置为争抢模式
20 'task_max_request' => 10000, //task进程的最大任务数
21 'daemonize' => 1, //以守护进程执行
22 'max_request' => 10000,
23 'dispatch_mode' => 2,
24 'log_file' => SWOOLE_PATH . DIRECTORY_SEPARATOR . 'App' . DIRECTORY_SEPARATOR . 'Runtime' . DIRECTORY_SEPARATOR . 'Logs' . DIRECTORY_SEPARATOR . 'Swoole' . date('Ymd') . '.log', //日志
25 );
26 }
27
28 /**
29 * 运行swoole服务
30 */
31 public function run() {
32 $this->_serv = new \swoole_server($this->_setting['host'], $this->_setting['port']);
33 $this->_serv->set(array(
34 'worker_num' => $this->_setting['worker_num'],
35 'task_worker_num' => $this->_setting['task_worker_num'],
36 'task_ipc_mode ' => $this->_setting['task_ipc_mode'],
37 'task_max_request' => $this->_setting['task_max_request'],
38 'daemonize' => $this->_setting['daemonize'],
39 'max_request' => $this->_setting['max_request'],
40 'dispatch_mode' => $this->_setting['dispatch_mode'],
41 'log_file' => $this->_setting['log_file']
42 ));
43 $this->_serv->on('Start', array($this, 'onStart'));
44 $this->_serv->on('Connect', array($this, 'onConnect'));
45 $this->_serv->on('WorkerStart', array($this, 'onWorkerStart'));
46 $this->_serv->on('ManagerStart', array($this, 'onManagerStart'));
47 $this->_serv->on('WorkerStop', array($this, 'onWorkerStop'));
48 $this->_serv->on('Receive', array($this, 'onReceive'));
49 $this->_serv->on('Task', array($this, 'onTask'));
50 $this->_serv->on('Finish', array($this, 'onFinish'));
51 $this->_serv->on('Shutdown', array($this, 'onShutdown'));
52 $this->_serv->on('Close', array($this, 'onClose'));
53 $this->_serv->start();
54 }
55
56 /**
57 * 设置swoole进程名称
58 * @param string $name swoole进程名称
59 */
60 private function setProcessName($name) {
61 if (function_exists('cli_set_process_title')) {
62 cli_set_process_title($name);
63 } else {
64 if (function_exists('swoole_set_process_name')) {
65 swoole_set_process_name($name);
66 } else {
67 trigger_error(__METHOD__ . " failed. require cli_set_process_title or swoole_set_process_name.");
68 }
69 }
70 }
71
72 /**
73 * Server启动在主进程的主线程回调此函数
74 * @param $serv
75 */
76 public function onStart($serv) {
77 if (!$this->_setting['daemonize']) {
78 echo 'Date:' . date('Y-m-d H:i:s') . "\t swoole_server master worker start\n";
79 }
80 $this->setProcessName($this->_setting['process_name'] . '-master');
81 //记录进程id,脚本实现自动重启
82 $pid = "{$serv->master_pid}\n{$serv->manager_pid}";
83 file_put_contents(SWOOLE_TASK_PID_PATH, $pid);
84 }
85
86 /**
87 * worker start 加载业务脚本常驻内存
88 * @param $server
89 * @param $workerId
90 */
91 public function onWorkerStart($serv, $workerId) {
92 if ($workerId >= $this->_setting['worker_num']) {
93 $this->setProcessName($this->_setting['process_name'] . '-task');
94 } else {
95 $this->setProcessName($this->_setting['process_name'] . '-event');
96 }
97 // 开启调试模式 建议开发阶段开启 部署阶段注释或者设为false
98 define('APP_DEBUG', True);
99 // 定义应用目录
100 define('APP_PATH', SWOOLE_PATH . DIRECTORY_SEPARATOR . 'Application' . DIRECTORY_SEPARATOR);
101 // 定义应用模式
102 define('APP_MODE', 'cli');
103 // 引入ThinkPHP入口文件
104 require SWOOLE_PATH . DIRECTORY_SEPARATOR . 'ThinkPHP' . DIRECTORY_SEPARATOR . 'ThinkPHP.php';
105 }
106
107 /**
108 * 监听连接进入事件
109 * @param $serv
110 * @param $fd
111 */
112 public function onConnect($serv, $fd) {
113 if (!$this->_setting['daemonize']) {
114 echo 'Date:' . date('Y-m-d H:i:s') . "\t swoole_server connect[" . $fd . "]\n";
115 }
116 }
117
118 /**
119 * worker 进程停止
120 * @param $server
121 * @param $workerId
122 */
123 public function onWorkerStop($serv, $workerId) {
124 if (!$this->_setting['daemonize']) {
125 echo 'Date:' . date('Y-m-d H:i:s') . "\t swoole_server[{$serv->setting['process_name']} worker:{$workerId} shutdown\n";
126 }
127 }
128
129 /**
130 * 当管理进程启动时调用
131 * @param $serv
132 */
133 public function onManagerStart($serv) {
134 if (!$this->_setting['daemonize']) {
135 echo 'Date:' . date('Y-m-d H:i:s') . "\t swoole_server manager worker start\n";
136 }
137 $this->setProcessName($this->_setting['process_name'] . '-manager');
138 }
139
140 /**
141 * 此事件在Server结束时发生
142 */
143 public function onShutdown($serv) {
144 if (file_exists(SWOOLE_TASK_PID_PATH)) {
145 unlink(SWOOLE_TASK_PID_PATH);
146 }
147 if (!$this->_setting['daemonize']) {
148 echo 'Date:' . date('Y-m-d H:i:s') . "\t swoole_server shutdown\n";
149 }
150 }
151
152 /**
153 * 监听数据发送事件
154 * @param $serv
155 * @param $fd
156 * @param $from_id
157 * @param $data
158 */
159 public function onReceive($serv, $fd, $from_id, $data) {
160 if (!$this->_setting['daemonize']) {
161 echo "Get Message From Client {$fd}:{$data}\n\n";
162 }
163 $result = json_decode($data, true);
164 switch ($result['action']) {
165 case 'reload': //重启
166 $serv->reload();
167 break;
168 case 'close': //关闭
169 $serv->shutdown();
170 break;
171 case 'status': //状态
172 $serv->send($fd, json_encode($serv->stats()));
173 break;
174 default:
175 $serv->task($data);
176 break;
177 }
178 }
179
180 /**
181 * 监听连接Task事件
182 * @param $serv
183 * @param $task_id
184 * @param $from_id
185 * @param $data
186 */
187 public function onTask($serv, $task_id, $from_id, $data) {
188 $result = json_decode($data, true);
189 //用TP处理各种逻辑
190 $serv->finish($data);
191 }
192
193 /**
194 * 监听连接Finish事件
195 * @param $serv
196 * @param $task_id
197 * @param $data
198 */
199 public function onFinish($serv, $task_id, $data) {
200 if (!$this->_setting['daemonize']) {
201 echo "Task {$task_id} finish\n\n";
202 echo "Result: {$data}\n\n";
203 }
204 }
205
206 /**
207 * 监听连接关闭事件
208 * @param $serv
209 * @param $fd
210 */
211 public function onClose($serv, $fd) {
212 if (!$this->_setting['daemonize']) {
213 echo 'Date:' . date('Y-m-d H:i:s') . "\t swoole_server close[" . $fd . "]\n";
214 }
215 }
216
217
218 }
相关命令:
1、服务启动
#启动服务,不指定绑定端口和ip,则使用默认配置
php swoole.php start
#启动服务 指定ip 和 port
php swoole.php -h127.0.0.1 -p9501 start
#启动服务 守护进程模式
php swoole.php -h127.0.0.1 -p9501 -d start
#启动服务 非守护进程模式
php swoole.php -h127.0.0.1 -p9501 -D start
#启动服务 指定进程名称(显示进程名为 swooleServ-9510-[master|manager|event|task]
php swoole.php -h127.0.0.1 -p9501 -n 9501 start
2、强制服务停止
php swoole.php stop
php swoole.php -p9501 stop
php swoole.php -h127.0.0.1 -p9501 stop
3、关闭服务
php swoole.php close
php swoole.php -p9501 close
php swoole.php -h127.0.0.1 -p9501 close
4、强制服务重启
php swoole.php restart
php swoole.php -p9501 restart
php swoole.php -h127.0.0.1 -p9501 restart
5、平滑服务重启
php swoole.php reload
php swoole.php -p9501 reload
php swoole.php -h127.0.0.1 -p9501 reload
6、服务状态
php swoole.php status
php swoole.php -h127.0.0.1 -p9501 status
7、swoole-task所有启动实例进程列表(一台服务器swoole-task可以有多个端口绑定的实例)
php swoole.php list
Swoole-ThinkPHP.zip ( 1.14 MB )
Mikel
