├── .gitignore
├── src
├── config
│ ├── timer.php
│ ├── swoole.php
│ └── server.php
├── template
│ ├── Task.php
│ ├── Timer.php
│ └── WorkerStart.php
├── facade
│ ├── Task.php
│ ├── Timer.php
│ ├── Http.php
│ └── Application.php
├── command.php
├── Task.php
├── SuperClosure.php
├── queue
│ ├── Queue.php
│ ├── Task.php
│ └── Process.php
├── Cookie.php
├── CacheTable.php
├── cache
│ └── driver
│ │ └── Table.php
├── WebSocketFrame.php
├── Server.php
├── Timer.php
├── command
│ ├── Server.php
│ └── Swoole.php
├── Application.php
├── log
│ └── File.php
├── Http.php
└── Session.php
├── composer.json
├── README.md
└── LICENSE
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | .DS_Store
3 | *.xml
4 | .idea/think-swoole.iml
5 |
--------------------------------------------------------------------------------
/src/config/timer.php:
--------------------------------------------------------------------------------
1 | "类"
9 | * *中间一个空格
10 | * 系统定时任务需要在swoole.php中开启
11 | * 自定义定时器不受其影响
12 | */
13 |
14 | return [
15 | '*/5 * * * * *' => '\\app\\lib\\Timer',
16 | ];
17 |
--------------------------------------------------------------------------------
/src/template/Task.php:
--------------------------------------------------------------------------------
1 | initialize($args);
17 | }
18 |
19 | abstract public function initialize($args);
20 |
21 | abstract public function run($serv, $taskId, $fromWorkerId);
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/src/template/Timer.php:
--------------------------------------------------------------------------------
1 | initialize($args);
18 | }
19 |
20 | abstract public function initialize($args);
21 |
22 | abstract public function run();
23 | }
24 |
--------------------------------------------------------------------------------
/src/facade/Task.php:
--------------------------------------------------------------------------------
1 | server = $server;
20 | $this->worker_id = $worker_id;
21 | $this->_initialize($server, $worker_id);
22 | }
23 |
24 | abstract public function _initialize($server, $worker_id);
25 |
26 | abstract public function run();
27 | }
28 |
--------------------------------------------------------------------------------
/src/command.php:
--------------------------------------------------------------------------------
1 |
10 | // +----------------------------------------------------------------------
11 |
12 | // 注册命令行指令
13 | \think\Console::addDefaultCommands([
14 | 'swoole' => '\\think\\swoole\\command\\Swoole',
15 | 'swoole:server' => '\\think\\swoole\\command\\Server',
16 | ]);
17 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "topthink/think-swoole",
3 | "description": "Swoole extend for thinkphp5.1",
4 | "license": "Apache-2.0",
5 | "type": "think-extend",
6 | "authors": [
7 | {
8 | "name": "liu21st",
9 | "email": "liu21st@gmail.com"
10 | }
11 | ],
12 | "require": {
13 | "ext-swoole": ">=1.9.5",
14 | "topthink/think-installer": "^2.0",
15 | "topthink/framework": "~5.1.20",
16 | "jeremeamia/superclosure": "^2.4",
17 | "xavier/xcron-expression": "^0.11",
18 | "topthink/think-queue": "^2.0"
19 | },
20 | "autoload": {
21 | "psr-4": {
22 | "think\\swoole\\": "src"
23 | },
24 | "files": [
25 | "src/command.php"
26 | ]
27 | },
28 | "extra": {
29 | "think-config": {
30 | "swoole": "src/config/swoole.php",
31 | "swoole_server": "src/config/server.php",
32 | "timer": "src/config/timer.php"
33 | }
34 | }
35 | }
--------------------------------------------------------------------------------
/src/Task.php:
--------------------------------------------------------------------------------
1 | task($task, $taskWorkerId, $finishCallback);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/facade/Http.php:
--------------------------------------------------------------------------------
1 |
10 | // +----------------------------------------------------------------------
11 |
12 | namespace think\swoole\facade;
13 |
14 | use think\Facade;
15 |
16 | /**
17 | * @see \think\swoole\Http
18 | * @mixin \think\swoole\Http
19 | * @method void option(array $option) static 参数设置
20 | * @method void start() static 启动服务
21 | * @method void stop() static 停止服务
22 | */
23 | class Http extends Facade
24 | {
25 | }
26 |
--------------------------------------------------------------------------------
/src/facade/Application.php:
--------------------------------------------------------------------------------
1 |
10 | // +----------------------------------------------------------------------
11 |
12 | namespace think\swoole\facade;
13 |
14 | use Swoole\Http\Request;
15 | use Swoole\Http\Response;
16 | use think\Facade;
17 |
18 | /**
19 | * @see \think\swoole\Application
20 | * @mixin \think\swoole\Application
21 | * @method void initialize() static 初始化应用
22 | * @method void swoole(Request $request, Response $response) static 处理Swoole请求
23 | */
24 | class Application extends Facade
25 | {
26 | }
27 |
--------------------------------------------------------------------------------
/src/SuperClosure.php:
--------------------------------------------------------------------------------
1 | closure = $closure;
20 | }
21 |
22 | final public function __sleep()
23 | {
24 | $serializer = new Serializer();
25 | $this->serialized = $serializer->serialize($this->closure);
26 | unset($this->closure);
27 |
28 | return ['serialized'];
29 | }
30 |
31 | final public function __wakeup()
32 | {
33 | $serializer = new Serializer();
34 | $this->closure = $serializer->unserialize($this->serialized);
35 | }
36 |
37 | final public function __invoke(...$args)
38 | {
39 | return Container::getInstance()->invokeFunction($this->closure, $args);
40 | }
41 |
42 | final public function call(...$args)
43 | {
44 | return Container::getInstance()->invokeFunction($this->closure, $args);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/queue/Queue.php:
--------------------------------------------------------------------------------
1 | [
15 | * "delay"=>0,//延迟时间
16 | * "sleep"=>3,//休息时间
17 | * "maxTries"=>0,//重试次数
18 | * "nums"=>2//进程数量
19 | * ],
20 | * ]
21 | */
22 | public function getConfig()
23 | {
24 | $config = Config::get('swoole.queue');
25 | $this->config = $this->getDefaultsValue($config);
26 | return $this->config;
27 | }
28 |
29 | public function getDefaultsValue($config = null)
30 | {
31 | if (!empty($config) && $config) {
32 | foreach ($config as $key => $val) {
33 | isset($config[$key]["delay"]) ? $config[$key] : ($config[$key]["delay"] = 0);
34 | isset($config[$key]["sleep"]) ? $config[$key] : ($config[$key]["sleep"] = 3);
35 | isset($config[$key]["maxTries"]) ? $config[$key] : ($config[$key]["maxTries"] = 0);
36 | isset($config[$key]["nums"]) ? $config[$key] : ($config[$key]["nums"] = 1);
37 | }
38 | return $config;
39 | }
40 | return false;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/Cookie.php:
--------------------------------------------------------------------------------
1 |
10 | // +----------------------------------------------------------------------
11 | namespace think\swoole;
12 |
13 | use Swoole\Http\Response;
14 | use think\Cookie as BaseCookie;
15 |
16 | /**
17 | * Swoole Cookie类
18 | */
19 | class Cookie extends BaseCookie
20 | {
21 | protected $response;
22 |
23 | /**
24 | * Cookie初始化
25 | * @access public
26 | * @param array $config
27 | * @return void
28 | */
29 | public function init(array $config = [])
30 | {
31 | $this->config = array_merge($this->config, array_change_key_case($config));
32 | }
33 |
34 | public function setResponse(Response $response)
35 | {
36 | $this->response = $response;
37 | }
38 |
39 | /**
40 | * Cookie 设置保存
41 | *
42 | * @access public
43 | * @param string $name cookie名称
44 | * @param mixed $value cookie值
45 | * @param array $option 可选参数
46 | * @return void
47 | */
48 | protected function setCookie($name, $value, $expire, $option = [])
49 | {
50 | $this->response->cookie($name, $value, $expire, $option['path'], $option['domain'], $option['secure'], $option['httponly']);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/queue/Task.php:
--------------------------------------------------------------------------------
1 | getConfig();
20 | $this->initTimerLists();
21 | }
22 |
23 | public static function instance()
24 | {
25 | if (is_null(self::$instance)) {
26 | self::$instance = new static();
27 | return self::$instance;
28 | }
29 | return self::$instance;
30 | }
31 |
32 | /**
33 | * 备注:和Process模式不同,这里属于定时执行,每隔一段时间执行一下任务,
34 | * 如果任务较多,可能存在都被投递到Task进程,
35 | * 因而推荐采用Process模式,这里可能会造成Task繁忙导致其他进程任务无法消耗
36 | */
37 | public function run()
38 | {
39 | foreach ($this->config as $key => $val) {
40 | if ($this->config[$key]["nexttime"] <= time()) {
41 | $this->config[$key]["nexttime"] = time() + $val['sleep'];
42 | for ($i = 0; $i < $val['nums']; $i++) {
43 | $this->job($key, $val);
44 | }
45 | }
46 | }
47 | }
48 |
49 | public function initTimerLists()
50 | {
51 | if ($this->config) {
52 | foreach ($this->config as $key => $val) {
53 | $this->config[$key]["nexttime"] = time();
54 | }
55 | }
56 | }
57 |
58 | public function job($key, $val)
59 | {
60 | SwooleTask::async(function ($serv, $task_id, $data) use ($key, $val) {
61 | $worker = new Worker();
62 | $worker->pop($key, $val['delay'], 0, $val['maxTries']);
63 | unset($worker);
64 | });
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/queue/Process.php:
--------------------------------------------------------------------------------
1 | getConfig();
20 | $maxtimes = Config::get('swoole.queue_maxtimes');
21 | $this->maxtimes = $maxtimes ? $maxtimes : 10000;
22 | }
23 |
24 | public function run($server)
25 | {
26 | $list = $this->config;
27 | foreach ($list as $key => $val) {
28 | for ($i = 0; $i < $val['nums']; $i++) {
29 | $p = $this->CreateProcess($key, $val);
30 | $server->addProcess($p);
31 | }
32 | }
33 | }
34 |
35 | public function CreateProcess($key = null, $val)
36 | {
37 | $index = $key . $val['nums'];
38 | $process = new \swoole_process(function ($process) use ($index, $key, $val) {
39 | if (is_null($index)) {
40 | $index = $this->new_index;
41 | $this->new_index++;
42 | }
43 | \swoole_set_process_name(sprintf('php-ps:%s', $index));
44 |
45 | while (true) {
46 | $this->job($key, $val);
47 | self::$times++;
48 | if (self::$times > $this->maxtimes) {//一定次数后挂掉进程,master进程会重新启动进程,这样可以防止长时间运行造成的内存泄露
49 | $process->exit();
50 | }
51 | }
52 | }, false, false);
53 | return $process;
54 | }
55 |
56 | public function job($key, $val)
57 | {
58 | $worker = new Worker();
59 | $worker->pop($key, $val['delay'], $val['sleep'], $val['maxTries']);
60 | unset($worker);
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/config/swoole.php:
--------------------------------------------------------------------------------
1 |
10 | // +----------------------------------------------------------------------
11 |
12 | use think\facade\Env;
13 |
14 | // +----------------------------------------------------------------------
15 | // | Swoole设置 php think swoole命令行下有效
16 | // +----------------------------------------------------------------------
17 | return [
18 | // 扩展自身配置
19 | 'host' => '0.0.0.0', // 监听地址
20 | 'port' => 9501, // 监听端口
21 | 'mode' => '', // 运行模式 默认为SWOOLE_PROCESS
22 | 'sock_type' => '', // sock type 默认为SWOOLE_SOCK_TCP
23 | 'server_type' => 'http', // 服务类型 支持 http websocket
24 | 'app_path' => '', // 应用地址 如果开启了 'daemonize'=>true 必须设置(使用绝对路径)
25 | 'file_monitor' => false, // 是否开启PHP文件更改监控(调试模式下自动开启)
26 | 'file_monitor_interval' => 2, // 文件变化监控检测时间间隔(秒)
27 | 'file_monitor_path' => [], // 文件监控目录 默认监控application和config目录
28 |
29 | // 可以支持swoole的所有配置参数
30 | 'pid_file' => Env::get('runtime_path') . 'swoole.pid',
31 | 'log_file' => Env::get('runtime_path') . 'swoole.log',
32 | 'document_root' => Env::get('root_path') . 'public',
33 | 'enable_static_handler' => true,
34 | 'timer' => true,//是否开启系统定时器
35 | 'interval' => 500,//系统定时器 时间间隔
36 | 'task_worker_num' => 1,//swoole 任务工作进程数量
37 | ];
38 |
--------------------------------------------------------------------------------
/src/config/server.php:
--------------------------------------------------------------------------------
1 |
10 | // +----------------------------------------------------------------------
11 |
12 | use think\facade\Env;
13 |
14 | // +----------------------------------------------------------------------
15 | // | Swoole设置 php think swoole:server 命令行下有效
16 | // +----------------------------------------------------------------------
17 | return [
18 | // 扩展自身配置
19 | 'host' => '0.0.0.0', // 监听地址
20 | 'port' => 9508, // 监听端口
21 | 'type' => 'socket', // 服务类型 支持 socket http server
22 | 'mode' => '', // 运行模式 默认为SWOOLE_PROCESS
23 | 'sock_type' => '', // sock type 默认为SWOOLE_SOCK_TCP
24 | 'swoole_class' => '', // 自定义服务类名称
25 |
26 | // 可以支持swoole的所有配置参数
27 | 'daemonize' => false,
28 | 'pid_file' => Env::get('runtime_path') . 'swoole_server.pid',
29 | 'log_file' => Env::get('runtime_path') . 'swoole_server.log',
30 |
31 | // 事件回调定义
32 | 'onOpen' => function ($server, $request) {
33 | echo "server: handshake success with fd{$request->fd}\n";
34 | },
35 |
36 | 'onMessage' => function ($server, $frame) {
37 | echo "receive from {$frame->fd}:{$frame->data},opcode:{$frame->opcode},fin:{$frame->finish}\n";
38 | $server->push($frame->fd, "this is server");
39 | },
40 |
41 | 'onRequest' => function ($request, $response) {
42 | $response->end("
Hello Swoole. #" . rand(1000, 9999) . "
");
43 | },
44 |
45 | 'onClose' => function ($ser, $fd) {
46 | echo "client {$fd} closed\n";
47 | },
48 | ];
49 |
--------------------------------------------------------------------------------
/src/CacheTable.php:
--------------------------------------------------------------------------------
1 | table = new \swoole_table($cache_size);
31 | $this->table->column('time', \swoole_table::TYPE_INT, 15);
32 | $this->table->column('data', \swoole_table::TYPE_STRING, $cache_data_size);
33 | $this->table->create();
34 | }
35 |
36 | public function getTable()
37 | {
38 | return $this->table;
39 | }
40 |
41 | public function set($key, $value)
42 | {
43 | $this->table->set($key, ['time' => 0, 'data' => $value]);
44 | }
45 |
46 | public function setex($key, $expire, $value)
47 | {
48 | $this->table->set($key, ['time' => time() + $expire, 'data' => $value]);
49 | }
50 |
51 | public function incr($key, $column, $incrby = 1)
52 | {
53 | $this->table->incr($key, $column, $incrby);
54 | }
55 |
56 | public function decr($key, $column, $decrby = 1)
57 | {
58 | $this->table->decr($key, $column, $decrby);
59 | }
60 |
61 | public function get($key, $field = null)
62 | {
63 | $data = $this->table->get($key, $field);
64 | if (false == $data) {
65 | return $data;
66 | }
67 | if (0 == $data['time']) {
68 | return $data['data'];
69 | }
70 | if (0 <= $data['time'] && $data['time'] < time()) {
71 | $this->del($key);
72 | return false;
73 | }
74 | return $data['data'];
75 | }
76 |
77 | public function exist($key)
78 | {
79 | return $this->table->exist($key);
80 | }
81 |
82 | public function del($key)
83 | {
84 | return $this->table->del($key);
85 | }
86 |
87 | public function clear()
88 | {
89 | foreach ($this->table as $key => $val) {
90 | $this->del($key);
91 | }
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/src/cache/driver/Table.php:
--------------------------------------------------------------------------------
1 | 0,
19 | 'prefix' => '',
20 | 'serialize' => true,
21 | ];
22 | public function __construct($options = [])
23 | {
24 | $this->handler = Container::get('cachetable');
25 | }
26 |
27 | public function set($name, $value, $expire = null)
28 | {
29 | $this->writeTimes++;
30 |
31 | if (is_null($expire)) {
32 | $expire = $this->options['expire'];
33 | }
34 |
35 | if ($this->tag && !$this->has($name)) {
36 | $first = true;
37 | }
38 |
39 | $key = $this->getCacheKey($name);
40 | $expire = $this->getExpireTime($expire);
41 |
42 | $value = $this->serialize($value);
43 |
44 | if ($expire) {
45 | $result = $this->handler->setex($key, $expire, $value);
46 | } else {
47 | $result = $this->handler->set($key, $value);
48 | }
49 |
50 | isset($first) && $this->setTagItem($key);
51 |
52 | return $result;
53 | }
54 |
55 | public function dec($name, $step = 1)
56 | {
57 | if ($this->has($name)) {
58 | $value = $this->get($name) - $step;
59 | $expire = $this->expire;
60 | } else {
61 | $value = -$step;
62 | $expire = 0;
63 | }
64 |
65 | return $this->set($name, $value, $expire) ? $value : false;
66 | }
67 |
68 | public function clear($tag = null)
69 | {
70 | $this->writeTimes++;
71 |
72 | return $this->handler->clear();
73 | }
74 |
75 | public function get($name, $default = false)
76 | {
77 | $this->readTimes++;
78 |
79 | $value = $this->handler->get($this->getCacheKey($name));
80 |
81 | if (is_null($value) || false === $value) {
82 | return $default;
83 | }
84 |
85 | return $this->unserialize($value);
86 | }
87 |
88 | public function has($name)
89 | {
90 | return $this->handler->exists($this->getCacheKey($name));
91 | }
92 |
93 | public function rm($name)
94 | {
95 | $this->writeTimes++;
96 | return $this->handler->del($this->getCacheKey($name));
97 | }
98 |
99 | public function inc($name, $step = 1)
100 | {
101 | if ($this->has($name)) {
102 | $value = $this->get($name) + $step;
103 | $expire = $this->expire;
104 | } else {
105 | $value = $step;
106 | $expire = 0;
107 | }
108 |
109 | return $this->set($name, $value, $expire) ? $value : false;
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/src/WebSocketFrame.php:
--------------------------------------------------------------------------------
1 | server = $server;
22 | $this->data = null;
23 | if (!empty($frame)) {
24 | $this->frame = $frame;
25 | $this->data = json_decode($this->frame->data, true);
26 | }
27 | }
28 |
29 | public static function getInstance($server = null, $frame = null)
30 | {
31 | if (empty(self::$instance)) {
32 | if (empty($server)) {
33 | $swoole = Container::get('swoole');
34 | $server = $swoole;
35 | }
36 | self::$instance = new static($server, $frame);
37 | }
38 | return self::$instance;
39 | }
40 |
41 | public static function destroy()
42 | {
43 | self::$instance = null;
44 | }
45 |
46 | public function getServer()
47 | {
48 | return $this->server;
49 | }
50 |
51 | public function getFrame()
52 | {
53 | return $this->frame;
54 | }
55 |
56 | public function getData()
57 | {
58 | return $this->data;
59 | }
60 |
61 | public function getArgs()
62 | {
63 | return isset($this->data['arguments']) ? $this->data['arguments'] : null;
64 | }
65 |
66 | public function __call($method, $params)
67 | {
68 | return call_user_func_array([$this->server, $method], $params);
69 | }
70 |
71 | public function pushToClient($data, $event = true)
72 | {
73 | if ($event) {
74 | $eventname = isset($this->data['event']) ? $this->data['event'] : false;
75 | if ($eventname) {
76 | $data['event'] = $eventname;
77 | }
78 | }
79 | $this->sendToClient($this->frame->fd, $data);
80 | }
81 |
82 | public function sendToClient($fd, $data)
83 | {
84 | if (is_string($data)) {
85 | $this->server->push($fd, $data);
86 | } elseif (is_array($data)) {
87 | $this->server->push($fd, json_encode($data));
88 | }
89 | }
90 |
91 | public function pushToClients($data)
92 | {
93 | foreach ($this->server->connections as $fd) {
94 | $this->sendToClient($fd, $data);
95 | }
96 | }
97 |
98 | public function offsetSet($offset, $value)
99 | {
100 | $this->data[$offset] = $value;
101 | }
102 |
103 | public function offsetExists($offset)
104 | {
105 | return isset($this->data[$offset]) ? true : false;
106 | }
107 |
108 | public function offsetUnset($offset)
109 | {
110 | unset($this->data[$offset]);
111 | }
112 |
113 | public function offsetGet($offset)
114 | {
115 | return isset($this->data[$offset]) ? $this->data[$offset] : null;
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/src/Server.php:
--------------------------------------------------------------------------------
1 |
10 | // +----------------------------------------------------------------------
11 |
12 | namespace think\swoole;
13 |
14 | use Swoole\Http\Server as HttpServer;
15 | use Swoole\Server as SwooleServer;
16 | use Swoole\Websocket\Server as Websocket;
17 |
18 | /**
19 | * Swoole Server扩展类
20 | */
21 | abstract class Server
22 | {
23 | /**
24 | * Swoole对象
25 | * @var object
26 | */
27 | protected $swoole;
28 |
29 | /**
30 | * SwooleServer类型
31 | * @var string
32 | */
33 | protected $serverType = 'http';
34 |
35 | /**
36 | * Socket的类型
37 | * @var int
38 | */
39 | protected $sockType = SWOOLE_SOCK_TCP;
40 |
41 | /**
42 | * 运行模式
43 | * @var int
44 | */
45 | protected $mode = SWOOLE_PROCESS;
46 |
47 | /**
48 | * 监听地址
49 | * @var string
50 | */
51 | protected $host = '0.0.0.0';
52 |
53 | /**
54 | * 监听端口
55 | * @var int
56 | */
57 | protected $port = 9501;
58 |
59 | /**
60 | * 配置
61 | * @var array
62 | */
63 | protected $option = [];
64 |
65 | /**
66 | * 支持的响应事件
67 | * @var array
68 | */
69 | protected $event = ['Start', 'Shutdown', 'WorkerStart', 'WorkerStop', 'WorkerExit', 'Connect', 'Receive', 'Packet', 'Close', 'BufferFull', 'BufferEmpty', 'Task', 'Finish', 'PipeMessage', 'WorkerError', 'ManagerStart', 'ManagerStop', 'Open', 'Message', 'HandShake', 'Request'];
70 |
71 | /**
72 | * 架构函数
73 | * @access public
74 | */
75 | public function __construct()
76 | {
77 | // 实例化 Swoole 服务
78 | switch ($this->serverType) {
79 | case 'socket':
80 | $this->swoole = new Websocket($this->host, $this->port, $this->mode, $this->sockType);
81 | break;
82 | case 'http':
83 | $this->swoole = new HttpServer($this->host, $this->port, $this->mode, $this->sockType);
84 | break;
85 | default:
86 | $this->swoole = new SwooleServer($this->host, $this->port, $this->mode, $this->sockType);
87 | }
88 |
89 | // 设置参数
90 | if (!empty($this->option)) {
91 | $this->swoole->set($this->option);
92 | }
93 |
94 | // 设置回调
95 | foreach ($this->event as $event) {
96 | if (method_exists($this, 'on' . $event)) {
97 | $this->swoole->on($event, [$this, 'on' . $event]);
98 | }
99 | }
100 |
101 | // 初始化
102 | $this->init();
103 |
104 | // 启动服务
105 | $this->swoole->start();
106 | }
107 |
108 | protected function init()
109 | {
110 | }
111 |
112 | /**
113 | * 魔术方法 有不存在的操作的时候执行
114 | * @access public
115 | * @param string $method 方法名
116 | * @param array $args 参数
117 | * @return mixed
118 | */
119 | public function __call($method, $args)
120 | {
121 | call_user_func_array([$this->swoole, $method], $args);
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/src/Timer.php:
--------------------------------------------------------------------------------
1 | config = Config::pull('timer');
31 |
32 | if (empty($this->config)) {
33 | $this->config = [];
34 | }
35 | }
36 |
37 | /**
38 | * 开始执行定时器任务
39 | * @param $serv 服务对象
40 | */
41 | public function run($serv)
42 | {
43 | if (count(self::$timerlists) > 0) {
44 | $this->startTask();
45 | } else {
46 | $this->initimerlists();
47 | }
48 | }
49 |
50 | /**
51 | * 到期后执行定时任务
52 | */
53 | public function startTask()
54 | {
55 | foreach (self::$timerlists as &$one) {
56 | if ($one['next_time'] <= time()) {
57 | $cron = CronExpression::factory($one['key']);
58 |
59 | $one['next_time'] = $cron->getNextRunDate()->getTimestamp();
60 |
61 | $this->syncTask($one['val']);
62 | }
63 | }
64 | unset($one);
65 | }
66 |
67 | /**
68 | * 根据定时配置计算下次执行时间并存储相关信息
69 | * @throws \Exception
70 | */
71 | public function initimerlists()
72 | {
73 | $i = 0;
74 | foreach ($this->config as $key => $val) {
75 | try {
76 | $cron = CronExpression::factory($key);
77 | $time = $cron->getNextRunDate()->getTimestamp();
78 |
79 | self::$timerlists[$i]['key'] = $key;
80 | self::$timerlists[$i]['val'] = $val;
81 | self::$timerlists[$i]['next_time'] = $time;
82 | } catch (\Exception $e) {
83 | var_dump($e);
84 | throw new \Exception("定时器异常");
85 | }
86 | $i++;
87 | }
88 | }
89 |
90 | /**
91 | * 异步投递任务到task worker
92 | * @param string $class
93 | */
94 | public function syncTask($class)
95 | {
96 | if (is_string($class) && class_exists($class)) {
97 | TaskF::async(function () use ($class) {
98 | $obj = new $class();
99 | $obj->run();
100 | unset($obj);
101 | });
102 | }
103 | }
104 |
105 | /**
106 | * 每隔固定时间执行一次
107 | * @param int $time 间隔时间
108 | * @param mixed $callback 可以是回调 可以是定时器任务模板
109 | * @return bool
110 | */
111 | public function tick($time, $callback)
112 | {
113 | if ($callback instanceof \Closure) {
114 | return SwooleTimer::tick($time, $callback);
115 | } elseif (is_object($callback) && method_exists($callback, 'run')) {
116 | return SwooleTimer::tick($time, function () use ($callback) {
117 | $callback->run();
118 | });
119 | }
120 |
121 | return false;
122 | }
123 |
124 | /**
125 | * 延迟执行
126 | * @param int $time 间隔时间
127 | * @param mixed $callback 可以是回调 可以是定时器任务模板
128 | * @return bool
129 | */
130 | public function after($time, $callback)
131 | {
132 | if ($callback instanceof \Closure) {
133 | return SwooleTimer::after($time, $callback);
134 | } elseif (is_object($callback) && method_exists($callback, 'run')) {
135 | return SwooleTimer::after($time, function () use ($callback) {
136 | $callback->run();
137 | unset($callback);
138 | });
139 | }
140 |
141 | return false;
142 | }
143 |
144 | /**
145 | * 清除定时器
146 | * @param int $timerId
147 | * @return bool
148 | */
149 | public function clear($timerId)
150 | {
151 | return SwooleTimer::clear($timerId);
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/src/command/Server.php:
--------------------------------------------------------------------------------
1 |
10 | // +----------------------------------------------------------------------
11 |
12 | namespace think\swoole\command;
13 |
14 | use Swoole\Process;
15 | use think\console\input\Argument;
16 | use think\console\input\Option;
17 | use think\facade\Config;
18 | use think\facade\Env;
19 | use think\swoole\Server as ThinkServer;
20 |
21 | /**
22 | * Swoole 命令行,支持操作:start|stop|restart|reload
23 | * 支持应用配置目录下的swoole_server.php文件进行参数配置
24 | */
25 | class Server extends Swoole
26 | {
27 | public function configure()
28 | {
29 | $this->setName('swoole:server')
30 | ->addArgument('action', Argument::OPTIONAL, "start|stop|restart|reload", 'start')
31 | ->addOption('host', 'H', Option::VALUE_OPTIONAL, 'the host of swoole server.', null)
32 | ->addOption('port', 'p', Option::VALUE_OPTIONAL, 'the port of swoole server.', null)
33 | ->addOption('daemon', 'd', Option::VALUE_NONE, 'Run the swoole server in daemon mode.')
34 | ->setDescription('Swoole Server for ThinkPHP');
35 | }
36 |
37 | protected function init()
38 | {
39 | $this->config = Config::pull('swoole_server');
40 |
41 | if (empty($this->config['pid_file'])) {
42 | $this->config['pid_file'] = Env::get('runtime_path') . 'swoole_server.pid';
43 | }
44 |
45 | // 避免pid混乱
46 | $this->config['pid_file'] .= '_' . $this->getPort();
47 | }
48 |
49 | /**
50 | * 启动server
51 | * @access protected
52 | * @return void
53 | */
54 | protected function start()
55 | {
56 | $pid = $this->getMasterPid();
57 |
58 | if ($this->isRunning($pid)) {
59 | $this->output->writeln('swoole server process is already running.');
60 | return false;
61 | }
62 |
63 | $this->output->writeln('Starting swoole server...');
64 |
65 | if (!empty($this->config['swoole_class'])) {
66 | $class = $this->config['swoole_class'];
67 |
68 | if (class_exists($class)) {
69 | $swoole = new $class;
70 | if (!$swoole instanceof ThinkServer) {
71 | $this->output->writeln("Swoole Server Class Must extends \\think\\swoole\\Server");
72 | return false;
73 | }
74 | } else {
75 | $this->output->writeln("Swoole Server Class Not Exists : {$class}");
76 | return false;
77 | }
78 | } else {
79 | $host = $this->getHost();
80 | $port = $this->getPort();
81 | $type = !empty($this->config['type']) ? $this->config['type'] : 'socket';
82 | $mode = !empty($this->config['mode']) ? $this->config['mode'] : SWOOLE_PROCESS;
83 | $sockType = !empty($this->config['sock_type']) ? $this->config['sock_type'] : SWOOLE_SOCK_TCP;
84 |
85 | switch ($type) {
86 | case 'socket':
87 | $swooleClass = 'Swoole\Websocket\Server';
88 | break;
89 | case 'http':
90 | $swooleClass = 'Swoole\Http\Server';
91 | break;
92 | default:
93 | $swooleClass = 'Swoole\Server';
94 | }
95 |
96 | $swoole = new $swooleClass($host, $port, $mode, $sockType);
97 |
98 | // 开启守护进程模式
99 | if ($this->input->hasOption('daemon')) {
100 | $this->config['daemonize'] = true;
101 | }
102 |
103 | foreach ($this->config as $name => $val) {
104 | if (0 === strpos($name, 'on')) {
105 | $swoole->on(substr($name, 2), $val);
106 | unset($this->config[$name]);
107 | }
108 | }
109 |
110 | // 设置服务器参数
111 | $swoole->set($this->config);
112 |
113 | $this->output->writeln("Swoole {$type} server started: <{$host}:{$port}>");
114 | $this->output->writeln('You can exit with `CTRL-C`');
115 |
116 | // 启动服务
117 | $swoole->start();
118 | }
119 | }
120 |
121 | /**
122 | * 柔性重启server
123 | * @access protected
124 | * @return void
125 | */
126 | protected function reload()
127 | {
128 | // 柔性重启使用管理PID
129 | $pid = $this->getMasterPid();
130 |
131 | if (!$this->isRunning($pid)) {
132 | $this->output->writeln('no swoole server process running.');
133 | return false;
134 | }
135 |
136 | $this->output->writeln('Reloading swoole server...');
137 | Process::kill($pid, SIGUSR1);
138 | $this->output->writeln('> success');
139 | }
140 |
141 | /**
142 | * 停止server
143 | * @access protected
144 | * @return void
145 | */
146 | protected function stop()
147 | {
148 | $pid = $this->getMasterPid();
149 |
150 | if (!$this->isRunning($pid)) {
151 | $this->output->writeln('no swoole server process running.');
152 | return false;
153 | }
154 |
155 | $this->output->writeln('Stopping swoole server...');
156 |
157 | Process::kill($pid, SIGTERM);
158 | $this->removePid();
159 |
160 | $this->output->writeln('> success');
161 | }
162 |
163 | }
164 |
--------------------------------------------------------------------------------
/src/Application.php:
--------------------------------------------------------------------------------
1 |
10 | // +----------------------------------------------------------------------
11 |
12 | namespace think\swoole;
13 |
14 | use Swoole\Http\Request;
15 | use Swoole\Http\Response;
16 | use think\App;
17 | use think\Db;
18 | use think\Error;
19 | use think\exception\HttpException;
20 | use think\facade\Config;
21 |
22 | /**
23 | * Swoole应用对象
24 | */
25 | class Application extends App
26 | {
27 | /**
28 | * 处理Swoole请求
29 | * @access public
30 | * @param \Swoole\Http\Request $request
31 | * @param \Swoole\Http\Response $response
32 | * @param void
33 | */
34 | public function swoole(Request $request, Response $response)
35 | {
36 | try {
37 | ob_start();
38 |
39 | // 重置应用的开始时间和内存占用
40 | $this->beginTime = microtime(true);
41 | $this->beginMem = memory_get_usage();
42 |
43 | // 重置数据库查询次数
44 | Db::$queryTimes = 0;
45 |
46 | // 重置数据库执行次数
47 | Db::$executeTimes = 0;
48 |
49 | // 销毁当前请求对象实例
50 | $this->delete('think\Request');
51 |
52 | // 设置Cookie类Response
53 | $this->cookie->setResponse($response);
54 |
55 | $_COOKIE = $request->cookie ?: [];
56 | $_GET = $request->get ?: [];
57 | $_POST = $request->post ?: [];
58 | $_FILES = $request->files ?: [];
59 | $header = $request->header ?: [];
60 | $server = $request->server ?: [];
61 |
62 | if (isset($header['x-requested-with'])) {
63 | $server['HTTP_X_REQUESTED_WITH'] = $header['x-requested-with'];
64 | }
65 |
66 | if (isset($header['referer'])) {
67 | $server['http_referer'] = $header['referer'];
68 | }
69 |
70 | if (isset($_GET[$this->config->get('var_pathinfo')])) {
71 | $server['path_info'] = $_GET[$this->config->get('var_pathinfo')];
72 | }
73 |
74 | $_SERVER = array_change_key_case($server, CASE_UPPER);
75 |
76 | // 重新实例化请求对象 处理swoole请求数据
77 | $this->request->withHeader($header)
78 | ->withServer($_SERVER)
79 | ->withGet($_GET)
80 | ->withPost($_POST)
81 | ->withCookie($_COOKIE)
82 | ->withInput($request->rawContent())
83 | ->withFiles($_FILES)
84 | ->setBaseUrl($request->server['request_uri'])
85 | ->setUrl($request->server['request_uri'] . (!empty($request->server['query_string']) ? '&' . $request->server['query_string'] : ''))
86 | ->setHost($request->header['host'])
87 | ->setPathinfo(ltrim($request->server['path_info'], '/'));
88 |
89 | // 更新请求对象实例
90 | $this->route->setRequest($this->request);
91 |
92 | // 重新加载全局中间件
93 | if (is_file($this->appPath . 'middleware.php')) {
94 | $middleware = include $this->appPath . 'middleware.php';
95 | if (is_array($middleware)) {
96 | $this->middleware->import($middleware);
97 | }
98 | }
99 |
100 | $resp = $this->run();
101 | $resp->send();
102 |
103 | $content = ob_get_clean();
104 | $status = $resp->getCode();
105 |
106 | // Trace调试注入
107 | if ($this->env->get('app_trace', $this->config->get('app_trace'))) {
108 | $this->debug->inject($resp, $content);
109 | }
110 |
111 | // 清除中间件数据
112 | $this->middleware->clear();
113 |
114 | // 发送状态码
115 | $response->status($status);
116 |
117 | // 发送Header
118 | foreach ($resp->getHeader() as $key => $val) {
119 | $response->header($key, $val);
120 | }
121 |
122 | $response->write($content);
123 | $response->end();
124 | } catch (HttpException $e) {
125 | $this->exception($response, $e);
126 | } catch (\Exception $e) {
127 | $this->exception($response, $e);
128 | } catch (\Throwable $e) {
129 | $this->exception($response, $e);
130 | }
131 | }
132 |
133 | public function swooleWebSocket($server, $frame)
134 | {
135 | try {
136 | // 重置应用的开始时间和内存占用
137 | $this->beginTime = microtime(true);
138 | $this->beginMem = memory_get_usage();
139 |
140 | // 销毁当前请求对象实例
141 | $this->delete('think\Request');
142 | WebSocketFrame::destroy();
143 | $request = $frame->data;
144 | $request = json_decode($request, true);
145 |
146 | // 重置应用的开始时间和内存占用
147 | $this->beginTime = microtime(true);
148 | $this->beginMem = memory_get_usage();
149 | WebSocketFrame::getInstance($server, $frame);
150 |
151 | $_COOKIE = isset($request['arguments']['cookie']) ? $request['arguments']['cookie'] : [];
152 | $_GET = isset($request['arguments']['get']) ? $request['arguments']['get'] : [];
153 | $_POST = isset($request['arguments']['post']) ? $request['arguments']['post'] : [];
154 | $_FILES = isset($request['arguments']['files']) ? $request['arguments']['files'] : [];
155 |
156 | $_SERVER["PATH_INFO"] = $request['url'] ?: '/';
157 | $_SERVER["REQUEST_URI"] = $request['url'] ?: '/';
158 | $_SERVER["SERVER_PROTOCOL"] = 'http';
159 | $_SERVER["REQUEST_METHOD"] = 'post';
160 |
161 | // 重新实例化请求对象 处理swoole请求数据
162 | $this->request
163 | ->withServer($_SERVER)
164 | ->withGet($_GET)
165 | ->withPost($_POST)
166 | ->withCookie($_COOKIE)
167 | ->withFiles($_FILES)
168 | ->setBaseUrl($request['url'])
169 | ->setUrl($request['url'])
170 | ->setHost(Config::get("app_host"))
171 | ->setPathinfo(ltrim($request['url'], '/'));
172 |
173 | // 更新请求对象实例
174 | $this->route->setRequest($this->request);
175 |
176 | $resp = $this->run();
177 | $resp->send();
178 |
179 | } catch (HttpException $e) {
180 | $this->webSocketException($server, $frame, $e);
181 | } catch (\Exception $e) {
182 | $this->webSocketException($server, $frame, $e);
183 | } catch (\Throwable $e) {
184 | $this->webSocketException($server, $frame, $e);
185 | }
186 | }
187 |
188 | protected function exception($response, $e)
189 | {
190 | if ($e instanceof \Exception) {
191 | $handler = Error::getExceptionHandler();
192 | $handler->report($e);
193 |
194 | $resp = $handler->render($e);
195 | $content = $resp->getContent();
196 | $code = $resp->getCode();
197 |
198 | $response->status($code);
199 | $response->end($content);
200 | } else {
201 | $response->status(500);
202 | $response->end($e->getMessage());
203 | }
204 |
205 | throw $e;
206 | }
207 |
208 | protected function webSocketException($server, $frame, $e)
209 | {
210 | $response = [
211 | 'code' => $e->getCode(),
212 | 'content' => $e->getMessage(),
213 | ];
214 | $server->push($frame->fd, json_encode($response));
215 |
216 | throw $e;
217 | }
218 | }
219 |
--------------------------------------------------------------------------------
/src/command/Swoole.php:
--------------------------------------------------------------------------------
1 |
10 | // +----------------------------------------------------------------------
11 |
12 | namespace think\swoole\command;
13 |
14 | use Swoole\Process;
15 | use think\console\Command;
16 | use think\console\Input;
17 | use think\console\input\Argument;
18 | use think\console\input\Option;
19 | use think\console\Output;
20 | use think\facade\Config;
21 | use think\facade\Env;
22 | use think\swoole\Http as HttpServer;
23 | use think\Container;
24 |
25 | /**
26 | * Swoole HTTP 命令行,支持操作:start|stop|restart|reload
27 | * 支持应用配置目录下的swoole.php文件进行参数配置
28 | */
29 | class Swoole extends Command
30 | {
31 | protected $config = [];
32 |
33 | public function configure()
34 | {
35 | $this->setName('swoole')
36 | ->addArgument('action', Argument::OPTIONAL, "start|stop|restart|reload", 'start')
37 | ->addOption('host', 'H', Option::VALUE_OPTIONAL, 'the host of swoole server.', null)
38 | ->addOption('port', 'p', Option::VALUE_OPTIONAL, 'the port of swoole server.', null)
39 | ->addOption('daemon', 'd', Option::VALUE_NONE, 'Run the swoole server in daemon mode.')
40 | ->setDescription('Swoole HTTP Server for ThinkPHP');
41 | }
42 |
43 | public function execute(Input $input, Output $output)
44 | {
45 | $action = $input->getArgument('action');
46 |
47 | $this->init();
48 |
49 | if (in_array($action, ['start', 'stop', 'reload', 'restart'])) {
50 | $this->$action();
51 | } else {
52 | $output->writeln("Invalid argument action:{$action}, Expected start|stop|restart|reload .");
53 | }
54 | }
55 |
56 | protected function init()
57 | {
58 | $this->config = Config::pull('swoole');
59 |
60 | if (empty($this->config['pid_file'])) {
61 | $this->config['pid_file'] = Env::get('runtime_path') . 'swoole.pid';
62 | }
63 |
64 | // 避免pid混乱
65 | $this->config['pid_file'] .= '_' . $this->getPort();
66 | }
67 |
68 | protected function getHost()
69 | {
70 | if ($this->input->hasOption('host')) {
71 | $host = $this->input->getOption('host');
72 | } else {
73 | $host = !empty($this->config['host']) ? $this->config['host'] : '0.0.0.0';
74 | }
75 |
76 | return $host;
77 | }
78 |
79 | protected function getPort()
80 | {
81 | if ($this->input->hasOption('port')) {
82 | $port = $this->input->getOption('port');
83 | } else {
84 | $port = !empty($this->config['port']) ? $this->config['port'] : 9501;
85 | }
86 |
87 | return $port;
88 | }
89 |
90 | /**
91 | * 启动server
92 | * @access protected
93 | * @return void
94 | */
95 | protected function start()
96 | {
97 | $pid = $this->getMasterPid();
98 |
99 | if ($this->isRunning($pid)) {
100 | $this->output->writeln('swoole http server process is already running.');
101 | return false;
102 | }
103 |
104 | $this->output->writeln('Starting swoole http server...');
105 |
106 | $host = $this->getHost();
107 | $port = $this->getPort();
108 | $mode = !empty($this->config['mode']) ? $this->config['mode'] : SWOOLE_PROCESS;
109 | $type = !empty($this->config['sock_type']) ? $this->config['sock_type'] : SWOOLE_SOCK_TCP;
110 |
111 | $ssl = !empty($this->config['ssl']) || !empty($this->config['open_http2_protocol']);
112 | if ($ssl) {
113 | $type = SWOOLE_SOCK_TCP | SWOOLE_SSL;
114 | }
115 |
116 | $swoole = new HttpServer($host, $port, $mode, $type);
117 |
118 | // 开启守护进程模式
119 | if ($this->input->hasOption('daemon')) {
120 | $this->config['daemonize'] = true;
121 | }
122 |
123 | // 设置应用目录
124 | $swoole->setAppPath($this->config['app_path']);
125 |
126 | // 创建内存表
127 | if (!empty($this->config['table'])) {
128 | $swoole->table($this->config['table']);
129 | unset($this->config['table']);
130 | }
131 |
132 | $swoole->cachetable();
133 |
134 | // 设置文件监控 调试模式自动开启
135 | if (Env::get('app_debug') || !empty($this->config['file_monitor'])) {
136 | $interval = isset($this->config['file_monitor_interval']) ? $this->config['file_monitor_interval'] : 2;
137 | $paths = isset($this->config['file_monitor_path']) ? $this->config['file_monitor_path'] : [];
138 | $swoole->setMonitor($interval, $paths);
139 | unset($this->config['file_monitor'], $this->config['file_monitor_interval'], $this->config['file_monitor_path']);
140 | }
141 |
142 | // 设置服务器参数
143 | if (isset($this->config['pid_file'])) {
144 |
145 | }
146 | $swoole->option($this->config);
147 |
148 | $this->output->writeln("Swoole http server started: ");
149 | $this->output->writeln('You can exit with `CTRL-C`');
150 |
151 | $hook = Container::get('hook');
152 | $hook->listen("swoole_server_start", $swoole);
153 |
154 | $swoole->start();
155 | }
156 |
157 | /**
158 | * 柔性重启server
159 | * @access protected
160 | * @return void
161 | */
162 | protected function reload()
163 | {
164 | $pid = $this->getMasterPid();
165 |
166 | if (!$this->isRunning($pid)) {
167 | $this->output->writeln('no swoole http server process running.');
168 | return false;
169 | }
170 |
171 | $this->output->writeln('Reloading swoole http server...');
172 | Process::kill($pid, SIGUSR1);
173 | $this->output->writeln('> success');
174 | }
175 |
176 | /**
177 | * 停止server
178 | * @access protected
179 | * @return void
180 | */
181 | protected function stop()
182 | {
183 | $pid = $this->getMasterPid();
184 |
185 | if (!$this->isRunning($pid)) {
186 | $this->output->writeln('no swoole http server process running.');
187 | return false;
188 | }
189 |
190 | $this->output->writeln('Stopping swoole http server...');
191 |
192 | Process::kill($pid, SIGTERM);
193 | $this->removePid();
194 |
195 | $this->output->writeln('> success');
196 | }
197 |
198 | /**
199 | * 重启server
200 | * @access protected
201 | * @return void
202 | */
203 | protected function restart()
204 | {
205 | $pid = $this->getMasterPid();
206 |
207 | if ($this->isRunning($pid)) {
208 | $this->stop();
209 | }
210 |
211 | $this->start();
212 | }
213 |
214 | /**
215 | * 获取主进程PID
216 | * @access protected
217 | * @return int
218 | */
219 | protected function getMasterPid()
220 | {
221 | $pidFile = $this->config['pid_file'];
222 |
223 | if (is_file($pidFile)) {
224 | $masterPid = (int) file_get_contents($pidFile);
225 | } else {
226 | $masterPid = 0;
227 | }
228 |
229 | return $masterPid;
230 | }
231 |
232 | /**
233 | * 删除PID文件
234 | * @access protected
235 | * @return void
236 | */
237 | protected function removePid()
238 | {
239 | $masterPid = $this->config['pid_file'];
240 |
241 | if (is_file($masterPid)) {
242 | unlink($masterPid);
243 | }
244 | }
245 |
246 | /**
247 | * 判断PID是否在运行
248 | * @access protected
249 | * @param int $pid
250 | * @return bool
251 | */
252 | protected function isRunning($pid)
253 | {
254 | if (empty($pid)) {
255 | return false;
256 | }
257 |
258 | return Process::kill($pid, 0);
259 | }
260 | }
261 |
--------------------------------------------------------------------------------
/src/log/File.php:
--------------------------------------------------------------------------------
1 |
10 | // +----------------------------------------------------------------------
11 |
12 | namespace think\log\driver;
13 |
14 | use think\App;
15 |
16 | /**
17 | * 本地化调试输出到文件
18 | */
19 | class File
20 | {
21 | protected $config = [
22 | 'time_format' => ' c ',
23 | 'single' => false,
24 | 'file_size' => 2097152,
25 | 'path' => '',
26 | 'apart_level' => [],
27 | 'max_files' => 0,
28 | 'json' => false,
29 | ];
30 |
31 | protected $app;
32 |
33 | // 实例化并传入参数
34 | public function __construct(App $app, $config = [])
35 | {
36 | $this->app = $app;
37 |
38 | if (is_array($config)) {
39 | $this->config = array_merge($this->config, $config);
40 | }
41 |
42 | if (empty($this->config['path'])) {
43 | $this->config['path'] = $this->app->getRuntimePath() . 'log' . DIRECTORY_SEPARATOR;
44 | } elseif (substr($this->config['path'], -1) != DIRECTORY_SEPARATOR) {
45 | $this->config['path'] .= DIRECTORY_SEPARATOR;
46 | }
47 | }
48 |
49 | /**
50 | * 日志写入接口
51 | * @access public
52 | * @param array $log 日志信息
53 | * @param bool $append 是否追加请求信息
54 | * @return bool
55 | */
56 | public function save(array $log = [], $append = false)
57 | {
58 | $destination = $this->getMasterLogFile();
59 |
60 | $path = dirname($destination);
61 | !is_dir($path) && mkdir($path, 0755, true);
62 |
63 | $info = [];
64 |
65 | foreach ($log as $type => $val) {
66 |
67 | foreach ($val as $msg) {
68 | if (!is_string($msg)) {
69 | $msg = var_export($msg, true);
70 | }
71 |
72 | $info[$type][] = $this->config['json'] ? $msg : '[ ' . $type . ' ] ' . $msg;
73 | }
74 |
75 | if (!$this->config['json'] && (true === $this->config['apart_level'] || in_array($type, $this->config['apart_level']))) {
76 | // 独立记录的日志级别
77 | $filename = $this->getApartLevelFile($path, $type);
78 |
79 | $this->write($info[$type], $filename, true, $append);
80 |
81 | unset($info[$type]);
82 | }
83 | }
84 |
85 | if ($info) {
86 | return $this->write($info, $destination, false, $append);
87 | }
88 |
89 | return true;
90 | }
91 |
92 | /**
93 | * 日志写入
94 | * @access protected
95 | * @param array $message 日志信息
96 | * @param string $destination 日志文件
97 | * @param bool $apart 是否独立文件写入
98 | * @param bool $append 是否追加请求信息
99 | * @return bool
100 | */
101 | protected function write($message, $destination, $apart = false, $append = false)
102 | {
103 | // 检测日志文件大小,超过配置大小则备份日志文件重新生成
104 | $this->checkLogSize($destination);
105 |
106 | // 日志信息封装
107 | $info['timestamp'] = date($this->config['time_format']);
108 |
109 | foreach ($message as $type => $msg) {
110 | $info[$type] = is_array($msg) ? implode("\r\n", $msg) : $msg;
111 | }
112 |
113 | if (PHP_SAPI == 'cli') {
114 | $message = $this->parseCliLog($info);
115 | } else {
116 | // 添加调试日志
117 | $this->getDebugLog($info, $append, $apart);
118 |
119 | $message = $this->parseLog($info);
120 | }
121 |
122 | return error_log($message, 3, $destination);
123 | }
124 |
125 | /**
126 | * 获取主日志文件名
127 | * @access public
128 | * @return string
129 | */
130 | protected function getMasterLogFile()
131 | {
132 | if ($this->config['single']) {
133 | $name = is_string($this->config['single']) ? $this->config['single'] : 'swoole';
134 |
135 | $destination = $this->config['path'] . $name . '.log';
136 | } else {
137 | if ($this->config['max_files']) {
138 | $filename = date('Ymd') . '_swoole.log';
139 | $files = glob($this->config['path'] . '*.log');
140 |
141 | try {
142 | if (count($files) > $this->config['max_files']) {
143 | unlink($files[0]);
144 | }
145 | } catch (\Exception $e) {
146 | }
147 | } else {
148 | $filename = date('Ym') . DIRECTORY_SEPARATOR . date('d') . '_swoole.log';
149 | }
150 |
151 | $destination = $this->config['path'] . $filename;
152 | }
153 |
154 | return $destination;
155 | }
156 |
157 | /**
158 | * 获取独立日志文件名
159 | * @access public
160 | * @param string $path 日志目录
161 | * @param string $type 日志类型
162 | * @return string
163 | */
164 | protected function getApartLevelFile($path, $type)
165 | {
166 | if ($this->config['single']) {
167 | $name = is_string($this->config['single']) ? $this->config['single'] : 'swoole';
168 |
169 | $name .= '_' . $type;
170 | } elseif ($this->config['max_files']) {
171 | $name = date('Ymd') . '_' . $type . '_swoole';
172 | } else {
173 | $name = date('d') . '_' . $type . '_swoole';
174 | }
175 |
176 | return $path . DIRECTORY_SEPARATOR . $name . '.log';
177 | }
178 |
179 | /**
180 | * 检查日志文件大小并自动生成备份文件
181 | * @access protected
182 | * @param string $destination 日志文件
183 | * @return void
184 | */
185 | protected function checkLogSize($destination)
186 | {
187 | if (is_file($destination) && floor($this->config['file_size']) <= filesize($destination)) {
188 | try {
189 | rename($destination, dirname($destination) . DIRECTORY_SEPARATOR . time() . '-' . basename($destination));
190 | } catch (\Exception $e) {
191 | }
192 | }
193 | }
194 |
195 | /**
196 | * CLI日志解析
197 | * @access protected
198 | * @param array $info 日志信息
199 | * @return string
200 | */
201 | protected function parseCliLog($info)
202 | {
203 | if ($this->config['json']) {
204 | $message = json_encode($info, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\r\n";
205 | } else {
206 | $now = $info['timestamp'];
207 | unset($info['timestamp']);
208 |
209 | $message = implode("\r\n", $info);
210 |
211 | $message = "[{$now}]" . $message . "\r\n";
212 | }
213 |
214 | return $message;
215 | }
216 |
217 | /**
218 | * 解析日志
219 | * @access protected
220 | * @param array $info 日志信息
221 | * @return string
222 | */
223 | protected function parseLog($info)
224 | {
225 | $requestInfo = [
226 | 'ip' => $this->app['request']->ip(),
227 | 'method' => $this->app['request']->method(),
228 | 'host' => $this->app['request']->host(),
229 | 'uri' => $this->app['request']->url(),
230 | ];
231 |
232 | if ($this->config['json']) {
233 | $info = $requestInfo + $info;
234 | return json_encode($info, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\r\n";
235 | }
236 |
237 | array_unshift($info, "---------------------------------------------------------------\r\n[{$info['timestamp']}] {$requestInfo['ip']} {$requestInfo['method']} {$requestInfo['host']}{$requestInfo['uri']}");
238 | unset($info['timestamp']);
239 |
240 | return implode("\r\n", $info) . "\r\n";
241 | }
242 |
243 | protected function getDebugLog(&$info, $append, $apart)
244 | {
245 | if ($this->app->isDebug() && $append) {
246 |
247 | if ($this->config['json']) {
248 | // 获取基本信息
249 | $runtime = round(microtime(true) - $this->app->getBeginTime(), 10);
250 | $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞';
251 |
252 | $memory_use = number_format((memory_get_usage() - $this->app->getBeginMem()) / 1024, 2);
253 |
254 | $info = [
255 | 'runtime' => number_format($runtime, 6) . 's',
256 | 'reqs' => $reqs . 'req/s',
257 | 'memory' => $memory_use . 'kb',
258 | 'file' => count(get_included_files()),
259 | ] + $info;
260 |
261 | } elseif (!$apart) {
262 | // 增加额外的调试信息
263 | $runtime = round(microtime(true) - $this->app->getBeginTime(), 10);
264 | $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞';
265 |
266 | $memory_use = number_format((memory_get_usage() - $this->app->getBeginMem()) / 1024, 2);
267 |
268 | $time_str = '[运行时间:' . number_format($runtime, 6) . 's] [吞吐率:' . $reqs . 'req/s]';
269 | $memory_str = ' [内存消耗:' . $memory_use . 'kb]';
270 | $file_load = ' [文件加载:' . count(get_included_files()) . ']';
271 |
272 | array_unshift($info, $time_str . $memory_str . $file_load);
273 | }
274 | }
275 | }
276 | }
277 |
--------------------------------------------------------------------------------
/src/Http.php:
--------------------------------------------------------------------------------
1 |
10 | // +----------------------------------------------------------------------
11 | namespace think\swoole;
12 |
13 | use Swoole\Http\Server as HttpServer;
14 | use Swoole\Table;
15 | use think\Facade;
16 | use think\facade\Config;
17 | use think\Loader;
18 | use think\swoole\facade\Timer as TimerF;
19 | use think\Container;
20 | use think\swoole\queue\Task as QueueTask;
21 | use think\swoole\queue\Process as QueueProcess;
22 | use Swoole\WebSocket\Server as WebSocketServer;
23 |
24 | /**
25 | * Swoole Http Server 命令行服务类
26 | */
27 | class Http extends Server
28 | {
29 | protected $app;
30 | protected $appPath;
31 | protected $table;
32 | protected $cachetable;
33 | protected $monitor;
34 | protected $server_type;
35 | protected $lastMtime;
36 | protected $fieldType = [
37 | 'int' => Table::TYPE_INT,
38 | 'string' => Table::TYPE_STRING,
39 | 'float' => Table::TYPE_FLOAT,
40 | ];
41 |
42 | protected $fieldSize = [
43 | Table::TYPE_INT => 4,
44 | Table::TYPE_STRING => 32,
45 | Table::TYPE_FLOAT => 8,
46 | ];
47 |
48 | /**
49 | * 架构函数
50 | * @access public
51 | */
52 | public function __construct($host, $port, $mode = SWOOLE_PROCESS, $sockType = SWOOLE_SOCK_TCP)
53 | {
54 | $this->server_type = Config::get('swoole.server_type');
55 | switch ($this->server_type) {
56 | case 'websocket':
57 | $this->swoole = new WebSocketServer($host, $port, $mode, SWOOLE_SOCK_TCP);
58 | break;
59 | default:
60 | $this->swoole = new HttpServer($host, $port, $mode, SWOOLE_SOCK_TCP);
61 | }
62 | if ("process" == Config::get('swoole.queue_type')) {
63 | $process = new QueueProcess();
64 | $process->run($this->swoole);
65 | }
66 | }
67 |
68 | public function setAppPath($path)
69 | {
70 | $this->appPath = $path;
71 | }
72 |
73 | public function setMonitor($interval = 2, $path = [])
74 | {
75 | $this->monitor['interval'] = $interval;
76 | $this->monitor['path'] = (array) $path;
77 | }
78 |
79 | public function table(array $option)
80 | {
81 | $size = !empty($option['size']) ? $option['size'] : 1024;
82 | $this->table = new Table($size);
83 |
84 | foreach ($option['column'] as $field => $type) {
85 | $length = null;
86 |
87 | if (is_array($type)) {
88 | list($type, $length) = $type;
89 | }
90 |
91 | if (isset($this->fieldType[$type])) {
92 | $type = $this->fieldType[$type];
93 | }
94 |
95 | $this->table->column($field, $type, isset($length) ? $length : $this->fieldSize[$type]);
96 | }
97 |
98 | $this->table->create();
99 | }
100 |
101 | public function cachetable()
102 | {
103 | $this->cachetable = new CacheTable();
104 | }
105 |
106 | public function option(array $option)
107 | {
108 | // 设置参数
109 | if (!empty($option)) {
110 | $this->swoole->set($option);
111 | }
112 |
113 | foreach ($this->event as $event) {
114 | // 自定义回调
115 | if (!empty($option[$event])) {
116 | $this->swoole->on($event, $option[$event]);
117 | } elseif (method_exists($this, 'on' . $event)) {
118 | $this->swoole->on($event, [$this, 'on' . $event]);
119 | }
120 | }
121 | if ("websocket" == $this->server_type) {
122 | foreach ($this->event as $event) {
123 | if (method_exists($this, 'Websocketon' . $event)) {
124 | $this->swoole->on($event, [$this, 'Websocketon' . $event]);
125 | }
126 | }
127 | }
128 | }
129 |
130 | /**
131 | * 此事件在Worker进程/Task进程启动时发生,这里创建的对象可以在进程生命周期内使用
132 | *
133 | * @param $server
134 | * @param $worker_id
135 | */
136 | public function onWorkerStart($server, $worker_id)
137 | {
138 | // 应用实例化
139 | $this->app = new Application($this->appPath);
140 | $this->lastMtime = time();
141 |
142 | //swoole server worker启动行为
143 | $hook = Container::get('hook');
144 | $hook->listen('swoole_worker_start', ['server' => $server, 'worker_id' => $worker_id]);
145 |
146 | // Swoole Server保存到容器
147 | $this->app->swoole = $server;
148 |
149 | if ($this->table) {
150 | $this->app['swoole_table'] = $this->table;
151 | }
152 |
153 | $this->app->cachetable = $this->cachetable;
154 |
155 | // 指定日志类驱动
156 | Loader::addClassMap([
157 | 'think\\log\\driver\\File' => __DIR__ . '/log/File.php',
158 | 'think\\cache\\driver\\Table' => __DIR__ . '/cache/driver/Table.php',
159 | ]);
160 |
161 | Facade::bind([
162 | 'think\facade\Cookie' => Cookie::class,
163 | 'think\facade\Session' => Session::class,
164 | facade\Application::class => Application::class,
165 | facade\Http::class => Http::class,
166 | facade\Task::class => Task::class,
167 | facade\Timer::class => Timer::class,
168 | ]);
169 |
170 | // 应用初始化
171 | $this->app->initialize();
172 |
173 | $this->app->bindTo([
174 | 'cookie' => Cookie::class,
175 | 'session' => Session::class,
176 | ]);
177 |
178 | $this->initServer($server, $worker_id);
179 |
180 | if (0 == $worker_id && $this->monitor) {
181 | $this->monitor($server);
182 | }
183 |
184 | //只在一个进程内执行定时任务
185 | if (0 == $worker_id) {
186 | $this->timer($server);
187 | }
188 | }
189 |
190 | /**
191 | * 自定义初始化Swoole
192 | * @param $server
193 | * @param $worker_id
194 | */
195 | public function initServer($server, $worker_id)
196 | {
197 | $wokerStart = Config::get('swoole.wokerstart');
198 | if ($wokerStart) {
199 | if (is_string($wokerStart) && class_exists($wokerStart)) {
200 | $obj = new $wokerStart($server, $worker_id);
201 | $obj->run();
202 | unset($obj);
203 | } elseif ($wokerStart instanceof \Closure) {
204 | $wokerStart($server, $worker_id);
205 | }
206 | }
207 | }
208 |
209 | /**
210 | * 文件监控
211 | *
212 | * @param $server
213 | */
214 | protected function monitor($server)
215 | {
216 | $paths = $this->monitor['path'] ?: [$this->app->getAppPath(), $this->app->getConfigPath()];
217 | $timer = $this->monitor['interval'] ?: 2;
218 |
219 | $server->tick($timer, function () use ($paths, $server) {
220 | foreach ($paths as $path) {
221 | $dir = new \RecursiveDirectoryIterator($path);
222 | $iterator = new \RecursiveIteratorIterator($dir);
223 |
224 | foreach ($iterator as $file) {
225 | if (pathinfo($file, PATHINFO_EXTENSION) != 'php') {
226 | continue;
227 | }
228 |
229 | if ($this->lastMtime < $file->getMTime()) {
230 | $this->lastMtime = $file->getMTime();
231 | echo '[update]' . $file . " reload...\n";
232 | $server->reload();
233 | return;
234 | }
235 | }
236 | }
237 | });
238 | }
239 |
240 | /**
241 | * 系统定时器
242 | *
243 | * @param $server
244 | */
245 | public function timer($server)
246 | {
247 | $timer = Config::get('swoole.timer');
248 | $interval = intval(Config::get('swoole.interval'));
249 | if ($timer) {
250 | $interval = $interval > 0 ? $interval : 1000;
251 | $server->tick($interval, function () use ($server) {
252 | TimerF::run($server);
253 | });
254 | }
255 | $queue_type = Config::get('swoole.queue_type');
256 | $task = QueueTask::instance();
257 | if ("task" == $queue_type) {
258 | $server->tick(1000, function () use ($queue_type, $task) {
259 | $task->run();
260 | });
261 | }
262 | }
263 |
264 | /**
265 | * request回调
266 | * @param $request
267 | * @param $response
268 | */
269 | public function onRequest($request, $response)
270 | {
271 | // 执行应用并响应
272 | $this->app->swoole($request, $response);
273 | }
274 |
275 | /**
276 | * Message回调
277 | * @param $server
278 | * @param $frame
279 | */
280 | public function WebsocketonMessage($server, $frame)
281 | {
282 | // 执行应用并响应
283 | $this->app->swooleWebSocket($server, $frame);
284 | }
285 |
286 | /**
287 | * Close
288 | */
289 | public function WebsocketonClose($server, $fd, $reactorId)
290 | {
291 | $data = [$server, $fd, $reactorId];
292 | $hook = Container::get('hook');
293 | $hook->listen('swoole_websocket_on_close', $data);
294 | }
295 |
296 | /**
297 | * 任务投递
298 | * @param HttpServer $serv
299 | * @param $task_id
300 | * @param $fromWorkerId
301 | * @param $data
302 | * @return mixed|null
303 | */
304 | public function onTask(HttpServer $serv, $task_id, $fromWorkerId, $data)
305 | {
306 | if (is_string($data) && class_exists($data)) {
307 | $taskObj = new $data;
308 | if (method_exists($taskObj, 'run')) {
309 | $taskObj->run($serv, $task_id, $fromWorkerId);
310 | unset($taskObj);
311 | return true;
312 | }
313 | }
314 |
315 | if (is_object($data) && method_exists($data, 'run')) {
316 | $data->run($serv, $task_id, $fromWorkerId);
317 | unset($data);
318 | return true;
319 | }
320 |
321 | if ($data instanceof SuperClosure) {
322 | return $data($serv, $task_id, $data);
323 | } else {
324 | $serv->finish($data);
325 | }
326 | }
327 |
328 | /**
329 | * 任务结束,如果有自定义任务结束回调方法则不会触发该方法
330 | * @param HttpServer $serv
331 | * @param $task_id
332 | * @param $data
333 | */
334 | public function onFinish(HttpServer $serv, $task_id, $data)
335 | {
336 | if ($data instanceof SuperClosure) {
337 | $data($serv, $task_id, $data);
338 | }
339 | }
340 | }
341 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ThinkPHP 5.1 Swoole 扩展
2 | ===============
3 |
4 | ## 安装
5 |
6 | 首先按照Swoole官网说明安装swoole扩展,然后使用
7 | ~~~
8 | composer require topthink/think-swoole
9 | ~~~
10 | 安装swoole扩展。
11 |
12 | ## 使用方法
13 |
14 | ### HttpServer
15 |
16 | 直接在命令行下启动服务端。
17 |
18 | ~~~
19 | php think swoole
20 | ~~~
21 |
22 | 启动完成后,会在0.0.0.0:9501启动一个HTTP Server,可以直接访问当前的应用。
23 |
24 | swoole的参数可以在应用配置目录下的swoole.php里面配置(具体参考配置文件内容)。
25 |
26 | 如果需要使用守护进程方式运行,可以使用
27 | ~~~
28 | php think swoole -d
29 | ~~~
30 | 或者在swoole.php文件中设置
31 | ~~~
32 | 'daemonize' => true
33 | ~~~
34 |
35 | 注意:由于onWorkerStart运行的时候没有HTTP_HOST,因此最好在应用配置文件中设置app_host
36 |
37 | 支持的操作包括
38 | ~~~
39 | php think swoole [start|stop|reload|restart]
40 | ~~~
41 |
42 | ### Server
43 |
44 | 可以支持直接启动一个Swoole server
45 |
46 | ~~~
47 | php think swoole:server
48 | ~~~
49 | 会在0.0.0.0:9508启动一个Websocket服务。
50 |
51 | 如果需要自定义参数,可以在config/swoole_server.php中进行配置,包括:
52 |
53 | 配置参数 | 描述
54 | --- | ---
55 | type| 服务类型
56 | host | 监听地址
57 | port | 监听端口
58 | mode | 运行模式
59 | sock_type | Socket type
60 |
61 |
62 | 并且支持swoole所有的参数。
63 | 也支持使用闭包方式定义相关事件回调。
64 |
65 | ~~~
66 | return [
67 | // 扩展自身配置
68 | 'host' => '0.0.0.0', // 监听地址
69 | 'port' => 9501, // 监听端口
70 | 'type' => 'socket', // 服务类型 支持 socket http server
71 | 'mode' => SWOOLE_PROCESS,
72 | 'sock_type' => SWOOLE_SOCK_TCP,
73 |
74 | // 可以支持swoole的所有配置参数
75 | 'daemonize' => false,
76 |
77 | // 事件回调定义
78 | 'onOpen' => function ($server, $request) {
79 | echo "server: handshake success with fd{$request->fd}\n";
80 | },
81 |
82 | 'onMessage' => function ($server, $frame) {
83 | echo "receive from {$frame->fd}:{$frame->data},opcode:{$frame->opcode},fin:{$frame->finish}\n";
84 | $server->push($frame->fd, "this is server");
85 | },
86 |
87 | 'onRequest' => function ($request, $response) {
88 | $response->end("Hello Swoole. #" . rand(1000, 9999) . "
");
89 | },
90 |
91 | 'onClose' => function ($ser, $fd) {
92 | echo "client {$fd} closed\n";
93 | },
94 | ];
95 | ~~~
96 |
97 | 也可以使用自定义的服务类
98 |
99 | ~~~
100 | 4,
112 | 'daemonize' => true,
113 | 'backlog' => 128
114 | ];
115 |
116 | public function onReceive($server, $fd, $from_id, $data)
117 | {
118 | $server->send($fd, 'Swoole: '.$data);
119 | }
120 | }
121 | ~~~
122 |
123 | 支持swoole所有的回调方法定义(回调方法必须是public类型)
124 | serverType 属性定义为 socket或者http 则支持swoole的swoole_websocket_server和swoole_http_server
125 |
126 | 然后在swoole_server.php中增加配置参数:
127 | ~~~
128 | return [
129 | 'swoole_class' => 'app\http\Swoole',
130 | ];
131 | ~~~
132 |
133 | 定义该参数后,其它配置参数均不再有效。
134 |
135 | 在命令行启动服务端
136 | ~~~
137 | php think swoole:server
138 | ~~~
139 |
140 |
141 | 支持reload|restart|stop|status 操作
142 | ~~~
143 | php think swoole:server reload
144 | ~~~
145 |
146 |
147 | ### 配置信息详解
148 |
149 | swoole.php
150 |
151 | ```php
152 |
161 | // +----------------------------------------------------------------------
162 | use think\facade\Env;
163 | // +----------------------------------------------------------------------
164 | // | Swoole设置 php think swoole命令行下有效
165 | // +----------------------------------------------------------------------
166 | return [
167 | // 扩展自身配置
168 | 'host' => '0.0.0.0', // 监听地址
169 | 'port' => 9501, // 监听端口
170 | 'mode' => '', // 运行模式 默认为SWOOLE_PROCESS
171 | 'sock_type' => '', // sock type 默认为SWOOLE_SOCK_TCP
172 | 'app_path' => '', // 应用地址 如果开启了 'daemonize'=>true 必须设置(使用绝对路径)
173 | 'file_monitor' => false, // 是否开启PHP文件更改监控(调试模式下自动开启)
174 | 'file_monitor_interval' => 2, // 文件变化监控检测时间间隔(秒)
175 | 'file_monitor_path' => [], // 文件监控目录 默认监控application和config目录
176 | // 可以支持swoole的所有配置参数
177 | 'pid_file' => Env::get('runtime_path') . 'swoole.pid',//swoole主进程pid存放文件
178 | 'log_file' => Env::get('runtime_path') . 'swoole.log',//swoole日志存放文件
179 | 'document_root' => Env::get('root_path') . 'public',//设置静态服务根目录
180 | 'enable_static_handler' => true,//是否由SWOOLE底层自动处理静态文件,TRUE表示SWOOLE判断是否存在静态文件,如果存在则直接返回静态文件信息
181 | 'timer' => true,//是否开启系统定时器
182 | 'interval' => 500,//系统定时器 时间间隔
183 | 'task_worker_num' => 1,//swoole 任务工作进程数量
184 | 'user' =>'www',//表示swoole worker进程所属的管理员名称,如果要绑定1024以下端口则必须要求具有root权限,如果设置了该项,则除主进程外的所有进程都运行于指定用户下
185 | ];
186 | ```
187 |
188 | timer.php
189 |
190 | ```php
191 | "类"
199 | * *中间一个空格
200 | * 系统定时任务需要在swoole.php中开启
201 | * 自定义定时器不受其影响
202 | */
203 | return [
204 | '*/5 * * * * *' => '\\app\\lib\\Timer',//时间配置方式参考crontab,主要是增加秒,其他和crontab一致,对应Value为定时器接口实现类的完整命名空间(包含类名)
205 | ];
206 | ```
207 |
208 | ### 异步任务投递
209 |
210 | 1.异步任务接口实现
211 |
212 | ```php
213 | function($server, $worker_id){
360 | //如果只在一个进程处理 则可以这样
361 |
362 | if (0==$worker_id){
363 | //这样只会在第一个woker进程处理
364 | }
365 | }
366 | ```
367 |
368 | 如果需要实现Workerstart的接口,可以这样实现
369 |
370 | ```php
371 | '\\app\\lib\\WorkStart'
401 | ```
402 |
403 | ### Websocket支持
404 | 需要在swoole.php修改
405 |
406 | ```a
407 | 'server_type' => 'websocket', // 服务类型 支持 http websocket
408 | ```
409 |
410 | 由于Swoole\WebSocket\Server继承于Swoole\Http\Server顾Http服务具有的功能Websocket也具有。如果采用如下websocket服务,
411 | 通讯数据格式已经固定,如采用自定义数据结构,请采用自定义服务
412 |
413 | WebSocket通讯数据结构
414 | ```json
415 | {
416 | "event":"",//事件名称
417 | "url":"",//目标地址
418 | "arguments":{//客户端投递数据
419 | "post":[],//post数据
420 | "get":[],//get数据
421 | "cookie":[],//cookie数据
422 | }
423 | }
424 | ```
425 | 详解
426 |
427 | * event 该参数为事件名称,用于触发客户端事件,即接收服务器推送数据处理指定任务
428 |
429 | * url,该为访问的目标地址,相当于http模式下http://xxx.com/url
430 |
431 | * arguments 客户端投递的所有数据,如果希望可以按照原来POST,GET的方式处理数据,可以提交时按照上述方式提交,会自动处理
432 |
433 | 客户端JS可以参考example里websocketclient.js
434 |
435 |
436 | 服务端发送数据
437 |
438 | ```php
439 | use think\swoole\WebSocketFrame;
440 |
441 | $client=WebSocketFrame::getInstance();
442 | //发送数据给当前请求的客户端
443 | $client->pushToClient([]);//参数为数组,字符串,数字
444 | //发送给所有客户端
445 | $client->pushToClients([]);//参数为数组,字符串,数字
446 |
447 | $client->getArgs();//获取arguments里的参数
448 |
449 | $client->getData();//获取客户端发送给的所有数据
450 |
451 | $client->getServer();//获取当前server
452 |
453 | $client->getFrame();//获取当前客户端给发送的原始数据
454 | ```
455 |
456 |
457 | 新增队列支持
458 |
459 | think-queue,是一个非常好用的队列服务,xavier-swoole的队列服务依赖于think-queue,使用方法和think-queue保持一致
460 |
461 | 主要增加如下功能
462 |
463 | 1. 采用多进程模式运行或采用Task异步执行
464 | 2. 可以指定同时消耗队列任务的进程数量
465 | 3. Task模式和Server共享Task进程,充分利用资源
466 | 4. Process模式采用进程模式,各个任务之间相互隔离,执行一定次数后,重启进程,防止内存泄露
467 |
468 |
469 | 如果任务较多且复杂,推荐采用Process模式
470 |
471 | ```php
472 | 'queue_type'=>'task',//task or process
473 | 'queue'=>[
474 | "Index"=>[
475 | "delay"=>0,//延迟时间
476 | "sleep"=>3,//休息时间
477 | "maxTries"=>0,//重试次数
478 | "nums"=>2//进程数量
479 | ],
480 | ]
481 | ```
482 |
483 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/src/Session.php:
--------------------------------------------------------------------------------
1 |
10 | // +----------------------------------------------------------------------
11 | namespace think\swoole;
12 |
13 | use Swoole\Table;
14 | use think\Container;
15 | use think\facade\Cache;
16 | use think\facade\Cookie as ThinkCookie;
17 | use think\Session as BaseSession;
18 |
19 | /**
20 | * Swoole Cookie类
21 | */
22 | class Session extends BaseSession
23 | {
24 | /**
25 | * Session数据
26 | * @var array
27 | */
28 | protected $data = [];
29 |
30 | /**
31 | * 记录Session name
32 | * @var string
33 | */
34 | protected $sessionName = 'PHPSESSID';
35 |
36 | /**
37 | * Session有效期
38 | * @var int
39 | */
40 | protected $expire = 0;
41 |
42 | /**
43 | * Swoole_table对象
44 | * @var Table
45 | */
46 | protected $swooleTable;
47 |
48 | /**
49 | * session初始化
50 | * @access public
51 | * @param array $config
52 | * @return void
53 | * @throws \think\Exception
54 | */
55 | public function init(array $config = [])
56 | {
57 | $config = $config ?: $this->config;
58 |
59 | if (!empty($config['name'])) {
60 | $this->sessionName = $config['name'];
61 | }
62 |
63 | if (!empty($config['expire'])) {
64 | $this->expire = $config['expire'];
65 | }
66 |
67 | if (!empty($config['auto_start'])) {
68 | $this->start();
69 | } else {
70 | $this->init = false;
71 | }
72 |
73 | return $this;
74 | }
75 |
76 | /**
77 | * session自动启动或者初始化
78 | * @access public
79 | * @return void
80 | */
81 | public function boot()
82 | {
83 | if (is_null($this->init)) {
84 | $this->init();
85 | }
86 |
87 | if (false === $this->init) {
88 | $this->start();
89 | }
90 | }
91 |
92 | public function name($name)
93 | {
94 | $this->sessionName = $name;
95 | }
96 |
97 | /**
98 | * session_id设置
99 | * @access public
100 | * @param string $id session_id
101 | * @param int $expire Session有效期
102 | * @return void
103 | */
104 | public function setId($id, $expire = null)
105 | {
106 | ThinkCookie::set($this->sessionName, $id, $expire);
107 | }
108 |
109 | /**
110 | * 获取session_id
111 | * @access public
112 | * @param bool $regenerate 不存在是否自动生成
113 | * @return string
114 | */
115 | public function getId($regenerate = true)
116 | {
117 | $sessionId = ThinkCookie::get($this->sessionName) ?: '';
118 |
119 | if (!$sessionId && $regenerate) {
120 | $sessionId = $this->regenerate();
121 | }
122 |
123 | return $sessionId;
124 | }
125 |
126 | /**
127 | * session设置
128 | * @access public
129 | * @param string $name session名称
130 | * @param mixed $value session值
131 | * @return void
132 | */
133 | public function set($name, $value, $prefix = null)
134 | {
135 | empty($this->init) && $this->boot();
136 |
137 | $sessionId = $this->getId();
138 |
139 | $this->setSession($sessionId, $name, $value);
140 | }
141 |
142 | /**
143 | * session设置
144 | * @access protected
145 | * @param string $sessionId session_id
146 | * @param string $name session名称
147 | * @param mixed $value session值
148 | * @param string|null $prefix 作用域(前缀)
149 | * @return void
150 | */
151 | protected function setSession($sessionId, $name, $value)
152 | {
153 | if (strpos($name, '.')) {
154 | // 二维数组赋值
155 | list($name1, $name2) = explode('.', $name);
156 |
157 | $this->data[$sessionId][$name1][$name2] = $value;
158 | } else {
159 | $this->data[$sessionId][$name] = $value;
160 | }
161 |
162 | // 持久化session数据
163 | $this->writeSessionData($sessionId);
164 | }
165 |
166 | /**
167 | * session获取
168 | * @access public
169 | * @param string $name session名称
170 | * @return mixed
171 | */
172 | public function get($name = '', $prefix = null)
173 | {
174 | empty($this->init) && $this->boot();
175 |
176 | $sessionId = $this->getId();
177 |
178 | return $this->readSession($sessionId, $name);
179 | }
180 |
181 | /**
182 | * session获取
183 | * @access protected
184 | * @param string $sessionId session_id
185 | * @param string $name session名称
186 | * @return mixed
187 | */
188 | protected function readSession($sessionId, $name = '')
189 | {
190 | $value = isset($this->data[$sessionId]) ? $this->data[$sessionId] : [];
191 |
192 | if (!is_array($value)) {
193 | $value = [];
194 | }
195 |
196 | if ('' != $name) {
197 | $name = explode('.', $name);
198 |
199 | foreach ($name as $val) {
200 | if (isset($value[$val])) {
201 | $value = $value[$val];
202 | } else {
203 | $value = null;
204 | break;
205 | }
206 | }
207 | }
208 |
209 | return $value;
210 | }
211 |
212 | /**
213 | * 删除session数据
214 | * @access public
215 | * @param string|array $name session名称
216 | * @return void
217 | */
218 | public function delete($name, $prefix = null)
219 | {
220 | empty($this->init) && $this->boot();
221 |
222 | $sessionId = $this->getId(false);
223 |
224 | if ($sessionId) {
225 | $this->deleteSession($sessionId, $name);
226 |
227 | // 持久化session数据
228 | $this->writeSessionData($sessionId);
229 | }
230 | }
231 |
232 | /**
233 | * 删除session数据
234 | * @access protected
235 | * @param string $sessionId session_id
236 | * @param string|array $name session名称
237 | * @return void
238 | */
239 | protected function deleteSession($sessionId, $name)
240 | {
241 | if (is_array($name)) {
242 | foreach ($name as $key) {
243 | $this->deleteSession($sessionId, $key);
244 | }
245 | } elseif (strpos($name, '.')) {
246 | list($name1, $name2) = explode('.', $name);
247 | unset($this->data[$sessionId][$name1][$name2]);
248 | } else {
249 | unset($this->data[$sessionId][$name]);
250 | }
251 | }
252 |
253 | protected function writeSessionData($sessionId)
254 | {
255 | if ($this->swooleTable) {
256 | $this->swooleTable->set('sess_' . $sessionId, [
257 | 'data' => json_encode($this->data[$sessionId]),
258 | 'expire' => time() + $this->expire,
259 | ]);
260 | } else {
261 | Cache::set('sess_' . $sessionId, $this->data[$sessionId], $this->expire);
262 | }
263 | }
264 |
265 | /**
266 | * 清空session数据
267 | * @access public
268 | * @return void
269 | */
270 | public function clear($prefix = null)
271 | {
272 | empty($this->init) && $this->boot();
273 |
274 | $sessionId = $this->getId(false);
275 |
276 | if ($sessionId) {
277 | $this->clearSession($sessionId);
278 | }
279 | }
280 |
281 | /**
282 | * 清空session数据
283 | * @access protected
284 | * @param string $sessionId session_id
285 | * @return void
286 | */
287 | protected function clearSession($sessionId)
288 | {
289 | $this->data[$sessionId] = [];
290 |
291 | if ($this->swooleTable) {
292 | $this->swooleTable->del('sess_' . $sessionId);
293 | } else {
294 | Cache::rm('sess_' . $sessionId);
295 | }
296 | }
297 |
298 | /**
299 | * 判断session数据
300 | * @access public
301 | * @param string $name session名称
302 | * @return bool
303 | */
304 | public function has($name, $prefix = null)
305 | {
306 | empty($this->init) && $this->boot();
307 |
308 | $sessionId = $this->getId(false);
309 |
310 | if ($sessionId) {
311 | return $this->hasSession($sessionId, $name);
312 | }
313 |
314 | return false;
315 | }
316 |
317 | /**
318 | * 判断session数据
319 | * @access protected
320 | * @param string $sessionId session_id
321 | * @param string $name session名称
322 | * @return bool
323 | */
324 | protected function hasSession($sessionId, $name)
325 | {
326 | $value = isset($this->data[$sessionId]) ? $this->data[$sessionId] : [];
327 |
328 | $name = explode('.', $name);
329 |
330 | foreach ($name as $val) {
331 | if (!isset($value[$val])) {
332 | return false;
333 | } else {
334 | $value = $value[$val];
335 | }
336 | }
337 |
338 | return true;
339 | }
340 |
341 | /**
342 | * 启动session
343 | * @access public
344 | * @return void
345 | */
346 | public function start()
347 | {
348 | $sessionId = $this->getId();
349 |
350 | // 读取缓存数据
351 | if (empty($this->data[$sessionId])) {
352 | if (!empty($this->config['use_swoole_table'])) {
353 | $this->swooleTable = Container::get('swoole_table');
354 |
355 | $result = $this->swooleTable->get('sess_' . $sessionId);
356 |
357 | if (0 == $result['expire'] || time() <= $result['expire']) {
358 | $data = $result['data'];
359 | }
360 | } else {
361 | $data = Cache::get('sess_' . $sessionId);
362 | }
363 |
364 | if (!empty($data)) {
365 | $this->data[$sessionId] = $data;
366 | }
367 | }
368 |
369 | $this->init = true;
370 | }
371 |
372 | /**
373 | * 销毁session
374 | * @access public
375 | * @return void
376 | */
377 | public function destroy()
378 | {
379 | $sessionId = $this->getId(false);
380 |
381 | if ($sessionId) {
382 | $this->destroySession($sessionId);
383 | }
384 |
385 | $this->init = null;
386 | }
387 |
388 | /**
389 | * 销毁session
390 | * @access protected
391 | * @param string $sessionId session_id
392 | * @return void
393 | */
394 | protected function destroySession($sessionId)
395 | {
396 | if (isset($this->data[$sessionId])) {
397 | unset($this->data[$sessionId]);
398 |
399 | if ($this->swooleTable) {
400 | $this->swooleTable->del('sess_' . $sessionId);
401 | } else {
402 | Cache::rm('sess_' . $sessionId);
403 | }
404 | }
405 | }
406 |
407 | /**
408 | * 生成session_id
409 | * @access public
410 | * @param bool $delete 是否删除关联会话文件
411 | * @return string
412 | */
413 | public function regenerate($delete = false)
414 | {
415 | if ($delete) {
416 | $this->destroy();
417 | }
418 |
419 | $sessionId = md5(microtime(true) . uniqid());
420 |
421 | $this->setId($sessionId);
422 |
423 | return $sessionId;
424 | }
425 |
426 | /**
427 | * 暂停session
428 | * @access public
429 | * @return void
430 | */
431 | public function pause()
432 | {
433 | $this->init = false;
434 | }
435 | }
436 |
--------------------------------------------------------------------------------