From bf9e9a2dfd3f531b81538022d3ff7f264ea13e33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=AA=E5=AD=90=E5=B0=8F=E6=96=B0?= Date: Sun, 22 Mar 2020 12:20:07 +0800 Subject: [PATCH] first commit --- .gitignore | 2 + .../Applications/autoReload/start.php | 44 + GatewayWorker/Applications/githubT/Events.php | 83 + .../githubT/start_businessworker.php | 37 + .../Applications/githubT/start_gateway.php | 76 + .../Applications/githubT/start_register.php | 28 + .../Applications/githubT/start_sync.php | 123 + GatewayWorker/MIT-LICENSE.txt | 21 + GatewayWorker/README.md | 46 + GatewayWorker/composer.json | 9 + GatewayWorker/sql | Bin 0 -> 32768 bytes GatewayWorker/start.php | 37 + GatewayWorker/start_for_win.bat | 2 + GatewayWorker/vendor/autoload.php | 7 + GatewayWorker/vendor/composer/ClassLoader.php | 445 +++ GatewayWorker/vendor/composer/LICENSE | 21 + .../vendor/composer/autoload_classmap.php | 9 + .../vendor/composer/autoload_namespaces.php | 9 + .../vendor/composer/autoload_psr4.php | 11 + .../vendor/composer/autoload_real.php | 52 + .../vendor/composer/autoload_static.php | 39 + GatewayWorker/vendor/composer/installed.json | 86 + ...ta_g.widora.io_GatewayWorker_start.php.pid | 1 + .../workerman/gateway-worker/.gitignore | 4 + .../workerman/gateway-worker/MIT-LICENSE.txt | 21 + .../vendor/workerman/gateway-worker/README.md | 38 + .../workerman/gateway-worker/composer.json | 12 + .../gateway-worker/src/BusinessWorker.php | 560 ++++ .../workerman/gateway-worker/src/Gateway.php | 1019 +++++++ .../gateway-worker/src/Lib/Context.php | 136 + .../workerman/gateway-worker/src/Lib/Db.php | 76 + .../gateway-worker/src/Lib/DbConnection.php | 1976 +++++++++++++ .../gateway-worker/src/Lib/Gateway.php | 1361 +++++++++ .../src/Protocols/GatewayProtocol.php | 216 ++ .../workerman/gateway-worker/src/Register.php | 190 ++ GatewayWorker/vendor/workerman/workerman.log | 663 +++++ .../vendor/workerman/workerman/.gitignore | 6 + .../vendor/workerman/workerman/Autoloader.php | 69 + .../Connection/AsyncTcpConnection.php | 376 +++ .../Connection/AsyncUdpConnection.php | 209 ++ .../Connection/ConnectionInterface.php | 125 + .../workerman/Connection/TcpConnection.php | 1007 +++++++ .../workerman/Connection/UdpConnection.php | 191 ++ .../vendor/workerman/workerman/Events/Ev.php | 195 ++ .../workerman/workerman/Events/Event.php | 219 ++ .../workerman/Events/EventInterface.php | 107 + .../workerman/workerman/Events/Libevent.php | 227 ++ .../workerman/workerman/Events/React/Base.php | 264 ++ .../workerman/Events/React/ExtEventLoop.php | 27 + .../Events/React/ExtLibEventLoop.php | 27 + .../Events/React/StreamSelectLoop.php | 26 + .../workerman/workerman/Events/Select.php | 338 +++ .../workerman/workerman/Events/Swoole.php | 221 ++ .../workerman/workerman/Lib/Constants.php | 42 + .../vendor/workerman/workerman/Lib/Timer.php | 182 ++ .../workerman/workerman/MIT-LICENSE.txt | 21 + .../workerman/workerman/Protocols/Frame.php | 61 + .../workerman/workerman/Protocols/Http.php | 755 +++++ .../workerman/Protocols/Http/mime.types | 90 + .../workerman/Protocols/ProtocolInterface.php | 52 + .../workerman/workerman/Protocols/Text.php | 70 + .../workerman/Protocols/Websocket.php | 503 ++++ .../workerman/workerman/Protocols/Ws.php | 472 +++ .../vendor/workerman/workerman/README.md | 606 ++++ .../vendor/workerman/workerman/WebServer.php | 328 +++ .../vendor/workerman/workerman/Worker.php | 2534 +++++++++++++++++ .../vendor/workerman/workerman/composer.json | 38 + README.md | 21 + staticweb/css/style.css | 316 ++ staticweb/images/bg.jpg | Bin 0 -> 162874 bytes staticweb/images/cube.png | Bin 0 -> 4201 bytes staticweb/images/githublogo.png | Bin 0 -> 59848 bytes staticweb/images/icons.png | Bin 0 -> 3444 bytes staticweb/index.html | 41 + staticweb/js/index.js | 126 + staticweb/js/jquery-2.2.4.min.js | 5 + staticweb/layer/layer.js | 2 + staticweb/layer/mobile/layer.js | 2 + staticweb/layer/mobile/need/layer.css | 1 + staticweb/layer/theme/default/icon-ext.png | Bin 0 -> 5911 bytes staticweb/layer/theme/default/icon.png | Bin 0 -> 11493 bytes staticweb/layer/theme/default/layer.css | 1 + staticweb/layer/theme/default/loading-0.gif | Bin 0 -> 5793 bytes staticweb/layer/theme/default/loading-1.gif | Bin 0 -> 701 bytes staticweb/layer/theme/default/loading-2.gif | Bin 0 -> 1787 bytes 85 files changed, 17363 insertions(+) create mode 100644 .gitignore create mode 100644 GatewayWorker/Applications/autoReload/start.php create mode 100644 GatewayWorker/Applications/githubT/Events.php create mode 100644 GatewayWorker/Applications/githubT/start_businessworker.php create mode 100644 GatewayWorker/Applications/githubT/start_gateway.php create mode 100644 GatewayWorker/Applications/githubT/start_register.php create mode 100644 GatewayWorker/Applications/githubT/start_sync.php create mode 100644 GatewayWorker/MIT-LICENSE.txt create mode 100644 GatewayWorker/README.md create mode 100644 GatewayWorker/composer.json create mode 100644 GatewayWorker/sql create mode 100644 GatewayWorker/start.php create mode 100644 GatewayWorker/start_for_win.bat create mode 100644 GatewayWorker/vendor/autoload.php create mode 100644 GatewayWorker/vendor/composer/ClassLoader.php create mode 100644 GatewayWorker/vendor/composer/LICENSE create mode 100644 GatewayWorker/vendor/composer/autoload_classmap.php create mode 100644 GatewayWorker/vendor/composer/autoload_namespaces.php create mode 100644 GatewayWorker/vendor/composer/autoload_psr4.php create mode 100644 GatewayWorker/vendor/composer/autoload_real.php create mode 100644 GatewayWorker/vendor/composer/autoload_static.php create mode 100644 GatewayWorker/vendor/composer/installed.json create mode 100644 GatewayWorker/vendor/workerman/_www_data_g.widora.io_GatewayWorker_start.php.pid create mode 100644 GatewayWorker/vendor/workerman/gateway-worker/.gitignore create mode 100644 GatewayWorker/vendor/workerman/gateway-worker/MIT-LICENSE.txt create mode 100644 GatewayWorker/vendor/workerman/gateway-worker/README.md create mode 100644 GatewayWorker/vendor/workerman/gateway-worker/composer.json create mode 100644 GatewayWorker/vendor/workerman/gateway-worker/src/BusinessWorker.php create mode 100644 GatewayWorker/vendor/workerman/gateway-worker/src/Gateway.php create mode 100644 GatewayWorker/vendor/workerman/gateway-worker/src/Lib/Context.php create mode 100644 GatewayWorker/vendor/workerman/gateway-worker/src/Lib/Db.php create mode 100644 GatewayWorker/vendor/workerman/gateway-worker/src/Lib/DbConnection.php create mode 100644 GatewayWorker/vendor/workerman/gateway-worker/src/Lib/Gateway.php create mode 100644 GatewayWorker/vendor/workerman/gateway-worker/src/Protocols/GatewayProtocol.php create mode 100644 GatewayWorker/vendor/workerman/gateway-worker/src/Register.php create mode 100644 GatewayWorker/vendor/workerman/workerman.log create mode 100644 GatewayWorker/vendor/workerman/workerman/.gitignore create mode 100644 GatewayWorker/vendor/workerman/workerman/Autoloader.php create mode 100644 GatewayWorker/vendor/workerman/workerman/Connection/AsyncTcpConnection.php create mode 100644 GatewayWorker/vendor/workerman/workerman/Connection/AsyncUdpConnection.php create mode 100644 GatewayWorker/vendor/workerman/workerman/Connection/ConnectionInterface.php create mode 100644 GatewayWorker/vendor/workerman/workerman/Connection/TcpConnection.php create mode 100644 GatewayWorker/vendor/workerman/workerman/Connection/UdpConnection.php create mode 100644 GatewayWorker/vendor/workerman/workerman/Events/Ev.php create mode 100644 GatewayWorker/vendor/workerman/workerman/Events/Event.php create mode 100644 GatewayWorker/vendor/workerman/workerman/Events/EventInterface.php create mode 100644 GatewayWorker/vendor/workerman/workerman/Events/Libevent.php create mode 100644 GatewayWorker/vendor/workerman/workerman/Events/React/Base.php create mode 100644 GatewayWorker/vendor/workerman/workerman/Events/React/ExtEventLoop.php create mode 100644 GatewayWorker/vendor/workerman/workerman/Events/React/ExtLibEventLoop.php create mode 100644 GatewayWorker/vendor/workerman/workerman/Events/React/StreamSelectLoop.php create mode 100644 GatewayWorker/vendor/workerman/workerman/Events/Select.php create mode 100644 GatewayWorker/vendor/workerman/workerman/Events/Swoole.php create mode 100644 GatewayWorker/vendor/workerman/workerman/Lib/Constants.php create mode 100644 GatewayWorker/vendor/workerman/workerman/Lib/Timer.php create mode 100644 GatewayWorker/vendor/workerman/workerman/MIT-LICENSE.txt create mode 100644 GatewayWorker/vendor/workerman/workerman/Protocols/Frame.php create mode 100644 GatewayWorker/vendor/workerman/workerman/Protocols/Http.php create mode 100644 GatewayWorker/vendor/workerman/workerman/Protocols/Http/mime.types create mode 100644 GatewayWorker/vendor/workerman/workerman/Protocols/ProtocolInterface.php create mode 100644 GatewayWorker/vendor/workerman/workerman/Protocols/Text.php create mode 100644 GatewayWorker/vendor/workerman/workerman/Protocols/Websocket.php create mode 100644 GatewayWorker/vendor/workerman/workerman/Protocols/Ws.php create mode 100644 GatewayWorker/vendor/workerman/workerman/README.md create mode 100644 GatewayWorker/vendor/workerman/workerman/WebServer.php create mode 100644 GatewayWorker/vendor/workerman/workerman/Worker.php create mode 100644 GatewayWorker/vendor/workerman/workerman/composer.json create mode 100644 README.md create mode 100644 staticweb/css/style.css create mode 100644 staticweb/images/bg.jpg create mode 100644 staticweb/images/cube.png create mode 100644 staticweb/images/githublogo.png create mode 100644 staticweb/images/icons.png create mode 100644 staticweb/index.html create mode 100644 staticweb/js/index.js create mode 100644 staticweb/js/jquery-2.2.4.min.js create mode 100755 staticweb/layer/layer.js create mode 100755 staticweb/layer/mobile/layer.js create mode 100755 staticweb/layer/mobile/need/layer.css create mode 100755 staticweb/layer/theme/default/icon-ext.png create mode 100755 staticweb/layer/theme/default/icon.png create mode 100755 staticweb/layer/theme/default/layer.css create mode 100755 staticweb/layer/theme/default/loading-0.gif create mode 100755 staticweb/layer/theme/default/loading-1.gif create mode 100755 staticweb/layer/theme/default/loading-2.gif diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..28d8e97 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.idea +.idea/* \ No newline at end of file diff --git a/GatewayWorker/Applications/autoReload/start.php b/GatewayWorker/Applications/autoReload/start.php new file mode 100644 index 0000000..0df6bff --- /dev/null +++ b/GatewayWorker/Applications/autoReload/start.php @@ -0,0 +1,44 @@ +name = 'FileMonitor'; +$worker->reloadable = false; +$worker->onWorkerStart = function() +{ + global $webDirSource; + Timer::add(300, 'check_files_remove', $webDirSource); +}; + +// check files func +function check_files_remove($webDirSource) +{ +// $files = []; +// $dateTime = date("Ymd",strtotime("-1 day")); +// if(is_dir($webDirSource)) { +// if($files = scandir($webDirSource)) { +// $files = array_slice($files,2); +// foreach($files as $file) { +// if ($file < $dateTime) { +// rmdir($webDirSource . $file); +// } +// } +// } +// } + + $pdo = new PDO('sqlite:' . $webDirSource . '/sql'); + $data = $pdo->query("select * from file_info where addtime < '".(time() - 60*60*2+5)."'")->fetchAll(); + foreach($data as $item){ + if(is_file($item['path'])){ + unlink($item['path']); + } + $pdo->exec("delete from file_info where id=".$item['id']); + } + $pdo = null; + return; +} \ No newline at end of file diff --git a/GatewayWorker/Applications/githubT/Events.php b/GatewayWorker/Applications/githubT/Events.php new file mode 100644 index 0000000..0bf2ae4 --- /dev/null +++ b/GatewayWorker/Applications/githubT/Events.php @@ -0,0 +1,83 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +/** + * 用于检测业务代码死循环或者长时间阻塞等问题 + * 如果发现业务卡死,可以将下面declare打开(去掉//注释),并执行php start.php reload + * 然后观察一段时间workerman.log看是否有process_timeout异常 + */ + +//declare(ticks=1); + +use \GatewayWorker\Lib\Gateway; +use Workerman\Connection\AsyncTcpConnection; + +/** + * 主逻辑 + * 主要是处理 onConnect onMessage onClose 三个方法 + * onConnect 和 onClose 如果不需要可以不用实现并删除 + */ +class Events +{ + /** + * 当客户端连接时触发 + * 如果业务不需此回调可以删除onConnect + * + * @param int $client_id 连接id + */ + public static function onConnect($client_id) + { + // 向当前client_id发送数据 +// Gateway::sendToClient($client_id, "Hello $client_id\r\n"); + // 向所有人发送 +// Gateway::sendToAll("$client_id login\r\n"); + } + + /** + * 当客户端发来消息时触发 + * @param int $client_id 连接id + * @param mixed $message 具体消息 + */ + public static function onMessage($client_id, $message) + { + try { + $task_connection = new AsyncTcpConnection('text://127.0.0.1:16345'); + $task_connection->onConnect = function ($task_connection) use ($message) { + $task_connection->send($message); + + }; + $task_connection->onMessage = function ($task_connection, $data) use ($client_id) { + if($data == "true") { + $task_connection->close(); + }else{ + $data && Gateway::sendToClient($client_id, $data . "\r\n"); + } + }; + $task_connection->connect(); + } catch (\Throwable $e) { + + } + + } + + /** + * 当用户断开连接时触发 + * @param int $client_id 连接id + */ + public static function onClose($client_id) + { + // 向所有人发送 + GateWay::sendToAll("$client_id logout\r\n"); + } +} diff --git a/GatewayWorker/Applications/githubT/start_businessworker.php b/GatewayWorker/Applications/githubT/start_businessworker.php new file mode 100644 index 0000000..501213f --- /dev/null +++ b/GatewayWorker/Applications/githubT/start_businessworker.php @@ -0,0 +1,37 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +use \Workerman\Worker; +use \Workerman\WebServer; +use \GatewayWorker\Gateway; +use \GatewayWorker\BusinessWorker; +use \Workerman\Autoloader; + +// 自动加载类 +require_once __DIR__ . '/../../vendor/autoload.php'; + +// bussinessWorker 进程 +$worker = new BusinessWorker(); +// worker名称 +$worker->name = 'YourAppBusinessWorker'; +// bussinessWorker进程数量 +$worker->count = 4; +// 服务注册地址 +$worker->registerAddress = '127.0.0.1:1238'; + +// 如果不是在根目录启动,则运行runAll方法 +if(!defined('GLOBAL_START')) +{ + Worker::runAll(); +} + diff --git a/GatewayWorker/Applications/githubT/start_gateway.php b/GatewayWorker/Applications/githubT/start_gateway.php new file mode 100644 index 0000000..f8476f3 --- /dev/null +++ b/GatewayWorker/Applications/githubT/start_gateway.php @@ -0,0 +1,76 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +use \Workerman\Worker; +use \Workerman\WebServer; +use \GatewayWorker\Gateway; +use \GatewayWorker\BusinessWorker; +use \Workerman\Autoloader; + +// 自动加载类 +require_once __DIR__ . '/../../vendor/autoload.php'; +$context = array( + // 更多ssl选项请参考手册 http://php.net/manual/zh/context.ssl.php + 'ssl' => array( + // 请使用绝对路径 + 'local_cert' => '/usr/local/nginx/conf/3598272_g.widora.cn.pem', // 也可以是crt文件 + 'local_pk' => '/usr/local/nginx/conf/3598272_g.widora.cn.key', + 'verify_peer' => false, + // 'allow_self_signed' => true, //如果是自签名证书需要开启此选项 + ) +); +// gateway 进程,这里使用Text协议,可以用telnet测试 +$gateway = new Gateway("websocket://0.0.0.0:8848",$context); +// 开启SSL,websocket+SSL 即wss +$gateway->transport = 'ssl'; +// gateway名称,status方便查看 +$gateway->name = 'YourAppGateway'; +// gateway进程数 +$gateway->count = 4; +// 本机ip,分布式部署时使用内网ip +$gateway->lanIp = '127.0.0.1'; +// 内部通讯起始端口,假如$gateway->count=4,起始端口为4000 +// 则一般会使用4000 4001 4002 4003 4个端口作为内部通讯端口 +$gateway->startPort = 2900; +// 服务注册地址 +$gateway->registerAddress = '127.0.0.1:1238'; + +// 心跳间隔 +$gateway->pingInterval = 10; +// 心跳数据 +$gateway->pingData = '{"type":"ping"}'; +Worker::$daemonize = true; +/* +// 当客户端连接上来时,设置连接的onWebSocketConnect,即在websocket握手时的回调 +$gateway->onConnect = function($connection) +{ + $connection->onWebSocketConnect = function($connection , $http_header) + { + // 可以在这里判断连接来源是否合法,不合法就关掉连接 + // $_SERVER['HTTP_ORIGIN']标识来自哪个站点的页面发起的websocket链接 + if($_SERVER['HTTP_ORIGIN'] != 'http://kedou.workerman.net') + { + $connection->close(); + } + // onWebSocketConnect 里面$_GET $_SERVER是可用的 + // var_dump($_GET, $_SERVER); + }; +}; +*/ + +// 如果不是在根目录启动,则运行runAll方法 +if(!defined('GLOBAL_START')) +{ + Worker::runAll(); +} + diff --git a/GatewayWorker/Applications/githubT/start_register.php b/GatewayWorker/Applications/githubT/start_register.php new file mode 100644 index 0000000..6f0dc4f --- /dev/null +++ b/GatewayWorker/Applications/githubT/start_register.php @@ -0,0 +1,28 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +use \Workerman\Worker; +use \GatewayWorker\Register; + +// 自动加载类 +require_once __DIR__ . '/../../vendor/autoload.php'; + +// register 必须是text协议 +$register = new Register('text://0.0.0.0:1238'); + +// 如果不是在根目录启动,则运行runAll方法 +if(!defined('GLOBAL_START')) +{ + Worker::runAll(); +} + diff --git a/GatewayWorker/Applications/githubT/start_sync.php b/GatewayWorker/Applications/githubT/start_sync.php new file mode 100644 index 0000000..5d60020 --- /dev/null +++ b/GatewayWorker/Applications/githubT/start_sync.php @@ -0,0 +1,123 @@ +count = 10; +$task_worker->name = 'TaskWorker_github'; +$datatime = date("Ymd"); +$webDir = dirname(dirname(dirname(dirname(__FILE__)))) . "/staticweb/source/".$datatime; + +$task_worker->onMessage = function ($connection, $task_data) use ($webDir,$datatime) { + $dataArr = json_decode($task_data, true); + if(!$dataArr){ + return; + } + if(isset($dataArr['type'])){ + switch($dataArr['type']){ + case "1": + $pdo = new PDO('sqlite:' . dirname(dirname(dirname(__FILE__))) . '/sql'); + $pdo->exec("UPDATE statistics SET down_num=down_num+1 where id=1"); + $connection->send("true"); + $pdo = null; + return true; + break; + + case "2": + $pdo = new PDO('sqlite:' . dirname(dirname(dirname(__FILE__))) . '/sql'); + $data = $pdo->query("select down_num from statistics where id=1")->fetch(); + $down_num = $data[0]; + $connection->send(json_encode([ + 'status' => 0, + 'msg' =>"完成", + 'data' =>[ + 'sta' => 4, + 'down_num' => $down_num + ] + ],JSON_UNESCAPED_UNICODE) . "\r\n"); + $pdo = null; + $connection->send("true"); + return true; + break; + } + } + //&& !preg_match("/^.*(?<=(\.gz|\.tar\.gz|\.bz2|\.tar|\.tar\.bz2|\.zip|\.tar\.xz|\.tar\.z|\.rpm|\.deb|\.rar))$/ix",$dataArr['url']) + if (!preg_match("/^((https|http):\/\/)?([\w\.\/\-\_^]+?)\.([tar\.gz|gz|tar\.bz2|bz2|tar|zip|tar\.xz|tar\.z|rpm|deb|rar]+?)$/iu",$dataArr['url'],$ext) && !preg_match("/^https:\/\/github.com\/([\w\/\-\.\_]+)$/iu", $dataArr['url']) + ) { + $connection->send(json_encode(["status" => 1, "msg" => "请输入正确的github地址:仅支持https协议 或
(gz|tar.gz|bz2|tar|tar.bz2|zip|tar.xz|tar.z|rpm|deb|rar)格式的压缩包下载链接"]) . "\r\n"); + $connection->send("true"); + return; + } + if (!is_dir($webDir)) { + mkdir($webDir, 0777); + } + if(stristr($dataArr['url'],'github') && !stristr($dataArr['url'],'.zip')) { + //github的检出、打包 + $connection->send(json_encode([ + 'status' => 0, + 'msg' => "正在检出代码", + 'data' => [ + 'sta' => 1, + ] + ], JSON_UNESCAPED_UNICODE) . "\r\n"); + $git_dir_path = md5($dataArr['url']); + if (!is_dir($webDir . "/" . $git_dir_path) && !is_file($webDir . "/{$git_dir_path}.tar")) { + system('cd ' . $webDir . ';git clone --depth=50 "' . $dataArr['url'] . '" ' . $git_dir_path); + } + $connection->send(json_encode([ + 'status' => 0, + 'msg' => "正在打包,请稍后", + 'data' => [ + 'sta' => 2, + ] + ], JSON_UNESCAPED_UNICODE) . "\r\n"); + if (!is_file($webDir . "/{$git_dir_path}.tar")) { + system('cd ' . $webDir . ";tar -cvf {$git_dir_path}.tar {$git_dir_path} "); + system("rm -rf " . $webDir . "/" . $git_dir_path); + } + $pdo = new PDO('sqlite:' . dirname(dirname(dirname(__FILE__))) . '/sql'); + $pdo->exec("insert into file_info ('path', 'addtime') VALUES ('" . $webDir . "/{$git_dir_path}.tar" . "','" . time() . "')"); + $pdo = null; + $connection->send(json_encode([ + 'status' => 0, + 'msg' => "下载压缩包", + 'data' => [ + 'sta' => 3, + 'url' => '/source/' . $datatime . "/$git_dir_path.tar", + ] + ], JSON_UNESCAPED_UNICODE) . "\r\n"); + $connection->send("true"); + }else{ + //普通的压缩包下载处理 + $connection->send(json_encode([ + 'status' => 0, + 'msg' => "准备中,请耐心等待~☕️", + 'data' => [ + 'sta' => 2, + ] + ], JSON_UNESCAPED_UNICODE) . "\r\n"); + $fileExt = end($ext); + $fileName = md5($dataArr['url']) . ".{$fileExt}"; + system('cd ' . $webDir . '; wget "'.$dataArr['url'] . '" -O '.$fileName); + $pdo = new PDO('sqlite:' . dirname(dirname(dirname(__FILE__))) . '/sql'); + $pdo->exec("insert into file_info ('path', 'addtime') VALUES ('" . $webDir . "/{$fileName}" . "','" . time() . "')"); + $pdo = null; + $connection->send(json_encode([ + 'status' => 0, + 'msg' => "下载压缩包", + 'data' => [ + 'sta' => 3, + 'url' => '/source/' . $datatime . "/{$fileName}", + ] + ], JSON_UNESCAPED_UNICODE) . "\r\n"); + $connection->send("true"); + + } + + + +}; +if (!defined('GLOBAL_START')) { + Worker::runAll(); +} diff --git a/GatewayWorker/MIT-LICENSE.txt b/GatewayWorker/MIT-LICENSE.txt new file mode 100644 index 0000000..fd6b1c8 --- /dev/null +++ b/GatewayWorker/MIT-LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2009-2015 walkor and contributors (see https://github.com/walkor/workerman/contributors) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/GatewayWorker/README.md b/GatewayWorker/README.md new file mode 100644 index 0000000..34f44c8 --- /dev/null +++ b/GatewayWorker/README.md @@ -0,0 +1,46 @@ +GatewayWorker windows 版本 +================= + +GatewayWorker基于[Workerman](https://github.com/walkor/Workerman)开发的一个项目框架,用于快速开发长连接应用,例如app推送服务端、即时IM服务端、游戏服务端、物联网、智能家居等等。 + +GatewayWorker使用经典的Gateway和Worker进程模型。Gateway进程负责维持客户端连接,并转发客户端的数据给Worker进程处理;Worker进程负责处理实际的业务逻辑,并将结果推送给对应的客户端。Gateway服务和Worker服务可以分开部署在不同的服务器上,实现分布式集群。 + +GatewayWorker提供非常方便的API,可以全局广播数据、可以向某个群体广播数据、也可以向某个特定客户端推送数据。配合Workerman的定时器,也可以定时推送数据。 + +GatewayWorker Linux 版本 +====================== +Linux 版本GatewayWorker 在这里 https://github.com/walkor/GatewayWorker + +启动 +======= +双击start_for_win.bat + +Applications\YourApp测试方法 +====== +使用telnet命令测试(不要使用windows自带的telnet) +```shell + telnet 127.0.0.1 8282 +Trying 127.0.0.1... +Connected to 127.0.0.1. +Escape character is '^]'. +Hello 3 +3 login +haha +3 said haha +``` + +手册 +======= +http://www.workerman.net/gatewaydoc/ + +使用GatewayWorker-for-win开发的项目 +======= +## [tadpole](http://kedou.workerman.net/) +[Live demo](http://kedou.workerman.net/) +[Source code](https://github.com/walkor/workerman) +![workerman-todpole](http://www.workerman.net/img/workerman-todpole.png) + +## [chat room](http://chat.workerman.net/) +[Live demo](http://chat.workerman.net/) +[Source code](https://github.com/walkor/workerman-chat) +![workerman-chat](http://www.workerman.net/img/workerman-chat.png) diff --git a/GatewayWorker/composer.json b/GatewayWorker/composer.json new file mode 100644 index 0000000..bf12c7f --- /dev/null +++ b/GatewayWorker/composer.json @@ -0,0 +1,9 @@ +{ + "name" : "workerman/gateway-worker-demo", + "keywords": ["distributed","communication"], + "homepage": "http://www.workerman.net", + "license" : "MIT", + "require": { + "workerman/gateway-worker" : ">=3.0.0" + } +} diff --git a/GatewayWorker/sql b/GatewayWorker/sql new file mode 100644 index 0000000000000000000000000000000000000000..d0131f765a7e061e366d72f04e1fe21411f6e495 GIT binary patch literal 32768 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 + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see http://www.php-fig.org/psr/psr-0/ + * @see http://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + // PSR-4 + private $prefixLengthsPsr4 = array(); + private $prefixDirsPsr4 = array(); + private $fallbackDirsPsr4 = array(); + + // PSR-0 + private $prefixesPsr0 = array(); + private $fallbackDirsPsr0 = array(); + + private $useIncludePath = false; + private $classMap = array(); + private $classMapAuthoritative = false; + private $missingClasses = array(); + private $apcuPrefix; + + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', $this->prefixesPsr0); + } + + return array(); + } + + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + */ + public function add($prefix, $paths, $prepend = false) + { + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + (array) $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + (array) $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = (array) $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + (array) $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + (array) $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + (array) $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + (array) $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 base directories + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + } + + /** + * Unregisters this instance as an autoloader. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return bool|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + includeFile($file); + + return true; + } + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath.'\\'; + if (isset($this->prefixDirsPsr4[$search])) { + foreach ($this->prefixDirsPsr4[$search] as $dir) { + $length = $this->prefixLengthsPsr4[$first][$search]; + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } +} + +/** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + */ +function includeFile($file) +{ + include $file; +} diff --git a/GatewayWorker/vendor/composer/LICENSE b/GatewayWorker/vendor/composer/LICENSE new file mode 100644 index 0000000..f27399a --- /dev/null +++ b/GatewayWorker/vendor/composer/LICENSE @@ -0,0 +1,21 @@ + +Copyright (c) Nils Adermann, Jordi Boggiano + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/GatewayWorker/vendor/composer/autoload_classmap.php b/GatewayWorker/vendor/composer/autoload_classmap.php new file mode 100644 index 0000000..7a91153 --- /dev/null +++ b/GatewayWorker/vendor/composer/autoload_classmap.php @@ -0,0 +1,9 @@ + array($vendorDir . '/workerman/workerman'), + 'GatewayWorker\\' => array($vendorDir . '/workerman/gateway-worker/src'), +); diff --git a/GatewayWorker/vendor/composer/autoload_real.php b/GatewayWorker/vendor/composer/autoload_real.php new file mode 100644 index 0000000..13673a1 --- /dev/null +++ b/GatewayWorker/vendor/composer/autoload_real.php @@ -0,0 +1,52 @@ += 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); + if ($useStaticLoader) { + require_once __DIR__ . '/autoload_static.php'; + + call_user_func(\Composer\Autoload\ComposerStaticInite69c95f9a095e47d6d5b943de8a25aa3::getInitializer($loader)); + } else { + $map = require __DIR__ . '/autoload_namespaces.php'; + foreach ($map as $namespace => $path) { + $loader->set($namespace, $path); + } + + $map = require __DIR__ . '/autoload_psr4.php'; + foreach ($map as $namespace => $path) { + $loader->setPsr4($namespace, $path); + } + + $classMap = require __DIR__ . '/autoload_classmap.php'; + if ($classMap) { + $loader->addClassMap($classMap); + } + } + + $loader->register(true); + + return $loader; + } +} diff --git a/GatewayWorker/vendor/composer/autoload_static.php b/GatewayWorker/vendor/composer/autoload_static.php new file mode 100644 index 0000000..56a271e --- /dev/null +++ b/GatewayWorker/vendor/composer/autoload_static.php @@ -0,0 +1,39 @@ + + array ( + 'Workerman\\' => 10, + ), + 'G' => + array ( + 'GatewayWorker\\' => 14, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'Workerman\\' => + array ( + 0 => __DIR__ . '/..' . '/workerman/workerman', + ), + 'GatewayWorker\\' => + array ( + 0 => __DIR__ . '/..' . '/workerman/gateway-worker/src', + ), + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInite69c95f9a095e47d6d5b943de8a25aa3::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInite69c95f9a095e47d6d5b943de8a25aa3::$prefixDirsPsr4; + + }, null, ClassLoader::class); + } +} diff --git a/GatewayWorker/vendor/composer/installed.json b/GatewayWorker/vendor/composer/installed.json new file mode 100644 index 0000000..558c9e4 --- /dev/null +++ b/GatewayWorker/vendor/composer/installed.json @@ -0,0 +1,86 @@ +[ + { + "name": "workerman/workerman", + "version": "v3.5.26", + "version_normalized": "3.5.26.0", + "source": { + "type": "git", + "url": "https://github.com/walkor/Workerman.git", + "reference": "62e53d3f8da7c9baad2c7a54a8a1dfcdb611625e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/walkor/Workerman/zipball/62e53d3f8da7c9baad2c7a54a8a1dfcdb611625e", + "reference": "62e53d3f8da7c9baad2c7a54a8a1dfcdb611625e", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "suggest": { + "ext-event": "For better performance. " + }, + "time": "2020-01-26T14:09:54+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Workerman\\": "./" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "walkor", + "email": "walkor@workerman.net", + "homepage": "http://www.workerman.net", + "role": "Developer" + } + ], + "description": "An asynchronous event driven PHP framework for easily building fast, scalable network applications.", + "homepage": "http://www.workerman.net", + "keywords": [ + "asynchronous", + "event-loop" + ] + }, + { + "name": "workerman/gateway-worker", + "version": "v3.0.13", + "version_normalized": "3.0.13.0", + "source": { + "type": "git", + "url": "https://github.com/walkor/GatewayWorker.git", + "reference": "38b44c95f21cd340b5a9cff3987ddb2abb9a2a38" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/walkor/GatewayWorker/zipball/38b44c95f21cd340b5a9cff3987ddb2abb9a2a38", + "reference": "38b44c95f21cd340b5a9cff3987ddb2abb9a2a38", + "shasum": "" + }, + "require": { + "workerman/workerman": ">=3.1.8" + }, + "time": "2019-07-02T11:55:24+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "GatewayWorker\\": "./src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "homepage": "http://www.workerman.net", + "keywords": [ + "communication", + "distributed" + ] + } +] diff --git a/GatewayWorker/vendor/workerman/_www_data_g.widora.io_GatewayWorker_start.php.pid b/GatewayWorker/vendor/workerman/_www_data_g.widora.io_GatewayWorker_start.php.pid new file mode 100644 index 0000000..7dbc9bc --- /dev/null +++ b/GatewayWorker/vendor/workerman/_www_data_g.widora.io_GatewayWorker_start.php.pid @@ -0,0 +1 @@ +23078 \ No newline at end of file diff --git a/GatewayWorker/vendor/workerman/gateway-worker/.gitignore b/GatewayWorker/vendor/workerman/gateway-worker/.gitignore new file mode 100644 index 0000000..8cb4441 --- /dev/null +++ b/GatewayWorker/vendor/workerman/gateway-worker/.gitignore @@ -0,0 +1,4 @@ +.buildpath +.project +.settings +.idea \ No newline at end of file diff --git a/GatewayWorker/vendor/workerman/gateway-worker/MIT-LICENSE.txt b/GatewayWorker/vendor/workerman/gateway-worker/MIT-LICENSE.txt new file mode 100644 index 0000000..fd6b1c8 --- /dev/null +++ b/GatewayWorker/vendor/workerman/gateway-worker/MIT-LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2009-2015 walkor and contributors (see https://github.com/walkor/workerman/contributors) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/GatewayWorker/vendor/workerman/gateway-worker/README.md b/GatewayWorker/vendor/workerman/gateway-worker/README.md new file mode 100644 index 0000000..a1365a5 --- /dev/null +++ b/GatewayWorker/vendor/workerman/gateway-worker/README.md @@ -0,0 +1,38 @@ +GatewayWorker +================= + +GatewayWorker基于[Workerman](https://github.com/walkor/Workerman)开发的一个项目框架,用于快速开发长连接应用,例如app推送服务端、即时IM服务端、游戏服务端、物联网、智能家居等等。 + +GatewayWorker使用经典的Gateway和Worker进程模型。Gateway进程负责维持客户端连接,并转发客户端的数据给Worker进程处理;Worker进程负责处理实际的业务逻辑,并将结果推送给对应的客户端。Gateway服务和Worker服务可以分开部署在不同的服务器上,实现分布式集群。 + +GatewayWorker提供非常方便的API,可以全局广播数据、可以向某个群体广播数据、也可以向某个特定客户端推送数据。配合Workerman的定时器,也可以定时推送数据。 + +快速开始 +====== +开发者可以从一个简单的demo开始(demo中包含了GatewayWorker内核,以及start_gateway.php start_business.php等启动入口文件)
+[点击这里下载demo](http://www.workerman.net/download/GatewayWorker.zip)。
+demo说明见源码readme。 + +手册 +======= +http://www.workerman.net/gatewaydoc/ + +安装内核 +======= + +只安装GatewayWorker内核文件(不包含start_gateway.php start_businessworker.php等启动入口文件) +``` +composer require workerman/gateway-worker +``` + +使用GatewayWorker开发的项目 +======= +## [tadpole](http://kedou.workerman.net/) +[Live demo](http://kedou.workerman.net/) +[Source code](https://github.com/walkor/workerman) +![workerman todpole](http://www.workerman.net/img/workerman-todpole.png) + +## [chat room](http://chat.workerman.net/) +[Live demo](http://chat.workerman.net/) +[Source code](https://github.com/walkor/workerman-chat) +![workerman-chat](http://www.workerman.net/img/workerman-chat.png) diff --git a/GatewayWorker/vendor/workerman/gateway-worker/composer.json b/GatewayWorker/vendor/workerman/gateway-worker/composer.json new file mode 100644 index 0000000..2f81674 --- /dev/null +++ b/GatewayWorker/vendor/workerman/gateway-worker/composer.json @@ -0,0 +1,12 @@ +{ + "name" : "workerman/gateway-worker", + "keywords": ["distributed","communication"], + "homepage": "http://www.workerman.net", + "license" : "MIT", + "require": { + "workerman/workerman" : ">=3.1.8" + }, + "autoload": { + "psr-4": {"GatewayWorker\\": "./src"} + } +} diff --git a/GatewayWorker/vendor/workerman/gateway-worker/src/BusinessWorker.php b/GatewayWorker/vendor/workerman/gateway-worker/src/BusinessWorker.php new file mode 100644 index 0000000..d97d202 --- /dev/null +++ b/GatewayWorker/vendor/workerman/gateway-worker/src/BusinessWorker.php @@ -0,0 +1,560 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace GatewayWorker; + +use Workerman\Connection\TcpConnection; + +use Workerman\Worker; +use Workerman\Lib\Timer; +use Workerman\Connection\AsyncTcpConnection; +use GatewayWorker\Protocols\GatewayProtocol; +use GatewayWorker\Lib\Context; + +/** + * + * BusinessWorker 用于处理Gateway转发来的数据 + * + * @author walkor + * + */ +class BusinessWorker extends Worker +{ + /** + * 保存与 gateway 的连接 connection 对象 + * + * @var array + */ + public $gatewayConnections = array(); + + /** + * 注册中心地址 + * + * @var string|array + */ + public $registerAddress = '127.0.0.1:1236'; + + /** + * 事件处理类,默认是 Event 类 + * + * @var string + */ + public $eventHandler = 'Events'; + + /** + * 业务超时时间,可用来定位程序卡在哪里 + * + * @var int + */ + public $processTimeout = 30; + + /** + * 业务超时时间,可用来定位程序卡在哪里 + * + * @var callable + */ + public $processTimeoutHandler = '\\Workerman\\Worker::log'; + + /** + * 秘钥 + * + * @var string + */ + public $secretKey = ''; + + /** + * businessWorker进程将消息转发给gateway进程的发送缓冲区大小 + * + * @var int + */ + public $sendToGatewayBufferSize = 10240000; + + /** + * 保存用户设置的 worker 启动回调 + * + * @var callback + */ + protected $_onWorkerStart = null; + + /** + * 保存用户设置的 workerReload 回调 + * + * @var callback + */ + protected $_onWorkerReload = null; + + /** + * 保存用户设置的 workerStop 回调 + * + * @var callback + */ + protected $_onWorkerStop= null; + + /** + * 到注册中心的连接 + * + * @var AsyncTcpConnection + */ + protected $_registerConnection = null; + + /** + * 处于连接状态的 gateway 通讯地址 + * + * @var array + */ + protected $_connectingGatewayAddresses = array(); + + /** + * 所有 geteway 内部通讯地址 + * + * @var array + */ + protected $_gatewayAddresses = array(); + + /** + * 等待连接个 gateway 地址 + * + * @var array + */ + protected $_waitingConnectGatewayAddresses = array(); + + /** + * Event::onConnect 回调 + * + * @var callback + */ + protected $_eventOnConnect = null; + + /** + * Event::onMessage 回调 + * + * @var callback + */ + protected $_eventOnMessage = null; + + /** + * Event::onClose 回调 + * + * @var callback + */ + protected $_eventOnClose = null; + + /** + * websocket回调 + * + * @var null + */ + protected $_eventOnWebSocketConnect = null; + + /** + * SESSION 版本缓存 + * + * @var array + */ + protected $_sessionVersion = array(); + + /** + * 用于保持长连接的心跳时间间隔 + * + * @var int + */ + const PERSISTENCE_CONNECTION_PING_INTERVAL = 25; + + /** + * 构造函数 + * + * @param string $socket_name + * @param array $context_option + */ + public function __construct($socket_name = '', $context_option = array()) + { + parent::__construct($socket_name, $context_option); + $backrace = debug_backtrace(); + $this->_autoloadRootPath = dirname($backrace[0]['file']); + } + + /** + * {@inheritdoc} + */ + public function run() + { + $this->_onWorkerStart = $this->onWorkerStart; + $this->_onWorkerReload = $this->onWorkerReload; + $this->_onWorkerStop = $this->onWorkerStop; + $this->onWorkerStop = array($this, 'onWorkerStop'); + $this->onWorkerStart = array($this, 'onWorkerStart'); + $this->onWorkerReload = array($this, 'onWorkerReload'); + parent::run(); + } + + /** + * 当进程启动时一些初始化工作 + * + * @return void + */ + protected function onWorkerStart() + { + if (!class_exists('\Protocols\GatewayProtocol')) { + class_alias('GatewayWorker\Protocols\GatewayProtocol', 'Protocols\GatewayProtocol'); + } + + if (!is_array($this->registerAddress)) { + $this->registerAddress = array($this->registerAddress); + } + $this->connectToRegister(); + + \GatewayWorker\Lib\Gateway::setBusinessWorker($this); + \GatewayWorker\Lib\Gateway::$secretKey = $this->secretKey; + if ($this->_onWorkerStart) { + call_user_func($this->_onWorkerStart, $this); + } + + if (is_callable($this->eventHandler . '::onWorkerStart')) { + call_user_func($this->eventHandler . '::onWorkerStart', $this); + } + + if (function_exists('pcntl_signal')) { + // 业务超时信号处理 + pcntl_signal(SIGALRM, array($this, 'timeoutHandler'), false); + } else { + $this->processTimeout = 0; + } + + // 设置回调 + if (is_callable($this->eventHandler . '::onConnect')) { + $this->_eventOnConnect = $this->eventHandler . '::onConnect'; + } + + if (is_callable($this->eventHandler . '::onMessage')) { + $this->_eventOnMessage = $this->eventHandler . '::onMessage'; + } else { + echo "Waring: {$this->eventHandler}::onMessage is not callable\n"; + } + + if (is_callable($this->eventHandler . '::onClose')) { + $this->_eventOnClose = $this->eventHandler . '::onClose'; + } + + if (is_callable($this->eventHandler . '::onWebSocketConnect')) { + $this->_eventOnWebSocketConnect = $this->eventHandler . '::onWebSocketConnect'; + } + + } + + /** + * onWorkerReload 回调 + * + * @param Worker $worker + */ + protected function onWorkerReload($worker) + { + // 防止进程立刻退出 + $worker->reloadable = false; + // 延迟 0.05 秒退出,避免 BusinessWorker 瞬间全部退出导致没有可用的 BusinessWorker 进程 + Timer::add(0.05, array('Workerman\Worker', 'stopAll')); + // 执行用户定义的 onWorkerReload 回调 + if ($this->_onWorkerReload) { + call_user_func($this->_onWorkerReload, $this); + } + } + + /** + * 当进程关闭时一些清理工作 + * + * @return void + */ + protected function onWorkerStop() + { + if ($this->_onWorkerStop) { + call_user_func($this->_onWorkerStop, $this); + } + if (is_callable($this->eventHandler . '::onWorkerStop')) { + call_user_func($this->eventHandler . '::onWorkerStop', $this); + } + } + + /** + * 连接服务注册中心 + * + * @return void + */ + public function connectToRegister() + { + foreach ($this->registerAddress as $register_address) { + $register_connection = new AsyncTcpConnection("text://{$register_address}"); + $secret_key = $this->secretKey; + $register_connection->onConnect = function () use ($register_connection, $secret_key, $register_address) { + $register_connection->send('{"event":"worker_connect","secret_key":"' . $secret_key . '"}'); + // 如果Register服务器不在本地服务器,则需要保持心跳 + if (strpos($register_address, '127.0.0.1') !== 0) { + $register_connection->ping_timer = Timer::add(self::PERSISTENCE_CONNECTION_PING_INTERVAL, function () use ($register_connection) { + $register_connection->send('{"event":"ping"}'); + }); + } + }; + $register_connection->onClose = function ($register_connection) { + if(!empty($register_connection->ping_timer)) { + Timer::del($register_connection->ping_timer); + } + $register_connection->reconnect(1); + }; + $register_connection->onMessage = array($this, 'onRegisterConnectionMessage'); + $register_connection->connect(); + } + } + + + /** + * 当注册中心发来消息时 + * + * @return void + */ + public function onRegisterConnectionMessage($register_connection, $data) + { + $data = json_decode($data, true); + if (!isset($data['event'])) { + echo "Received bad data from Register\n"; + return; + } + $event = $data['event']; + switch ($event) { + case 'broadcast_addresses': + if (!is_array($data['addresses'])) { + echo "Received bad data from Register. Addresses empty\n"; + return; + } + $addresses = $data['addresses']; + $this->_gatewayAddresses = array(); + foreach ($addresses as $addr) { + $this->_gatewayAddresses[$addr] = $addr; + } + $this->checkGatewayConnections($addresses); + break; + default: + echo "Receive bad event:$event from Register.\n"; + } + } + + /** + * 当 gateway 转发来数据时 + * + * @param TcpConnection $connection + * @param mixed $data + */ + public function onGatewayMessage($connection, $data) + { + $cmd = $data['cmd']; + if ($cmd === GatewayProtocol::CMD_PING) { + return; + } + // 上下文数据 + Context::$client_ip = $data['client_ip']; + Context::$client_port = $data['client_port']; + Context::$local_ip = $data['local_ip']; + Context::$local_port = $data['local_port']; + Context::$connection_id = $data['connection_id']; + Context::$client_id = Context::addressToClientId($data['local_ip'], $data['local_port'], + $data['connection_id']); + // $_SERVER 变量 + $_SERVER = array( + 'REMOTE_ADDR' => long2ip($data['client_ip']), + 'REMOTE_PORT' => $data['client_port'], + 'GATEWAY_ADDR' => long2ip($data['local_ip']), + 'GATEWAY_PORT' => $data['gateway_port'], + 'GATEWAY_CLIENT_ID' => Context::$client_id, + ); + // 检查session版本,如果是过期的session数据则拉取最新的数据 + if ($cmd !== GatewayProtocol::CMD_ON_CLOSE && isset($this->_sessionVersion[Context::$client_id]) && $this->_sessionVersion[Context::$client_id] !== crc32($data['ext_data'])) { + $_SESSION = Context::$old_session = \GatewayWorker\Lib\Gateway::getSession(Context::$client_id); + } else { + if (!isset($this->_sessionVersion[Context::$client_id])) { + $this->_sessionVersion[Context::$client_id] = crc32($data['ext_data']); + } + // 尝试解析 session + if ($data['ext_data'] != '') { + Context::$old_session = $_SESSION = Context::sessionDecode($data['ext_data']); + } else { + Context::$old_session = $_SESSION = null; + } + } + + if ($this->processTimeout) { + pcntl_alarm($this->processTimeout); + } + // 尝试执行 Event::onConnection、Event::onMessage、Event::onClose + switch ($cmd) { + case GatewayProtocol::CMD_ON_CONNECT: + if ($this->_eventOnConnect) { + call_user_func($this->_eventOnConnect, Context::$client_id); + } + break; + case GatewayProtocol::CMD_ON_MESSAGE: + if ($this->_eventOnMessage) { + call_user_func($this->_eventOnMessage, Context::$client_id, $data['body']); + } + break; + case GatewayProtocol::CMD_ON_CLOSE: + unset($this->_sessionVersion[Context::$client_id]); + if ($this->_eventOnClose) { + call_user_func($this->_eventOnClose, Context::$client_id); + } + break; + case GatewayProtocol::CMD_ON_WEBSOCKET_CONNECT: + if ($this->_eventOnWebSocketConnect) { + call_user_func($this->_eventOnWebSocketConnect, Context::$client_id, $data['body']); + } + break; + } + if ($this->processTimeout) { + pcntl_alarm(0); + } + + // session 必须是数组 + if ($_SESSION !== null && !is_array($_SESSION)) { + throw new \Exception('$_SESSION must be an array. But $_SESSION=' . var_export($_SESSION, true) . ' is not array.'); + } + + // 判断 session 是否被更改 + if ($_SESSION !== Context::$old_session && $cmd !== GatewayProtocol::CMD_ON_CLOSE) { + $session_str_now = $_SESSION !== null ? Context::sessionEncode($_SESSION) : ''; + \GatewayWorker\Lib\Gateway::setSocketSession(Context::$client_id, $session_str_now); + $this->_sessionVersion[Context::$client_id] = crc32($session_str_now); + } + + Context::clear(); + } + + /** + * 当与 Gateway 的连接断开时触发 + * + * @param TcpConnection $connection + * @return void + */ + public function onGatewayClose($connection) + { + $addr = $connection->remoteAddress; + unset($this->gatewayConnections[$addr], $this->_connectingGatewayAddresses[$addr]); + if (isset($this->_gatewayAddresses[$addr]) && !isset($this->_waitingConnectGatewayAddresses[$addr])) { + Timer::add(1, array($this, 'tryToConnectGateway'), array($addr), false); + $this->_waitingConnectGatewayAddresses[$addr] = $addr; + } + } + + /** + * 尝试连接 Gateway 内部通讯地址 + * + * @param string $addr + */ + public function tryToConnectGateway($addr) + { + if (!isset($this->gatewayConnections[$addr]) && !isset($this->_connectingGatewayAddresses[$addr]) && isset($this->_gatewayAddresses[$addr])) { + $gateway_connection = new AsyncTcpConnection("GatewayProtocol://$addr"); + $gateway_connection->remoteAddress = $addr; + $gateway_connection->onConnect = array($this, 'onConnectGateway'); + $gateway_connection->onMessage = array($this, 'onGatewayMessage'); + $gateway_connection->onClose = array($this, 'onGatewayClose'); + $gateway_connection->onError = array($this, 'onGatewayError'); + $gateway_connection->maxSendBufferSize = $this->sendToGatewayBufferSize; + if (TcpConnection::$defaultMaxSendBufferSize == $gateway_connection->maxSendBufferSize) { + $gateway_connection->maxSendBufferSize = 50 * 1024 * 1024; + } + $gateway_data = GatewayProtocol::$empty; + $gateway_data['cmd'] = GatewayProtocol::CMD_WORKER_CONNECT; + $gateway_data['body'] = json_encode(array( + 'worker_key' =>"{$this->name}:{$this->id}", + 'secret_key' => $this->secretKey, + )); + $gateway_connection->send($gateway_data); + $gateway_connection->connect(); + $this->_connectingGatewayAddresses[$addr] = $addr; + } + unset($this->_waitingConnectGatewayAddresses[$addr]); + } + + /** + * 检查 gateway 的通信端口是否都已经连 + * 如果有未连接的端口,则尝试连接 + * + * @param array $addresses_list + */ + public function checkGatewayConnections($addresses_list) + { + if (empty($addresses_list)) { + return; + } + foreach ($addresses_list as $addr) { + if (!isset($this->_waitingConnectGatewayAddresses[$addr])) { + $this->tryToConnectGateway($addr); + } + } + } + + /** + * 当连接上 gateway 的通讯端口时触发 + * 将连接 connection 对象保存起来 + * + * @param TcpConnection $connection + * @return void + */ + public function onConnectGateway($connection) + { + $this->gatewayConnections[$connection->remoteAddress] = $connection; + unset($this->_connectingGatewayAddresses[$connection->remoteAddress], $this->_waitingConnectGatewayAddresses[$connection->remoteAddress]); + } + + /** + * 当与 gateway 的连接出现错误时触发 + * + * @param TcpConnection $connection + * @param int $error_no + * @param string $error_msg + */ + public function onGatewayError($connection, $error_no, $error_msg) + { + echo "GatewayConnection Error : $error_no ,$error_msg\n"; + } + + /** + * 获取所有 Gateway 内部通讯地址 + * + * @return array + */ + public function getAllGatewayAddresses() + { + return $this->_gatewayAddresses; + } + + /** + * 业务超时回调 + * + * @param int $signal + * @throws \Exception + */ + public function timeoutHandler($signal) + { + switch ($signal) { + // 超时时钟 + case SIGALRM: + // 超时异常 + $e = new \Exception("process_timeout", 506); + $trace_str = $e->getTraceAsString(); + // 去掉第一行timeoutHandler的调用栈 + $trace_str = $e->getMessage() . ":\n" . substr($trace_str, strpos($trace_str, "\n") + 1) . "\n"; + // 开发者没有设置超时处理函数,或者超时处理函数返回空则执行退出 + if (!$this->processTimeoutHandler || !call_user_func($this->processTimeoutHandler, $trace_str, $e)) { + Worker::stopAll(); + } + break; + } + } +} diff --git a/GatewayWorker/vendor/workerman/gateway-worker/src/Gateway.php b/GatewayWorker/vendor/workerman/gateway-worker/src/Gateway.php new file mode 100644 index 0000000..fa2ab1a --- /dev/null +++ b/GatewayWorker/vendor/workerman/gateway-worker/src/Gateway.php @@ -0,0 +1,1019 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace GatewayWorker; + +use GatewayWorker\Lib\Context; + +use Workerman\Connection\TcpConnection; + +use Workerman\Worker; +use Workerman\Lib\Timer; +use Workerman\Autoloader; +use Workerman\Connection\AsyncTcpConnection; +use GatewayWorker\Protocols\GatewayProtocol; + +/** + * + * Gateway,基于Worker 开发 + * 用于转发客户端的数据给Worker处理,以及转发Worker的数据给客户端 + * + * @author walkor + * + */ +class Gateway extends Worker +{ + /** + * 版本 + * + * @var string + */ + const VERSION = '3.0.13'; + + /** + * 本机 IP + * 单机部署默认 127.0.0.1,如果是分布式部署,需要设置成本机 IP + * + * @var string + */ + public $lanIp = '127.0.0.1'; + + /** + * 本机端口 + * + * @var string + */ + public $lanPort = 0; + + /** + * gateway 内部通讯起始端口,每个 gateway 实例应该都不同,步长1000 + * + * @var int + */ + public $startPort = 2000; + + /** + * 注册服务地址,用于注册 Gateway BusinessWorker,使之能够通讯 + * + * @var string|array + */ + public $registerAddress = '127.0.0.1:1236'; + + /** + * 是否可以平滑重启,gateway 不能平滑重启,否则会导致连接断开 + * + * @var bool + */ + public $reloadable = false; + + /** + * 心跳时间间隔 + * + * @var int + */ + public $pingInterval = 0; + + /** + * $pingNotResponseLimit * $pingInterval 时间内,客户端未发送任何数据,断开客户端连接 + * + * @var int + */ + public $pingNotResponseLimit = 0; + + /** + * 服务端向客户端发送的心跳数据 + * + * @var string + */ + public $pingData = ''; + + /** + * 秘钥 + * + * @var string + */ + public $secretKey = ''; + + /** + * 路由函数 + * + * @var callback + */ + public $router = null; + + + /** + * gateway进程转发给businessWorker进程的发送缓冲区大小 + * + * @var int + */ + public $sendToWorkerBufferSize = 10240000; + + /** + * gateway进程将数据发给客户端时每个客户端发送缓冲区大小 + * + * @var int + */ + public $sendToClientBufferSize = 1024000; + + /** + * 协议加速 + * + * @var bool + */ + public $protocolAccelerate = false; + + /** + * 保存客户端的所有 connection 对象 + * + * @var array + */ + protected $_clientConnections = array(); + + /** + * uid 到 connection 的映射,一对多关系 + */ + protected $_uidConnections = array(); + + /** + * group 到 connection 的映射,一对多关系 + * + * @var array + */ + protected $_groupConnections = array(); + + /** + * 保存所有 worker 的内部连接的 connection 对象 + * + * @var array + */ + protected $_workerConnections = array(); + + /** + * gateway 内部监听 worker 内部连接的 worker + * + * @var Worker + */ + protected $_innerTcpWorker = null; + + /** + * 当 worker 启动时 + * + * @var callback + */ + protected $_onWorkerStart = null; + + /** + * 当有客户端连接时 + * + * @var callback + */ + protected $_onConnect = null; + + /** + * 当客户端发来消息时 + * + * @var callback + */ + protected $_onMessage = null; + + /** + * 当客户端连接关闭时 + * + * @var callback + */ + protected $_onClose = null; + + /** + * 当 worker 停止时 + * + * @var callback + */ + protected $_onWorkerStop = null; + + /** + * 进程启动时间 + * + * @var int + */ + protected $_startTime = 0; + + /** + * gateway 监听的端口 + * + * @var int + */ + protected $_gatewayPort = 0; + + /** + * connectionId 记录器 + * @var int + */ + protected static $_connectionIdRecorder = 0; + + /** + * 用于保持长连接的心跳时间间隔 + * + * @var int + */ + const PERSISTENCE_CONNECTION_PING_INTERVAL = 25; + + /** + * 构造函数 + * + * @param string $socket_name + * @param array $context_option + */ + public function __construct($socket_name, $context_option = array()) + { + parent::__construct($socket_name, $context_option); + $this->_gatewayPort = substr(strrchr($socket_name,':'),1); + $this->router = array("\\GatewayWorker\\Gateway", 'routerBind'); + + $backtrace = debug_backtrace(); + $this->_autoloadRootPath = dirname($backtrace[0]['file']); + } + + /** + * {@inheritdoc} + */ + public function run() + { + // 保存用户的回调,当对应的事件发生时触发 + $this->_onWorkerStart = $this->onWorkerStart; + $this->onWorkerStart = array($this, 'onWorkerStart'); + // 保存用户的回调,当对应的事件发生时触发 + $this->_onConnect = $this->onConnect; + $this->onConnect = array($this, 'onClientConnect'); + + // onMessage禁止用户设置回调 + $this->onMessage = array($this, 'onClientMessage'); + + // 保存用户的回调,当对应的事件发生时触发 + $this->_onClose = $this->onClose; + $this->onClose = array($this, 'onClientClose'); + // 保存用户的回调,当对应的事件发生时触发 + $this->_onWorkerStop = $this->onWorkerStop; + $this->onWorkerStop = array($this, 'onWorkerStop'); + + if (!is_array($this->registerAddress)) { + $this->registerAddress = array($this->registerAddress); + } + + // 记录进程启动的时间 + $this->_startTime = time(); + // 运行父方法 + parent::run(); + } + + /** + * 当客户端发来数据时,转发给worker处理 + * + * @param TcpConnection $connection + * @param mixed $data + */ + public function onClientMessage($connection, $data) + { + $connection->pingNotResponseCount = -1; + $this->sendToWorker(GatewayProtocol::CMD_ON_MESSAGE, $connection, $data); + } + + /** + * 当客户端连接上来时,初始化一些客户端的数据 + * 包括全局唯一的client_id、初始化session等 + * + * @param TcpConnection $connection + */ + public function onClientConnect($connection) + { + $connection->id = self::generateConnectionId(); + // 保存该连接的内部通讯的数据包报头,避免每次重新初始化 + $connection->gatewayHeader = array( + 'local_ip' => ip2long($this->lanIp), + 'local_port' => $this->lanPort, + 'client_ip' => ip2long($connection->getRemoteIp()), + 'client_port' => $connection->getRemotePort(), + 'gateway_port' => $this->_gatewayPort, + 'connection_id' => $connection->id, + 'flag' => 0, + ); + // 连接的 session + $connection->session = ''; + // 该连接的心跳参数 + $connection->pingNotResponseCount = -1; + // 该链接发送缓冲区大小 + $connection->maxSendBufferSize = $this->sendToClientBufferSize; + // 保存客户端连接 connection 对象 + $this->_clientConnections[$connection->id] = $connection; + + // 如果用户有自定义 onConnect 回调,则执行 + if ($this->_onConnect) { + call_user_func($this->_onConnect, $connection); + } elseif ($connection->protocol === '\Workerman\Protocols\Websocket') { + $connection->onWebSocketConnect = array($this, 'onWebsocketConnect'); + } + + $this->sendToWorker(GatewayProtocol::CMD_ON_CONNECT, $connection); + } + + /** + * websocket握手时触发 + * + * @param $connection + * @param $http_buffer + */ + public function onWebsocketConnect($connection, $http_buffer) + { + $this->sendToWorker(GatewayProtocol::CMD_ON_WEBSOCKET_CONNECT, $connection, array('get' => $_GET, 'server' => $_SERVER, 'cookie' => $_COOKIE)); + } + + /** + * 生成connection id + * @return int + */ + protected function generateConnectionId() + { + $max_unsigned_int = 4294967295; + if (self::$_connectionIdRecorder >= $max_unsigned_int) { + self::$_connectionIdRecorder = 0; + } + while(++self::$_connectionIdRecorder <= $max_unsigned_int) { + if(!isset($this->_clientConnections[self::$_connectionIdRecorder])) { + break; + } + } + return self::$_connectionIdRecorder; + } + + /** + * 发送数据给 worker 进程 + * + * @param int $cmd + * @param TcpConnection $connection + * @param mixed $body + * @return bool + */ + protected function sendToWorker($cmd, $connection, $body = '') + { + $gateway_data = $connection->gatewayHeader; + $gateway_data['cmd'] = $cmd; + $gateway_data['body'] = $body; + $gateway_data['ext_data'] = $connection->session; + if ($this->_workerConnections) { + // 调用路由函数,选择一个worker把请求转发给它 + /** @var TcpConnection $worker_connection */ + $worker_connection = call_user_func($this->router, $this->_workerConnections, $connection, $cmd, $body); + if (false === $worker_connection->send($gateway_data)) { + $msg = "SendBufferToWorker fail. May be the send buffer are overflow. See http://doc2.workerman.net/send-buffer-overflow.html"; + static::log($msg); + return false; + } + } // 没有可用的 worker + else { + // gateway 启动后 1-2 秒内 SendBufferToWorker fail 是正常现象,因为与 worker 的连接还没建立起来, + // 所以不记录日志,只是关闭连接 + $time_diff = 2; + if (time() - $this->_startTime >= $time_diff) { + $msg = 'SendBufferToWorker fail. The connections between Gateway and BusinessWorker are not ready. See http://doc2.workerman.net/send-buffer-to-worker-fail.html'; + static::log($msg); + } + $connection->destroy(); + return false; + } + return true; + } + + /** + * 随机路由,返回 worker connection 对象 + * + * @param array $worker_connections + * @param TcpConnection $client_connection + * @param int $cmd + * @param mixed $buffer + * @return TcpConnection + */ + public static function routerRand($worker_connections, $client_connection, $cmd, $buffer) + { + return $worker_connections[array_rand($worker_connections)]; + } + + /** + * client_id 与 worker 绑定 + * + * @param array $worker_connections + * @param TcpConnection $client_connection + * @param int $cmd + * @param mixed $buffer + * @return TcpConnection + */ + public static function routerBind($worker_connections, $client_connection, $cmd, $buffer) + { + if (!isset($client_connection->businessworker_address) || !isset($worker_connections[$client_connection->businessworker_address])) { + $client_connection->businessworker_address = array_rand($worker_connections); + } + return $worker_connections[$client_connection->businessworker_address]; + } + + /** + * 当客户端关闭时 + * + * @param TcpConnection $connection + */ + public function onClientClose($connection) + { + // 尝试通知 worker,触发 Event::onClose + $this->sendToWorker(GatewayProtocol::CMD_ON_CLOSE, $connection); + unset($this->_clientConnections[$connection->id]); + // 清理 uid 数据 + if (!empty($connection->uid)) { + $uid = $connection->uid; + unset($this->_uidConnections[$uid][$connection->id]); + if (empty($this->_uidConnections[$uid])) { + unset($this->_uidConnections[$uid]); + } + } + // 清理 group 数据 + if (!empty($connection->groups)) { + foreach ($connection->groups as $group) { + unset($this->_groupConnections[$group][$connection->id]); + if (empty($this->_groupConnections[$group])) { + unset($this->_groupConnections[$group]); + } + } + } + // 触发 onClose + if ($this->_onClose) { + call_user_func($this->_onClose, $connection); + } + } + + /** + * 当 Gateway 启动的时候触发的回调函数 + * + * @return void + */ + public function onWorkerStart() + { + // 分配一个内部通讯端口 + $this->lanPort = $this->startPort + $this->id; + + // 如果有设置心跳,则定时执行 + if ($this->pingInterval > 0) { + $timer_interval = $this->pingNotResponseLimit > 0 ? $this->pingInterval / 2 : $this->pingInterval; + Timer::add($timer_interval, array($this, 'ping')); + } + + // 如果BusinessWorker ip不是127.0.0.1,则需要加gateway到BusinessWorker的心跳 + if ($this->lanIp !== '127.0.0.1') { + Timer::add(self::PERSISTENCE_CONNECTION_PING_INTERVAL, array($this, 'pingBusinessWorker')); + } + + if (!class_exists('\Protocols\GatewayProtocol')) { + class_alias('GatewayWorker\Protocols\GatewayProtocol', 'Protocols\GatewayProtocol'); + } + + // 初始化 gateway 内部的监听,用于监听 worker 的连接已经连接上发来的数据 + $this->_innerTcpWorker = new Worker("GatewayProtocol://{$this->lanIp}:{$this->lanPort}"); + $this->_innerTcpWorker->listen(); + $this->_innerTcpWorker->name = 'GatewayInnerWorker'; + + // 重新设置自动加载根目录 + Autoloader::setRootPath($this->_autoloadRootPath); + + // 设置内部监听的相关回调 + $this->_innerTcpWorker->onMessage = array($this, 'onWorkerMessage'); + + $this->_innerTcpWorker->onConnect = array($this, 'onWorkerConnect'); + $this->_innerTcpWorker->onClose = array($this, 'onWorkerClose'); + + // 注册 gateway 的内部通讯地址,worker 去连这个地址,以便 gateway 与 worker 之间建立起 TCP 长连接 + $this->registerAddress(); + + if ($this->_onWorkerStart) { + call_user_func($this->_onWorkerStart, $this); + } + } + + + /** + * 当 worker 通过内部通讯端口连接到 gateway 时 + * + * @param TcpConnection $connection + */ + public function onWorkerConnect($connection) + { + $connection->maxSendBufferSize = $this->sendToWorkerBufferSize; + $connection->authorized = $this->secretKey ? false : true; + } + + /** + * 当 worker 发来数据时 + * + * @param TcpConnection $connection + * @param mixed $data + * @throws \Exception + * + * @return void + */ + public function onWorkerMessage($connection, $data) + { + $cmd = $data['cmd']; + if (empty($connection->authorized) && $cmd !== GatewayProtocol::CMD_WORKER_CONNECT && $cmd !== GatewayProtocol::CMD_GATEWAY_CLIENT_CONNECT) { + self::log("Unauthorized request from " . $connection->getRemoteIp() . ":" . $connection->getRemotePort()); + $connection->close(); + return; + } + switch ($cmd) { + // BusinessWorker连接Gateway + case GatewayProtocol::CMD_WORKER_CONNECT: + $worker_info = json_decode($data['body'], true); + if ($worker_info['secret_key'] !== $this->secretKey) { + self::log("Gateway: Worker key does not match ".var_export($this->secretKey, true)." !== ". var_export($this->secretKey)); + $connection->close(); + return; + } + $key = $connection->getRemoteIp() . ':' . $worker_info['worker_key']; + // 在一台服务器上businessWorker->name不能相同 + if (isset($this->_workerConnections[$key])) { + self::log("Gateway: Worker->name conflict. Key:{$key}"); + $connection->close(); + return; + } + $connection->key = $key; + $this->_workerConnections[$key] = $connection; + $connection->authorized = true; + return; + // GatewayClient连接Gateway + case GatewayProtocol::CMD_GATEWAY_CLIENT_CONNECT: + $worker_info = json_decode($data['body'], true); + if ($worker_info['secret_key'] !== $this->secretKey) { + self::log("Gateway: GatewayClient key does not match ".var_export($this->secretKey, true)." !== ".var_export($this->secretKey, true)); + $connection->close(); + return; + } + $connection->authorized = true; + return; + // 向某客户端发送数据,Gateway::sendToClient($client_id, $message); + case GatewayProtocol::CMD_SEND_TO_ONE: + if (isset($this->_clientConnections[$data['connection_id']])) { + $this->_clientConnections[$data['connection_id']]->send($data['body']); + } + return; + // 踢出用户,Gateway::closeClient($client_id, $message); + case GatewayProtocol::CMD_KICK: + if (isset($this->_clientConnections[$data['connection_id']])) { + $this->_clientConnections[$data['connection_id']]->close($data['body']); + } + return; + // 立即销毁用户连接, Gateway::destroyClient($client_id); + case GatewayProtocol::CMD_DESTROY: + if (isset($this->_clientConnections[$data['connection_id']])) { + $this->_clientConnections[$data['connection_id']]->destroy(); + } + return; + // 广播, Gateway::sendToAll($message, $client_id_array) + case GatewayProtocol::CMD_SEND_TO_ALL: + $raw = (bool)($data['flag'] & GatewayProtocol::FLAG_NOT_CALL_ENCODE); + $body = $data['body']; + if (!$raw && $this->protocolAccelerate && $this->protocol) { + $body = $this->preEncodeForClient($body); + $raw = true; + } + $ext_data = $data['ext_data'] ? json_decode($data['ext_data'], true) : ''; + // $client_id_array 不为空时,只广播给 $client_id_array 指定的客户端 + if (isset($ext_data['connections'])) { + foreach ($ext_data['connections'] as $connection_id) { + if (isset($this->_clientConnections[$connection_id])) { + $this->_clientConnections[$connection_id]->send($body, $raw); + } + } + } // $client_id_array 为空时,广播给所有在线客户端 + else { + $exclude_connection_id = !empty($ext_data['exclude']) ? $ext_data['exclude'] : null; + foreach ($this->_clientConnections as $client_connection) { + if (!isset($exclude_connection_id[$client_connection->id])) { + $client_connection->send($body, $raw); + } + } + } + return; + case GatewayProtocol::CMD_SELECT: + $client_info_array = array(); + $ext_data = json_decode($data['ext_data'], true); + if (!$ext_data) { + echo 'CMD_SELECT ext_data=' . var_export($data['ext_data'], true) . '\r\n'; + $buffer = serialize($client_info_array); + $connection->send(pack('N', strlen($buffer)) . $buffer, true); + return; + } + $fields = $ext_data['fields']; + $where = $ext_data['where']; + if ($where) { + $connection_box_map = array( + 'groups' => $this->_groupConnections, + 'uid' => $this->_uidConnections + ); + // $where = ['groups'=>[x,x..], 'uid'=>[x,x..], 'connection_id'=>[x,x..]] + foreach ($where as $key => $items) { + if ($key !== 'connection_id') { + $connections_box = $connection_box_map[$key]; + foreach ($items as $item) { + if (isset($connections_box[$item])) { + foreach ($connections_box[$item] as $connection_id => $client_connection) { + if (!isset($client_info_array[$connection_id])) { + $client_info_array[$connection_id] = array(); + // $fields = ['groups', 'uid', 'session'] + foreach ($fields as $field) { + $client_info_array[$connection_id][$field] = isset($client_connection->$field) ? $client_connection->$field : null; + } + } + } + + } + } + } else { + foreach ($items as $connection_id) { + if (isset($this->_clientConnections[$connection_id])) { + $client_connection = $this->_clientConnections[$connection_id]; + $client_info_array[$connection_id] = array(); + // $fields = ['groups', 'uid', 'session'] + foreach ($fields as $field) { + $client_info_array[$connection_id][$field] = isset($client_connection->$field) ? $client_connection->$field : null; + } + } + } + } + } + } else { + foreach ($this->_clientConnections as $connection_id => $client_connection) { + foreach ($fields as $field) { + $client_info_array[$connection_id][$field] = isset($client_connection->$field) ? $client_connection->$field : null; + } + } + } + $buffer = serialize($client_info_array); + $connection->send(pack('N', strlen($buffer)) . $buffer, true); + return; + // 获取在线群组列表 + case GatewayProtocol::CMD_GET_GROUP_ID_LIST: + $buffer = serialize(array_keys($this->_groupConnections)); + $connection->send(pack('N', strlen($buffer)) . $buffer, true); + return; + // 重新赋值 session + case GatewayProtocol::CMD_SET_SESSION: + if (isset($this->_clientConnections[$data['connection_id']])) { + $this->_clientConnections[$data['connection_id']]->session = $data['ext_data']; + } + return; + // session合并 + case GatewayProtocol::CMD_UPDATE_SESSION: + if (!isset($this->_clientConnections[$data['connection_id']])) { + return; + } else { + if (!$this->_clientConnections[$data['connection_id']]->session) { + $this->_clientConnections[$data['connection_id']]->session = $data['ext_data']; + return; + } + $session = Context::sessionDecode($this->_clientConnections[$data['connection_id']]->session); + $session_for_merge = Context::sessionDecode($data['ext_data']); + $session = array_replace_recursive($session, $session_for_merge); + $this->_clientConnections[$data['connection_id']]->session = Context::sessionEncode($session); + } + return; + case GatewayProtocol::CMD_GET_SESSION_BY_CLIENT_ID: + if (!isset($this->_clientConnections[$data['connection_id']])) { + $session = serialize(null); + } else { + if (!$this->_clientConnections[$data['connection_id']]->session) { + $session = serialize(array()); + } else { + $session = $this->_clientConnections[$data['connection_id']]->session; + } + } + $connection->send(pack('N', strlen($session)) . $session, true); + return; + // 获得客户端sessions + case GatewayProtocol::CMD_GET_ALL_CLIENT_SESSIONS: + $client_info_array = array(); + foreach ($this->_clientConnections as $connection_id => $client_connection) { + $client_info_array[$connection_id] = $client_connection->session; + } + $buffer = serialize($client_info_array); + $connection->send(pack('N', strlen($buffer)) . $buffer, true); + return; + // 判断某个 client_id 是否在线 Gateway::isOnline($client_id) + case GatewayProtocol::CMD_IS_ONLINE: + $buffer = serialize((int)isset($this->_clientConnections[$data['connection_id']])); + $connection->send(pack('N', strlen($buffer)) . $buffer, true); + return; + // 将 client_id 与 uid 绑定 + case GatewayProtocol::CMD_BIND_UID: + $uid = $data['ext_data']; + if (empty($uid)) { + echo "bindUid(client_id, uid) uid empty, uid=" . var_export($uid, true); + return; + } + $connection_id = $data['connection_id']; + if (!isset($this->_clientConnections[$connection_id])) { + return; + } + $client_connection = $this->_clientConnections[$connection_id]; + if (isset($client_connection->uid)) { + $current_uid = $client_connection->uid; + unset($this->_uidConnections[$current_uid][$connection_id]); + if (empty($this->_uidConnections[$current_uid])) { + unset($this->_uidConnections[$current_uid]); + } + } + $client_connection->uid = $uid; + $this->_uidConnections[$uid][$connection_id] = $client_connection; + return; + // client_id 与 uid 解绑 Gateway::unbindUid($client_id, $uid); + case GatewayProtocol::CMD_UNBIND_UID: + $connection_id = $data['connection_id']; + if (!isset($this->_clientConnections[$connection_id])) { + return; + } + $client_connection = $this->_clientConnections[$connection_id]; + if (isset($client_connection->uid)) { + $current_uid = $client_connection->uid; + unset($this->_uidConnections[$current_uid][$connection_id]); + if (empty($this->_uidConnections[$current_uid])) { + unset($this->_uidConnections[$current_uid]); + } + $client_connection->uid_info = ''; + $client_connection->uid = null; + } + return; + // 发送数据给 uid Gateway::sendToUid($uid, $msg); + case GatewayProtocol::CMD_SEND_TO_UID: + $uid_array = json_decode($data['ext_data'], true); + foreach ($uid_array as $uid) { + if (!empty($this->_uidConnections[$uid])) { + foreach ($this->_uidConnections[$uid] as $connection) { + /** @var TcpConnection $connection */ + $connection->send($data['body']); + } + } + } + return; + // 将 $client_id 加入用户组 Gateway::joinGroup($client_id, $group); + case GatewayProtocol::CMD_JOIN_GROUP: + $group = $data['ext_data']; + if (empty($group)) { + echo "join(group) group empty, group=" . var_export($group, true); + return; + } + $connection_id = $data['connection_id']; + if (!isset($this->_clientConnections[$connection_id])) { + return; + } + $client_connection = $this->_clientConnections[$connection_id]; + if (!isset($client_connection->groups)) { + $client_connection->groups = array(); + } + $client_connection->groups[$group] = $group; + $this->_groupConnections[$group][$connection_id] = $client_connection; + return; + // 将 $client_id 从某个用户组中移除 Gateway::leaveGroup($client_id, $group); + case GatewayProtocol::CMD_LEAVE_GROUP: + $group = $data['ext_data']; + if (empty($group)) { + echo "leave(group) group empty, group=" . var_export($group, true); + return; + } + $connection_id = $data['connection_id']; + if (!isset($this->_clientConnections[$connection_id])) { + return; + } + $client_connection = $this->_clientConnections[$connection_id]; + if (!isset($client_connection->groups[$group])) { + return; + } + unset($client_connection->groups[$group], $this->_groupConnections[$group][$connection_id]); + if (empty($this->_groupConnections[$group])) { + unset($this->_groupConnections[$group]); + } + return; + // 解散分组 + case GatewayProtocol::CMD_UNGROUP: + $group = $data['ext_data']; + if (empty($group)) { + echo "leave(group) group empty, group=" . var_export($group, true); + return; + } + if (empty($this->_groupConnections[$group])) { + return; + } + foreach ($this->_groupConnections[$group] as $client_connection) { + unset($client_connection->groups[$group]); + } + unset($this->_groupConnections[$group]); + return; + // 向某个用户组发送消息 Gateway::sendToGroup($group, $msg); + case GatewayProtocol::CMD_SEND_TO_GROUP: + $raw = (bool)($data['flag'] & GatewayProtocol::FLAG_NOT_CALL_ENCODE); + $body = $data['body']; + if (!$raw && $this->protocolAccelerate && $this->protocol) { + $body = $this->preEncodeForClient($body); + $raw = true; + } + $ext_data = json_decode($data['ext_data'], true); + $group_array = $ext_data['group']; + $exclude_connection_id = $ext_data['exclude']; + + foreach ($group_array as $group) { + if (!empty($this->_groupConnections[$group])) { + foreach ($this->_groupConnections[$group] as $connection) { + if(!isset($exclude_connection_id[$connection->id])) + { + /** @var TcpConnection $connection */ + $connection->send($body, $raw); + } + } + } + } + return; + // 获取某用户组成员信息 Gateway::getClientSessionsByGroup($group); + case GatewayProtocol::CMD_GET_CLIENT_SESSIONS_BY_GROUP: + $group = $data['ext_data']; + if (!isset($this->_groupConnections[$group])) { + $buffer = serialize(array()); + $connection->send(pack('N', strlen($buffer)) . $buffer, true); + return; + } + $client_info_array = array(); + foreach ($this->_groupConnections[$group] as $connection_id => $client_connection) { + $client_info_array[$connection_id] = $client_connection->session; + } + $buffer = serialize($client_info_array); + $connection->send(pack('N', strlen($buffer)) . $buffer, true); + return; + // 获取用户组成员数 Gateway::getClientCountByGroup($group); + case GatewayProtocol::CMD_GET_CLIENT_COUNT_BY_GROUP: + $group = $data['ext_data']; + $count = 0; + if ($group !== '') { + if (isset($this->_groupConnections[$group])) { + $count = count($this->_groupConnections[$group]); + } + } else { + $count = count($this->_clientConnections); + } + $buffer = serialize($count); + $connection->send(pack('N', strlen($buffer)) . $buffer, true); + return; + // 获取与某个 uid 绑定的所有 client_id Gateway::getClientIdByUid($uid); + case GatewayProtocol::CMD_GET_CLIENT_ID_BY_UID: + $uid = $data['ext_data']; + if (empty($this->_uidConnections[$uid])) { + $buffer = serialize(array()); + } else { + $buffer = serialize(array_keys($this->_uidConnections[$uid])); + } + $connection->send(pack('N', strlen($buffer)) . $buffer, true); + return; + default : + $err_msg = "gateway inner pack err cmd=$cmd"; + echo $err_msg; + } + } + + + /** + * 当worker连接关闭时 + * + * @param TcpConnection $connection + */ + public function onWorkerClose($connection) + { + if (isset($connection->key)) { + unset($this->_workerConnections[$connection->key]); + } + } + + /** + * 存储当前 Gateway 的内部通信地址 + * + * @return bool + */ + public function registerAddress() + { + $address = $this->lanIp . ':' . $this->lanPort; + foreach ($this->registerAddress as $register_address) { + $register_connection = new AsyncTcpConnection("text://{$register_address}"); + $secret_key = $this->secretKey; + $register_connection->onConnect = function($register_connection) use ($address, $secret_key, $register_address){ + $register_connection->send('{"event":"gateway_connect", "address":"' . $address . '", "secret_key":"' . $secret_key . '"}'); + // 如果Register服务器不在本地服务器,则需要保持心跳 + if (strpos($register_address, '127.0.0.1') !== 0) { + $register_connection->ping_timer = Timer::add(self::PERSISTENCE_CONNECTION_PING_INTERVAL, function () use ($register_connection) { + $register_connection->send('{"event":"ping"}'); + }); + } + }; + $register_connection->onClose = function ($register_connection) { + if(!empty($register_connection->ping_timer)) { + Timer::del($register_connection->ping_timer); + } + $register_connection->reconnect(1); + }; + $register_connection->connect(); + } + } + + + /** + * 心跳逻辑 + * + * @return void + */ + public function ping() + { + $ping_data = $this->pingData ? (string)$this->pingData : null; + $raw = false; + if ($this->protocolAccelerate && $ping_data && $this->protocol) { + $ping_data = $this->preEncodeForClient($ping_data); + $raw = true; + } + // 遍历所有客户端连接 + foreach ($this->_clientConnections as $connection) { + // 上次发送的心跳还没有回复次数大于限定值就断开 + if ($this->pingNotResponseLimit > 0 && + $connection->pingNotResponseCount >= $this->pingNotResponseLimit * 2 + ) { + $connection->destroy(); + continue; + } + // $connection->pingNotResponseCount 为 -1 说明最近客户端有发来消息,则不给客户端发送心跳 + $connection->pingNotResponseCount++; + if ($ping_data) { + if ($connection->pingNotResponseCount === 0 || + ($this->pingNotResponseLimit > 0 && $connection->pingNotResponseCount % 2 === 1) + ) { + continue; + } + $connection->send($ping_data, $raw); + } + } + } + + /** + * 向 BusinessWorker 发送心跳数据,用于保持长连接 + * + * @return void + */ + public function pingBusinessWorker() + { + $gateway_data = GatewayProtocol::$empty; + $gateway_data['cmd'] = GatewayProtocol::CMD_PING; + foreach ($this->_workerConnections as $connection) { + $connection->send($gateway_data); + } + } + + /** + * @param mixed $data + * + * @return string + */ + protected function preEncodeForClient($data) + { + foreach ($this->_clientConnections as $client_connection) { + return call_user_func(array($client_connection->protocol, 'encode'), $data, $client_connection); + } + } + + /** + * 当 gateway 关闭时触发,清理数据 + * + * @return void + */ + public function onWorkerStop() + { + // 尝试触发用户设置的回调 + if ($this->_onWorkerStop) { + call_user_func($this->_onWorkerStop, $this); + } + } + + /** + * Log. + * @param string $msg + */ + public static function log($msg){ + Timer::add(1, function() use ($msg) { + Worker::log($msg); + }, null, false); + } +} diff --git a/GatewayWorker/vendor/workerman/gateway-worker/src/Lib/Context.php b/GatewayWorker/vendor/workerman/gateway-worker/src/Lib/Context.php new file mode 100644 index 0000000..22ebccb --- /dev/null +++ b/GatewayWorker/vendor/workerman/gateway-worker/src/Lib/Context.php @@ -0,0 +1,136 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace GatewayWorker\Lib; + +use Exception; + +/** + * 上下文 包含当前用户 uid, 内部通信 local_ip local_port socket_id,以及客户端 client_ip client_port + */ +class Context +{ + /** + * 内部通讯 id + * + * @var string + */ + public static $local_ip; + + /** + * 内部通讯端口 + * + * @var int + */ + public static $local_port; + + /** + * 客户端 ip + * + * @var string + */ + public static $client_ip; + + /** + * 客户端端口 + * + * @var int + */ + public static $client_port; + + /** + * client_id + * + * @var string + */ + public static $client_id; + + /** + * 连接 connection->id + * + * @var int + */ + public static $connection_id; + + /** + * 旧的session + * + * @var string + */ + public static $old_session; + + /** + * 编码 session + * + * @param mixed $session_data + * @return string + */ + public static function sessionEncode($session_data = '') + { + if ($session_data !== '') { + return serialize($session_data); + } + return ''; + } + + /** + * 解码 session + * + * @param string $session_buffer + * @return mixed + */ + public static function sessionDecode($session_buffer) + { + return unserialize($session_buffer); + } + + /** + * 清除上下文 + * + * @return void + */ + public static function clear() + { + self::$local_ip = self::$local_port = self::$client_ip = self::$client_port = + self::$client_id = self::$connection_id = self::$old_session = null; + } + + /** + * 通讯地址到 client_id 的转换 + * + * @param int $local_ip + * @param int $local_port + * @param int $connection_id + * @return string + */ + public static function addressToClientId($local_ip, $local_port, $connection_id) + { + return bin2hex(pack('NnN', $local_ip, $local_port, $connection_id)); + } + + /** + * client_id 到通讯地址的转换 + * + * @param string $client_id + * @return array + * @throws Exception + */ + public static function clientIdToAddress($client_id) + { + if (strlen($client_id) !== 20) { + echo new Exception("client_id $client_id is invalid"); + return false; + } + return unpack('Nlocal_ip/nlocal_port/Nconnection_id', pack('H*', $client_id)); + } +} diff --git a/GatewayWorker/vendor/workerman/gateway-worker/src/Lib/Db.php b/GatewayWorker/vendor/workerman/gateway-worker/src/Lib/Db.php new file mode 100644 index 0000000..9f0e4b6 --- /dev/null +++ b/GatewayWorker/vendor/workerman/gateway-worker/src/Lib/Db.php @@ -0,0 +1,76 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace GatewayWorker\Lib; + +use Config\Db as DbConfig; +use Exception; + +/** + * 数据库类 + */ +class Db +{ + /** + * 实例数组 + * + * @var array + */ + protected static $instance = array(); + + /** + * 获取实例 + * + * @param string $config_name + * @return DbConnection + * @throws Exception + */ + public static function instance($config_name) + { + if (!isset(DbConfig::$$config_name)) { + echo "\\Config\\Db::$config_name not set\n"; + throw new Exception("\\Config\\Db::$config_name not set\n"); + } + + if (empty(self::$instance[$config_name])) { + $config = DbConfig::$$config_name; + self::$instance[$config_name] = new DbConnection($config['host'], $config['port'], + $config['user'], $config['password'], $config['dbname'],$config['charset']); + } + return self::$instance[$config_name]; + } + + /** + * 关闭数据库实例 + * + * @param string $config_name + */ + public static function close($config_name) + { + if (isset(self::$instance[$config_name])) { + self::$instance[$config_name]->closeConnection(); + self::$instance[$config_name] = null; + } + } + + /** + * 关闭所有数据库实例 + */ + public static function closeAll() + { + foreach (self::$instance as $connection) { + $connection->closeConnection(); + } + self::$instance = array(); + } +} diff --git a/GatewayWorker/vendor/workerman/gateway-worker/src/Lib/DbConnection.php b/GatewayWorker/vendor/workerman/gateway-worker/src/Lib/DbConnection.php new file mode 100644 index 0000000..df6daff --- /dev/null +++ b/GatewayWorker/vendor/workerman/gateway-worker/src/Lib/DbConnection.php @@ -0,0 +1,1976 @@ +type = 'SELECT'; + if (!is_array($cols)) { + $cols = array($cols); + } + $this->cols($cols); + return $this; + } + + /** + * 从哪个表删除 + * + * @param string $table + * @return self + */ + public function delete($table) + { + $this->type = 'DELETE'; + $this->table = $this->quoteName($table); + $this->fromRaw($this->quoteName($table)); + return $this; + } + + /** + * 更新哪个表 + * + * @param string $table + * @return self + */ + public function update($table) + { + $this->type = 'UPDATE'; + $this->table = $this->quoteName($table); + return $this; + } + + /** + * 向哪个表插入 + * + * @param string $table + * @return self + */ + public function insert($table) + { + $this->type = 'INSERT'; + $this->table = $this->quoteName($table); + return $this; + } + + /** + * + * 设置 SQL_CALC_FOUND_ROWS 标记. + * + * @param bool $enable + * @return self + */ + public function calcFoundRows($enable = true) + { + $this->setFlag('SQL_CALC_FOUND_ROWS', $enable); + return $this; + } + + /** + * 设置 SQL_CACHE 标记 + * + * @param bool $enable + * @return self + */ + public function cache($enable = true) + { + $this->setFlag('SQL_CACHE', $enable); + return $this; + } + + /** + * 设置 SQL_NO_CACHE 标记 + * + * @param bool $enable + * @return self + */ + public function noCache($enable = true) + { + $this->setFlag('SQL_NO_CACHE', $enable); + return $this; + } + + /** + * 设置 STRAIGHT_JOIN 标记. + * + * @param bool $enable + * @return self + */ + public function straightJoin($enable = true) + { + $this->setFlag('STRAIGHT_JOIN', $enable); + return $this; + } + + /** + * 设置 HIGH_PRIORITY 标记 + * + * @param bool $enable + * @return self + */ + public function highPriority($enable = true) + { + $this->setFlag('HIGH_PRIORITY', $enable); + return $this; + } + + /** + * 设置 SQL_SMALL_RESULT 标记 + * + * @param bool $enable + * @return self + */ + public function smallResult($enable = true) + { + $this->setFlag('SQL_SMALL_RESULT', $enable); + return $this; + } + + /** + * 设置 SQL_BIG_RESULT 标记 + * + * @param bool $enable + * @return self + */ + public function bigResult($enable = true) + { + $this->setFlag('SQL_BIG_RESULT', $enable); + return $this; + } + + /** + * 设置 SQL_BUFFER_RESULT 标记 + * + * @param bool $enable + * @return self + */ + public function bufferResult($enable = true) + { + $this->setFlag('SQL_BUFFER_RESULT', $enable); + return $this; + } + + /** + * 设置 FOR UPDATE 标记 + * + * @param bool $enable + * @return self + */ + public function forUpdate($enable = true) + { + $this->for_update = (bool)$enable; + return $this; + } + + /** + * 设置 DISTINCT 标记 + * + * @param bool $enable + * @return self + */ + public function distinct($enable = true) + { + $this->setFlag('DISTINCT', $enable); + return $this; + } + + /** + * 设置 LOW_PRIORITY 标记 + * + * @param bool $enable + * @return self + */ + public function lowPriority($enable = true) + { + $this->setFlag('LOW_PRIORITY', $enable); + return $this; + } + + /** + * 设置 IGNORE 标记 + * + * @param bool $enable + * @return self + */ + public function ignore($enable = true) + { + $this->setFlag('IGNORE', $enable); + return $this; + } + + /** + * 设置 QUICK 标记 + * + * @param bool $enable + * @return self + */ + public function quick($enable = true) + { + $this->setFlag('QUICK', $enable); + return $this; + } + + /** + * 设置 DELAYED 标记 + * + * @param bool $enable + * @return self + */ + public function delayed($enable = true) + { + $this->setFlag('DELAYED', $enable); + return $this; + } + + /** + * 序列化 + * + * @return string + */ + public function __toString() + { + $union = ''; + if ($this->union) { + $union = implode(' ', $this->union) . ' '; + } + return $union . $this->build(); + } + + /** + * 设置每页多少条记录 + * + * @param int $paging + * @return self + */ + public function setPaging($paging) + { + $this->paging = (int)$paging; + return $this; + } + + /** + * 获取每页多少条记录 + * + * @return int + */ + public function getPaging() + { + return $this->paging; + } + + /** + * 获取绑定在占位符上的值 + */ + public function getBindValues() + { + switch ($this->type) { + case 'SELECT': + return $this->getBindValuesSELECT(); + case 'DELETE': + case 'UPDATE': + case 'INSERT': + return $this->getBindValuesCOMMON(); + default : + throw new Exception("type err"); + } + } + + /** + * 获取绑定在占位符上的值 + * + * @return array + */ + public function getBindValuesSELECT() + { + $bind_values = $this->bind_values; + $i = 1; + foreach ($this->bind_where as $val) { + $bind_values[$i] = $val; + $i++; + } + foreach ($this->bind_having as $val) { + $bind_values[$i] = $val; + $i++; + } + return $bind_values; + } + + /** + * + * SELECT选择哪些列 + * + * @param mixed $key + * @param string $val + * @return void + */ + protected function addColSELECT($key, $val) + { + if (is_string($key)) { + $this->cols[$val] = $key; + } else { + $this->addColWithAlias($val); + } + } + + /** + * SELECT 增加选择的列 + * + * @param string $spec + */ + protected function addColWithAlias($spec) + { + $parts = explode(' ', $spec); + $count = count($parts); + if ($count == 2) { + $this->cols[$parts[1]] = $parts[0]; + } elseif ($count == 3 && strtoupper($parts[1]) == 'AS') { + $this->cols[$parts[2]] = $parts[0]; + } else { + $this->cols[] = $spec; + } + } + + /** + * from 哪个表 + * + * @param string $table + * @return self + */ + public function from($table) + { + return $this->fromRaw($this->quoteName($table)); + } + + /** + * from的表 + * + * @param string $table + * @return self + */ + public function fromRaw($table) + { + $this->from[] = array($table); + $this->from_key++; + return $this; + } + + /** + * + * 子查询 + * + * @param string $table + * @param string $name The alias name for the sub-select. + * @return self + */ + public function fromSubSelect($table, $name) + { + $this->from[] = array("($table) AS " . $this->quoteName($name)); + $this->from_key++; + return $this; + } + + + /** + * 增加 join 语句 + * + * @param string $table + * @param string $cond + * @param string $type + * @return self + * @throws Exception + */ + public function join($table, $cond = null, $type = '') + { + return $this->joinInternal($type, $table, $cond); + } + + /** + * 增加 join 语句 + * + * @param string $join inner, left, natural + * @param string $table + * @param string $cond + * @return self + * @throws Exception + */ + protected function joinInternal($join, $table, $cond = null) + { + if (!$this->from) { + throw new Exception('Cannot join() without from()'); + } + + $join = strtoupper(ltrim("$join JOIN")); + $table = $this->quoteName($table); + $cond = $this->fixJoinCondition($cond); + $this->from[$this->from_key][] = rtrim("$join $table $cond"); + return $this; + } + + /** + * quote + * + * @param string $cond + * @return string + * + */ + protected function fixJoinCondition($cond) + { + if (!$cond) { + return ''; + } + + $cond = $this->quoteNamesIn($cond); + + if (strtoupper(substr(ltrim($cond), 0, 3)) == 'ON ') { + return $cond; + } + + if (strtoupper(substr(ltrim($cond), 0, 6)) == 'USING ') { + return $cond; + } + + return 'ON ' . $cond; + } + + /** + * inner join + * + * @param string $table + * @param string $cond + * @return self + * @throws Exception + */ + public function innerJoin($table, $cond = null) + { + return $this->joinInternal('INNER', $table, $cond); + } + + /** + * left join + * + * @param string $table + * @param string $cond + * @return self + * @throws Exception + */ + public function leftJoin($table, $cond = null) + { + return $this->joinInternal('LEFT', $table, $cond); + } + + /** + * right join + * + * @param string $table + * @param string $cond + * @return self + * @throws Exception + */ + public function rightJoin($table, $cond = null) + { + return $this->joinInternal('RIGHT', $table, $cond); + } + + /** + * joinSubSelect + * + * @param string $join inner, left, natural + * @param string $spec + * @param string $name sub-select 的别名 + * @param string $cond + * @return self + * @throws Exception + */ + public function joinSubSelect($join, $spec, $name, $cond = null) + { + if (!$this->from) { + throw new \Exception('Cannot join() without from() first.'); + } + + $join = strtoupper(ltrim("$join JOIN")); + $name = $this->quoteName($name); + $cond = $this->fixJoinCondition($cond); + $this->from[$this->from_key][] = rtrim("$join ($spec) AS $name $cond"); + return $this; + } + + /** + * group by 语句 + * + * @param array $cols + * @return self + */ + public function groupBy(array $cols) + { + foreach ($cols as $col) { + $this->group_by[] = $this->quoteNamesIn($col); + } + return $this; + } + + /** + * having 语句 + * + * @param string $cond + * @return self + */ + public function having($cond) + { + $this->addClauseCondWithBind('having', 'AND', func_get_args()); + return $this; + } + + /** + * or having 语句 + * + * @param string $cond The HAVING condition. + * @return self + */ + public function orHaving($cond) + { + $this->addClauseCondWithBind('having', 'OR', func_get_args()); + return $this; + } + + /** + * 设置每页的记录数量 + * + * @param int $page + * @return self + */ + public function page($page) + { + $this->limit = 0; + $this->offset = 0; + + $page = (int)$page; + if ($page > 0) { + $this->limit = $this->paging; + $this->offset = $this->paging * ($page - 1); + } + return $this; + } + + /** + * union + * + * @return self + */ + public function union() + { + $this->union[] = $this->build() . ' UNION'; + $this->reset(); + return $this; + } + + /** + * unionAll + * + * @return self + */ + public function unionAll() + { + $this->union[] = $this->build() . ' UNION ALL'; + $this->reset(); + return $this; + } + + /** + * 重置 + */ + protected function reset() + { + $this->resetFlags(); + $this->cols = array(); + $this->from = array(); + $this->from_key = -1; + $this->where = array(); + $this->group_by = array(); + $this->having = array(); + $this->order_by = array(); + $this->limit = 0; + $this->offset = 0; + $this->for_update = false; + } + + /** + * 清除所有数据 + */ + protected function resetAll() + { + $this->union = array(); + $this->for_update = false; + $this->cols = array(); + $this->from = array(); + $this->from_key = -1; + $this->group_by = array(); + $this->having = array(); + $this->bind_having = array(); + $this->paging = 10; + $this->bind_values = array(); + $this->where = array(); + $this->bind_where = array(); + $this->order_by = array(); + $this->limit = 0; + $this->offset = 0; + $this->flags = array(); + $this->table = ''; + $this->last_insert_id_names = array(); + $this->col_values = array(); + $this->returning = array(); + $this->parameters = array(); + } + + /** + * 创建 SELECT SQL + * + * @return string + */ + protected function buildSELECT() + { + return 'SELECT' + . $this->buildFlags() + . $this->buildCols() + . $this->buildFrom() + . $this->buildWhere() + . $this->buildGroupBy() + . $this->buildHaving() + . $this->buildOrderBy() + . $this->buildLimit() + . $this->buildForUpdate(); + } + + /** + * 创建 DELETE SQL + */ + protected function buildDELETE() + { + return 'DELETE' + . $this->buildFlags() + . $this->buildFrom() + . $this->buildWhere() + . $this->buildOrderBy() + . $this->buildLimit() + . $this->buildReturning(); + } + + /** + * 生成 SELECT 列语句 + * + * @return string + * @throws Exception + */ + protected function buildCols() + { + if (!$this->cols) { + throw new Exception('No columns in the SELECT.'); + } + + $cols = array(); + foreach ($this->cols as $key => $val) { + if (is_int($key)) { + $cols[] = $this->quoteNamesIn($val); + } else { + $cols[] = $this->quoteNamesIn("$val AS $key"); + } + } + + return $this->indentCsv($cols); + } + + /** + * 生成 FROM 语句. + * + * @return string + */ + protected function buildFrom() + { + if (!$this->from) { + return ''; + } + + $refs = array(); + foreach ($this->from as $from) { + $refs[] = implode(' ', $from); + } + return ' FROM' . $this->indentCsv($refs); + } + + /** + * 生成 GROUP BY 语句. + * + * @return string + */ + protected function buildGroupBy() + { + if (!$this->group_by) { + return ''; + } + return ' GROUP BY' . $this->indentCsv($this->group_by); + } + + /** + * 生成 HAVING 语句. + * + * @return string + */ + protected function buildHaving() + { + if (!$this->having) { + return ''; + } + return ' HAVING' . $this->indent($this->having); + } + + /** + * 生成 FOR UPDATE 语句 + * + * @return string + */ + protected function buildForUpdate() + { + if (!$this->for_update) { + return ''; + } + return ' FOR UPDATE'; + } + + /** + * where + * + * @param string|array $cond + * @return self + */ + public function where($cond) + { + if (is_array($cond)) { + foreach ($cond as $key => $val) { + if (is_string($key)) { + $this->addWhere('AND', array($key, $val)); + } else { + $this->addWhere('AND', array($val)); + } + } + } else { + $this->addWhere('AND', func_get_args()); + } + return $this; + } + + /** + * or where + * + * @param string|array $cond + * @return self + */ + public function orWhere($cond) + { + if (is_array($cond)) { + foreach ($cond as $key => $val) { + if (is_string($key)) { + $this->addWhere('OR', array($key, $val)); + } else { + $this->addWhere('OR', array($val)); + } + } + } else { + $this->addWhere('OR', func_get_args()); + } + return $this; + } + + /** + * limit + * + * @param int $limit + * @return self + */ + public function limit($limit) + { + $this->limit = (int)$limit; + return $this; + } + + /** + * limit offset + * + * @param int $offset + * @return self + */ + public function offset($offset) + { + $this->offset = (int)$offset; + return $this; + } + + /** + * orderby. + * + * @param array $cols + * @return self + */ + public function orderBy(array $cols) + { + return $this->addOrderBy($cols); + } + + /** + * order by ASC OR DESC + * + * @param array $cols + * @param bool $order_asc + * @return self + */ + public function orderByASC(array $cols, $order_asc = true) + { + $this->order_asc = $order_asc; + return $this->addOrderBy($cols); + } + + /** + * order by DESC + * + * @param array $cols + * @return self + */ + public function orderByDESC(array $cols) + { + $this->order_asc = false; + return $this->addOrderBy($cols); + } + + // -------------abstractquery---------- + /** + * 返回逗号分隔的字符串 + * + * @param array $list + * @return string + */ + protected function indentCsv(array $list) + { + return ' ' . implode(',', $list); + } + + /** + * 返回空格分隔的字符串 + * + * @param array $list + * @return string + */ + protected function indent(array $list) + { + return ' ' . implode(' ', $list); + } + + /** + * 批量为占位符绑定值 + * + * @param array $bind_values + * @return self + * + */ + public function bindValues(array $bind_values) + { + foreach ($bind_values as $key => $val) { + $this->bindValue($key, $val); + } + return $this; + } + + /** + * 单个为占位符绑定值 + * + * @param string $name + * @param mixed $value + * @return self + */ + public function bindValue($name, $value) + { + $this->bind_values[$name] = $value; + return $this; + } + + /** + * 生成 flag + * + * @return string + */ + protected function buildFlags() + { + if (!$this->flags) { + return ''; + } + return ' ' . implode(' ', array_keys($this->flags)); + } + + /** + * 设置 flag. + * + * @param string $flag + * @param bool $enable + */ + protected function setFlag($flag, $enable = true) + { + if ($enable) { + $this->flags[$flag] = true; + } else { + unset($this->flags[$flag]); + } + } + + /** + * 重置 flag + */ + protected function resetFlags() + { + $this->flags = array(); + } + + /** + * + * 添加 where 语句 + * + * @param string $andor 'AND' or 'OR + * @param array $conditions + * @return self + * + */ + protected function addWhere($andor, $conditions) + { + $this->addClauseCondWithBind('where', $andor, $conditions); + return $this; + } + + /** + * 添加条件和绑定值 + * + * @param string $clause where 、having等 + * @param string $andor AND、OR等 + * @param array $conditions + */ + protected function addClauseCondWithBind($clause, $andor, $conditions) + { + $cond = array_shift($conditions); + $cond = $this->quoteNamesIn($cond); + + $bind =& $this->{"bind_{$clause}"}; + foreach ($conditions as $value) { + $bind[] = $value; + } + + $clause =& $this->$clause; + if ($clause) { + $clause[] = "$andor $cond"; + } else { + $clause[] = $cond; + } + } + + /** + * 生成 where 语句 + * + * @return string + */ + protected function buildWhere() + { + if (!$this->where) { + return ''; + } + return ' WHERE' . $this->indent($this->where); + } + + /** + * 增加 order by + * + * @param array $spec The columns and direction to order by. + * @return self + */ + protected function addOrderBy(array $spec) + { + foreach ($spec as $col) { + $this->order_by[] = $this->quoteNamesIn($col); + } + return $this; + } + + /** + * 生成 order by 语句 + * + * @return string + */ + protected function buildOrderBy() + { + if (!$this->order_by) { + return ''; + } + + if ($this->order_asc) { + return ' ORDER BY' . $this->indentCsv($this->order_by) . ' ASC'; + } else { + return ' ORDER BY' . $this->indentCsv($this->order_by) . ' DESC'; + } + } + + /** + * 生成 limit 语句 + * + * @return string + */ + protected function buildLimit() + { + $has_limit = $this->type == 'DELETE' || $this->type == 'UPDATE'; + $has_offset = $this->type == 'SELECT'; + + if ($has_offset && $this->limit) { + $clause = " LIMIT {$this->limit}"; + if ($this->offset) { + $clause .= " OFFSET {$this->offset}"; + } + return $clause; + } elseif ($has_limit && $this->limit) { + return " LIMIT {$this->limit}"; + } + return ''; + } + + /** + * Quotes + * + * @param string $spec + * @return string|array + */ + public function quoteName($spec) + { + $spec = trim($spec); + $seps = array(' AS ', ' ', '.'); + foreach ($seps as $sep) { + $pos = strripos($spec, $sep); + if ($pos) { + return $this->quoteNameWithSeparator($spec, $sep, $pos); + } + } + return $this->replaceName($spec); + } + + /** + * 指定分隔符的 Quotes + * + * @param string $spec + * @param string $sep + * @param int $pos + * @return string + */ + protected function quoteNameWithSeparator($spec, $sep, $pos) + { + $len = strlen($sep); + $part1 = $this->quoteName(substr($spec, 0, $pos)); + $part2 = $this->replaceName(substr($spec, $pos + $len)); + return "{$part1}{$sep}{$part2}"; + } + + /** + * Quotes "table.col" 格式的字符串 + * + * @param string $text + * @return string|array + */ + public function quoteNamesIn($text) + { + $list = $this->getListForQuoteNamesIn($text); + $last = count($list) - 1; + $text = null; + foreach ($list as $key => $val) { + if (($key + 1) % 3) { + $text .= $this->quoteNamesInLoop($val, $key == $last); + } + } + return $text; + } + + /** + * 返回 quote 元素列表 + * + * @param string $text + * @return array + */ + protected function getListForQuoteNamesIn($text) + { + $apos = "'"; + $quot = '"'; + return preg_split( + "/(($apos+|$quot+|\\$apos+|\\$quot+).*?\\2)/", + $text, + -1, + PREG_SPLIT_DELIM_CAPTURE + ); + } + + /** + * 循环 quote + * + * @param string $val + * @param bool $is_last + * @return string + */ + protected function quoteNamesInLoop($val, $is_last) + { + if ($is_last) { + return $this->replaceNamesAndAliasIn($val); + } + return $this->replaceNamesIn($val); + } + + /** + * 替换成别名 + * + * @param string $val + * @return string + */ + protected function replaceNamesAndAliasIn($val) + { + $quoted = $this->replaceNamesIn($val); + $pos = strripos($quoted, ' AS '); + if ($pos) { + $alias = $this->replaceName(substr($quoted, $pos + 4)); + $quoted = substr($quoted, 0, $pos) . " AS $alias"; + } + return $quoted; + } + + /** + * Quotes name + * + * @param string $name + * @return string + */ + protected function replaceName($name) + { + $name = trim($name); + if ($name == '*') { + return $name; + } + return '`' . $name . '`'; + } + + /** + * Quotes + * + * @param string $text + * @return string|array + */ + protected function replaceNamesIn($text) + { + $is_string_literal = strpos($text, "'") !== false + || strpos($text, '"') !== false; + if ($is_string_literal) { + return $text; + } + + $word = '[a-z_][a-z0-9_]+'; + + $find = "/(\\b)($word)\\.($word)(\\b)/i"; + + $repl = '$1`$2`.`$3`$4'; + + $text = preg_replace($find, $repl, $text); + + return $text; + } + + // ---------- insert -------------- + /** + * 设置 `table.column` 与 last-insert-id 的映射 + * + * @param array $last_insert_id_names + */ + public function setLastInsertIdNames(array $last_insert_id_names) + { + $this->last_insert_id_names = $last_insert_id_names; + } + + /** + * insert into. + * + * @param string $table + * @return self + */ + public function into($table) + { + $this->table = $this->quoteName($table); + return $this; + } + + /** + * 生成 INSERT 语句 + * + * @return string + */ + protected function buildINSERT() + { + return 'INSERT' + . $this->buildFlags() + . $this->buildInto() + . $this->buildValuesForInsert() + . $this->buildReturning(); + } + + /** + * 生成 INTO 语句 + * + * @return string + */ + protected function buildInto() + { + return " INTO " . $this->table; + } + + /** + * PDO::lastInsertId() + * + * @param string $col + * @return mixed + */ + public function getLastInsertIdName($col) + { + $key = str_replace('`', '', $this->table) . '.' . $col; + if (isset($this->last_insert_id_names[$key])) { + return $this->last_insert_id_names[$key]; + } + + return null; + } + + /** + * 设置一列,如果有第二各参数,则把第二个参数绑定在占位符上 + * + * @param string $col + * @return self + */ + public function col($col) + { + return call_user_func_array(array($this, 'addCol'), func_get_args()); + } + + /** + * 设置多列 + * + * @param array $cols + * @return self + */ + public function cols(array $cols) + { + if ($this->type == 'SELECT') { + foreach ($cols as $key => $val) { + $this->addColSELECT($key, $val); + } + return $this; + } + return $this->addCols($cols); + } + + /** + * 直接设置列的值 + * + * @param string $col + * @param string $value + * @return self + */ + public function set($col, $value) + { + return $this->setCol($col, $value); + } + + /** + * 为 INSERT 语句绑定值 + * + * @return string + */ + protected function buildValuesForInsert() + { + return ' (' . $this->indentCsv(array_keys($this->col_values)) . ') VALUES (' . + $this->indentCsv(array_values($this->col_values)) . ')'; + } + + // ------update------- + /** + * 更新哪个表 + * + * @param string $table + * @return self + */ + public function table($table) + { + $this->table = $this->quoteName($table); + return $this; + } + + /** + * 生成完整 SQL 语句 + * + * @return string + * @throws Exception + */ + protected function build() + { + switch ($this->type) { + case 'DELETE': + return $this->buildDELETE(); + case 'INSERT': + return $this->buildINSERT(); + case 'UPDATE': + return $this->buildUPDATE(); + case 'SELECT': + return $this->buildSELECT(); + } + throw new Exception("type empty"); + } + + /** + * 生成更新的 SQL 语句 + */ + protected function buildUPDATE() + { + return 'UPDATE' + . $this->buildFlags() + . $this->buildTable() + . $this->buildValuesForUpdate() + . $this->buildWhere() + . $this->buildOrderBy() + . $this->buildLimit() + . $this->buildReturning(); + } + + /** + * 哪个表 + * + * @return string + */ + protected function buildTable() + { + return " {$this->table}"; + } + + /** + * 为更新语句绑定值 + * + * @return string + */ + protected function buildValuesForUpdate() + { + $values = array(); + foreach ($this->col_values as $col => $value) { + $values[] = "{$col} = {$value}"; + } + return ' SET' . $this->indentCsv($values); + } + + // ----------Dml--------------- + /** + * 获取绑定的值 + * + * @return array + */ + public function getBindValuesCOMMON() + { + $bind_values = $this->bind_values; + $i = 1; + foreach ($this->bind_where as $val) { + $bind_values[$i] = $val; + $i++; + } + return $bind_values; + } + + /** + * 设置列 + * + * @param string $col + * @return self + */ + protected function addCol($col) + { + $key = $this->quoteName($col); + $this->col_values[$key] = ":$col"; + $args = func_get_args(); + if (count($args) > 1) { + $this->bindValue($col, $args[1]); + } + return $this; + } + + /** + * 设置多个列 + * + * @param array $cols + * @return self + */ + protected function addCols(array $cols) + { + foreach ($cols as $key => $val) { + if (is_int($key)) { + $this->addCol($val); + } else { + $this->addCol($key, $val); + } + } + return $this; + } + + /** + * 设置单列的值 + * + * @param string $col . + * @param string $value + * @return self + */ + protected function setCol($col, $value) + { + if ($value === null) { + $value = 'NULL'; + } + + $key = $this->quoteName($col); + $value = $this->quoteNamesIn($value); + $this->col_values[$key] = $value; + return $this; + } + + /** + * 增加返回的列 + * + * @param array $cols + * @return self + * + */ + protected function addReturning(array $cols) + { + foreach ($cols as $col) { + $this->returning[] = $this->quoteNamesIn($col); + } + return $this; + } + + /** + * 生成 RETURNING 语句 + * + * @return string + */ + protected function buildReturning() + { + if (!$this->returning) { + return ''; + } + return ' RETURNING' . $this->indentCsv($this->returning); + } + + /** + * 构造函数 + * + * @param string $host + * @param int $port + * @param string $user + * @param string $password + * @param string $db_name + * @param string $charset + */ + public function __construct($host, $port, $user, $password, $db_name, $charset = 'utf8') + { + $this->settings = array( + 'host' => $host, + 'port' => $port, + 'user' => $user, + 'password' => $password, + 'dbname' => $db_name, + 'charset' => $charset, + ); + $this->connect(); + } + + /** + * 创建 PDO 实例 + */ + protected function connect() + { + $dsn = 'mysql:dbname=' . $this->settings["dbname"] . ';host=' . + $this->settings["host"] . ';port=' . $this->settings['port']; + $this->pdo = new PDO($dsn, $this->settings["user"], $this->settings["password"], + array( + PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES ' . (!empty($this->settings['charset']) ? + $this->settings['charset'] : 'utf8') + )); + $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $this->pdo->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false); + $this->pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); + } + + /** + * 关闭连接 + */ + public function closeConnection() + { + $this->pdo = null; + } + + /** + * 执行 + * + * @param string $query + * @param string $parameters + * @throws PDOException + */ + protected function execute($query, $parameters = "") + { + try { + $this->sQuery = @$this->pdo->prepare($query); + $this->bindMore($parameters); + if (!empty($this->parameters)) { + foreach ($this->parameters as $param) { + $parameters = explode("\x7F", $param); + $this->sQuery->bindParam($parameters[0], $parameters[1]); + } + } + $this->success = $this->sQuery->execute(); + } catch (PDOException $e) { + // 服务端断开时重连一次 + if ($e->errorInfo[1] == 2006 || $e->errorInfo[1] == 2013) { + $this->closeConnection(); + $this->connect(); + + try { + $this->sQuery = $this->pdo->prepare($query); + $this->bindMore($parameters); + if (!empty($this->parameters)) { + foreach ($this->parameters as $param) { + $parameters = explode("\x7F", $param); + $this->sQuery->bindParam($parameters[0], $parameters[1]); + } + } + $this->success = $this->sQuery->execute(); + } catch (PDOException $ex) { + $this->rollBackTrans(); + throw $ex; + } + } else { + $this->rollBackTrans(); + $msg = $e->getMessage(); + $err_msg = "SQL:".$this->lastSQL()." ".$msg; + $exception = new \PDOException($err_msg, (int)$e->getCode()); + throw $exception; + } + } + $this->parameters = array(); + } + + /** + * 绑定 + * + * @param string $para + * @param string $value + */ + public function bind($para, $value) + { + if (is_string($para)) { + $this->parameters[sizeof($this->parameters)] = ":" . $para . "\x7F" . $value; + } else { + $this->parameters[sizeof($this->parameters)] = $para . "\x7F" . $value; + } + } + + /** + * 绑定多个 + * + * @param array $parray + */ + public function bindMore($parray) + { + if (empty($this->parameters) && is_array($parray)) { + $columns = array_keys($parray); + foreach ($columns as $i => &$column) { + $this->bind($column, $parray[$column]); + } + } + } + + /** + * 执行 SQL + * + * @param string $query + * @param array $params + * @param int $fetchmode + * @return mixed + */ + public function query($query = '', $params = null, $fetchmode = PDO::FETCH_ASSOC) + { + $query = trim($query); + if (empty($query)) { + $query = $this->build(); + if (!$params) { + $params = $this->getBindValues(); + } + } + + $this->resetAll(); + $this->lastSql = $query; + + $this->execute($query, $params); + + $rawStatement = explode(" ", $query); + + $statement = strtolower(trim($rawStatement[0])); + if ($statement === 'select' || $statement === 'show') { + return $this->sQuery->fetchAll($fetchmode); + } elseif ($statement === 'update' || $statement === 'delete') { + return $this->sQuery->rowCount(); + } elseif ($statement === 'insert') { + if ($this->sQuery->rowCount() > 0) { + return $this->lastInsertId(); + } + } else { + return null; + } + + return null; + } + + /** + * 返回一列 + * + * @param string $query + * @param array $params + * @return array + */ + public function column($query = '', $params = null) + { + $query = trim($query); + if (empty($query)) { + $query = $this->build(); + if (!$params) { + $params = $this->getBindValues(); + } + } + + $this->resetAll(); + $this->lastSql = $query; + + $this->execute($query, $params); + $columns = $this->sQuery->fetchAll(PDO::FETCH_NUM); + $column = null; + foreach ($columns as $cells) { + $column[] = $cells[0]; + } + return $column; + } + + /** + * 返回一行 + * + * @param string $query + * @param array $params + * @param int $fetchmode + * @return array + */ + public function row($query = '', $params = null, $fetchmode = PDO::FETCH_ASSOC) + { + $query = trim($query); + if (empty($query)) { + $query = $this->build(); + if (!$params) { + $params = $this->getBindValues(); + } + } + + $this->resetAll(); + $this->lastSql = $query; + + $this->execute($query, $params); + return $this->sQuery->fetch($fetchmode); + } + + /** + * 返回单个值 + * + * @param string $query + * @param array $params + * @return string + */ + public function single($query = '', $params = null) + { + $query = trim($query); + if (empty($query)) { + $query = $this->build(); + if (!$params) { + $params = $this->getBindValues(); + } + } + + $this->resetAll(); + $this->lastSql = $query; + + $this->execute($query, $params); + return $this->sQuery->fetchColumn(); + } + + /** + * 返回 lastInsertId + * + * @return string + */ + public function lastInsertId() + { + return $this->pdo->lastInsertId(); + } + + /** + * 返回最后一条执行的 sql + * + * @return string + */ + public function lastSQL() + { + return $this->lastSql; + } + + /** + * 开始事务 + */ + public function beginTrans() + { + try { + $this->pdo->beginTransaction(); + } catch (PDOException $e) { + // 服务端断开时重连一次 + if ($e->errorInfo[1] == 2006 || $e->errorInfo[1] == 2013) { + $this->pdo->beginTransaction(); + } else { + throw $e; + } + } + } + + /** + * 提交事务 + */ + public function commitTrans() + { + $this->pdo->commit(); + } + + /** + * 事务回滚 + */ + public function rollBackTrans() + { + if ($this->pdo->inTransaction()) { + $this->pdo->rollBack(); + } + } +} diff --git a/GatewayWorker/vendor/workerman/gateway-worker/src/Lib/Gateway.php b/GatewayWorker/vendor/workerman/gateway-worker/src/Lib/Gateway.php new file mode 100644 index 0000000..1b8a498 --- /dev/null +++ b/GatewayWorker/vendor/workerman/gateway-worker/src/Lib/Gateway.php @@ -0,0 +1,1361 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace GatewayWorker\Lib; + +use Exception; +use GatewayWorker\Protocols\GatewayProtocol; +use Workerman\Connection\TcpConnection; + +/** + * 数据发送相关 + */ +class Gateway +{ + /** + * gateway 实例 + * + * @var object + */ + protected static $businessWorker = null; + + /** + * 注册中心地址 + * + * @var string|array + */ + public static $registerAddress = '127.0.0.1:1236'; + + /** + * 秘钥 + * @var string + */ + public static $secretKey = ''; + + /** + * 链接超时时间 + * @var int + */ + public static $connectTimeout = 3; + + /** + * 与Gateway是否是长链接 + * @var bool + */ + public static $persistentConnection = false; + + /** + * 向所有客户端连接(或者 client_id_array 指定的客户端连接)广播消息 + * + * @param string $message 向客户端发送的消息 + * @param array $client_id_array 客户端 id 数组 + * @param array $exclude_client_id 不给这些client_id发 + * @param bool $raw 是否发送原始数据(即不调用gateway的协议的encode方法) + * @return void + * @throws Exception + */ + public static function sendToAll($message, $client_id_array = null, $exclude_client_id = null, $raw = false) + { + $gateway_data = GatewayProtocol::$empty; + $gateway_data['cmd'] = GatewayProtocol::CMD_SEND_TO_ALL; + $gateway_data['body'] = $message; + if ($raw) { + $gateway_data['flag'] |= GatewayProtocol::FLAG_NOT_CALL_ENCODE; + } + + if ($exclude_client_id) { + if (!is_array($exclude_client_id)) { + $exclude_client_id = array($exclude_client_id); + } + if ($client_id_array) { + $exclude_client_id = array_flip($exclude_client_id); + } + } + + if ($client_id_array) { + if (!is_array($client_id_array)) { + echo new \Exception('bad $client_id_array:'.var_export($client_id_array, true)); + return; + } + $data_array = array(); + foreach ($client_id_array as $client_id) { + if (isset($exclude_client_id[$client_id])) { + continue; + } + $address = Context::clientIdToAddress($client_id); + if ($address) { + $key = long2ip($address['local_ip']) . ":{$address['local_port']}"; + $data_array[$key][$address['connection_id']] = $address['connection_id']; + } + } + foreach ($data_array as $addr => $connection_id_list) { + $the_gateway_data = $gateway_data; + $the_gateway_data['ext_data'] = json_encode(array('connections' => $connection_id_list)); + static::sendToGateway($addr, $the_gateway_data); + } + return; + } elseif (empty($client_id_array) && is_array($client_id_array)) { + return; + } + + if (!$exclude_client_id) { + return static::sendToAllGateway($gateway_data); + } + + $address_connection_array = static::clientIdArrayToAddressArray($exclude_client_id); + + // 如果有businessWorker实例,说明运行在workerman环境中,通过businessWorker中的长连接发送数据 + if (static::$businessWorker) { + foreach (static::$businessWorker->gatewayConnections as $address => $gateway_connection) { + $gateway_data['ext_data'] = isset($address_connection_array[$address]) ? + json_encode(array('exclude'=> $address_connection_array[$address])) : ''; + /** @var TcpConnection $gateway_connection */ + $gateway_connection->send($gateway_data); + } + } // 运行在其它环境中,通过注册中心得到gateway地址 + else { + $all_addresses = static::getAllGatewayAddressesFromRegister(); + foreach ($all_addresses as $address) { + $gateway_data['ext_data'] = isset($address_connection_array[$address]) ? + json_encode(array('exclude'=> $address_connection_array[$address])) : ''; + static::sendToGateway($address, $gateway_data); + } + } + + } + + /** + * 向某个client_id对应的连接发消息 + * + * @param int $client_id + * @param string $message + * @return void + */ + public static function sendToClient($client_id, $message) + { + return static::sendCmdAndMessageToClient($client_id, GatewayProtocol::CMD_SEND_TO_ONE, $message); + } + + /** + * 向当前客户端连接发送消息 + * + * @param string $message + * @return bool + */ + public static function sendToCurrentClient($message) + { + return static::sendCmdAndMessageToClient(null, GatewayProtocol::CMD_SEND_TO_ONE, $message); + } + + /** + * 判断某个uid是否在线 + * + * @param string $uid + * @return int 0|1 + */ + public static function isUidOnline($uid) + { + return (int)static::getClientIdByUid($uid); + } + + /** + * 判断client_id对应的连接是否在线 + * + * @param int $client_id + * @return int 0|1 + */ + public static function isOnline($client_id) + { + $address_data = Context::clientIdToAddress($client_id); + if (!$address_data) { + return 0; + } + $address = long2ip($address_data['local_ip']) . ":{$address_data['local_port']}"; + if (isset(static::$businessWorker)) { + if (!isset(static::$businessWorker->gatewayConnections[$address])) { + return 0; + } + } + $gateway_data = GatewayProtocol::$empty; + $gateway_data['cmd'] = GatewayProtocol::CMD_IS_ONLINE; + $gateway_data['connection_id'] = $address_data['connection_id']; + return (int)static::sendAndRecv($address, $gateway_data); + } + + /** + * 获取所有在线用户的session,client_id为 key(弃用,请用getAllClientSessions代替) + * + * @param string $group + * @return array + */ + public static function getAllClientInfo($group = '') + { + echo "Warning: Gateway::getAllClientInfo is deprecated and will be removed in a future, please use Gateway::getAllClientSessions instead."; + return static::getAllClientSessions($group); + } + + /** + * 获取所有在线client_id的session,client_id为 key + * + * @param string $group + * @return array + */ + public static function getAllClientSessions($group = '') + { + $gateway_data = GatewayProtocol::$empty; + if (!$group) { + $gateway_data['cmd'] = GatewayProtocol::CMD_GET_ALL_CLIENT_SESSIONS; + } else { + $gateway_data['cmd'] = GatewayProtocol::CMD_GET_CLIENT_SESSIONS_BY_GROUP; + $gateway_data['ext_data'] = $group; + } + $status_data = array(); + $all_buffer_array = static::getBufferFromAllGateway($gateway_data); + foreach ($all_buffer_array as $local_ip => $buffer_array) { + foreach ($buffer_array as $local_port => $data) { + if ($data) { + foreach ($data as $connection_id => $session_buffer) { + $client_id = Context::addressToClientId($local_ip, $local_port, $connection_id); + if ($client_id === Context::$client_id) { + $status_data[$client_id] = (array)$_SESSION; + } else { + $status_data[$client_id] = $session_buffer ? Context::sessionDecode($session_buffer) : array(); + } + } + } + } + } + return $status_data; + } + + /** + * 获取某个组的连接信息(弃用,请用getClientSessionsByGroup代替) + * + * @param string $group + * @return array + */ + public static function getClientInfoByGroup($group) + { + echo "Warning: Gateway::getClientInfoByGroup is deprecated and will be removed in a future, please use Gateway::getClientSessionsByGroup instead."; + return static::getAllClientSessions($group); + } + + /** + * 获取某个组的所有client_id的session信息 + * + * @param string $group + * + * @return array + */ + public static function getClientSessionsByGroup($group) + { + if (static::isValidGroupId($group)) { + return static::getAllClientSessions($group); + } + return array(); + } + + /** + * 获取所有在线client_id数 + * + * @return int + */ + public static function getAllClientIdCount() + { + return static::getClientCountByGroup(); + } + + /** + * 获取所有在线client_id数(getAllClientIdCount的别名) + * + * @return int + */ + public static function getAllClientCount() + { + return static::getAllClientIdCount(); + } + + /** + * 获取某个组的在线client_id数 + * + * @param string $group + * @return int + */ + public static function getClientIdCountByGroup($group = '') + { + $gateway_data = GatewayProtocol::$empty; + $gateway_data['cmd'] = GatewayProtocol::CMD_GET_CLIENT_COUNT_BY_GROUP; + $gateway_data['ext_data'] = $group; + $total_count = 0; + $all_buffer_array = static::getBufferFromAllGateway($gateway_data); + foreach ($all_buffer_array as $local_ip => $buffer_array) { + foreach ($buffer_array as $local_port => $count) { + if ($count) { + $total_count += $count; + } + } + } + return $total_count; + } + + /** + * getClientIdCountByGroup 函数的别名 + * + * @param string $group + * @return int + */ + public static function getClientCountByGroup($group = '') + { + return static::getClientIdCountByGroup($group); + } + + /** + * 获取某个群组在线client_id列表 + * + * @param string $group + * @return array + */ + public static function getClientIdListByGroup($group) + { + if (!static::isValidGroupId($group)) { + return array(); + } + + $data = static::select(array('uid'), array('groups' => is_array($group) ? $group : array($group))); + $client_id_map = array(); + foreach ($data as $local_ip => $buffer_array) { + foreach ($buffer_array as $local_port => $items) { + //$items = ['connection_id'=>['uid'=>x, 'group'=>[x,x..], 'session'=>[..]], 'client_id'=>[..], ..]; + foreach ($items as $connection_id => $info) { + $client_id = Context::addressToClientId($local_ip, $local_port, $connection_id); + $client_id_map[$client_id] = $client_id; + } + } + } + return $client_id_map; + } + + /** + * 获取集群所有在线client_id列表 + * + * @return array + */ + public static function getAllClientIdList() + { + return static::formatClientIdFromGatewayBuffer(static::select(array('uid'))); + } + + /** + * 格式化client_id + * + * @param $data + * @return array + */ + protected static function formatClientIdFromGatewayBuffer($data) + { + $client_id_list = array(); + foreach ($data as $local_ip => $buffer_array) { + foreach ($buffer_array as $local_port => $items) { + //$items = ['connection_id'=>['uid'=>x, 'group'=>[x,x..], 'session'=>[..]], 'client_id'=>[..], ..]; + foreach ($items as $connection_id => $info) { + $client_id = Context::addressToClientId($local_ip, $local_port, $connection_id); + $client_id_list[$client_id] = $client_id; + } + } + } + return $client_id_list; + } + + + /** + * 获取与 uid 绑定的 client_id 列表 + * + * @param string $uid + * @return array + */ + public static function getClientIdByUid($uid) + { + $gateway_data = GatewayProtocol::$empty; + $gateway_data['cmd'] = GatewayProtocol::CMD_GET_CLIENT_ID_BY_UID; + $gateway_data['ext_data'] = $uid; + $client_list = array(); + $all_buffer_array = static::getBufferFromAllGateway($gateway_data); + foreach ($all_buffer_array as $local_ip => $buffer_array) { + foreach ($buffer_array as $local_port => $connection_id_array) { + if ($connection_id_array) { + foreach ($connection_id_array as $connection_id) { + $client_list[] = Context::addressToClientId($local_ip, $local_port, $connection_id); + } + } + } + } + return $client_list; + } + + /** + * 获取某个群组在线uid列表 + * + * @param string $group + * @return array + */ + public static function getUidListByGroup($group) + { + if (!static::isValidGroupId($group)) { + return array(); + } + + $group = is_array($group) ? $group : array($group); + $data = static::select(array('uid'), array('groups' => $group)); + $uid_map = array(); + foreach ($data as $local_ip => $buffer_array) { + foreach ($buffer_array as $local_port => $items) { + //$items = ['connection_id'=>['uid'=>x, 'group'=>[x,x..], 'session'=>[..]], 'client_id'=>[..], ..]; + foreach ($items as $connection_id => $info) { + if (!empty($info['uid'])) { + $uid_map[$info['uid']] = $info['uid']; + } + } + } + } + return $uid_map; + } + + /** + * 获取某个群组在线uid数 + * + * @param string $group + * @return int + */ + public static function getUidCountByGroup($group) + { + if (static::isValidGroupId($group)) { + return count(static::getUidListByGroup($group)); + } + return 0; + } + + /** + * 获取全局在线uid列表 + * + * @return array + */ + public static function getAllUidList() + { + $data = static::select(array('uid')); + $uid_map = array(); + foreach ($data as $local_ip => $buffer_array) { + foreach ($buffer_array as $local_port => $items) { + //$items = ['connection_id'=>['uid'=>x, 'group'=>[x,x..], 'session'=>[..]], 'client_id'=>[..], ..]; + foreach ($items as $connection_id => $info) { + if (!empty($info['uid'])) { + $uid_map[$info['uid']] = $info['uid']; + } + } + } + } + return $uid_map; + } + + /** + * 获取全局在线uid数 + * @return int + */ + public static function getAllUidCount() + { + return count(static::getAllUidList()); + } + + /** + * 通过client_id获取uid + * + * @param $client_id + * @return mixed + */ + public static function getUidByClientId($client_id) + { + $data = static::select(array('uid'), array('client_id'=>array($client_id))); + foreach ($data as $local_ip => $buffer_array) { + foreach ($buffer_array as $local_port => $items) { + //$items = ['connection_id'=>['uid'=>x, 'group'=>[x,x..], 'session'=>[..]], 'client_id'=>[..], ..]; + foreach ($items as $info) { + return $info['uid']; + } + } + } + } + + /** + * 获取所有在线的群组id + * + * @return array + */ + public static function getAllGroupIdList() + { + $gateway_data = GatewayProtocol::$empty; + $gateway_data['cmd'] = GatewayProtocol::CMD_GET_GROUP_ID_LIST; + $group_id_list = array(); + $all_buffer_array = static::getBufferFromAllGateway($gateway_data); + foreach ($all_buffer_array as $local_ip => $buffer_array) { + foreach ($buffer_array as $local_port => $group_id_array) { + if (is_array($group_id_array)) { + foreach ($group_id_array as $group_id) { + if (!isset($group_id_list[$group_id])) { + $group_id_list[$group_id] = $group_id; + } + } + } + } + } + return $group_id_list; + } + + + /** + * 获取所有在线分组的uid数量,也就是每个分组的在线用户数 + * + * @return array + */ + public static function getAllGroupUidCount() + { + $group_uid_map = static::getAllGroupUidList(); + $group_uid_count_map = array(); + foreach ($group_uid_map as $group_id => $uid_list) { + $group_uid_count_map[$group_id] = count($uid_list); + } + return $group_uid_count_map; + } + + + + /** + * 获取所有分组uid在线列表 + * + * @return array + */ + public static function getAllGroupUidList() + { + $data = static::select(array('uid','groups')); + $group_uid_map = array(); + foreach ($data as $local_ip => $buffer_array) { + foreach ($buffer_array as $local_port => $items) { + //$items = ['connection_id'=>['uid'=>x, 'group'=>[x,x..], 'session'=>[..]], 'client_id'=>[..], ..]; + foreach ($items as $connection_id => $info) { + if (empty($info['uid']) || empty($info['groups'])) { + break; + } + $uid = $info['uid']; + foreach ($info['groups'] as $group_id) { + if(!isset($group_uid_map[$group_id])) { + $group_uid_map[$group_id] = array(); + } + $group_uid_map[$group_id][$uid] = $uid; + } + } + } + } + return $group_uid_map; + } + + /** + * 获取所有群组在线client_id列表 + * + * @return array + */ + public static function getAllGroupClientIdList() + { + $data = static::select(array('groups')); + $group_client_id_map = array(); + foreach ($data as $local_ip => $buffer_array) { + foreach ($buffer_array as $local_port => $items) { + //$items = ['connection_id'=>['uid'=>x, 'group'=>[x,x..], 'session'=>[..]], 'client_id'=>[..], ..]; + foreach ($items as $connection_id => $info) { + if (empty($info['groups'])) { + break; + } + $client_id = Context::addressToClientId($local_ip, $local_port, $connection_id); + foreach ($info['groups'] as $group_id) { + if(!isset($group_client_id_map[$group_id])) { + $group_client_id_map[$group_id] = array(); + } + $group_client_id_map[$group_id][$client_id] = $client_id; + } + } + } + } + return $group_client_id_map; + } + + /** + * 获取所有群组在线client_id数量,也就是获取每个群组在线连接数 + * + * @return array + */ + public static function getAllGroupClientIdCount() + { + $group_client_map = static::getAllGroupClientIdList(); + $group_client_count_map = array(); + foreach ($group_client_map as $group_id => $client_id_list) { + $group_client_count_map[$group_id] = count($client_id_list); + } + return $group_client_count_map; + } + + + /** + * 根据条件到gateway搜索数据 + * + * @param array $fields + * @param array $where + * @return array + */ + protected static function select($fields = array('session','uid','groups'), $where = array()) + { + $t = microtime(true); + $gateway_data = GatewayProtocol::$empty; + $gateway_data['cmd'] = GatewayProtocol::CMD_SELECT; + $gateway_data['ext_data'] = array('fields' => $fields, 'where' => $where); + $gateway_data_list = array(); + // 有client_id,能计算出需要和哪些gateway通讯,只和必要的gateway通讯能降低系统负载 + if (isset($where['client_id'])) { + $client_id_list = $where['client_id']; + unset($gateway_data['ext_data']['where']['client_id']); + $gateway_data['ext_data']['where']['connection_id'] = array(); + foreach ($client_id_list as $client_id) { + $address_data = Context::clientIdToAddress($client_id); + if (!$address_data) { + continue; + } + $address = long2ip($address_data['local_ip']) . ":{$address_data['local_port']}"; + if (!isset($gateway_data_list[$address])) { + $gateway_data_list[$address] = $gateway_data; + } + $gateway_data_list[$address]['ext_data']['where']['connection_id'][$address_data['connection_id']] = $address_data['connection_id']; + } + foreach ($gateway_data_list as $address => $item) { + $gateway_data_list[$address]['ext_data'] = json_encode($item['ext_data']); + } + // 有其它条件,则还是需要向所有gateway发送 + if (count($where) !== 1) { + $gateway_data['ext_data'] = json_encode($gateway_data['ext_data']); + foreach (static::getAllGatewayAddress() as $address) { + if (!isset($gateway_data_list[$address])) { + $gateway_data_list[$address] = $gateway_data; + } + } + } + $data = static::getBufferFromSomeGateway($gateway_data_list); + } else { + $gateway_data['ext_data'] = json_encode($gateway_data['ext_data']); + $data = static::getBufferFromAllGateway($gateway_data); + } + + return $data; + } + + /** + * 生成验证包,用于验证此客户端的合法性 + * + * @return string + */ + protected static function generateAuthBuffer() + { + $gateway_data = GatewayProtocol::$empty; + $gateway_data['cmd'] = GatewayProtocol::CMD_GATEWAY_CLIENT_CONNECT; + $gateway_data['body'] = json_encode(array( + 'secret_key' => static::$secretKey, + )); + return GatewayProtocol::encode($gateway_data); + } + + /** + * 批量向某些gateway发包,并得到返回数组 + * + * @param array $gateway_data_array + * @return array + * @throws Exception + */ + protected static function getBufferFromSomeGateway($gateway_data_array) + { + $gateway_buffer_array = array(); + $auth_buffer = static::$secretKey ? static::generateAuthBuffer() : ''; + foreach ($gateway_data_array as $address => $gateway_data) { + if ($auth_buffer) { + $gateway_buffer_array[$address] = $auth_buffer.GatewayProtocol::encode($gateway_data); + } else { + $gateway_buffer_array[$address] = GatewayProtocol::encode($gateway_data); + } + } + return static::getBufferFromGateway($gateway_buffer_array); + } + + /** + * 批量向所有 gateway 发包,并得到返回数组 + * + * @param string $gateway_data + * @return array + * @throws Exception + */ + protected static function getBufferFromAllGateway($gateway_data) + { + $addresses = static::getAllGatewayAddress(); + $gateway_buffer_array = array(); + $gateway_buffer = GatewayProtocol::encode($gateway_data); + $gateway_buffer = static::$secretKey ? static::generateAuthBuffer() . $gateway_buffer : $gateway_buffer; + foreach ($addresses as $address) { + $gateway_buffer_array[$address] = $gateway_buffer; + } + + return static::getBufferFromGateway($gateway_buffer_array); + } + + /** + * 获取所有gateway内部通讯地址 + * + * @return array + * @throws Exception + */ + protected static function getAllGatewayAddress() + { + if (isset(static::$businessWorker)) { + $addresses = static::$businessWorker->getAllGatewayAddresses(); + if (empty($addresses)) { + throw new Exception('businessWorker::getAllGatewayAddresses return empty'); + } + } else { + $addresses = static::getAllGatewayAddressesFromRegister(); + if (empty($addresses)) { + return array(); + } + } + return $addresses; + } + + /** + * 批量向gateway发送并获取数据 + * @param $gateway_buffer_array + * @return array + */ + protected static function getBufferFromGateway($gateway_buffer_array) + { + $client_array = $status_data = $client_address_map = $receive_buffer_array = $recv_length_array = array(); + // 批量向所有gateway进程发送请求数据 + foreach ($gateway_buffer_array as $address => $gateway_buffer) { + $client = stream_socket_client("tcp://$address", $errno, $errmsg, static::$connectTimeout); + if ($client && strlen($gateway_buffer) === stream_socket_sendto($client, $gateway_buffer)) { + $socket_id = (int)$client; + $client_array[$socket_id] = $client; + $client_address_map[$socket_id] = explode(':', $address); + $receive_buffer_array[$socket_id] = ''; + } + } + // 超时5秒 + $timeout = 5; + $time_start = microtime(true); + // 批量接收请求 + while (count($client_array) > 0) { + $write = $except = array(); + $read = $client_array; + if (@stream_select($read, $write, $except, $timeout)) { + foreach ($read as $client) { + $socket_id = (int)$client; + $buffer = stream_socket_recvfrom($client, 65535); + if ($buffer !== '' && $buffer !== false) { + $receive_buffer_array[$socket_id] .= $buffer; + $receive_length = strlen($receive_buffer_array[$socket_id]); + if (empty($recv_length_array[$socket_id]) && $receive_length >= 4) { + $recv_length_array[$socket_id] = current(unpack('N', $receive_buffer_array[$socket_id])); + } + if (!empty($recv_length_array[$socket_id]) && $receive_length >= $recv_length_array[$socket_id] + 4) { + unset($client_array[$socket_id]); + } + } elseif (feof($client)) { + unset($client_array[$socket_id]); + } + } + } + if (microtime(true) - $time_start > $timeout) { + break; + } + } + $format_buffer_array = array(); + foreach ($receive_buffer_array as $socket_id => $buffer) { + $local_ip = ip2long($client_address_map[$socket_id][0]); + $local_port = $client_address_map[$socket_id][1]; + $format_buffer_array[$local_ip][$local_port] = unserialize(substr($buffer, 4)); + } + return $format_buffer_array; + } + + /** + * 踢掉某个客户端,并以$message通知被踢掉客户端 + * + * @param int $client_id + * @param string $message + * @return void + */ + public static function closeClient($client_id, $message = null) + { + if ($client_id === Context::$client_id) { + return static::closeCurrentClient($message); + } // 不是发给当前用户则使用存储中的地址 + else { + $address_data = Context::clientIdToAddress($client_id); + if (!$address_data) { + return false; + } + $address = long2ip($address_data['local_ip']) . ":{$address_data['local_port']}"; + return static::kickAddress($address, $address_data['connection_id'], $message); + } + } + + /** + * 踢掉当前客户端,并以$message通知被踢掉客户端 + * + * @param string $message + * @return bool + * @throws Exception + */ + public static function closeCurrentClient($message = null) + { + if (!Context::$connection_id) { + throw new Exception('closeCurrentClient can not be called in async context'); + } + $address = long2ip(Context::$local_ip) . ':' . Context::$local_port; + return static::kickAddress($address, Context::$connection_id, $message); + } + + /** + * 踢掉某个客户端并直接立即销毁相关连接 + * + * @param int $client_id + * @return bool + */ + public static function destoryClient($client_id) + { + if ($client_id === Context::$client_id) { + return static::destoryCurrentClient(); + } // 不是发给当前用户则使用存储中的地址 + else { + $address_data = Context::clientIdToAddress($client_id); + if (!$address_data) { + return false; + } + $address = long2ip($address_data['local_ip']) . ":{$address_data['local_port']}"; + return static::destroyAddress($address, $address_data['connection_id']); + } + } + + /** + * 踢掉当前客户端并直接立即销毁相关连接 + * + * @return bool + * @throws Exception + */ + public static function destoryCurrentClient() + { + if (!Context::$connection_id) { + throw new Exception('destoryCurrentClient can not be called in async context'); + } + $address = long2ip(Context::$local_ip) . ':' . Context::$local_port; + return static::destroyAddress($address, Context::$connection_id); + } + + /** + * 将 client_id 与 uid 绑定 + * + * @param int $client_id + * @param int|string $uid + * @return void + */ + public static function bindUid($client_id, $uid) + { + static::sendCmdAndMessageToClient($client_id, GatewayProtocol::CMD_BIND_UID, '', $uid); + } + + /** + * 将 client_id 与 uid 解除绑定 + * + * @param int $client_id + * @param int|string $uid + * @return void + */ + public static function unbindUid($client_id, $uid) + { + static::sendCmdAndMessageToClient($client_id, GatewayProtocol::CMD_UNBIND_UID, '', $uid); + } + + /** + * 将 client_id 加入组 + * + * @param int $client_id + * @param int|string $group + * @return void + */ + public static function joinGroup($client_id, $group) + { + + static::sendCmdAndMessageToClient($client_id, GatewayProtocol::CMD_JOIN_GROUP, '', $group); + } + + /** + * 将 client_id 离开组 + * + * @param int $client_id + * @param int|string $group + * + * @return void + */ + public static function leaveGroup($client_id, $group) + { + static::sendCmdAndMessageToClient($client_id, GatewayProtocol::CMD_LEAVE_GROUP, '', $group); + } + + /** + * 取消分组 + * + * @param int|string $group + * + * @return void + */ + public static function ungroup($group) + { + if (!static::isValidGroupId($group)) { + return false; + } + $gateway_data = GatewayProtocol::$empty; + $gateway_data['cmd'] = GatewayProtocol::CMD_UNGROUP; + $gateway_data['ext_data'] = $group; + return static::sendToAllGateway($gateway_data); + + } + + /** + * 向所有 uid 发送 + * + * @param int|string|array $uid + * @param string $message + * + * @return void + */ + public static function sendToUid($uid, $message) + { + $gateway_data = GatewayProtocol::$empty; + $gateway_data['cmd'] = GatewayProtocol::CMD_SEND_TO_UID; + $gateway_data['body'] = $message; + + if (!is_array($uid)) { + $uid = array($uid); + } + + $gateway_data['ext_data'] = json_encode($uid); + + static::sendToAllGateway($gateway_data); + } + + /** + * 向 group 发送 + * + * @param int|string|array $group 组(不允许是 0 '0' false null array()等为空的值) + * @param string $message 消息 + * @param array $exclude_client_id 不给这些client_id发 + * @param bool $raw 发送原始数据(即不调用gateway的协议的encode方法) + * + * @return void + */ + public static function sendToGroup($group, $message, $exclude_client_id = null, $raw = false) + { + if (!static::isValidGroupId($group)) { + return false; + } + $gateway_data = GatewayProtocol::$empty; + $gateway_data['cmd'] = GatewayProtocol::CMD_SEND_TO_GROUP; + $gateway_data['body'] = $message; + if ($raw) { + $gateway_data['flag'] |= GatewayProtocol::FLAG_NOT_CALL_ENCODE; + } + + if (!is_array($group)) { + $group = array($group); + } + + // 分组发送,没有排除的client_id,直接发送 + $default_ext_data_buffer = json_encode(array('group'=> $group, 'exclude'=> null)); + if (empty($exclude_client_id)) { + $gateway_data['ext_data'] = $default_ext_data_buffer; + return static::sendToAllGateway($gateway_data); + } + + // 分组发送,有排除的client_id,需要将client_id转换成对应gateway进程内的connectionId + if (!is_array($exclude_client_id)) { + $exclude_client_id = array($exclude_client_id); + } + + $address_connection_array = static::clientIdArrayToAddressArray($exclude_client_id); + // 如果有businessWorker实例,说明运行在workerman环境中,通过businessWorker中的长连接发送数据 + if (static::$businessWorker) { + foreach (static::$businessWorker->gatewayConnections as $address => $gateway_connection) { + $gateway_data['ext_data'] = isset($address_connection_array[$address]) ? + json_encode(array('group'=> $group, 'exclude'=> $address_connection_array[$address])) : + $default_ext_data_buffer; + /** @var TcpConnection $gateway_connection */ + $gateway_connection->send($gateway_data); + } + } // 运行在其它环境中,通过注册中心得到gateway地址 + else { + $addresses = static::getAllGatewayAddressesFromRegister(); + foreach ($addresses as $address) { + $gateway_data['ext_data'] = isset($address_connection_array[$address]) ? + json_encode(array('group'=> $group, 'exclude'=> $address_connection_array[$address])) : + $default_ext_data_buffer; + static::sendToGateway($address, $gateway_data); + } + } + } + + /** + * 更新 session,框架自动调用,开发者不要调用 + * + * @param int $client_id + * @param string $session_str + * @return bool + */ + public static function setSocketSession($client_id, $session_str) + { + return static::sendCmdAndMessageToClient($client_id, GatewayProtocol::CMD_SET_SESSION, '', $session_str); + } + + /** + * 设置 session,原session值会被覆盖 + * + * @param int $client_id + * @param array $session + * + * @return void + */ + public static function setSession($client_id, array $session) + { + if (Context::$client_id === $client_id) { + $_SESSION = $session; + Context::$old_session = $_SESSION; + } + static::setSocketSession($client_id, Context::sessionEncode($session)); + } + + /** + * 更新 session,实际上是与老的session合并 + * + * @param int $client_id + * @param array $session + * + * @return void + */ + public static function updateSession($client_id, array $session) + { + if (Context::$client_id === $client_id) { + $_SESSION = array_replace_recursive((array)$_SESSION, $session); + Context::$old_session = $_SESSION; + } + static::sendCmdAndMessageToClient($client_id, GatewayProtocol::CMD_UPDATE_SESSION, '', Context::sessionEncode($session)); + } + + /** + * 获取某个client_id的session + * + * @param int $client_id + * @return mixed false表示出错、null表示用户不存在、array表示具体的session信息 + */ + public static function getSession($client_id) + { + $address_data = Context::clientIdToAddress($client_id); + if (!$address_data) { + return false; + } + $address = long2ip($address_data['local_ip']) . ":{$address_data['local_port']}"; + if (isset(static::$businessWorker)) { + if (!isset(static::$businessWorker->gatewayConnections[$address])) { + return null; + } + } + $gateway_data = GatewayProtocol::$empty; + $gateway_data['cmd'] = GatewayProtocol::CMD_GET_SESSION_BY_CLIENT_ID; + $gateway_data['connection_id'] = $address_data['connection_id']; + return static::sendAndRecv($address, $gateway_data); + } + + /** + * 向某个用户网关发送命令和消息 + * + * @param int $client_id + * @param int $cmd + * @param string $message + * @param string $ext_data + * @return boolean + */ + protected static function sendCmdAndMessageToClient($client_id, $cmd, $message, $ext_data = '') + { + // 如果是发给当前用户则直接获取上下文中的地址 + if ($client_id === Context::$client_id || $client_id === null) { + $address = long2ip(Context::$local_ip) . ':' . Context::$local_port; + $connection_id = Context::$connection_id; + } else { + $address_data = Context::clientIdToAddress($client_id); + if (!$address_data) { + return false; + } + $address = long2ip($address_data['local_ip']) . ":{$address_data['local_port']}"; + $connection_id = $address_data['connection_id']; + } + $gateway_data = GatewayProtocol::$empty; + $gateway_data['cmd'] = $cmd; + $gateway_data['connection_id'] = $connection_id; + $gateway_data['body'] = $message; + if (!empty($ext_data)) { + $gateway_data['ext_data'] = $ext_data; + } + + return static::sendToGateway($address, $gateway_data); + } + + /** + * 发送数据并返回 + * + * @param int $address + * @param mixed $data + * @return bool + * @throws Exception + */ + protected static function sendAndRecv($address, $data) + { + $buffer = GatewayProtocol::encode($data); + $buffer = static::$secretKey ? static::generateAuthBuffer() . $buffer : $buffer; + $client = stream_socket_client("tcp://$address", $errno, $errmsg, static::$connectTimeout); + if (!$client) { + throw new Exception("can not connect to tcp://$address $errmsg"); + } + if (strlen($buffer) === stream_socket_sendto($client, $buffer)) { + $timeout = 5; + // 阻塞读 + stream_set_blocking($client, 1); + // 1秒超时 + stream_set_timeout($client, 1); + $all_buffer = ''; + $time_start = microtime(true); + $pack_len = 0; + while (1) { + $buf = stream_socket_recvfrom($client, 655350); + if ($buf !== '' && $buf !== false) { + $all_buffer .= $buf; + } else { + if (feof($client)) { + throw new Exception("connection close tcp://$address"); + } elseif (microtime(true) - $time_start > $timeout) { + break; + } + continue; + } + $recv_len = strlen($all_buffer); + if (!$pack_len && $recv_len >= 4) { + $pack_len= current(unpack('N', $all_buffer)); + } + // 回复的数据都是以\n结尾 + if (($pack_len && $recv_len >= $pack_len + 4) || microtime(true) - $time_start > $timeout) { + break; + } + } + // 返回结果 + return unserialize(substr($all_buffer, 4)); + } else { + throw new Exception("sendAndRecv($address, \$bufer) fail ! Can not send data!", 502); + } + } + + /** + * 发送数据到网关 + * + * @param string $address + * @param array $gateway_data + * @return bool + */ + protected static function sendToGateway($address, $gateway_data) + { + return static::sendBufferToGateway($address, GatewayProtocol::encode($gateway_data)); + } + + /** + * 发送buffer数据到网关 + * @param string $address + * @param string $gateway_buffer + * @return bool + */ + protected static function sendBufferToGateway($address, $gateway_buffer) + { + // 有$businessWorker说明是workerman环境,使用$businessWorker发送数据 + if (static::$businessWorker) { + if (!isset(static::$businessWorker->gatewayConnections[$address])) { + return false; + } + return static::$businessWorker->gatewayConnections[$address]->send($gateway_buffer, true); + } + // 非workerman环境 + $gateway_buffer = static::$secretKey ? static::generateAuthBuffer() . $gateway_buffer : $gateway_buffer; + $flag = static::$persistentConnection ? STREAM_CLIENT_PERSISTENT | STREAM_CLIENT_CONNECT : STREAM_CLIENT_CONNECT; + $client = stream_socket_client("tcp://$address", $errno, $errmsg, static::$connectTimeout, $flag); + return strlen($gateway_buffer) == stream_socket_sendto($client, $gateway_buffer); + } + + /** + * 向所有 gateway 发送数据 + * + * @param string $gateway_data + * @throws Exception + * + * @return void + */ + protected static function sendToAllGateway($gateway_data) + { + $buffer = GatewayProtocol::encode($gateway_data); + // 如果有businessWorker实例,说明运行在workerman环境中,通过businessWorker中的长连接发送数据 + if (static::$businessWorker) { + foreach (static::$businessWorker->gatewayConnections as $gateway_connection) { + /** @var TcpConnection $gateway_connection */ + $gateway_connection->send($buffer, true); + } + } // 运行在其它环境中,通过注册中心得到gateway地址 + else { + $all_addresses = static::getAllGatewayAddressesFromRegister(); + foreach ($all_addresses as $address) { + static::sendBufferToGateway($address, $buffer); + } + } + } + + /** + * 踢掉某个网关的 socket + * + * @param string $address + * @param int $connection_id + * @return bool + */ + protected static function kickAddress($address, $connection_id, $message) + { + $gateway_data = GatewayProtocol::$empty; + $gateway_data['cmd'] = GatewayProtocol::CMD_KICK; + $gateway_data['connection_id'] = $connection_id; + $gateway_data['body'] = $message; + return static::sendToGateway($address, $gateway_data); + } + + /** + * 销毁某个网关的 socket + * + * @param string $address + * @param int $connection_id + * @return bool + */ + protected static function destroyAddress($address, $connection_id) + { + $gateway_data = GatewayProtocol::$empty; + $gateway_data['cmd'] = GatewayProtocol::CMD_DESTROY; + $gateway_data['connection_id'] = $connection_id; + return static::sendToGateway($address, $gateway_data); + } + + /** + * 将clientid数组转换成address数组 + * + * @param array $client_id_array + * @return array + */ + protected static function clientIdArrayToAddressArray(array $client_id_array) + { + $address_connection_array = array(); + foreach ($client_id_array as $client_id) { + $address_data = Context::clientIdToAddress($client_id); + if ($address_data) { + $address = long2ip($address_data['local_ip']) . + ":{$address_data['local_port']}"; + $address_connection_array[$address][$address_data['connection_id']] = $address_data['connection_id']; + } + } + return $address_connection_array; + } + + /** + * 设置 gateway 实例 + * + * @param \GatewayWorker\BusinessWorker $business_worker_instance + */ + public static function setBusinessWorker($business_worker_instance) + { + static::$businessWorker = $business_worker_instance; + } + + /** + * 获取通过注册中心获取所有 gateway 通讯地址 + * + * @return array + * @throws Exception + */ + protected static function getAllGatewayAddressesFromRegister() + { + static $addresses_cache, $last_update; + $time_now = time(); + $expiration_time = 1; + $register_addresses = (array)static::$registerAddress; + if(empty($addresses_cache) || $time_now - $last_update > $expiration_time) { + foreach ($register_addresses as $register_address) { + $client = stream_socket_client('tcp://' . $register_address, $errno, $errmsg, static::$connectTimeout); + if (!$client) { + continue; + } + } + if (!$client) { + throw new Exception('Can not connect to tcp://' . $register_address . ' ' . $errmsg); + } + + fwrite($client, '{"event":"worker_connect","secret_key":"' . static::$secretKey . '"}' . "\n"); + stream_set_timeout($client, 5); + $ret = fgets($client, 655350); + if (!$ret || !$data = json_decode(trim($ret), true)) { + throw new Exception('getAllGatewayAddressesFromRegister fail. tcp://' . + $register_address . ' return ' . var_export($ret, true)); + } + $last_update = $time_now; + $addresses_cache = $data['addresses']; + } + if (!$addresses_cache) { + throw new Exception('Gateway::getAllGatewayAddressesFromRegister() with registerAddress:' . + json_encode(static::$registerAddress) . ' return ' . var_export($addresses_cache, true)); + } + return $addresses_cache; + } + + /** + * 检查群组id是否合法 + * + * @param $group + * @return bool + */ + protected static function isValidGroupId($group) + { + if (empty($group)) { + echo new \Exception('group('.var_export($group, true).') empty'); + return false; + } + return true; + } +} + +if (!class_exists('\Protocols\GatewayProtocol')) { + class_alias('GatewayWorker\Protocols\GatewayProtocol', 'Protocols\GatewayProtocol'); +} diff --git a/GatewayWorker/vendor/workerman/gateway-worker/src/Protocols/GatewayProtocol.php b/GatewayWorker/vendor/workerman/gateway-worker/src/Protocols/GatewayProtocol.php new file mode 100644 index 0000000..9eec3be --- /dev/null +++ b/GatewayWorker/vendor/workerman/gateway-worker/src/Protocols/GatewayProtocol.php @@ -0,0 +1,216 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace GatewayWorker\Protocols; + +/** + * Gateway 与 Worker 间通讯的二进制协议 + * + * struct GatewayProtocol + * { + * unsigned int pack_len, + * unsigned char cmd,//命令字 + * unsigned int local_ip, + * unsigned short local_port, + * unsigned int client_ip, + * unsigned short client_port, + * unsigned int connection_id, + * unsigned char flag, + * unsigned short gateway_port, + * unsigned int ext_len, + * char[ext_len] ext_data, + * char[pack_length-HEAD_LEN] body//包体 + * } + * NCNnNnNCnN + */ +class GatewayProtocol +{ + // 发给worker,gateway有一个新的连接 + const CMD_ON_CONNECT = 1; + + // 发给worker的,客户端有消息 + const CMD_ON_MESSAGE = 3; + + // 发给worker上的关闭链接事件 + const CMD_ON_CLOSE = 4; + + // 发给gateway的向单个用户发送数据 + const CMD_SEND_TO_ONE = 5; + + // 发给gateway的向所有用户发送数据 + const CMD_SEND_TO_ALL = 6; + + // 发给gateway的踢出用户 + // 1、如果有待发消息,将在发送完后立即销毁用户连接 + // 2、如果无待发消息,将立即销毁用户连接 + const CMD_KICK = 7; + + // 发给gateway的立即销毁用户连接 + const CMD_DESTROY = 8; + + // 发给gateway,通知用户session更新 + const CMD_UPDATE_SESSION = 9; + + // 获取在线状态 + const CMD_GET_ALL_CLIENT_SESSIONS = 10; + + // 判断是否在线 + const CMD_IS_ONLINE = 11; + + // client_id绑定到uid + const CMD_BIND_UID = 12; + + // 解绑 + const CMD_UNBIND_UID = 13; + + // 向uid发送数据 + const CMD_SEND_TO_UID = 14; + + // 根据uid获取绑定的clientid + const CMD_GET_CLIENT_ID_BY_UID = 15; + + // 加入组 + const CMD_JOIN_GROUP = 20; + + // 离开组 + const CMD_LEAVE_GROUP = 21; + + // 向组成员发消息 + const CMD_SEND_TO_GROUP = 22; + + // 获取组成员 + const CMD_GET_CLIENT_SESSIONS_BY_GROUP = 23; + + // 获取组在线连接数 + const CMD_GET_CLIENT_COUNT_BY_GROUP = 24; + + // 按照条件查找 + const CMD_SELECT = 25; + + // 获取在线的群组ID + const CMD_GET_GROUP_ID_LIST = 26; + + // 取消分组 + const CMD_UNGROUP = 27; + + // worker连接gateway事件 + const CMD_WORKER_CONNECT = 200; + + // 心跳 + const CMD_PING = 201; + + // GatewayClient连接gateway事件 + const CMD_GATEWAY_CLIENT_CONNECT = 202; + + // 根据client_id获取session + const CMD_GET_SESSION_BY_CLIENT_ID = 203; + + // 发给gateway,覆盖session + const CMD_SET_SESSION = 204; + + // 当websocket握手时触发,只有websocket协议支持此命令字 + const CMD_ON_WEBSOCKET_CONNECT = 205; + + // 包体是标量 + const FLAG_BODY_IS_SCALAR = 0x01; + + // 通知gateway在send时不调用协议encode方法,在广播组播时提升性能 + const FLAG_NOT_CALL_ENCODE = 0x02; + + /** + * 包头长度 + * + * @var int + */ + const HEAD_LEN = 28; + + public static $empty = array( + 'cmd' => 0, + 'local_ip' => 0, + 'local_port' => 0, + 'client_ip' => 0, + 'client_port' => 0, + 'connection_id' => 0, + 'flag' => 0, + 'gateway_port' => 0, + 'ext_data' => '', + 'body' => '', + ); + + /** + * 返回包长度 + * + * @param string $buffer + * @return int return current package length + */ + public static function input($buffer) + { + if (strlen($buffer) < self::HEAD_LEN) { + return 0; + } + + $data = unpack("Npack_len", $buffer); + return $data['pack_len']; + } + + /** + * 获取整个包的 buffer + * + * @param mixed $data + * @return string + */ + public static function encode($data) + { + $flag = (int)is_scalar($data['body']); + if (!$flag) { + $data['body'] = serialize($data['body']); + } + $data['flag'] |= $flag; + $ext_len = strlen($data['ext_data']); + $package_len = self::HEAD_LEN + $ext_len + strlen($data['body']); + return pack("NCNnNnNCnN", $package_len, + $data['cmd'], $data['local_ip'], + $data['local_port'], $data['client_ip'], + $data['client_port'], $data['connection_id'], + $data['flag'], $data['gateway_port'], + $ext_len) . $data['ext_data'] . $data['body']; + } + + /** + * 从二进制数据转换为数组 + * + * @param string $buffer + * @return array + */ + public static function decode($buffer) + { + $data = unpack("Npack_len/Ccmd/Nlocal_ip/nlocal_port/Nclient_ip/nclient_port/Nconnection_id/Cflag/ngateway_port/Next_len", + $buffer); + if ($data['ext_len'] > 0) { + $data['ext_data'] = substr($buffer, self::HEAD_LEN, $data['ext_len']); + if ($data['flag'] & self::FLAG_BODY_IS_SCALAR) { + $data['body'] = substr($buffer, self::HEAD_LEN + $data['ext_len']); + } else { + $data['body'] = unserialize(substr($buffer, self::HEAD_LEN + $data['ext_len'])); + } + } else { + $data['ext_data'] = ''; + if ($data['flag'] & self::FLAG_BODY_IS_SCALAR) { + $data['body'] = substr($buffer, self::HEAD_LEN); + } else { + $data['body'] = unserialize(substr($buffer, self::HEAD_LEN)); + } + } + return $data; + } +} diff --git a/GatewayWorker/vendor/workerman/gateway-worker/src/Register.php b/GatewayWorker/vendor/workerman/gateway-worker/src/Register.php new file mode 100644 index 0000000..aa045e0 --- /dev/null +++ b/GatewayWorker/vendor/workerman/gateway-worker/src/Register.php @@ -0,0 +1,190 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace GatewayWorker; + +use Workerman\Worker; +use Workerman\Lib\Timer; + +/** + * + * 注册中心,用于注册 Gateway 和 BusinessWorker + * + * @author walkor + * + */ +class Register extends Worker +{ + /** + * {@inheritdoc} + */ + public $name = 'Register'; + + /** + * {@inheritdoc} + */ + public $reloadable = false; + + /** + * 秘钥 + * @var string + */ + public $secretKey = ''; + + /** + * 所有 gateway 的连接 + * + * @var array + */ + protected $_gatewayConnections = array(); + + /** + * 所有 worker 的连接 + * + * @var array + */ + protected $_workerConnections = array(); + + /** + * 进程启动时间 + * + * @var int + */ + protected $_startTime = 0; + + /** + * {@inheritdoc} + */ + public function run() + { + // 设置 onMessage 连接回调 + $this->onConnect = array($this, 'onConnect'); + + // 设置 onMessage 回调 + $this->onMessage = array($this, 'onMessage'); + + // 设置 onClose 回调 + $this->onClose = array($this, 'onClose'); + + // 记录进程启动的时间 + $this->_startTime = time(); + + // 强制使用text协议 + $this->protocol = '\Workerman\Protocols\Text'; + + // 运行父方法 + parent::run(); + } + + /** + * 设置个定时器,将未及时发送验证的连接关闭 + * + * @param \Workerman\Connection\ConnectionInterface $connection + * @return void + */ + public function onConnect($connection) + { + $connection->timeout_timerid = Timer::add(10, function () use ($connection) { + Worker::log("Register auth timeout (".$connection->getRemoteIp()."). See http://doc2.workerman.net/register-auth-timeout.html"); + $connection->close(); + }, null, false); + } + + /** + * 设置消息回调 + * + * @param \Workerman\Connection\ConnectionInterface $connection + * @param string $buffer + * @return void + */ + public function onMessage($connection, $buffer) + { + // 删除定时器 + Timer::del($connection->timeout_timerid); + $data = @json_decode($buffer, true); + if (empty($data['event'])) { + $error = "Bad request for Register service. Request info(IP:".$connection->getRemoteIp().", Request Buffer:$buffer). See http://doc2.workerman.net/register-auth-timeout.html"; + Worker::log($error); + return $connection->close($error); + } + $event = $data['event']; + $secret_key = isset($data['secret_key']) ? $data['secret_key'] : ''; + // 开始验证 + switch ($event) { + // 是 gateway 连接 + case 'gateway_connect': + if (empty($data['address'])) { + echo "address not found\n"; + return $connection->close(); + } + if ($secret_key !== $this->secretKey) { + Worker::log("Register: Key does not match ".var_export($secret_key, true)." !== ".var_export($this->secretKey, true)); + return $connection->close(); + } + $this->_gatewayConnections[$connection->id] = $data['address']; + $this->broadcastAddresses(); + break; + // 是 worker 连接 + case 'worker_connect': + if ($secret_key !== $this->secretKey) { + Worker::log("Register: Key does not match ".var_export($secret_key, true)." !== ".var_export($this->secretKey, true)); + return $connection->close(); + } + $this->_workerConnections[$connection->id] = $connection; + $this->broadcastAddresses($connection); + break; + case 'ping': + break; + default: + Worker::log("Register unknown event:$event IP: ".$connection->getRemoteIp()." Buffer:$buffer. See http://doc2.workerman.net/register-auth-timeout.html"); + $connection->close(); + } + } + + /** + * 连接关闭时 + * + * @param \Workerman\Connection\ConnectionInterface $connection + */ + public function onClose($connection) + { + if (isset($this->_gatewayConnections[$connection->id])) { + unset($this->_gatewayConnections[$connection->id]); + $this->broadcastAddresses(); + } + if (isset($this->_workerConnections[$connection->id])) { + unset($this->_workerConnections[$connection->id]); + } + } + + /** + * 向 BusinessWorker 广播 gateway 内部通讯地址 + * + * @param \Workerman\Connection\ConnectionInterface $connection + */ + public function broadcastAddresses($connection = null) + { + $data = array( + 'event' => 'broadcast_addresses', + 'addresses' => array_unique(array_values($this->_gatewayConnections)), + ); + $buffer = json_encode($data); + if ($connection) { + $connection->send($buffer); + return; + } + foreach ($this->_workerConnections as $con) { + $con->send($buffer); + } + } +} diff --git a/GatewayWorker/vendor/workerman/workerman.log b/GatewayWorker/vendor/workerman/workerman.log new file mode 100644 index 0000000..3aad6fd --- /dev/null +++ b/GatewayWorker/vendor/workerman/workerman.log @@ -0,0 +1,663 @@ +2020-02-09 12:43:24 pid:26953 Workerman[start.php] start in DEBUG mode +2020-02-09 12:43:24 pid:26953 worker[YourAppGateway:26959] exit with status 65280 +2020-02-09 12:43:24 pid:26953 worker[YourAppGateway:26961] exit with status 65280 +2020-02-09 12:43:24 pid:26953 worker[YourAppGateway:26958] exit with status 65280 +2020-02-09 12:43:24 pid:26953 worker[YourAppGateway:26960] exit with status 65280 +2020-02-09 12:43:24 pid:26953 worker[YourAppGateway:26963] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[Register:26962] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:26964] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:26965] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:26967] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:26966] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[Register:26968] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:26969] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:26971] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:26972] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:26970] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[Register:26973] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:26974] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:26975] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:26977] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[Register:26978] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:26976] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:26979] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[Register:26982] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:26980] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:26983] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:26981] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:26986] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:26984] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[Register:26985] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:26987] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:26988] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:26989] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:26990] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:26993] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[Register:26991] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:26992] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[Register:26997] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:26994] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:26995] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:26996] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:26998] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[Register:26999] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[Register:27004] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27000] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27003] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27001] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27002] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[Register:27005] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[Register:27010] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27006] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27007] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27008] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[Register:27011] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27009] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27012] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27017] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27013] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27014] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27018] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[Register:27015] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27016] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27019] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27020] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27021] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[Register:27022] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[Register:27027] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27026] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27023] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27024] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27025] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27029] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27033] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[Register:27028] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27030] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27032] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27031] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27036] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27034] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[Register:27035] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27039] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27042] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[Register:27041] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27038] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27037] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27040] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27043] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27048] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27049] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[Register:27044] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27045] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27046] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27047] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27050] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[Register:27051] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27053] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27052] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27055] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27057] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27054] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[Register:27056] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27058] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27059] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27061] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27060] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[Register:27062] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27065] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27064] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27063] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27066] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[Register:27067] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27068] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27070] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27069] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[Register:27072] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27074] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27071] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27073] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27075] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[Register:27076] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27077] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27082] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27078] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27079] exit with status 65280 +2020-02-09 12:43:25 pid:26953 worker[YourAppGateway:27083] exit with status 65280 +2020-02-09 12:43:26 pid:26953 worker[YourAppGateway:27080] exit with status 65280 +2020-02-09 12:43:26 pid:26953 worker[Register:27081] exit with status 65280 +2020-02-09 12:43:26 pid:26953 worker[YourAppGateway:27084] exit with status 65280 +2020-02-09 12:43:26 pid:26953 worker[YourAppGateway:27085] exit with status 65280 +2020-02-09 12:43:26 pid:26953 worker[YourAppGateway:27086] exit with status 65280 +2020-02-09 12:43:26 pid:26953 worker[YourAppGateway:27087] exit with status 65280 +2020-02-09 12:43:26 pid:26953 worker[Register:27088] exit with status 65280 +2020-02-09 12:43:26 pid:26953 worker[YourAppGateway:27089] exit with status 65280 +2020-02-09 12:43:26 pid:26953 worker[YourAppGateway:27090] exit with status 65280 +2020-02-09 12:43:26 pid:26953 worker[YourAppGateway:27091] exit with status 65280 +2020-02-09 12:43:26 pid:26953 worker[Register:27093] exit with status 65280 +2020-02-09 12:43:26 pid:26953 worker[YourAppGateway:27092] exit with status 65280 +2020-02-09 12:43:26 pid:26953 worker[YourAppGateway:27095] exit with status 65280 +2020-02-09 12:43:26 pid:26953 worker[YourAppGateway:27094] exit with status 65280 +2020-02-09 12:43:26 pid:26953 Workerman[start.php] stopping ... +2020-02-09 12:43:26 pid:26953 worker[YourAppGateway:27096] exit with status 65280 +2020-02-09 12:43:26 pid:26953 worker[Register:27097] exit with status 65280 +2020-02-09 12:43:26 pid:26953 worker[YourAppGateway:27098] exit with status 65280 +2020-02-09 12:43:26 pid:26953 worker[YourAppGateway:27099] exit with status 65280 +2020-02-09 12:43:26 pid:26953 worker[YourAppGateway:27100] exit with status 65280 +2020-02-09 12:43:26 pid:26953 Workerman[start.php] has been stopped +2020-02-09 12:44:55 pid:27236 Workerman[start.php] start in DEBUG mode +2020-02-09 12:44:55 pid:27236 worker[YourAppGateway:27241] exit with status 65280 +2020-02-09 12:44:55 pid:27236 worker[YourAppGateway:27243] exit with status 65280 +2020-02-09 12:44:55 pid:27236 worker[YourAppGateway:27242] exit with status 65280 +2020-02-09 12:44:55 pid:27236 worker[YourAppGateway:27244] exit with status 65280 +2020-02-09 12:44:55 pid:27236 worker[Register:27245] exit with status 65280 +2020-02-09 12:44:55 pid:27236 worker[YourAppGateway:27248] exit with status 65280 +2020-02-09 12:44:55 pid:27236 worker[YourAppGateway:27246] exit with status 65280 +2020-02-09 12:44:55 pid:27236 worker[YourAppGateway:27247] exit with status 65280 +2020-02-09 12:44:55 pid:27236 worker[YourAppGateway:27249] exit with status 65280 +2020-02-09 12:44:55 pid:27236 worker[YourAppGateway:27251] exit with status 65280 +2020-02-09 12:44:55 pid:27236 worker[Register:27250] exit with status 65280 +2020-02-09 12:44:55 pid:27236 worker[YourAppGateway:27252] exit with status 65280 +2020-02-09 12:44:55 pid:27236 worker[YourAppGateway:27253] exit with status 65280 +2020-02-09 12:44:55 pid:27236 worker[Register:27256] exit with status 65280 +2020-02-09 12:44:55 pid:27236 worker[YourAppGateway:27254] exit with status 65280 +2020-02-09 12:44:55 pid:27236 worker[YourAppGateway:27257] exit with status 65280 +2020-02-09 12:44:55 pid:27236 worker[YourAppGateway:27255] exit with status 65280 +2020-02-09 12:44:55 pid:27236 worker[Register:27259] exit with status 65280 +2020-02-09 12:44:55 pid:27236 worker[YourAppGateway:27258] exit with status 65280 +2020-02-09 12:44:55 pid:27236 worker[YourAppGateway:27260] exit with status 65280 +2020-02-09 12:44:55 pid:27236 worker[YourAppGateway:27261] exit with status 65280 +2020-02-09 12:44:55 pid:27236 worker[YourAppGateway:27262] exit with status 65280 +2020-02-09 12:44:55 pid:27236 worker[Register:27263] exit with status 65280 +2020-02-09 12:44:55 pid:27236 worker[YourAppGateway:27264] exit with status 65280 +2020-02-09 12:44:55 pid:27236 worker[YourAppGateway:27265] exit with status 65280 +2020-02-09 12:44:55 pid:27236 worker[YourAppGateway:27267] exit with status 65280 +2020-02-09 12:44:55 pid:27236 worker[YourAppGateway:27266] exit with status 65280 +2020-02-09 12:44:55 pid:27236 worker[Register:27268] exit with status 65280 +2020-02-09 12:44:55 pid:27236 worker[YourAppGateway:27269] exit with status 65280 +2020-02-09 12:44:55 pid:27236 worker[YourAppGateway:27270] exit with status 65280 +2020-02-09 12:44:55 pid:27236 worker[YourAppGateway:27271] exit with status 65280 +2020-02-09 12:44:55 pid:27236 worker[Register:27273] exit with status 65280 +2020-02-09 12:44:55 pid:27236 worker[YourAppGateway:27272] exit with status 65280 +2020-02-09 12:44:55 pid:27236 worker[YourAppGateway:27274] exit with status 65280 +2020-02-09 12:44:55 pid:27236 worker[YourAppGateway:27275] exit with status 65280 +2020-02-09 12:44:55 pid:27236 worker[Register:27277] exit with status 65280 +2020-02-09 12:44:55 pid:27236 worker[YourAppGateway:27276] exit with status 65280 +2020-02-09 12:44:55 pid:27236 worker[YourAppGateway:27279] exit with status 65280 +2020-02-09 12:44:55 pid:27236 worker[YourAppGateway:27280] exit with status 65280 +2020-02-09 12:44:55 pid:27236 worker[YourAppGateway:27282] exit with status 65280 +2020-02-09 12:44:55 pid:27236 worker[YourAppGateway:27278] exit with status 65280 +2020-02-09 12:44:55 pid:27236 worker[Register:27281] exit with status 65280 +2020-02-09 12:44:55 pid:27236 worker[YourAppGateway:27283] exit with status 65280 +2020-02-09 12:44:55 pid:27236 worker[YourAppGateway:27285] exit with status 65280 +2020-02-09 12:44:55 pid:27236 worker[YourAppGateway:27284] exit with status 65280 +2020-02-09 12:44:55 pid:27236 worker[YourAppGateway:27286] exit with status 65280 +2020-02-09 12:44:55 pid:27236 worker[Register:27287] exit with status 65280 +2020-02-09 12:44:55 pid:27236 worker[YourAppGateway:27288] exit with status 65280 +2020-02-09 12:44:55 pid:27236 worker[YourAppGateway:27289] exit with status 65280 +2020-02-09 12:44:55 pid:27236 worker[YourAppGateway:27290] exit with status 65280 +2020-02-09 12:44:55 pid:27236 worker[YourAppGateway:27291] exit with status 65280 +2020-02-09 12:44:55 pid:27236 worker[Register:27292] exit with status 65280 +2020-02-09 12:44:55 pid:27236 worker[YourAppGateway:27293] exit with status 65280 +2020-02-09 12:44:55 pid:27236 worker[YourAppGateway:27295] exit with status 65280 +2020-02-09 12:44:55 pid:27236 Workerman[start.php] stopping ... +2020-02-09 12:44:55 pid:27236 worker[YourAppGateway:27296] exit with status 65280 +2020-02-09 12:44:55 pid:27236 worker[YourAppGateway:27294] exit with status 65280 +2020-02-09 12:44:55 pid:27236 worker[Register:27297] exit with status 65280 +2020-02-09 12:44:55 pid:27236 worker[YourAppGateway:27298] exit with status 65280 +2020-02-09 12:44:55 pid:27236 worker[YourAppGateway:27299] exit with status 65280 +2020-02-09 12:44:55 pid:27236 Workerman[start.php] has been stopped +2020-02-09 12:47:37 pid:27329 Workerman[start.php] start in DEBUG mode +2020-02-09 12:47:37 pid:27329 worker[YourAppGateway:27334] exit with status 65280 +2020-02-09 12:47:37 pid:27329 worker[YourAppGateway:27336] exit with status 65280 +2020-02-09 12:47:37 pid:27329 worker[YourAppGateway:27337] exit with status 65280 +2020-02-09 12:47:37 pid:27329 worker[YourAppGateway:27335] exit with status 65280 +2020-02-09 12:47:37 pid:27329 worker[Register:27338] exit with status 65280 +2020-02-09 12:47:37 pid:27329 worker[YourAppGateway:27339] exit with status 65280 +2020-02-09 12:47:37 pid:27329 worker[YourAppGateway:27340] exit with status 65280 +2020-02-09 12:47:37 pid:27329 worker[YourAppGateway:27341] exit with status 65280 +2020-02-09 12:47:37 pid:27329 worker[YourAppGateway:27342] exit with status 65280 +2020-02-09 12:47:37 pid:27329 Workerman[start.php] stopping ... +2020-02-09 12:47:37 pid:27329 worker[YourAppGateway:27344] exit with status 65280 +2020-02-09 12:47:37 pid:27329 worker[Register:27343] exit with status 65280 +2020-02-09 12:47:37 pid:27329 worker[YourAppGateway:27345] exit with status 65280 +2020-02-09 12:47:37 pid:27329 worker[YourAppGateway:27346] exit with status 65280 +2020-02-09 12:47:37 pid:27329 worker[YourAppGateway:27347] exit with status 65280 +2020-02-09 12:47:37 pid:27329 Workerman[start.php] has been stopped +2020-02-09 12:48:48 pid:27425 Workerman[start.php] start in DEBUG mode +2020-02-09 12:48:50 pid:27425 Workerman[start.php] stopping ... +2020-02-09 12:48:50 pid:27425 Workerman[start.php] has been stopped +2020-02-09 12:53:39 pid:27519 Workerman[start.php] start in DEBUG mode +2020-02-09 12:53:39 pid:27519 Workerman[start.php] stopping ... +2020-02-09 12:53:40 pid:27519 Workerman[start.php] has been stopped +2020-02-09 13:18:17 pid:27630 Workerman[start.php] start in DEBUG mode +2020-02-09 13:22:47 pid:27630 Workerman[start.php] stopping ... +2020-02-09 13:22:47 pid:27630 Workerman[start.php] has been stopped +2020-02-09 13:22:47 pid:27661 Workerman[start.php] start in DEBUG mode +2020-02-09 13:22:58 pid:27661 Workerman[start.php] stopping ... +2020-02-09 13:22:58 pid:27661 Workerman[start.php] has been stopped +2020-02-09 13:23:09 pid:27683 Workerman[start.php] start in DEBUG mode +2020-02-09 13:23:16 pid:27683 Workerman[start.php] stopping ... +2020-02-09 13:23:16 pid:27683 Workerman[start.php] has been stopped +2020-02-09 13:24:08 pid:27705 Workerman[start.php] start in DEBUG mode +2020-02-09 13:24:09 pid:27717 Worker[27717] process terminated +2020-02-09 13:24:09 pid:27714 Worker[27714] process terminated +2020-02-09 13:24:09 pid:27716 Worker[27716] process terminated +2020-02-09 13:24:10 pid:27719 Worker[27719] process terminated +2020-02-09 13:24:10 pid:27720 Worker[27720] process terminated +2020-02-09 13:24:10 pid:27718 Worker[27718] process terminated +2020-02-09 13:24:13 pid:27705 Workerman[start.php] stopping ... +2020-02-09 13:24:13 pid:27705 Workerman[start.php] has been stopped +2020-02-09 13:24:20 pid:27744 Workerman[start.php] start in DEBUG mode +2020-02-09 13:24:33 pid:27744 Workerman[start.php] stopping ... +2020-02-09 13:24:33 pid:27744 Workerman[start.php] has been stopped +2020-02-09 13:24:50 pid:27766 Workerman[start.php] start in DEBUG mode +2020-02-09 13:25:04 pid:27766 Workerman[start.php] stopping ... +2020-02-09 13:25:04 pid:27766 Workerman[start.php] has been stopped +2020-02-09 13:26:19 pid:27788 Workerman[start.php] start in DEBUG mode +2020-02-09 13:29:44 pid:27788 Workerman[start.php] stopping ... +2020-02-09 13:29:44 pid:27788 Workerman[start.php] has been stopped +2020-02-09 13:29:45 pid:27811 Workerman[start.php] start in DEBUG mode +2020-02-09 13:30:21 pid:27811 Workerman[start.php] stopping ... +2020-02-09 13:30:21 pid:27811 Workerman[start.php] has been stopped +2020-02-09 13:30:22 pid:27838 Workerman[start.php] start in DEBUG mode +2020-02-09 13:31:47 pid:27838 Workerman[start.php] stopping ... +2020-02-09 13:31:47 pid:27838 Workerman[start.php] has been stopped +2020-02-10 13:00:27 pid:16428 Workerman[start.php] restart +2020-02-10 13:00:27 pid:16428 Workerman[start.php] is stopping ... +2020-02-10 13:00:27 pid:15363 Workerman[start.php] stopping ... +2020-02-10 13:00:28 pid:15363 Workerman[start.php] has been stopped +2020-02-10 13:00:28 pid:16428 Workerman[start.php] stop success +2020-02-10 13:00:31 pid:16465 Workerman[start.php] restart +2020-02-10 13:00:31 pid:16465 Workerman[start.php] is stopping ... +2020-02-10 13:00:31 pid:16430 Workerman[start.php] stopping ... +2020-02-10 13:00:32 pid:16430 Workerman[start.php] has been stopped +2020-02-10 13:00:32 pid:16465 Workerman[start.php] stop success +2020-02-10 13:08:40 pid:17046 Workerman[start.php] restart +2020-02-10 13:08:40 pid:17046 Workerman[start.php] is stopping ... +2020-02-10 13:08:40 pid:16467 Workerman[start.php] stopping ... +2020-02-10 13:08:40 pid:16467 Workerman[start.php] has been stopped +2020-02-10 13:08:40 pid:17046 Workerman[start.php] stop success +2020-02-10 13:12:28 pid:17351 Workerman[start.php] restart +2020-02-10 13:12:28 pid:17351 Workerman[start.php] is stopping ... +2020-02-10 13:12:28 pid:17050 Workerman[start.php] stopping ... +2020-02-10 13:12:29 pid:17050 Workerman[start.php] has been stopped +2020-02-10 13:12:29 pid:17351 Workerman[start.php] stop success +2020-02-10 13:14:43 pid:17553 Workerman[start.php] restart +2020-02-10 13:14:43 pid:17553 Workerman[start.php] is stopping ... +2020-02-10 13:14:43 pid:17353 Workerman[start.php] stopping ... +2020-02-10 13:14:43 pid:17353 Workerman[start.php] has been stopped +2020-02-10 13:14:43 pid:17553 Workerman[start.php] stop success +2020-02-10 13:43:18 pid:18373 Workerman[start.php] restart +2020-02-10 13:43:18 pid:18373 Workerman[start.php] is stopping ... +2020-02-10 13:43:18 pid:17556 Workerman[start.php] stopping ... +2020-02-10 13:43:19 pid:17556 Workerman[start.php] has been stopped +2020-02-10 13:43:19 pid:18373 Workerman[start.php] stop success +2020-02-10 13:45:26 pid:18579 Workerman[start.php] restart +2020-02-10 13:45:26 pid:18579 Workerman[start.php] is stopping ... +2020-02-10 13:45:26 pid:18375 Workerman[start.php] stopping ... +2020-02-10 13:45:26 pid:18375 Workerman[start.php] has been stopped +2020-02-10 13:45:26 pid:18579 Workerman[start.php] stop success +2020-02-10 13:49:22 pid:18737 Workerman[start.php] restart +2020-02-10 13:49:22 pid:18737 Workerman[start.php] is stopping ... +2020-02-10 13:49:22 pid:18581 Workerman[start.php] stopping ... +2020-02-10 13:49:23 pid:18581 Workerman[start.php] has been stopped +2020-02-10 13:49:23 pid:18737 Workerman[start.php] stop success +2020-02-10 13:51:55 pid:18934 Workerman[start.php] restart +2020-02-10 13:51:55 pid:18934 Workerman[start.php] is stopping ... +2020-02-10 13:51:55 pid:18739 Workerman[start.php] stopping ... +2020-02-10 13:51:56 pid:18739 Workerman[start.php] has been stopped +2020-02-10 13:51:56 pid:18934 Workerman[start.php] stop success +2020-02-10 13:53:17 pid:19042 Workerman[start.php] restart +2020-02-10 13:53:17 pid:19042 Workerman[start.php] is stopping ... +2020-02-10 13:53:17 pid:18936 Workerman[start.php] stopping ... +2020-02-10 13:53:18 pid:18936 Workerman[start.php] has been stopped +2020-02-10 13:53:18 pid:19042 Workerman[start.php] stop success +2020-02-10 14:04:01 pid:19414 Workerman[start.php] restart +2020-02-10 14:04:01 pid:19414 Workerman[start.php] is stopping ... +2020-02-10 14:04:01 pid:19044 Workerman[start.php] stopping ... +2020-02-10 14:04:01 pid:19044 Workerman[start.php] has been stopped +2020-02-10 14:04:01 pid:19414 Workerman[start.php] stop success +2020-02-10 14:05:13 pid:19588 Workerman[start.php] restart +2020-02-10 14:05:13 pid:19588 Workerman[start.php] is stopping ... +2020-02-10 14:05:13 pid:19417 Workerman[start.php] stopping ... +2020-02-10 14:05:13 pid:19417 Workerman[start.php] has been stopped +2020-02-10 14:05:13 pid:19588 Workerman[start.php] stop success +2020-02-10 14:10:58 pid:19922 Workerman[start.php] restart +2020-02-10 14:10:58 pid:19922 Workerman[start.php] is stopping ... +2020-02-10 14:10:58 pid:19590 Workerman[start.php] stopping ... +2020-02-10 14:10:59 pid:19590 Workerman[start.php] has been stopped +2020-02-10 14:10:59 pid:19922 Workerman[start.php] stop success +2020-02-10 14:11:07 pid:19975 Workerman[start.php] stop +2020-02-10 14:11:07 pid:19975 Workerman[start.php] is stopping ... +2020-02-10 14:11:07 pid:19924 Workerman[start.php] stopping ... +2020-02-10 14:11:08 pid:19924 Workerman[start.php] has been stopped +2020-02-10 14:11:08 pid:19975 Workerman[start.php] stop success +2020-02-10 14:11:12 pid:19985 Workerman[start.php] start in DAEMON mode +2020-02-10 14:15:15 pid:20384 Workerman[start.php] restart +2020-02-10 14:15:15 pid:20384 Workerman[start.php] is stopping ... +2020-02-10 14:15:15 pid:19987 Workerman[start.php] stopping ... +2020-02-10 14:15:16 pid:19987 Workerman[start.php] has been stopped +2020-02-10 14:15:16 pid:20384 Workerman[start.php] stop success +2020-02-10 14:16:02 pid:20384 Workerman[start.php] stopping ... +2020-02-10 14:16:03 pid:20384 Workerman[start.php] has been stopped +2020-02-10 14:17:45 pid:20556 Workerman[start.php] restart +2020-02-10 14:17:45 pid:20556 Workerman[start.php] is stopping ... +2020-02-10 14:17:45 pid:20556 Workerman[start.php] stop success +2020-02-10 14:18:11 pid:20556 Workerman[start.php] stopping ... +2020-02-10 14:18:12 pid:20556 Workerman[start.php] has been stopped +2020-02-10 14:18:22 pid:20684 Workerman[start.php] restart +2020-02-10 14:18:22 pid:20684 Workerman[start.php] is stopping ... +2020-02-10 14:18:22 pid:20684 Workerman[start.php] stop success +2020-02-10 14:24:01 pid:20684 Workerman[start.php] stopping ... +2020-02-10 14:24:01 pid:20684 Workerman[start.php] has been stopped +2020-02-10 14:24:06 pid:20824 Workerman[start.php] restart +2020-02-10 14:24:06 pid:20824 Workerman[start.php] is stopping ... +2020-02-10 14:24:06 pid:20824 Workerman[start.php] stop success +2020-02-10 14:25:24 pid:20939 Workerman[start.php] restart +2020-02-10 14:25:24 pid:20939 Workerman[start.php] is stopping ... +2020-02-10 14:25:24 pid:20826 Workerman[start.php] stopping ... +2020-02-10 14:25:25 pid:20826 Workerman[start.php] has been stopped +2020-02-10 14:25:25 pid:20939 Workerman[start.php] stop success +2020-02-11 08:52:45 pid:11182 Workerman[start.php] restart +2020-02-11 08:52:45 pid:11182 Workerman[start.php] is stopping ... +2020-02-11 08:52:45 pid:20942 Workerman[start.php] stopping ... +2020-02-11 08:52:46 pid:20942 Workerman[start.php] has been stopped +2020-02-11 08:52:46 pid:11182 Workerman[start.php] stop success +2020-02-11 08:54:06 pid:11311 Workerman[start.php] restart +2020-02-11 08:54:06 pid:11311 Workerman[start.php] is stopping ... +2020-02-11 08:54:06 pid:11186 Workerman[start.php] stopping ... +2020-02-11 08:54:07 pid:11186 Workerman[start.php] has been stopped +2020-02-11 08:54:07 pid:11311 Workerman[start.php] stop success +2020-02-11 08:56:04 pid:11524 Workerman[start.php] restart +2020-02-11 08:56:04 pid:11524 Workerman[start.php] is stopping ... +2020-02-11 08:56:04 pid:11313 Workerman[start.php] stopping ... +2020-02-11 08:56:05 pid:11313 Workerman[start.php] has been stopped +2020-02-11 08:56:05 pid:11524 Workerman[start.php] stop success +2020-02-11 18:15:18 pid:25124 Workerman[start.php] restart +2020-02-11 18:15:18 pid:25124 Workerman[start.php] is stopping ... +2020-02-11 18:15:18 pid:11526 Workerman[start.php] stopping ... +2020-02-11 18:15:19 pid:11526 Workerman[start.php] has been stopped +2020-02-11 18:15:19 pid:25124 Workerman[start.php] stop success +2020-02-11 22:35:14 pid:31485 Workerman[start.php] restart +2020-02-11 22:35:14 pid:31485 Workerman[start.php] is stopping ... +2020-02-11 22:35:14 pid:25128 Workerman[start.php] stopping ... +2020-02-11 22:35:15 pid:25128 Workerman[start.php] has been stopped +2020-02-11 22:35:15 pid:31485 Workerman[start.php] stop success +2020-02-11 22:41:23 pid:31721 Workerman[start.php] restart +2020-02-11 22:41:23 pid:31721 Workerman[start.php] is stopping ... +2020-02-11 22:41:23 pid:31490 Workerman[start.php] stopping ... +2020-02-11 22:41:24 pid:31490 Workerman[start.php] has been stopped +2020-02-11 22:41:24 pid:31721 Workerman[start.php] stop success +2020-02-11 23:03:20 pid:460 Workerman[start.php] restart +2020-02-11 23:03:20 pid:460 Workerman[start.php] is stopping ... +2020-02-11 23:03:20 pid:31723 Workerman[start.php] stopping ... +2020-02-11 23:03:20 pid:31723 Workerman[start.php] has been stopped +2020-02-11 23:03:20 pid:460 Workerman[start.php] stop success +2020-02-12 10:04:48 pid:16620 Workerman[start.php] restart +2020-02-12 10:04:48 pid:16620 Workerman[start.php] is stopping ... +2020-02-12 10:04:48 pid:463 Workerman[start.php] stopping ... +2020-02-12 10:04:48 pid:463 Workerman[start.php] has been stopped +2020-02-12 10:04:48 pid:16620 Workerman[start.php] stop success +2020-02-12 10:05:27 pid:16699 Workerman[start.php] restart +2020-02-12 10:05:27 pid:16699 Workerman[start.php] is stopping ... +2020-02-12 10:05:27 pid:16625 Workerman[start.php] stopping ... +2020-02-12 10:05:29 pid:16625 worker[TaskWorker_github:16638] exit with status 9 +2020-02-12 10:05:29 pid:16625 Workerman[start.php] has been stopped +2020-02-12 10:05:29 pid:16699 Workerman[start.php] stop success +2020-02-12 20:11:14 pid:32273 Workerman[start.php] restart +2020-02-12 20:11:14 pid:32273 Workerman[start.php] is stopping ... +2020-02-12 20:11:14 pid:16701 Workerman[start.php] stopping ... +2020-02-12 20:11:16 pid:16701 worker[TaskWorker_github:16717] exit with status 9 +2020-02-12 20:11:16 pid:16701 worker[TaskWorker_github:16723] exit with status 9 +2020-02-12 20:11:16 pid:16701 Workerman[start.php] has been stopped +2020-02-12 20:11:16 pid:32273 Workerman[start.php] stop success +2020-02-12 20:11:46 pid:32328 Workerman[start.php] restart +2020-02-12 20:11:46 pid:32328 Workerman[start.php] is stopping ... +2020-02-12 20:11:46 pid:32277 Workerman[start.php] stopping ... +2020-02-12 20:11:47 pid:32277 Workerman[start.php] has been stopped +2020-02-12 20:11:47 pid:32328 Workerman[start.php] stop success +2020-02-12 20:11:57 pid:32367 Workerman[start.php] stop +2020-02-12 20:11:57 pid:32367 Workerman[start.php] is stopping ... +2020-02-12 20:11:57 pid:32331 Workerman[start.php] stopping ... +2020-02-12 20:11:58 pid:32331 Workerman[start.php] has been stopped +2020-02-12 20:11:58 pid:32367 Workerman[start.php] stop success +2020-02-12 20:12:01 pid:32397 Workerman[start.php] start in DAEMON mode +2020-02-12 20:23:30 pid:988 Workerman[start.php] stop +2020-02-12 20:23:30 pid:988 Workerman[start.php] is stopping ... +2020-02-12 20:23:30 pid:32399 Workerman[start.php] stopping ... +2020-02-12 20:23:30 pid:32399 Workerman[start.php] has been stopped +2020-02-12 20:23:30 pid:988 Workerman[start.php] stop success +2020-02-12 20:23:33 pid:1016 Workerman[start.php] start in DAEMON mode +2020-02-16 20:24:20 pid:32647 Workerman[start.php] restart +2020-02-16 20:24:20 pid:32647 Workerman[start.php] is stopping ... +2020-02-16 20:24:20 pid:1018 Workerman[start.php] stopping ... +2020-02-16 20:24:21 pid:1018 Workerman[start.php] has been stopped +2020-02-16 20:24:21 pid:32647 Workerman[start.php] stop success +2020-02-19 16:37:35 pid:4261 Workerman[start.php] restart +2020-02-19 16:37:35 pid:4261 Workerman[start.php] is stopping ... +2020-02-19 16:37:35 pid:32651 Workerman[start.php] stopping ... +2020-02-19 16:37:35 pid:32651 Workerman[start.php] has been stopped +2020-02-19 16:37:35 pid:4261 Workerman[start.php] stop success +2020-02-19 16:42:11 pid:4349 Workerman[start.php] restart +2020-02-19 16:42:11 pid:4349 Workerman[start.php] is stopping ... +2020-02-19 16:42:11 pid:4265 Workerman[start.php] stopping ... +2020-02-19 16:42:11 pid:4265 Workerman[start.php] has been stopped +2020-02-19 16:42:11 pid:4349 Workerman[start.php] stop success +2020-02-19 16:42:54 pid:4386 Workerman[start.php] restart +2020-02-19 16:42:54 pid:4386 Workerman[start.php] is stopping ... +2020-02-19 16:42:54 pid:4351 Workerman[start.php] stopping ... +2020-02-19 16:42:55 pid:4351 Workerman[start.php] has been stopped +2020-02-19 16:42:55 pid:4386 Workerman[start.php] stop success +2020-02-19 16:45:46 pid:4444 Workerman[start.php] restart +2020-02-19 16:45:46 pid:4444 Workerman[start.php] is stopping ... +2020-02-19 16:45:46 pid:4389 Workerman[start.php] stopping ... +2020-02-19 16:45:46 pid:4389 Workerman[start.php] has been stopped +2020-02-19 16:45:46 pid:4444 Workerman[start.php] stop success +2020-02-19 16:46:31 pid:4459 SendBufferToWorker fail. The connections between Gateway and BusinessWorker are not ready. See http://doc2.workerman.net/send-buffer-to-worker-fail.html +2020-02-19 16:46:31 pid:4459 SendBufferToWorker fail. The connections between Gateway and BusinessWorker are not ready. See http://doc2.workerman.net/send-buffer-to-worker-fail.html +2020-02-19 16:46:40 pid:4478 Workerman[start.php] restart +2020-02-19 16:46:40 pid:4478 Workerman[start.php] is stopping ... +2020-02-19 16:46:40 pid:4446 Workerman[start.php] stopping ... +2020-02-19 16:46:40 pid:4446 Workerman[start.php] has been stopped +2020-02-19 16:46:40 pid:4478 Workerman[start.php] stop success +2020-02-19 16:46:44 pid:4491 SendBufferToWorker fail. The connections between Gateway and BusinessWorker are not ready. See http://doc2.workerman.net/send-buffer-to-worker-fail.html +2020-02-19 16:46:44 pid:4491 SendBufferToWorker fail. The connections between Gateway and BusinessWorker are not ready. See http://doc2.workerman.net/send-buffer-to-worker-fail.html +2020-02-19 16:46:57 pid:4490 SendBufferToWorker fail. The connections between Gateway and BusinessWorker are not ready. See http://doc2.workerman.net/send-buffer-to-worker-fail.html +2020-02-19 16:46:57 pid:4490 SendBufferToWorker fail. The connections between Gateway and BusinessWorker are not ready. See http://doc2.workerman.net/send-buffer-to-worker-fail.html +2020-02-19 16:46:57 pid:4491 SendBufferToWorker fail. The connections between Gateway and BusinessWorker are not ready. See http://doc2.workerman.net/send-buffer-to-worker-fail.html +2020-02-19 16:46:57 pid:4491 SendBufferToWorker fail. The connections between Gateway and BusinessWorker are not ready. See http://doc2.workerman.net/send-buffer-to-worker-fail.html +2020-02-19 16:46:58 pid:4493 SendBufferToWorker fail. The connections between Gateway and BusinessWorker are not ready. See http://doc2.workerman.net/send-buffer-to-worker-fail.html +2020-02-19 16:46:58 pid:4493 SendBufferToWorker fail. The connections between Gateway and BusinessWorker are not ready. See http://doc2.workerman.net/send-buffer-to-worker-fail.html +2020-02-19 16:46:58 pid:4492 SendBufferToWorker fail. The connections between Gateway and BusinessWorker are not ready. See http://doc2.workerman.net/send-buffer-to-worker-fail.html +2020-02-19 16:46:58 pid:4492 SendBufferToWorker fail. The connections between Gateway and BusinessWorker are not ready. See http://doc2.workerman.net/send-buffer-to-worker-fail.html +2020-02-19 16:47:21 pid:4493 SendBufferToWorker fail. The connections between Gateway and BusinessWorker are not ready. See http://doc2.workerman.net/send-buffer-to-worker-fail.html +2020-02-19 16:47:21 pid:4493 SendBufferToWorker fail. The connections between Gateway and BusinessWorker are not ready. See http://doc2.workerman.net/send-buffer-to-worker-fail.html +2020-02-19 16:48:26 pid:4491 SendBufferToWorker fail. The connections between Gateway and BusinessWorker are not ready. See http://doc2.workerman.net/send-buffer-to-worker-fail.html +2020-02-19 16:48:26 pid:4491 SendBufferToWorker fail. The connections between Gateway and BusinessWorker are not ready. See http://doc2.workerman.net/send-buffer-to-worker-fail.html +2020-02-19 16:48:34 pid:4493 SendBufferToWorker fail. The connections between Gateway and BusinessWorker are not ready. See http://doc2.workerman.net/send-buffer-to-worker-fail.html +2020-02-19 16:48:34 pid:4493 SendBufferToWorker fail. The connections between Gateway and BusinessWorker are not ready. See http://doc2.workerman.net/send-buffer-to-worker-fail.html +2020-02-19 16:49:40 pid:17324 Workerman[start.php] restart +2020-02-19 16:49:40 pid:17324 Workerman[start.php] is stopping ... +2020-02-19 16:49:40 pid:4481 Workerman[start.php] stopping ... +2020-02-19 16:49:41 pid:4481 Workerman[start.php] has been stopped +2020-02-19 16:49:41 pid:17324 Workerman[start.php] stop success +2020-02-19 16:49:49 pid:19832 Workerman[start.php] restart +2020-02-19 16:49:49 pid:19832 Workerman[start.php] is stopping ... +2020-02-19 16:49:49 pid:17331 Workerman[start.php] stopping ... +2020-02-19 16:49:49 pid:17331 Workerman[start.php] has been stopped +2020-02-19 16:49:49 pid:19832 Workerman[start.php] stop success +2020-02-19 16:49:54 pid:19846 SendBufferToWorker fail. The connections between Gateway and BusinessWorker are not ready. See http://doc2.workerman.net/send-buffer-to-worker-fail.html +2020-02-19 16:49:54 pid:19846 SendBufferToWorker fail. The connections between Gateway and BusinessWorker are not ready. See http://doc2.workerman.net/send-buffer-to-worker-fail.html +2020-02-19 16:50:08 pid:19845 SendBufferToWorker fail. The connections between Gateway and BusinessWorker are not ready. See http://doc2.workerman.net/send-buffer-to-worker-fail.html +2020-02-19 16:50:08 pid:19845 SendBufferToWorker fail. The connections between Gateway and BusinessWorker are not ready. See http://doc2.workerman.net/send-buffer-to-worker-fail.html +2020-02-19 16:50:09 pid:19845 SendBufferToWorker fail. The connections between Gateway and BusinessWorker are not ready. See http://doc2.workerman.net/send-buffer-to-worker-fail.html +2020-02-19 16:50:09 pid:19845 SendBufferToWorker fail. The connections between Gateway and BusinessWorker are not ready. See http://doc2.workerman.net/send-buffer-to-worker-fail.html +2020-02-19 16:50:09 pid:19846 SendBufferToWorker fail. The connections between Gateway and BusinessWorker are not ready. See http://doc2.workerman.net/send-buffer-to-worker-fail.html +2020-02-19 16:50:09 pid:19846 SendBufferToWorker fail. The connections between Gateway and BusinessWorker are not ready. See http://doc2.workerman.net/send-buffer-to-worker-fail.html +2020-02-19 16:50:13 pid:19849 SendBufferToWorker fail. The connections between Gateway and BusinessWorker are not ready. See http://doc2.workerman.net/send-buffer-to-worker-fail.html +2020-02-19 16:50:13 pid:19849 SendBufferToWorker fail. The connections between Gateway and BusinessWorker are not ready. See http://doc2.workerman.net/send-buffer-to-worker-fail.html +2020-02-19 16:50:25 pid:19861 Workerman[start.php] restart +2020-02-19 16:50:25 pid:19861 Workerman[start.php] is stopping ... +2020-02-19 16:50:25 pid:19835 Workerman[start.php] stopping ... +2020-02-19 16:50:25 pid:19835 Workerman[start.php] has been stopped +2020-02-19 16:50:26 pid:19861 Workerman[start.php] stop success +2020-02-19 16:52:27 pid:20011 Workerman[start.php] restart +2020-02-19 16:52:27 pid:20011 Workerman[start.php] is stopping ... +2020-02-19 16:52:27 pid:19864 Workerman[start.php] stopping ... +2020-02-19 16:52:28 pid:19864 Workerman[start.php] has been stopped +2020-02-19 16:52:28 pid:20011 Workerman[start.php] stop success +2020-02-19 16:54:38 pid:20079 Workerman[start.php] restart +2020-02-19 16:54:38 pid:20079 Workerman[start.php] is stopping ... +2020-02-19 16:54:38 pid:20013 Workerman[start.php] stopping ... +2020-02-19 16:54:39 pid:20013 Workerman[start.php] has been stopped +2020-02-19 16:54:39 pid:20079 Workerman[start.php] stop success +2020-02-19 16:56:16 pid:20196 Workerman[start.php] restart +2020-02-19 16:56:16 pid:20196 Workerman[start.php] is stopping ... +2020-02-19 16:56:16 pid:20082 Workerman[start.php] stopping ... +2020-02-19 16:56:17 pid:20082 Workerman[start.php] has been stopped +2020-02-19 16:56:17 pid:20196 Workerman[start.php] stop success +2020-02-19 19:46:13 pid:24013 Workerman[start.php] restart +2020-02-19 19:46:13 pid:24013 Workerman[start.php] is stopping ... +2020-02-19 19:46:13 pid:20199 Workerman[start.php] stopping ... +2020-02-19 19:46:13 pid:20199 Workerman[start.php] has been stopped +2020-02-19 19:46:13 pid:24013 Workerman[start.php] stop success +2020-02-19 19:49:28 pid:24150 Workerman[start.php] restart +2020-02-19 19:49:28 pid:24150 Workerman[start.php] is stopping ... +2020-02-19 19:49:28 pid:24016 Workerman[start.php] stopping ... +2020-02-19 19:49:29 pid:24016 Workerman[start.php] has been stopped +2020-02-19 19:49:29 pid:24150 Workerman[start.php] stop success +2020-02-19 19:52:23 pid:24221 Workerman[start.php] restart +2020-02-19 19:52:23 pid:24221 Workerman[start.php] is stopping ... +2020-02-19 19:52:23 pid:24152 Workerman[start.php] stopping ... +2020-02-19 19:52:24 pid:24152 Workerman[start.php] has been stopped +2020-02-19 19:52:24 pid:24221 Workerman[start.php] stop success +2020-02-19 19:53:39 pid:24269 Workerman[start.php] restart +2020-02-19 19:53:39 pid:24269 Workerman[start.php] is stopping ... +2020-02-19 19:53:39 pid:24224 Workerman[start.php] stopping ... +2020-02-19 19:53:40 pid:24224 Workerman[start.php] has been stopped +2020-02-19 19:53:40 pid:24269 Workerman[start.php] stop success +2020-02-19 19:55:55 pid:24437 Workerman[start.php] restart +2020-02-19 19:55:55 pid:24437 Workerman[start.php] is stopping ... +2020-02-19 19:55:55 pid:24272 Workerman[start.php] stopping ... +2020-02-19 19:55:56 pid:24272 Workerman[start.php] has been stopped +2020-02-19 19:55:56 pid:24437 Workerman[start.php] stop success +2020-02-19 19:56:35 pid:24489 Workerman[start.php] restart +2020-02-19 19:56:35 pid:24489 Workerman[start.php] is stopping ... +2020-02-19 19:56:35 pid:24440 Workerman[start.php] stopping ... +2020-02-19 19:56:35 pid:24440 Workerman[start.php] has been stopped +2020-02-19 19:56:35 pid:24489 Workerman[start.php] stop success +2020-02-19 19:57:37 pid:24539 Workerman[start.php] restart +2020-02-19 19:57:37 pid:24539 Workerman[start.php] is stopping ... +2020-02-19 19:57:37 pid:24492 Workerman[start.php] stopping ... +2020-02-19 19:57:37 pid:24492 Workerman[start.php] has been stopped +2020-02-19 19:57:37 pid:24539 Workerman[start.php] stop success +2020-02-19 20:07:53 pid:24725 Workerman[start.php] restart +2020-02-19 20:07:53 pid:24725 Workerman[start.php] is stopping ... +2020-02-19 20:07:53 pid:24542 Workerman[start.php] stopping ... +2020-02-19 20:07:53 pid:24542 Workerman[start.php] has been stopped +2020-02-19 20:07:53 pid:24725 Workerman[start.php] stop success +2020-02-19 20:11:03 pid:24806 Workerman[start.php] restart +2020-02-19 20:11:03 pid:24806 Workerman[start.php] is stopping ... +2020-02-19 20:11:03 pid:24727 Workerman[start.php] stopping ... +2020-02-19 20:11:04 pid:24727 Workerman[start.php] has been stopped +2020-02-19 20:11:04 pid:24806 Workerman[start.php] stop success +2020-02-19 20:11:45 pid:24859 Workerman[start.php] restart +2020-02-19 20:11:45 pid:24859 Workerman[start.php] is stopping ... +2020-02-19 20:11:45 pid:24808 Workerman[start.php] stopping ... +2020-02-19 20:11:45 pid:24808 Workerman[start.php] has been stopped +2020-02-19 20:11:45 pid:24859 Workerman[start.php] stop success +2020-02-19 20:12:04 pid:24894 Workerman[start.php] restart +2020-02-19 20:12:04 pid:24894 Workerman[start.php] is stopping ... +2020-02-19 20:12:04 pid:24863 Workerman[start.php] stopping ... +2020-02-19 20:12:05 pid:24863 Workerman[start.php] has been stopped +2020-02-19 20:12:05 pid:24894 Workerman[start.php] stop success +2020-02-19 20:13:00 pid:24932 Workerman[start.php] restart +2020-02-19 20:13:00 pid:24932 Workerman[start.php] is stopping ... +2020-02-19 20:13:00 pid:24897 Workerman[start.php] stopping ... +2020-02-19 20:13:01 pid:24897 Workerman[start.php] has been stopped +2020-02-19 20:13:01 pid:24932 Workerman[start.php] stop success +2020-02-19 20:14:08 pid:25022 Workerman[start.php] restart +2020-02-19 20:14:08 pid:25022 Workerman[start.php] is stopping ... +2020-02-19 20:14:08 pid:24935 Workerman[start.php] stopping ... +2020-02-19 20:14:09 pid:24935 Workerman[start.php] has been stopped +2020-02-19 20:14:09 pid:25022 Workerman[start.php] stop success +2020-02-19 20:16:06 pid:25194 Workerman[start.php] restart +2020-02-19 20:16:06 pid:25194 Workerman[start.php] is stopping ... +2020-02-19 20:16:06 pid:25026 Workerman[start.php] stopping ... +2020-02-19 20:16:06 pid:25026 Workerman[start.php] has been stopped +2020-02-19 20:16:06 pid:25194 Workerman[start.php] stop success +2020-02-19 20:19:10 pid:25370 Workerman[start.php] restart +2020-02-19 20:19:10 pid:25370 Workerman[start.php] is stopping ... +2020-02-19 20:19:10 pid:25197 Workerman[start.php] stopping ... +2020-02-19 20:19:11 pid:25197 Workerman[start.php] has been stopped +2020-02-19 20:19:11 pid:25370 Workerman[start.php] stop success +2020-02-19 20:33:16 pid:25633 Workerman[start.php] restart +2020-02-19 20:33:16 pid:25633 Workerman[start.php] is stopping ... +2020-02-19 20:33:16 pid:25373 Workerman[start.php] stopping ... +2020-02-19 20:33:17 pid:25373 Workerman[start.php] has been stopped +2020-02-19 20:33:17 pid:25633 Workerman[start.php] stop success +2020-02-20 08:44:21 pid:4142 Workerman[start.php] restart +2020-02-20 08:44:21 pid:4142 Workerman[start.php] is stopping ... +2020-02-20 08:44:21 pid:25635 Workerman[start.php] stopping ... +2020-02-20 08:44:22 pid:25635 Workerman[start.php] has been stopped +2020-02-20 08:44:22 pid:4142 Workerman[start.php] stop success +2020-02-20 08:46:36 pid:4219 Workerman[start.php] restart +2020-02-20 08:46:36 pid:4219 Workerman[start.php] is stopping ... +2020-02-20 08:46:36 pid:4147 Workerman[start.php] stopping ... +2020-02-20 08:46:37 pid:4147 Workerman[start.php] has been stopped +2020-02-20 08:46:37 pid:4219 Workerman[start.php] stop success +2020-02-20 08:47:21 pid:4264 Workerman[start.php] restart +2020-02-20 08:47:21 pid:4264 Workerman[start.php] is stopping ... +2020-02-20 08:47:21 pid:4221 Workerman[start.php] stopping ... +2020-02-20 08:47:22 pid:4221 Workerman[start.php] has been stopped +2020-02-20 08:47:22 pid:4264 Workerman[start.php] stop success +2020-02-20 08:48:31 pid:4320 Workerman[start.php] restart +2020-02-20 08:48:31 pid:4320 Workerman[start.php] is stopping ... +2020-02-20 08:48:31 pid:4267 Workerman[start.php] stopping ... +2020-02-20 08:48:31 pid:4267 Workerman[start.php] has been stopped +2020-02-20 08:48:31 pid:4320 Workerman[start.php] stop success +2020-02-20 08:49:22 pid:4363 Workerman[start.php] restart +2020-02-20 08:49:22 pid:4363 Workerman[start.php] is stopping ... +2020-02-20 08:49:22 pid:4323 Workerman[start.php] stopping ... +2020-02-20 08:49:23 pid:4323 Workerman[start.php] has been stopped +2020-02-20 08:49:23 pid:4363 Workerman[start.php] stop success +2020-02-20 08:51:10 pid:4447 Workerman[start.php] restart +2020-02-20 08:51:10 pid:4447 Workerman[start.php] is stopping ... +2020-02-20 08:51:10 pid:4367 Workerman[start.php] stopping ... +2020-02-20 08:51:11 pid:4367 Workerman[start.php] has been stopped +2020-02-20 08:51:11 pid:4447 Workerman[start.php] stop success +2020-02-20 08:51:37 pid:4487 Workerman[start.php] restart +2020-02-20 08:51:37 pid:4487 Workerman[start.php] is stopping ... +2020-02-20 08:51:37 pid:4449 Workerman[start.php] stopping ... +2020-02-20 08:51:38 pid:4449 Workerman[start.php] has been stopped +2020-02-20 08:51:38 pid:4487 Workerman[start.php] stop success +2020-02-20 08:55:48 pid:4596 Workerman[start.php] restart +2020-02-20 08:55:48 pid:4596 Workerman[start.php] is stopping ... +2020-02-20 08:55:48 pid:4489 Workerman[start.php] stopping ... +2020-02-20 08:55:48 pid:4489 Workerman[start.php] has been stopped +2020-02-20 08:55:48 pid:4596 Workerman[start.php] stop success +2020-02-20 08:58:34 pid:4686 Workerman[start.php] restart +2020-02-20 08:58:34 pid:4686 Workerman[start.php] is stopping ... +2020-02-20 08:58:34 pid:4598 Workerman[start.php] stopping ... +2020-02-20 08:58:35 pid:4598 Workerman[start.php] has been stopped +2020-02-20 08:58:35 pid:4686 Workerman[start.php] stop success +2020-02-20 08:59:53 pid:4742 Workerman[start.php] restart +2020-02-20 08:59:53 pid:4742 Workerman[start.php] is stopping ... +2020-02-20 08:59:53 pid:4690 Workerman[start.php] stopping ... +2020-02-20 08:59:53 pid:4690 Workerman[start.php] has been stopped +2020-02-20 08:59:53 pid:4742 Workerman[start.php] stop success +2020-02-20 09:09:05 pid:4954 Workerman[start.php] restart +2020-02-20 09:09:05 pid:4954 Workerman[start.php] is stopping ... +2020-02-20 09:09:05 pid:4746 Workerman[start.php] stopping ... +2020-02-20 09:09:05 pid:4746 Workerman[start.php] has been stopped +2020-02-20 09:09:05 pid:4954 Workerman[start.php] stop success +2020-02-20 09:11:12 pid:5025 Workerman[start.php] restart +2020-02-20 09:11:12 pid:5025 Workerman[start.php] is stopping ... +2020-02-20 09:11:12 pid:4956 Workerman[start.php] stopping ... +2020-02-20 09:11:13 pid:4956 Workerman[start.php] has been stopped +2020-02-20 09:11:13 pid:5025 Workerman[start.php] stop success +2020-03-14 14:46:08 pid:23074 Workerman[start.php] restart +2020-03-14 14:46:08 pid:23074 Workerman[start.php] is stopping ... +2020-03-14 14:46:08 pid:5027 Workerman[start.php] stopping ... +2020-03-14 14:46:08 pid:5027 Workerman[start.php] has been stopped +2020-03-14 14:46:08 pid:23074 Workerman[start.php] stop success diff --git a/GatewayWorker/vendor/workerman/workerman/.gitignore b/GatewayWorker/vendor/workerman/workerman/.gitignore new file mode 100644 index 0000000..f3f9e18 --- /dev/null +++ b/GatewayWorker/vendor/workerman/workerman/.gitignore @@ -0,0 +1,6 @@ +logs +.buildpath +.project +.settings +.idea +.DS_Store diff --git a/GatewayWorker/vendor/workerman/workerman/Autoloader.php b/GatewayWorker/vendor/workerman/workerman/Autoloader.php new file mode 100644 index 0000000..7d760e9 --- /dev/null +++ b/GatewayWorker/vendor/workerman/workerman/Autoloader.php @@ -0,0 +1,69 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman; + +/** + * Autoload. + */ +class Autoloader +{ + /** + * Autoload root path. + * + * @var string + */ + protected static $_autoloadRootPath = ''; + + /** + * Set autoload root path. + * + * @param string $root_path + * @return void + */ + public static function setRootPath($root_path) + { + self::$_autoloadRootPath = $root_path; + } + + /** + * Load files by namespace. + * + * @param string $name + * @return boolean + */ + public static function loadByNamespace($name) + { + $class_path = \str_replace('\\', \DIRECTORY_SEPARATOR, $name); + if (\strpos($name, 'Workerman\\') === 0) { + $class_file = __DIR__ . \substr($class_path, \strlen('Workerman')) . '.php'; + } else { + if (self::$_autoloadRootPath) { + $class_file = self::$_autoloadRootPath . \DIRECTORY_SEPARATOR . $class_path . '.php'; + } + if (empty($class_file) || !\is_file($class_file)) { + $class_file = __DIR__ . \DIRECTORY_SEPARATOR . '..' . \DIRECTORY_SEPARATOR . "$class_path.php"; + } + } + + if (\is_file($class_file)) { + require_once($class_file); + if (\class_exists($name, false)) { + return true; + } + } + return false; + } +} + +\spl_autoload_register('\Workerman\Autoloader::loadByNamespace'); \ No newline at end of file diff --git a/GatewayWorker/vendor/workerman/workerman/Connection/AsyncTcpConnection.php b/GatewayWorker/vendor/workerman/workerman/Connection/AsyncTcpConnection.php new file mode 100644 index 0000000..744b7aa --- /dev/null +++ b/GatewayWorker/vendor/workerman/workerman/Connection/AsyncTcpConnection.php @@ -0,0 +1,376 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Connection; + +use Workerman\Events\EventInterface; +use Workerman\Lib\Timer; +use Workerman\Worker; +use \Exception; + +/** + * AsyncTcpConnection. + */ +class AsyncTcpConnection extends TcpConnection +{ + /** + * Emitted when socket connection is successfully established. + * + * @var callable|null + */ + public $onConnect = null; + + /** + * Transport layer protocol. + * + * @var string + */ + public $transport = 'tcp'; + + /** + * Status. + * + * @var int + */ + protected $_status = self::STATUS_INITIAL; + + /** + * Remote host. + * + * @var string + */ + protected $_remoteHost = ''; + + /** + * Remote port. + * + * @var int + */ + protected $_remotePort = 80; + + /** + * Connect start time. + * + * @var float + */ + protected $_connectStartTime = 0; + + /** + * Remote URI. + * + * @var string + */ + protected $_remoteURI = ''; + + /** + * Context option. + * + * @var array + */ + protected $_contextOption = null; + + /** + * Reconnect timer. + * + * @var int + */ + protected $_reconnectTimer = null; + + + /** + * PHP built-in protocols. + * + * @var array + */ + protected static $_builtinTransports = array( + 'tcp' => 'tcp', + 'udp' => 'udp', + 'unix' => 'unix', + 'ssl' => 'ssl', + 'sslv2' => 'sslv2', + 'sslv3' => 'sslv3', + 'tls' => 'tls' + ); + + /** + * Construct. + * + * @param string $remote_address + * @param array $context_option + * @throws Exception + */ + public function __construct($remote_address, array $context_option = array()) + { + $address_info = \parse_url($remote_address); + if (!$address_info) { + list($scheme, $this->_remoteAddress) = \explode(':', $remote_address, 2); + if (!$this->_remoteAddress) { + Worker::safeEcho(new \Exception('bad remote_address')); + } + } else { + if (!isset($address_info['port'])) { + $address_info['port'] = 0; + } + if (!isset($address_info['path'])) { + $address_info['path'] = '/'; + } + if (!isset($address_info['query'])) { + $address_info['query'] = ''; + } else { + $address_info['query'] = '?' . $address_info['query']; + } + $this->_remoteAddress = "{$address_info['host']}:{$address_info['port']}"; + $this->_remoteHost = $address_info['host']; + $this->_remotePort = $address_info['port']; + $this->_remoteURI = "{$address_info['path']}{$address_info['query']}"; + $scheme = isset($address_info['scheme']) ? $address_info['scheme'] : 'tcp'; + } + + $this->id = $this->_id = self::$_idRecorder++; + if(\PHP_INT_MAX === self::$_idRecorder){ + self::$_idRecorder = 0; + } + // Check application layer protocol class. + if (!isset(self::$_builtinTransports[$scheme])) { + $scheme = \ucfirst($scheme); + $this->protocol = '\\Protocols\\' . $scheme; + if (!\class_exists($this->protocol)) { + $this->protocol = "\\Workerman\\Protocols\\$scheme"; + if (!\class_exists($this->protocol)) { + throw new Exception("class \\Protocols\\$scheme not exist"); + } + } + } else { + $this->transport = self::$_builtinTransports[$scheme]; + } + + // For statistics. + ++self::$statistics['connection_count']; + $this->maxSendBufferSize = self::$defaultMaxSendBufferSize; + $this->_contextOption = $context_option; + static::$connections[$this->_id] = $this; + } + + /** + * Do connect. + * + * @return void + */ + public function connect() + { + if ($this->_status !== self::STATUS_INITIAL && $this->_status !== self::STATUS_CLOSING && + $this->_status !== self::STATUS_CLOSED) { + return; + } + $this->_status = self::STATUS_CONNECTING; + $this->_connectStartTime = \microtime(true); + if ($this->transport !== 'unix') { + if (!$this->_remotePort) { + $this->_remotePort = $this->transport === 'ssl' ? 443 : 80; + $this->_remoteAddress = $this->_remoteHost.':'.$this->_remotePort; + } + // Open socket connection asynchronously. + if ($this->_contextOption) { + $context = \stream_context_create($this->_contextOption); + $this->_socket = \stream_socket_client("tcp://{$this->_remoteHost}:{$this->_remotePort}", + $errno, $errstr, 0, \STREAM_CLIENT_ASYNC_CONNECT, $context); + } else { + $this->_socket = \stream_socket_client("tcp://{$this->_remoteHost}:{$this->_remotePort}", + $errno, $errstr, 0, \STREAM_CLIENT_ASYNC_CONNECT); + } + } else { + $this->_socket = \stream_socket_client("{$this->transport}://{$this->_remoteAddress}", $errno, $errstr, 0, + \STREAM_CLIENT_ASYNC_CONNECT); + } + // If failed attempt to emit onError callback. + if (!$this->_socket || !\is_resource($this->_socket)) { + $this->emitError(\WORKERMAN_CONNECT_FAIL, $errstr); + if ($this->_status === self::STATUS_CLOSING) { + $this->destroy(); + } + if ($this->_status === self::STATUS_CLOSED) { + $this->onConnect = null; + } + return; + } + // Add socket to global event loop waiting connection is successfully established or faild. + Worker::$globalEvent->add($this->_socket, EventInterface::EV_WRITE, array($this, 'checkConnection')); + // For windows. + if(\DIRECTORY_SEPARATOR === '\\') { + Worker::$globalEvent->add($this->_socket, EventInterface::EV_EXCEPT, array($this, 'checkConnection')); + } + } + + /** + * Reconnect. + * + * @param int $after + * @return void + */ + public function reconnect($after = 0) + { + $this->_status = self::STATUS_INITIAL; + static::$connections[$this->_id] = $this; + if ($this->_reconnectTimer) { + Timer::del($this->_reconnectTimer); + } + if ($after > 0) { + $this->_reconnectTimer = Timer::add($after, array($this, 'connect'), null, false); + return; + } + $this->connect(); + } + + /** + * CancelReconnect. + */ + public function cancelReconnect() + { + if ($this->_reconnectTimer) { + Timer::del($this->_reconnectTimer); + } + } + + /** + * Get remote address. + * + * @return string + */ + public function getRemoteHost() + { + return $this->_remoteHost; + } + + /** + * Get remote URI. + * + * @return string + */ + public function getRemoteURI() + { + return $this->_remoteURI; + } + + /** + * Try to emit onError callback. + * + * @param int $code + * @param string $msg + * @return void + */ + protected function emitError($code, $msg) + { + $this->_status = self::STATUS_CLOSING; + if ($this->onError) { + try { + \call_user_func($this->onError, $this, $code, $msg); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + } + + /** + * Check connection is successfully established or faild. + * + * @param resource $socket + * @return void + */ + public function checkConnection() + { + // Remove EV_EXPECT for windows. + if(\DIRECTORY_SEPARATOR === '\\') { + Worker::$globalEvent->del($this->_socket, EventInterface::EV_EXCEPT); + } + + // Remove write listener. + Worker::$globalEvent->del($this->_socket, EventInterface::EV_WRITE); + + if ($this->_status !== self::STATUS_CONNECTING) { + return; + } + + // Check socket state. + if ($address = \stream_socket_get_name($this->_socket, true)) { + // Nonblocking. + \stream_set_blocking($this->_socket, false); + // Compatible with hhvm + if (\function_exists('stream_set_read_buffer')) { + \stream_set_read_buffer($this->_socket, 0); + } + // Try to open keepalive for tcp and disable Nagle algorithm. + if (\function_exists('socket_import_stream') && $this->transport === 'tcp') { + $raw_socket = \socket_import_stream($this->_socket); + \socket_set_option($raw_socket, \SOL_SOCKET, \SO_KEEPALIVE, 1); + \socket_set_option($raw_socket, \SOL_TCP, \TCP_NODELAY, 1); + } + + // SSL handshake. + if ($this->transport === 'ssl') { + $this->_sslHandshakeCompleted = $this->doSslHandshake($this->_socket); + if ($this->_sslHandshakeCompleted === false) { + return; + } + } else { + // There are some data waiting to send. + if ($this->_sendBuffer) { + Worker::$globalEvent->add($this->_socket, EventInterface::EV_WRITE, array($this, 'baseWrite')); + } + } + + // Register a listener waiting read event. + Worker::$globalEvent->add($this->_socket, EventInterface::EV_READ, array($this, 'baseRead')); + + $this->_status = self::STATUS_ESTABLISHED; + $this->_remoteAddress = $address; + + // Try to emit onConnect callback. + if ($this->onConnect) { + try { + \call_user_func($this->onConnect, $this); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + // Try to emit protocol::onConnect + if (\method_exists($this->protocol, 'onConnect')) { + try { + \call_user_func(array($this->protocol, 'onConnect'), $this); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + } else { + // Connection failed. + $this->emitError(\WORKERMAN_CONNECT_FAIL, 'connect ' . $this->_remoteAddress . ' fail after ' . round(\microtime(true) - $this->_connectStartTime, 4) . ' seconds'); + if ($this->_status === self::STATUS_CLOSING) { + $this->destroy(); + } + if ($this->_status === self::STATUS_CLOSED) { + $this->onConnect = null; + } + } + } +} diff --git a/GatewayWorker/vendor/workerman/workerman/Connection/AsyncUdpConnection.php b/GatewayWorker/vendor/workerman/workerman/Connection/AsyncUdpConnection.php new file mode 100644 index 0000000..7df93ad --- /dev/null +++ b/GatewayWorker/vendor/workerman/workerman/Connection/AsyncUdpConnection.php @@ -0,0 +1,209 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Connection; + +use Workerman\Events\EventInterface; +use Workerman\Worker; +use \Exception; + +/** + * AsyncTcpConnection. + */ +class AsyncUdpConnection extends UdpConnection +{ + /** + * Emitted when socket connection is successfully established. + * + * @var callable + */ + public $onConnect = null; + + /** + * Emitted when socket connection closed. + * + * @var callable + */ + public $onClose = null; + + /** + * Connected or not. + * + * @var bool + */ + protected $connected = false; + + /** + * Context option. + * + * @var array + */ + protected $_contextOption = null; + + /** + * Construct. + * + * @param string $remote_address + * @throws Exception + */ + public function __construct($remote_address, $context_option = null) + { + // Get the application layer communication protocol and listening address. + list($scheme, $address) = \explode(':', $remote_address, 2); + // Check application layer protocol class. + if ($scheme !== 'udp') { + $scheme = \ucfirst($scheme); + $this->protocol = '\\Protocols\\' . $scheme; + if (!\class_exists($this->protocol)) { + $this->protocol = "\\Workerman\\Protocols\\$scheme"; + if (!\class_exists($this->protocol)) { + throw new Exception("class \\Protocols\\$scheme not exist"); + } + } + } + + $this->_remoteAddress = \substr($address, 2); + $this->_contextOption = $context_option; + } + + /** + * For udp package. + * + * @param resource $socket + * @return bool + */ + public function baseRead($socket) + { + $recv_buffer = \stream_socket_recvfrom($socket, Worker::MAX_UDP_PACKAGE_SIZE, 0, $remote_address); + if (false === $recv_buffer || empty($remote_address)) { + return false; + } + + if ($this->onMessage) { + if ($this->protocol) { + $parser = $this->protocol; + $recv_buffer = $parser::decode($recv_buffer, $this); + } + ++ConnectionInterface::$statistics['total_request']; + try { + \call_user_func($this->onMessage, $this, $recv_buffer); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + return true; + } + + /** + * Sends data on the connection. + * + * @param string $send_buffer + * @param bool $raw + * @return void|boolean + */ + public function send($send_buffer, $raw = false) + { + if (false === $raw && $this->protocol) { + $parser = $this->protocol; + $send_buffer = $parser::encode($send_buffer, $this); + if ($send_buffer === '') { + return; + } + } + if ($this->connected === false) { + $this->connect(); + } + return \strlen($send_buffer) === \stream_socket_sendto($this->_socket, $send_buffer, 0); + } + + + /** + * Close connection. + * + * @param mixed $data + * @param bool $raw + * + * @return bool + */ + public function close($data = null, $raw = false) + { + if ($data !== null) { + $this->send($data, $raw); + } + Worker::$globalEvent->del($this->_socket, EventInterface::EV_READ); + \fclose($this->_socket); + $this->connected = false; + // Try to emit onClose callback. + if ($this->onClose) { + try { + \call_user_func($this->onClose, $this); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + $this->onConnect = $this->onMessage = $this->onClose = null; + return true; + } + + /** + * Connect. + * + * @return void + */ + public function connect() + { + if ($this->connected === true) { + return; + } + if ($this->_contextOption) { + $context = \stream_context_create($this->_contextOption); + $this->_socket = \stream_socket_client("udp://{$this->_remoteAddress}", $errno, $errmsg, + 30, \STREAM_CLIENT_CONNECT, $context); + } else { + $this->_socket = \stream_socket_client("udp://{$this->_remoteAddress}", $errno, $errmsg); + } + + if (!$this->_socket) { + Worker::safeEcho(new \Exception($errmsg)); + return; + } + + \stream_set_blocking($this->_socket, false); + + if ($this->onMessage) { + Worker::$globalEvent->add($this->_socket, EventInterface::EV_READ, array($this, 'baseRead')); + } + $this->connected = true; + // Try to emit onConnect callback. + if ($this->onConnect) { + try { + \call_user_func($this->onConnect, $this); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + } + +} diff --git a/GatewayWorker/vendor/workerman/workerman/Connection/ConnectionInterface.php b/GatewayWorker/vendor/workerman/workerman/Connection/ConnectionInterface.php new file mode 100644 index 0000000..05954e2 --- /dev/null +++ b/GatewayWorker/vendor/workerman/workerman/Connection/ConnectionInterface.php @@ -0,0 +1,125 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Connection; + +/** + * ConnectionInterface. + */ +abstract class ConnectionInterface +{ + /** + * Statistics for status command. + * + * @var array + */ + public static $statistics = array( + 'connection_count' => 0, + 'total_request' => 0, + 'throw_exception' => 0, + 'send_fail' => 0, + ); + + /** + * Emitted when data is received. + * + * @var callable + */ + public $onMessage = null; + + /** + * Emitted when the other end of the socket sends a FIN packet. + * + * @var callable + */ + public $onClose = null; + + /** + * Emitted when an error occurs with connection. + * + * @var callable + */ + public $onError = null; + + /** + * Sends data on the connection. + * + * @param mixed $send_buffer + * @return void|boolean + */ + abstract public function send($send_buffer); + + /** + * Get remote IP. + * + * @return string + */ + abstract public function getRemoteIp(); + + /** + * Get remote port. + * + * @return int + */ + abstract public function getRemotePort(); + + /** + * Get remote address. + * + * @return string + */ + abstract public function getRemoteAddress(); + + /** + * Get local IP. + * + * @return string + */ + abstract public function getLocalIp(); + + /** + * Get local port. + * + * @return int + */ + abstract public function getLocalPort(); + + /** + * Get local address. + * + * @return string + */ + abstract public function getLocalAddress(); + + /** + * Is ipv4. + * + * @return bool + */ + abstract public function isIPv4(); + + /** + * Is ipv6. + * + * @return bool + */ + abstract public function isIPv6(); + + /** + * Close connection. + * + * @param $data + * @return void + */ + abstract public function close($data = null); +} diff --git a/GatewayWorker/vendor/workerman/workerman/Connection/TcpConnection.php b/GatewayWorker/vendor/workerman/workerman/Connection/TcpConnection.php new file mode 100644 index 0000000..af394ed --- /dev/null +++ b/GatewayWorker/vendor/workerman/workerman/Connection/TcpConnection.php @@ -0,0 +1,1007 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Connection; + +use Workerman\Events\EventInterface; +use Workerman\Worker; +use \Exception; + +/** + * TcpConnection. + */ +class TcpConnection extends ConnectionInterface +{ + /** + * Read buffer size. + * + * @var int + */ + const READ_BUFFER_SIZE = 65535; + + /** + * Status initial. + * + * @var int + */ + const STATUS_INITIAL = 0; + + /** + * Status connecting. + * + * @var int + */ + const STATUS_CONNECTING = 1; + + /** + * Status connection established. + * + * @var int + */ + const STATUS_ESTABLISHED = 2; + + /** + * Status closing. + * + * @var int + */ + const STATUS_CLOSING = 4; + + /** + * Status closed. + * + * @var int + */ + const STATUS_CLOSED = 8; + + /** + * Emitted when data is received. + * + * @var callable + */ + public $onMessage = null; + + /** + * Emitted when the other end of the socket sends a FIN packet. + * + * @var callable + */ + public $onClose = null; + + /** + * Emitted when an error occurs with connection. + * + * @var callable + */ + public $onError = null; + + /** + * Emitted when the send buffer becomes full. + * + * @var callable + */ + public $onBufferFull = null; + + /** + * Emitted when the send buffer becomes empty. + * + * @var callable + */ + public $onBufferDrain = null; + + /** + * Application layer protocol. + * The format is like this Workerman\\Protocols\\Http. + * + * @var \Workerman\Protocols\ProtocolInterface + */ + public $protocol = null; + + /** + * Transport (tcp/udp/unix/ssl). + * + * @var string + */ + public $transport = 'tcp'; + + /** + * Which worker belong to. + * + * @var Worker + */ + public $worker = null; + + /** + * Bytes read. + * + * @var int + */ + public $bytesRead = 0; + + /** + * Bytes written. + * + * @var int + */ + public $bytesWritten = 0; + + /** + * Connection->id. + * + * @var int + */ + public $id = 0; + + /** + * A copy of $worker->id which used to clean up the connection in worker->connections + * + * @var int + */ + protected $_id = 0; + + /** + * Sets the maximum send buffer size for the current connection. + * OnBufferFull callback will be emited When the send buffer is full. + * + * @var int + */ + public $maxSendBufferSize = 1048576; + + /** + * Default send buffer size. + * + * @var int + */ + public static $defaultMaxSendBufferSize = 1048576; + + /** + * Sets the maximum acceptable packet size for the current connection. + * + * @var int + */ + public $maxPackageSize = 1048576; + + /** + * Default maximum acceptable packet size. + * + * @var int + */ + public static $defaultMaxPackageSize = 10485760; + + /** + * Id recorder. + * + * @var int + */ + protected static $_idRecorder = 1; + + /** + * Socket + * + * @var resource + */ + protected $_socket = null; + + /** + * Send buffer. + * + * @var string + */ + protected $_sendBuffer = ''; + + /** + * Receive buffer. + * + * @var string + */ + protected $_recvBuffer = ''; + + /** + * Current package length. + * + * @var int + */ + protected $_currentPackageLength = 0; + + /** + * Connection status. + * + * @var int + */ + protected $_status = self::STATUS_ESTABLISHED; + + /** + * Remote address. + * + * @var string + */ + protected $_remoteAddress = ''; + + /** + * Is paused. + * + * @var bool + */ + protected $_isPaused = false; + + /** + * SSL handshake completed or not. + * + * @var bool + */ + protected $_sslHandshakeCompleted = false; + + /** + * All connection instances. + * + * @var array + */ + public static $connections = array(); + + /** + * Status to string. + * + * @var array + */ + public static $_statusToString = array( + self::STATUS_INITIAL => 'INITIAL', + self::STATUS_CONNECTING => 'CONNECTING', + self::STATUS_ESTABLISHED => 'ESTABLISHED', + self::STATUS_CLOSING => 'CLOSING', + self::STATUS_CLOSED => 'CLOSED', + ); + + + /** + * Adding support of custom functions within protocols + * + * @param string $name + * @param array $arguments + * @return void + */ + public function __call($name, array $arguments) { + // Try to emit custom function within protocol + if (\method_exists($this->protocol, $name)) { + try { + return \call_user_func(array($this->protocol, $name), $this, $arguments); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + } + + /** + * Construct. + * + * @param resource $socket + * @param string $remote_address + */ + public function __construct($socket, $remote_address = '') + { + ++self::$statistics['connection_count']; + $this->id = $this->_id = self::$_idRecorder++; + if(self::$_idRecorder === \PHP_INT_MAX){ + self::$_idRecorder = 0; + } + $this->_socket = $socket; + \stream_set_blocking($this->_socket, 0); + // Compatible with hhvm + if (\function_exists('stream_set_read_buffer')) { + \stream_set_read_buffer($this->_socket, 0); + } + Worker::$globalEvent->add($this->_socket, EventInterface::EV_READ, array($this, 'baseRead')); + $this->maxSendBufferSize = self::$defaultMaxSendBufferSize; + $this->maxPackageSize = self::$defaultMaxPackageSize; + $this->_remoteAddress = $remote_address; + static::$connections[$this->id] = $this; + } + + /** + * Get status. + * + * @param bool $raw_output + * + * @return int|string + */ + public function getStatus($raw_output = true) + { + if ($raw_output) { + return $this->_status; + } + return self::$_statusToString[$this->_status]; + } + + /** + * Sends data on the connection. + * + * @param mixed $send_buffer + * @param bool $raw + * @return bool|null + */ + public function send($send_buffer, $raw = false) + { + if ($this->_status === self::STATUS_CLOSING || $this->_status === self::STATUS_CLOSED) { + return false; + } + + // Try to call protocol::encode($send_buffer) before sending. + if (false === $raw && $this->protocol !== null) { + $parser = $this->protocol; + $send_buffer = $parser::encode($send_buffer, $this); + if ($send_buffer === '') { + return; + } + } + + if ($this->_status !== self::STATUS_ESTABLISHED || + ($this->transport === 'ssl' && $this->_sslHandshakeCompleted !== true) + ) { + if ($this->_sendBuffer && $this->bufferIsFull()) { + ++self::$statistics['send_fail']; + return false; + } + $this->_sendBuffer .= $send_buffer; + $this->checkBufferWillFull(); + return; + } + + // Attempt to send data directly. + if ($this->_sendBuffer === '') { + if ($this->transport === 'ssl') { + Worker::$globalEvent->add($this->_socket, EventInterface::EV_WRITE, array($this, 'baseWrite')); + $this->_sendBuffer = $send_buffer; + $this->checkBufferWillFull(); + return; + } + \set_error_handler(function(){}); + $len = \fwrite($this->_socket, $send_buffer); + \restore_error_handler(); + // send successful. + if ($len === \strlen($send_buffer)) { + $this->bytesWritten += $len; + return true; + } + // Send only part of the data. + if ($len > 0) { + $this->_sendBuffer = \substr($send_buffer, $len); + $this->bytesWritten += $len; + } else { + // Connection closed? + if (!\is_resource($this->_socket) || \feof($this->_socket)) { + ++self::$statistics['send_fail']; + if ($this->onError) { + try { + \call_user_func($this->onError, $this, \WORKERMAN_SEND_FAIL, 'client closed'); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + $this->destroy(); + return false; + } + $this->_sendBuffer = $send_buffer; + } + Worker::$globalEvent->add($this->_socket, EventInterface::EV_WRITE, array($this, 'baseWrite')); + // Check if the send buffer will be full. + $this->checkBufferWillFull(); + return; + } + + if ($this->bufferIsFull()) { + ++self::$statistics['send_fail']; + return false; + } + + $this->_sendBuffer .= $send_buffer; + // Check if the send buffer is full. + $this->checkBufferWillFull(); + } + + /** + * Get remote IP. + * + * @return string + */ + public function getRemoteIp() + { + $pos = \strrpos($this->_remoteAddress, ':'); + if ($pos) { + return (string) \substr($this->_remoteAddress, 0, $pos); + } + return ''; + } + + /** + * Get remote port. + * + * @return int + */ + public function getRemotePort() + { + if ($this->_remoteAddress) { + return (int) \substr(\strrchr($this->_remoteAddress, ':'), 1); + } + return 0; + } + + /** + * Get remote address. + * + * @return string + */ + public function getRemoteAddress() + { + return $this->_remoteAddress; + } + + /** + * Get local IP. + * + * @return string + */ + public function getLocalIp() + { + $address = $this->getLocalAddress(); + $pos = \strrpos($address, ':'); + if (!$pos) { + return ''; + } + return \substr($address, 0, $pos); + } + + /** + * Get local port. + * + * @return int + */ + public function getLocalPort() + { + $address = $this->getLocalAddress(); + $pos = \strrpos($address, ':'); + if (!$pos) { + return 0; + } + return (int)\substr(\strrchr($address, ':'), 1); + } + + /** + * Get local address. + * + * @return string + */ + public function getLocalAddress() + { + return (string)@\stream_socket_get_name($this->_socket, false); + } + + /** + * Get send buffer queue size. + * + * @return integer + */ + public function getSendBufferQueueSize() + { + return \strlen($this->_sendBuffer); + } + + /** + * Get recv buffer queue size. + * + * @return integer + */ + public function getRecvBufferQueueSize() + { + return \strlen($this->_recvBuffer); + } + + /** + * Is ipv4. + * + * return bool. + */ + public function isIpV4() + { + if ($this->transport === 'unix') { + return false; + } + return \strpos($this->getRemoteIp(), ':') === false; + } + + /** + * Is ipv6. + * + * return bool. + */ + public function isIpV6() + { + if ($this->transport === 'unix') { + return false; + } + return \strpos($this->getRemoteIp(), ':') !== false; + } + + /** + * Pauses the reading of data. That is onMessage will not be emitted. Useful to throttle back an upload. + * + * @return void + */ + public function pauseRecv() + { + Worker::$globalEvent->del($this->_socket, EventInterface::EV_READ); + $this->_isPaused = true; + } + + /** + * Resumes reading after a call to pauseRecv. + * + * @return void + */ + public function resumeRecv() + { + if ($this->_isPaused === true) { + Worker::$globalEvent->add($this->_socket, EventInterface::EV_READ, array($this, 'baseRead')); + $this->_isPaused = false; + $this->baseRead($this->_socket, false); + } + } + + + + /** + * Base read handler. + * + * @param resource $socket + * @param bool $check_eof + * @return void + */ + public function baseRead($socket, $check_eof = true) + { + // SSL handshake. + if ($this->transport === 'ssl' && $this->_sslHandshakeCompleted !== true) { + if ($this->doSslHandshake($socket)) { + $this->_sslHandshakeCompleted = true; + if ($this->_sendBuffer) { + Worker::$globalEvent->add($socket, EventInterface::EV_WRITE, array($this, 'baseWrite')); + } + } else { + return; + } + } + + \set_error_handler(function(){}); + $buffer = \fread($socket, self::READ_BUFFER_SIZE); + \restore_error_handler(); + + // Check connection closed. + if ($buffer === '' || $buffer === false) { + if ($check_eof && (\feof($socket) || !\is_resource($socket) || $buffer === false)) { + $this->destroy(); + return; + } + } else { + $this->bytesRead += \strlen($buffer); + $this->_recvBuffer .= $buffer; + } + + $recv_len = \strlen($this->_recvBuffer); + // If the application layer protocol has been set up. + if ($this->protocol !== null) { + $parser = $this->protocol; + while ($this->_recvBuffer !== '' && !$this->_isPaused) { + // The current packet length is known. + if ($this->_currentPackageLength) { + // Data is not enough for a package. + if ($this->_currentPackageLength > $recv_len) { + break; + } + } else { + // Get current package length. + \set_error_handler(function($code, $msg, $file, $line){ + Worker::safeEcho("$msg in file $file on line $line\n"); + }); + $this->_currentPackageLength = $parser::input($this->_recvBuffer, $this); + \restore_error_handler(); + // The packet length is unknown. + if ($this->_currentPackageLength === 0) { + break; + } elseif ($this->_currentPackageLength > 0 && $this->_currentPackageLength <= $this->maxPackageSize) { + // Data is not enough for a package. + if ($this->_currentPackageLength > $recv_len) { + break; + } + } // Wrong package. + else { + Worker::safeEcho('Error package. package_length=' . \var_export($this->_currentPackageLength, true)); + $this->destroy(); + return; + } + } + + // The data is enough for a packet. + ++self::$statistics['total_request']; + // The current packet length is equal to the length of the buffer. + if ($recv_len === $this->_currentPackageLength) { + $one_request_buffer = $this->_recvBuffer; + $this->_recvBuffer = ''; + } else { + // Get a full package from the buffer. + $one_request_buffer = \substr($this->_recvBuffer, 0, $this->_currentPackageLength); + // Remove the current package from the receive buffer. + $this->_recvBuffer = \substr($this->_recvBuffer, $this->_currentPackageLength); + } + // Reset the current packet length to 0. + $this->_currentPackageLength = 0; + if (!$this->onMessage) { + continue; + } + try { + // Decode request buffer before Emitting onMessage callback. + \call_user_func($this->onMessage, $this, $parser::decode($one_request_buffer, $this)); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + return; + } + + if ($this->_recvBuffer === '' || $this->_isPaused) { + return; + } + + // Applications protocol is not set. + ++self::$statistics['total_request']; + if (!$this->onMessage) { + $this->_recvBuffer = ''; + return; + } + try { + \call_user_func($this->onMessage, $this, $this->_recvBuffer); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + // Clean receive buffer. + $this->_recvBuffer = ''; + } + + /** + * Base write handler. + * + * @return void|bool + */ + public function baseWrite() + { + \set_error_handler(function(){}); + if ($this->transport === 'ssl') { + $len = \fwrite($this->_socket, $this->_sendBuffer, 8192); + } else { + $len = \fwrite($this->_socket, $this->_sendBuffer); + } + \restore_error_handler(); + if ($len === \strlen($this->_sendBuffer)) { + $this->bytesWritten += $len; + Worker::$globalEvent->del($this->_socket, EventInterface::EV_WRITE); + $this->_sendBuffer = ''; + // Try to emit onBufferDrain callback when the send buffer becomes empty. + if ($this->onBufferDrain) { + try { + \call_user_func($this->onBufferDrain, $this); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + if ($this->_status === self::STATUS_CLOSING) { + $this->destroy(); + } + return true; + } + if ($len > 0) { + $this->bytesWritten += $len; + $this->_sendBuffer = \substr($this->_sendBuffer, $len); + } else { + ++self::$statistics['send_fail']; + $this->destroy(); + } + } + + /** + * SSL handshake. + * + * @param $socket + * @return bool + */ + public function doSslHandshake($socket){ + if (\feof($socket)) { + $this->destroy(); + return false; + } + $async = $this instanceof AsyncTcpConnection; + + /** + * We disabled ssl3 because https://blog.qualys.com/ssllabs/2014/10/15/ssl-3-is-dead-killed-by-the-poodle-attack. + * You can enable ssl3 by the codes below. + */ + /*if($async){ + $type = STREAM_CRYPTO_METHOD_SSLv2_CLIENT | STREAM_CRYPTO_METHOD_SSLv23_CLIENT | STREAM_CRYPTO_METHOD_SSLv3_CLIENT; + }else{ + $type = STREAM_CRYPTO_METHOD_SSLv2_SERVER | STREAM_CRYPTO_METHOD_SSLv23_SERVER | STREAM_CRYPTO_METHOD_SSLv3_SERVER; + }*/ + + if($async){ + $type = \STREAM_CRYPTO_METHOD_SSLv2_CLIENT | \STREAM_CRYPTO_METHOD_SSLv23_CLIENT; + }else{ + $type = \STREAM_CRYPTO_METHOD_SSLv2_SERVER | \STREAM_CRYPTO_METHOD_SSLv23_SERVER; + } + + // Hidden error. + \set_error_handler(function($errno, $errstr, $file){ + if (!Worker::$daemonize) { + Worker::safeEcho("SSL handshake error: $errstr \n"); + } + }); + $ret = \stream_socket_enable_crypto($socket, true, $type); + \restore_error_handler(); + // Negotiation has failed. + if (false === $ret) { + $this->destroy(); + return false; + } elseif (0 === $ret) { + // There isn't enough data and should try again. + return 0; + } + if (isset($this->onSslHandshake)) { + try { + \call_user_func($this->onSslHandshake, $this); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + return true; + } + + /** + * This method pulls all the data out of a readable stream, and writes it to the supplied destination. + * + * @param self $dest + * @return void + */ + public function pipe(self $dest) + { + $source = $this; + $this->onMessage = function ($source, $data) use ($dest) { + $dest->send($data); + }; + $this->onClose = function ($source) use ($dest) { + $dest->destroy(); + }; + $dest->onBufferFull = function ($dest) use ($source) { + $source->pauseRecv(); + }; + $dest->onBufferDrain = function ($dest) use ($source) { + $source->resumeRecv(); + }; + } + + /** + * Remove $length of data from receive buffer. + * + * @param int $length + * @return void + */ + public function consumeRecvBuffer($length) + { + $this->_recvBuffer = \substr($this->_recvBuffer, $length); + } + + /** + * Close connection. + * + * @param mixed $data + * @param bool $raw + * @return void + */ + public function close($data = null, $raw = false) + { + if($this->_status === self::STATUS_CONNECTING){ + $this->destroy(); + return; + } + + if ($this->_status === self::STATUS_CLOSING || $this->_status === self::STATUS_CLOSED) { + return; + } + + if ($data !== null) { + $this->send($data, $raw); + } + + $this->_status = self::STATUS_CLOSING; + + if ($this->_sendBuffer === '') { + $this->destroy(); + } else { + $this->pauseRecv(); + } + } + + /** + * Get the real socket. + * + * @return resource + */ + public function getSocket() + { + return $this->_socket; + } + + /** + * Check whether the send buffer will be full. + * + * @return void + */ + protected function checkBufferWillFull() + { + if ($this->maxSendBufferSize <= \strlen($this->_sendBuffer)) { + if ($this->onBufferFull) { + try { + \call_user_func($this->onBufferFull, $this); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + } + } + + /** + * Whether send buffer is full. + * + * @return bool + */ + protected function bufferIsFull() + { + // Buffer has been marked as full but still has data to send then the packet is discarded. + if ($this->maxSendBufferSize <= \strlen($this->_sendBuffer)) { + if ($this->onError) { + try { + \call_user_func($this->onError, $this, \WORKERMAN_SEND_FAIL, 'send buffer full and drop package'); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + return true; + } + return false; + } + + /** + * Whether send buffer is Empty. + * + * @return bool + */ + public function bufferIsEmpty() + { + return empty($this->_sendBuffer); + } + + /** + * Destroy connection. + * + * @return void + */ + public function destroy() + { + // Avoid repeated calls. + if ($this->_status === self::STATUS_CLOSED) { + return; + } + // Remove event listener. + Worker::$globalEvent->del($this->_socket, EventInterface::EV_READ); + Worker::$globalEvent->del($this->_socket, EventInterface::EV_WRITE); + + // Close socket. + \set_error_handler(function(){}); + \fclose($this->_socket); + \restore_error_handler(); + + $this->_status = self::STATUS_CLOSED; + // Try to emit onClose callback. + if ($this->onClose) { + try { + \call_user_func($this->onClose, $this); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + // Try to emit protocol::onClose + if ($this->protocol && \method_exists($this->protocol, 'onClose')) { + try { + \call_user_func(array($this->protocol, 'onClose'), $this); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + $this->_sendBuffer = $this->_recvBuffer = ''; + if ($this->_status === self::STATUS_CLOSED) { + // Cleaning up the callback to avoid memory leaks. + $this->onMessage = $this->onClose = $this->onError = $this->onBufferFull = $this->onBufferDrain = null; + // Remove from worker->connections. + if ($this->worker) { + unset($this->worker->connections[$this->_id]); + } + unset(static::$connections[$this->_id]); + } + } + + /** + * Destruct. + * + * @return void + */ + public function __destruct() + { + static $mod; + self::$statistics['connection_count']--; + if (Worker::getGracefulStop()) { + if (!isset($mod)) { + $mod = \ceil((self::$statistics['connection_count'] + 1) / 3); + } + + if (0 === self::$statistics['connection_count'] % $mod) { + Worker::log('worker[' . \posix_getpid() . '] remains ' . self::$statistics['connection_count'] . ' connection(s)'); + } + + if(0 === self::$statistics['connection_count']) { + Worker::stopAll(); + } + } + } +} diff --git a/GatewayWorker/vendor/workerman/workerman/Connection/UdpConnection.php b/GatewayWorker/vendor/workerman/workerman/Connection/UdpConnection.php new file mode 100644 index 0000000..48c0092 --- /dev/null +++ b/GatewayWorker/vendor/workerman/workerman/Connection/UdpConnection.php @@ -0,0 +1,191 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Connection; + +/** + * UdpConnection. + */ +class UdpConnection extends ConnectionInterface +{ + /** + * Application layer protocol. + * The format is like this Workerman\\Protocols\\Http. + * + * @var \Workerman\Protocols\ProtocolInterface + */ + public $protocol = null; + + /** + * Udp socket. + * + * @var resource + */ + protected $_socket = null; + + /** + * Remote address. + * + * @var string + */ + protected $_remoteAddress = ''; + + /** + * Construct. + * + * @param resource $socket + * @param string $remote_address + */ + public function __construct($socket, $remote_address) + { + $this->_socket = $socket; + $this->_remoteAddress = $remote_address; + } + + /** + * Sends data on the connection. + * + * @param string $send_buffer + * @param bool $raw + * @return void|boolean + */ + public function send($send_buffer, $raw = false) + { + if (false === $raw && $this->protocol) { + $parser = $this->protocol; + $send_buffer = $parser::encode($send_buffer, $this); + if ($send_buffer === '') { + return; + } + } + return \strlen($send_buffer) === \stream_socket_sendto($this->_socket, $send_buffer, 0, $this->_remoteAddress); + } + + /** + * Get remote IP. + * + * @return string + */ + public function getRemoteIp() + { + $pos = \strrpos($this->_remoteAddress, ':'); + if ($pos) { + return \trim(\substr($this->_remoteAddress, 0, $pos), '[]'); + } + return ''; + } + + /** + * Get remote port. + * + * @return int + */ + public function getRemotePort() + { + if ($this->_remoteAddress) { + return (int)\substr(\strrchr($this->_remoteAddress, ':'), 1); + } + return 0; + } + + /** + * Get remote address. + * + * @return string + */ + public function getRemoteAddress() + { + return $this->_remoteAddress; + } + + /** + * Get local IP. + * + * @return string + */ + public function getLocalIp() + { + $address = $this->getLocalAddress(); + $pos = \strrpos($address, ':'); + if (!$pos) { + return ''; + } + return \substr($address, 0, $pos); + } + + /** + * Get local port. + * + * @return int + */ + public function getLocalPort() + { + $address = $this->getLocalAddress(); + $pos = \strrpos($address, ':'); + if (!$pos) { + return 0; + } + return (int)\substr(\strrchr($address, ':'), 1); + } + + /** + * Get local address. + * + * @return string + */ + public function getLocalAddress() + { + return (string)@\stream_socket_get_name($this->_socket, false); + } + + /** + * Is ipv4. + * + * @return bool. + */ + public function isIpV4() + { + if ($this->transport === 'unix') { + return false; + } + return \strpos($this->getRemoteIp(), ':') === false; + } + + /** + * Is ipv6. + * + * @return bool. + */ + public function isIpV6() + { + if ($this->transport === 'unix') { + return false; + } + return \strpos($this->getRemoteIp(), ':') !== false; + } + + /** + * Close connection. + * + * @param mixed $data + * @param bool $raw + * @return bool + */ + public function close($data = null, $raw = false) + { + if ($data !== null) { + $this->send($data, $raw); + } + return true; + } +} diff --git a/GatewayWorker/vendor/workerman/workerman/Events/Ev.php b/GatewayWorker/vendor/workerman/workerman/Events/Ev.php new file mode 100644 index 0000000..8dba860 --- /dev/null +++ b/GatewayWorker/vendor/workerman/workerman/Events/Ev.php @@ -0,0 +1,195 @@ + + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events; + +use Workerman\Worker; +use \EvWatcher; + +/** + * ev eventloop + */ +class Ev implements EventInterface +{ + /** + * All listeners for read/write event. + * + * @var array + */ + protected $_allEvents = array(); + + /** + * Event listeners of signal. + * + * @var array + */ + protected $_eventSignal = array(); + + /** + * All timer event listeners. + * [func, args, event, flag, time_interval] + * + * @var array + */ + protected $_eventTimer = array(); + + /** + * Timer id. + * + * @var int + */ + protected static $_timerId = 1; + + /** + * Add a timer. + * {@inheritdoc} + */ + public function add($fd, $flag, $func, $args = null) + { + $callback = function ($event, $socket) use ($fd, $func) { + try { + \call_user_func($func, $fd); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + }; + switch ($flag) { + case self::EV_SIGNAL: + $event = new \EvSignal($fd, $callback); + $this->_eventSignal[$fd] = $event; + return true; + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + $repeat = $flag === self::EV_TIMER_ONCE ? 0 : $fd; + $param = array($func, (array)$args, $flag, $fd, self::$_timerId); + $event = new \EvTimer($fd, $repeat, array($this, 'timerCallback'), $param); + $this->_eventTimer[self::$_timerId] = $event; + return self::$_timerId++; + default : + $fd_key = (int)$fd; + $real_flag = $flag === self::EV_READ ? \Ev::READ : \Ev::WRITE; + $event = new \EvIo($fd, $real_flag, $callback); + $this->_allEvents[$fd_key][$flag] = $event; + return true; + } + + } + + /** + * Remove a timer. + * {@inheritdoc} + */ + public function del($fd, $flag) + { + switch ($flag) { + case self::EV_READ: + case self::EV_WRITE: + $fd_key = (int)$fd; + if (isset($this->_allEvents[$fd_key][$flag])) { + $this->_allEvents[$fd_key][$flag]->stop(); + unset($this->_allEvents[$fd_key][$flag]); + } + if (empty($this->_allEvents[$fd_key])) { + unset($this->_allEvents[$fd_key]); + } + break; + case self::EV_SIGNAL: + $fd_key = (int)$fd; + if (isset($this->_eventSignal[$fd_key])) { + $this->_eventSignal[$fd_key]->stop(); + unset($this->_eventSignal[$fd_key]); + } + break; + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + if (isset($this->_eventTimer[$fd])) { + $this->_eventTimer[$fd]->stop(); + unset($this->_eventTimer[$fd]); + } + break; + } + return true; + } + + /** + * Timer callback. + * + * @param EvWatcher $event + */ + public function timerCallback(EvWatcher $event) + { + $param = $event->data; + $timer_id = $param[4]; + if ($param[2] === self::EV_TIMER_ONCE) { + $this->_eventTimer[$timer_id]->stop(); + unset($this->_eventTimer[$timer_id]); + } + try { + \call_user_func_array($param[0], $param[1]); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + + /** + * Remove all timers. + * + * @return void + */ + public function clearAllTimer() + { + foreach ($this->_eventTimer as $event) { + $event->stop(); + } + $this->_eventTimer = array(); + } + + /** + * Main loop. + * + * @see EventInterface::loop() + */ + public function loop() + { + \Ev::run(); + } + + /** + * Destroy loop. + * + * @return void + */ + public function destroy() + { + foreach ($this->_allEvents as $event) { + $event->stop(); + } + } + + /** + * Get timer count. + * + * @return integer + */ + public function getTimerCount() + { + return \count($this->_eventTimer); + } +} diff --git a/GatewayWorker/vendor/workerman/workerman/Events/Event.php b/GatewayWorker/vendor/workerman/workerman/Events/Event.php new file mode 100644 index 0000000..c7d7aa2 --- /dev/null +++ b/GatewayWorker/vendor/workerman/workerman/Events/Event.php @@ -0,0 +1,219 @@ + + * @copyright 有个鬼<42765633@qq.com> + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events; + +use Workerman\Worker; + +/** + * libevent eventloop + */ +class Event implements EventInterface +{ + /** + * Event base. + * @var object + */ + protected $_eventBase = null; + + /** + * All listeners for read/write event. + * @var array + */ + protected $_allEvents = array(); + + /** + * Event listeners of signal. + * @var array + */ + protected $_eventSignal = array(); + + /** + * All timer event listeners. + * [func, args, event, flag, time_interval] + * @var array + */ + protected $_eventTimer = array(); + + /** + * Timer id. + * @var int + */ + protected static $_timerId = 1; + + /** + * construct + * @return void + */ + public function __construct() + { + if (\class_exists('\\\\EventBase', false)) { + $class_name = '\\\\EventBase'; + } else { + $class_name = '\EventBase'; + } + $this->_eventBase = new $class_name(); + } + + /** + * @see EventInterface::add() + */ + public function add($fd, $flag, $func, $args=array()) + { + if (\class_exists('\\\\Event', false)) { + $class_name = '\\\\Event'; + } else { + $class_name = '\Event'; + } + switch ($flag) { + case self::EV_SIGNAL: + + $fd_key = (int)$fd; + $event = $class_name::signal($this->_eventBase, $fd, $func); + if (!$event||!$event->add()) { + return false; + } + $this->_eventSignal[$fd_key] = $event; + return true; + + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + + $param = array($func, (array)$args, $flag, $fd, self::$_timerId); + $event = new $class_name($this->_eventBase, -1, $class_name::TIMEOUT|$class_name::PERSIST, array($this, "timerCallback"), $param); + if (!$event||!$event->addTimer($fd)) { + return false; + } + $this->_eventTimer[self::$_timerId] = $event; + return self::$_timerId++; + + default : + $fd_key = (int)$fd; + $real_flag = $flag === self::EV_READ ? $class_name::READ | $class_name::PERSIST : $class_name::WRITE | $class_name::PERSIST; + $event = new $class_name($this->_eventBase, $fd, $real_flag, $func, $fd); + if (!$event||!$event->add()) { + return false; + } + $this->_allEvents[$fd_key][$flag] = $event; + return true; + } + } + + /** + * @see Events\EventInterface::del() + */ + public function del($fd, $flag) + { + switch ($flag) { + + case self::EV_READ: + case self::EV_WRITE: + + $fd_key = (int)$fd; + if (isset($this->_allEvents[$fd_key][$flag])) { + $this->_allEvents[$fd_key][$flag]->del(); + unset($this->_allEvents[$fd_key][$flag]); + } + if (empty($this->_allEvents[$fd_key])) { + unset($this->_allEvents[$fd_key]); + } + break; + + case self::EV_SIGNAL: + $fd_key = (int)$fd; + if (isset($this->_eventSignal[$fd_key])) { + $this->_eventSignal[$fd_key]->del(); + unset($this->_eventSignal[$fd_key]); + } + break; + + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + if (isset($this->_eventTimer[$fd])) { + $this->_eventTimer[$fd]->del(); + unset($this->_eventTimer[$fd]); + } + break; + } + return true; + } + + /** + * Timer callback. + * @param null $fd + * @param int $what + * @param int $timer_id + */ + public function timerCallback($fd, $what, $param) + { + $timer_id = $param[4]; + + if ($param[2] === self::EV_TIMER_ONCE) { + $this->_eventTimer[$timer_id]->del(); + unset($this->_eventTimer[$timer_id]); + } + + try { + \call_user_func_array($param[0], $param[1]); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + + /** + * @see Events\EventInterface::clearAllTimer() + * @return void + */ + public function clearAllTimer() + { + foreach ($this->_eventTimer as $event) { + $event->del(); + } + $this->_eventTimer = array(); + } + + + /** + * @see EventInterface::loop() + */ + public function loop() + { + $this->_eventBase->loop(); + } + + /** + * Destroy loop. + * + * @return void + */ + public function destroy() + { + foreach ($this->_eventSignal as $event) { + $event->del(); + } + } + + /** + * Get timer count. + * + * @return integer + */ + public function getTimerCount() + { + return \count($this->_eventTimer); + } +} diff --git a/GatewayWorker/vendor/workerman/workerman/Events/EventInterface.php b/GatewayWorker/vendor/workerman/workerman/Events/EventInterface.php new file mode 100644 index 0000000..88f38f2 --- /dev/null +++ b/GatewayWorker/vendor/workerman/workerman/Events/EventInterface.php @@ -0,0 +1,107 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events; + +interface EventInterface +{ + /** + * Read event. + * + * @var int + */ + const EV_READ = 1; + + /** + * Write event. + * + * @var int + */ + const EV_WRITE = 2; + + /** + * Except event + * + * @var int + */ + const EV_EXCEPT = 3; + + /** + * Signal event. + * + * @var int + */ + const EV_SIGNAL = 4; + + /** + * Timer event. + * + * @var int + */ + const EV_TIMER = 8; + + /** + * Timer once event. + * + * @var int + */ + const EV_TIMER_ONCE = 16; + + /** + * Add event listener to event loop. + * + * @param mixed $fd + * @param int $flag + * @param callable $func + * @param mixed $args + * @return bool + */ + public function add($fd, $flag, $func, $args = null); + + /** + * Remove event listener from event loop. + * + * @param mixed $fd + * @param int $flag + * @return bool + */ + public function del($fd, $flag); + + /** + * Remove all timers. + * + * @return void + */ + public function clearAllTimer(); + + /** + * Main loop. + * + * @return void + */ + public function loop(); + + /** + * Destroy loop. + * + * @return mixed + */ + public function destroy(); + + /** + * Get Timer count. + * + * @return mixed + */ + public function getTimerCount(); +} diff --git a/GatewayWorker/vendor/workerman/workerman/Events/Libevent.php b/GatewayWorker/vendor/workerman/workerman/Events/Libevent.php new file mode 100644 index 0000000..ac3427c --- /dev/null +++ b/GatewayWorker/vendor/workerman/workerman/Events/Libevent.php @@ -0,0 +1,227 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events; + +use Workerman\Worker; + +/** + * libevent eventloop + */ +class Libevent implements EventInterface +{ + /** + * Event base. + * + * @var resource + */ + protected $_eventBase = null; + + /** + * All listeners for read/write event. + * + * @var array + */ + protected $_allEvents = array(); + + /** + * Event listeners of signal. + * + * @var array + */ + protected $_eventSignal = array(); + + /** + * All timer event listeners. + * [func, args, event, flag, time_interval] + * + * @var array + */ + protected $_eventTimer = array(); + + /** + * construct + */ + public function __construct() + { + $this->_eventBase = \event_base_new(); + } + + /** + * {@inheritdoc} + */ + public function add($fd, $flag, $func, $args = array()) + { + switch ($flag) { + case self::EV_SIGNAL: + $fd_key = (int)$fd; + $real_flag = \EV_SIGNAL | \EV_PERSIST; + $this->_eventSignal[$fd_key] = \event_new(); + if (!\event_set($this->_eventSignal[$fd_key], $fd, $real_flag, $func, null)) { + return false; + } + if (!\event_base_set($this->_eventSignal[$fd_key], $this->_eventBase)) { + return false; + } + if (!\event_add($this->_eventSignal[$fd_key])) { + return false; + } + return true; + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + $event = \event_new(); + $timer_id = (int)$event; + if (!\event_set($event, 0, \EV_TIMEOUT, array($this, 'timerCallback'), $timer_id)) { + return false; + } + + if (!\event_base_set($event, $this->_eventBase)) { + return false; + } + + $time_interval = $fd * 1000000; + if (!\event_add($event, $time_interval)) { + return false; + } + $this->_eventTimer[$timer_id] = array($func, (array)$args, $event, $flag, $time_interval); + return $timer_id; + + default : + $fd_key = (int)$fd; + $real_flag = $flag === self::EV_READ ? \EV_READ | \EV_PERSIST : \EV_WRITE | \EV_PERSIST; + + $event = \event_new(); + + if (!\event_set($event, $fd, $real_flag, $func, null)) { + return false; + } + + if (!\event_base_set($event, $this->_eventBase)) { + return false; + } + + if (!\event_add($event)) { + return false; + } + + $this->_allEvents[$fd_key][$flag] = $event; + + return true; + } + + } + + /** + * {@inheritdoc} + */ + public function del($fd, $flag) + { + switch ($flag) { + case self::EV_READ: + case self::EV_WRITE: + $fd_key = (int)$fd; + if (isset($this->_allEvents[$fd_key][$flag])) { + \event_del($this->_allEvents[$fd_key][$flag]); + unset($this->_allEvents[$fd_key][$flag]); + } + if (empty($this->_allEvents[$fd_key])) { + unset($this->_allEvents[$fd_key]); + } + break; + case self::EV_SIGNAL: + $fd_key = (int)$fd; + if (isset($this->_eventSignal[$fd_key])) { + \event_del($this->_eventSignal[$fd_key]); + unset($this->_eventSignal[$fd_key]); + } + break; + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + // 这里 fd 为timerid + if (isset($this->_eventTimer[$fd])) { + \event_del($this->_eventTimer[$fd][2]); + unset($this->_eventTimer[$fd]); + } + break; + } + return true; + } + + /** + * Timer callback. + * + * @param mixed $_null1 + * @param int $_null2 + * @param mixed $timer_id + */ + protected function timerCallback($_null1, $_null2, $timer_id) + { + if ($this->_eventTimer[$timer_id][3] === self::EV_TIMER) { + \event_add($this->_eventTimer[$timer_id][2], $this->_eventTimer[$timer_id][4]); + } + try { + \call_user_func_array($this->_eventTimer[$timer_id][0], $this->_eventTimer[$timer_id][1]); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + if (isset($this->_eventTimer[$timer_id]) && $this->_eventTimer[$timer_id][3] === self::EV_TIMER_ONCE) { + $this->del($timer_id, self::EV_TIMER_ONCE); + } + } + + /** + * {@inheritdoc} + */ + public function clearAllTimer() + { + foreach ($this->_eventTimer as $task_data) { + \event_del($task_data[2]); + } + $this->_eventTimer = array(); + } + + /** + * {@inheritdoc} + */ + public function loop() + { + \event_base_loop($this->_eventBase); + } + + /** + * Destroy loop. + * + * @return void + */ + public function destroy() + { + foreach ($this->_eventSignal as $event) { + \event_del($event); + } + } + + /** + * Get timer count. + * + * @return integer + */ + public function getTimerCount() + { + return \count($this->_eventTimer); + } +} + diff --git a/GatewayWorker/vendor/workerman/workerman/Events/React/Base.php b/GatewayWorker/vendor/workerman/workerman/Events/React/Base.php new file mode 100644 index 0000000..a0a33ae --- /dev/null +++ b/GatewayWorker/vendor/workerman/workerman/Events/React/Base.php @@ -0,0 +1,264 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events\React; + +use Workerman\Events\EventInterface; +use React\EventLoop\TimerInterface; +use React\EventLoop\LoopInterface; + +/** + * Class StreamSelectLoop + * @package Workerman\Events\React + */ +class Base implements LoopInterface +{ + /** + * @var array + */ + protected $_timerIdMap = array(); + + /** + * @var int + */ + protected $_timerIdIndex = 0; + + /** + * @var array + */ + protected $_signalHandlerMap = array(); + + /** + * @var LoopInterface + */ + protected $_eventLoop = null; + + /** + * Base constructor. + */ + public function __construct() + { + $this->_eventLoop = new \React\EventLoop\StreamSelectLoop(); + } + + /** + * Add event listener to event loop. + * + * @param $fd + * @param $flag + * @param $func + * @param array $args + * @return bool + */ + public function add($fd, $flag, $func, array $args = array()) + { + $args = (array)$args; + switch ($flag) { + case EventInterface::EV_READ: + return $this->addReadStream($fd, $func); + case EventInterface::EV_WRITE: + return $this->addWriteStream($fd, $func); + case EventInterface::EV_SIGNAL: + if (isset($this->_signalHandlerMap[$fd])) { + $this->removeSignal($fd, $this->_signalHandlerMap[$fd]); + } + $this->_signalHandlerMap[$fd] = $func; + return $this->addSignal($fd, $func); + case EventInterface::EV_TIMER: + $timer_obj = $this->addPeriodicTimer($fd, function() use ($func, $args) { + \call_user_func_array($func, $args); + }); + $this->_timerIdMap[++$this->_timerIdIndex] = $timer_obj; + return $this->_timerIdIndex; + case EventInterface::EV_TIMER_ONCE: + $index = ++$this->_timerIdIndex; + $timer_obj = $this->addTimer($fd, function() use ($func, $args, $index) { + $this->del($index,EventInterface::EV_TIMER_ONCE); + \call_user_func_array($func, $args); + }); + $this->_timerIdMap[$index] = $timer_obj; + return $this->_timerIdIndex; + } + return false; + } + + /** + * Remove event listener from event loop. + * + * @param mixed $fd + * @param int $flag + * @return bool + */ + public function del($fd, $flag) + { + switch ($flag) { + case EventInterface::EV_READ: + return $this->removeReadStream($fd); + case EventInterface::EV_WRITE: + return $this->removeWriteStream($fd); + case EventInterface::EV_SIGNAL: + if (!isset($this->_eventLoop[$fd])) { + return false; + } + $func = $this->_eventLoop[$fd]; + unset($this->_eventLoop[$fd]); + return $this->removeSignal($fd, $func); + + case EventInterface::EV_TIMER: + case EventInterface::EV_TIMER_ONCE: + if (isset($this->_timerIdMap[$fd])){ + $timer_obj = $this->_timerIdMap[$fd]; + unset($this->_timerIdMap[$fd]); + $this->cancelTimer($timer_obj); + return true; + } + } + return false; + } + + + /** + * Main loop. + * + * @return void + */ + public function loop() + { + $this->run(); + } + + + /** + * Destroy loop. + * + * @return void + */ + public function destroy() + { + + } + + /** + * Get timer count. + * + * @return integer + */ + public function getTimerCount() + { + return \count($this->_timerIdMap); + } + + /** + * @param resource $stream + * @param callable $listener + */ + public function addReadStream($stream, $listener) + { + return $this->_eventLoop->addReadStream($stream, $listener); + } + + /** + * @param resource $stream + * @param callable $listener + */ + public function addWriteStream($stream, $listener) + { + return $this->_eventLoop->addWriteStream($stream, $listener); + } + + /** + * @param resource $stream + */ + public function removeReadStream($stream) + { + return $this->_eventLoop->removeReadStream($stream); + } + + /** + * @param resource $stream + */ + public function removeWriteStream($stream) + { + return $this->_eventLoop->removeWriteStream($stream); + } + + /** + * @param float|int $interval + * @param callable $callback + * @return \React\EventLoop\Timer\Timer|TimerInterface + */ + public function addTimer($interval, $callback) + { + return $this->_eventLoop->addTimer($interval, $callback); + } + + /** + * @param float|int $interval + * @param callable $callback + * @return \React\EventLoop\Timer\Timer|TimerInterface + */ + public function addPeriodicTimer($interval, $callback) + { + return $this->_eventLoop->addPeriodicTimer($interval, $callback); + } + + /** + * @param TimerInterface $timer + */ + public function cancelTimer(TimerInterface $timer) + { + return $this->_eventLoop->cancelTimer($timer); + } + + /** + * @param callable $listener + */ + public function futureTick($listener) + { + return $this->_eventLoop->futureTick($listener); + } + + /** + * @param int $signal + * @param callable $listener + */ + public function addSignal($signal, $listener) + { + return $this->_eventLoop->addSignal($signal, $listener); + } + + /** + * @param int $signal + * @param callable $listener + */ + public function removeSignal($signal, $listener) + { + return $this->_eventLoop->removeSignal($signal, $listener); + } + + /** + * Run. + */ + public function run() + { + return $this->_eventLoop->run(); + } + + /** + * Stop. + */ + public function stop() + { + return $this->_eventLoop->stop(); + } +} diff --git a/GatewayWorker/vendor/workerman/workerman/Events/React/ExtEventLoop.php b/GatewayWorker/vendor/workerman/workerman/Events/React/ExtEventLoop.php new file mode 100644 index 0000000..3dab25b --- /dev/null +++ b/GatewayWorker/vendor/workerman/workerman/Events/React/ExtEventLoop.php @@ -0,0 +1,27 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events\React; + +/** + * Class ExtEventLoop + * @package Workerman\Events\React + */ +class ExtEventLoop extends Base +{ + + public function __construct() + { + $this->_eventLoop = new \React\EventLoop\ExtEventLoop(); + } +} diff --git a/GatewayWorker/vendor/workerman/workerman/Events/React/ExtLibEventLoop.php b/GatewayWorker/vendor/workerman/workerman/Events/React/ExtLibEventLoop.php new file mode 100644 index 0000000..eb02b35 --- /dev/null +++ b/GatewayWorker/vendor/workerman/workerman/Events/React/ExtLibEventLoop.php @@ -0,0 +1,27 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events\React; +use Workerman\Events\EventInterface; + +/** + * Class ExtLibEventLoop + * @package Workerman\Events\React + */ +class ExtLibEventLoop extends Base +{ + public function __construct() + { + $this->_eventLoop = new \React\EventLoop\ExtLibeventLoop(); + } +} diff --git a/GatewayWorker/vendor/workerman/workerman/Events/React/StreamSelectLoop.php b/GatewayWorker/vendor/workerman/workerman/Events/React/StreamSelectLoop.php new file mode 100644 index 0000000..7f5f94b --- /dev/null +++ b/GatewayWorker/vendor/workerman/workerman/Events/React/StreamSelectLoop.php @@ -0,0 +1,26 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events\React; + +/** + * Class StreamSelectLoop + * @package Workerman\Events\React + */ +class StreamSelectLoop extends Base +{ + public function __construct() + { + $this->_eventLoop = new \React\EventLoop\StreamSelectLoop(); + } +} diff --git a/GatewayWorker/vendor/workerman/workerman/Events/Select.php b/GatewayWorker/vendor/workerman/workerman/Events/Select.php new file mode 100644 index 0000000..1939ed3 --- /dev/null +++ b/GatewayWorker/vendor/workerman/workerman/Events/Select.php @@ -0,0 +1,338 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events; + +/** + * select eventloop + */ +class Select implements EventInterface +{ + /** + * All listeners for read/write event. + * + * @var array + */ + public $_allEvents = array(); + + /** + * Event listeners of signal. + * + * @var array + */ + public $_signalEvents = array(); + + /** + * Fds waiting for read event. + * + * @var array + */ + protected $_readFds = array(); + + /** + * Fds waiting for write event. + * + * @var array + */ + protected $_writeFds = array(); + + /** + * Fds waiting for except event. + * + * @var array + */ + protected $_exceptFds = array(); + + /** + * Timer scheduler. + * {['data':timer_id, 'priority':run_timestamp], ..} + * + * @var \SplPriorityQueue + */ + protected $_scheduler = null; + + /** + * All timer event listeners. + * [[func, args, flag, timer_interval], ..] + * + * @var array + */ + protected $_eventTimer = array(); + + /** + * Timer id. + * + * @var int + */ + protected $_timerId = 1; + + /** + * Select timeout. + * + * @var int + */ + protected $_selectTimeout = 100000000; + + /** + * Paired socket channels + * + * @var array + */ + protected $channel = array(); + + /** + * Construct. + */ + public function __construct() + { + // Init SplPriorityQueue. + $this->_scheduler = new \SplPriorityQueue(); + $this->_scheduler->setExtractFlags(\SplPriorityQueue::EXTR_BOTH); + } + + /** + * {@inheritdoc} + */ + public function add($fd, $flag, $func, $args = array()) + { + switch ($flag) { + case self::EV_READ: + case self::EV_WRITE: + $count = $flag === self::EV_READ ? \count($this->_readFds) : \count($this->_writeFds); + if ($count >= 1024) { + echo "Warning: system call select exceeded the maximum number of connections 1024, please install event/libevent extension for more connections.\n"; + } else if (\DIRECTORY_SEPARATOR !== '/' && $count >= 256) { + echo "Warning: system call select exceeded the maximum number of connections 256.\n"; + } + $fd_key = (int)$fd; + $this->_allEvents[$fd_key][$flag] = array($func, $fd); + if ($flag === self::EV_READ) { + $this->_readFds[$fd_key] = $fd; + } else { + $this->_writeFds[$fd_key] = $fd; + } + break; + case self::EV_EXCEPT: + $fd_key = (int)$fd; + $this->_allEvents[$fd_key][$flag] = array($func, $fd); + $this->_exceptFds[$fd_key] = $fd; + break; + case self::EV_SIGNAL: + // Windows not support signal. + if(\DIRECTORY_SEPARATOR !== '/') { + return false; + } + $fd_key = (int)$fd; + $this->_signalEvents[$fd_key][$flag] = array($func, $fd); + \pcntl_signal($fd, array($this, 'signalHandler')); + break; + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + $timer_id = $this->_timerId++; + $run_time = \microtime(true) + $fd; + $this->_scheduler->insert($timer_id, -$run_time); + $this->_eventTimer[$timer_id] = array($func, (array)$args, $flag, $fd); + $select_timeout = ($run_time - \microtime(true)) * 1000000; + if( $this->_selectTimeout > $select_timeout ){ + $this->_selectTimeout = $select_timeout; + } + return $timer_id; + } + + return true; + } + + /** + * Signal handler. + * + * @param int $signal + */ + public function signalHandler($signal) + { + \call_user_func_array($this->_signalEvents[$signal][self::EV_SIGNAL][0], array($signal)); + } + + /** + * {@inheritdoc} + */ + public function del($fd, $flag) + { + $fd_key = (int)$fd; + switch ($flag) { + case self::EV_READ: + unset($this->_allEvents[$fd_key][$flag], $this->_readFds[$fd_key]); + if (empty($this->_allEvents[$fd_key])) { + unset($this->_allEvents[$fd_key]); + } + return true; + case self::EV_WRITE: + unset($this->_allEvents[$fd_key][$flag], $this->_writeFds[$fd_key]); + if (empty($this->_allEvents[$fd_key])) { + unset($this->_allEvents[$fd_key]); + } + return true; + case self::EV_EXCEPT: + unset($this->_allEvents[$fd_key][$flag], $this->_exceptFds[$fd_key]); + if(empty($this->_allEvents[$fd_key])) + { + unset($this->_allEvents[$fd_key]); + } + return true; + case self::EV_SIGNAL: + if(\DIRECTORY_SEPARATOR !== '/') { + return false; + } + unset($this->_signalEvents[$fd_key]); + \pcntl_signal($fd, SIG_IGN); + break; + case self::EV_TIMER: + case self::EV_TIMER_ONCE; + unset($this->_eventTimer[$fd_key]); + return true; + } + return false; + } + + /** + * Tick for timer. + * + * @return void + */ + protected function tick() + { + while (!$this->_scheduler->isEmpty()) { + $scheduler_data = $this->_scheduler->top(); + $timer_id = $scheduler_data['data']; + $next_run_time = -$scheduler_data['priority']; + $time_now = \microtime(true); + $this->_selectTimeout = ($next_run_time - $time_now) * 1000000; + if ($this->_selectTimeout <= 0) { + $this->_scheduler->extract(); + + if (!isset($this->_eventTimer[$timer_id])) { + continue; + } + + // [func, args, flag, timer_interval] + $task_data = $this->_eventTimer[$timer_id]; + if ($task_data[2] === self::EV_TIMER) { + $next_run_time = $time_now + $task_data[3]; + $this->_scheduler->insert($timer_id, -$next_run_time); + } + \call_user_func_array($task_data[0], $task_data[1]); + if (isset($this->_eventTimer[$timer_id]) && $task_data[2] === self::EV_TIMER_ONCE) { + $this->del($timer_id, self::EV_TIMER_ONCE); + } + continue; + } + return; + } + $this->_selectTimeout = 100000000; + } + + /** + * {@inheritdoc} + */ + public function clearAllTimer() + { + $this->_scheduler = new \SplPriorityQueue(); + $this->_scheduler->setExtractFlags(\SplPriorityQueue::EXTR_BOTH); + $this->_eventTimer = array(); + } + + /** + * {@inheritdoc} + */ + public function loop() + { + while (1) { + if(\DIRECTORY_SEPARATOR === '/') { + // Calls signal handlers for pending signals + \pcntl_signal_dispatch(); + } + + $read = $this->_readFds; + $write = $this->_writeFds; + $except = $this->_exceptFds; + + if ($read || $write || $except) { + // Waiting read/write/signal/timeout events. + set_error_handler(function(){}); + $ret = stream_select($read, $write, $except, 0, $this->_selectTimeout); + restore_error_handler(); + } else { + usleep($this->_selectTimeout); + $ret = false; + } + + + if (!$this->_scheduler->isEmpty()) { + $this->tick(); + } + + if (!$ret) { + continue; + } + + if ($read) { + foreach ($read as $fd) { + $fd_key = (int)$fd; + if (isset($this->_allEvents[$fd_key][self::EV_READ])) { + \call_user_func_array($this->_allEvents[$fd_key][self::EV_READ][0], + array($this->_allEvents[$fd_key][self::EV_READ][1])); + } + } + } + + if ($write) { + foreach ($write as $fd) { + $fd_key = (int)$fd; + if (isset($this->_allEvents[$fd_key][self::EV_WRITE])) { + \call_user_func_array($this->_allEvents[$fd_key][self::EV_WRITE][0], + array($this->_allEvents[$fd_key][self::EV_WRITE][1])); + } + } + } + + if($except) { + foreach($except as $fd) { + $fd_key = (int) $fd; + if(isset($this->_allEvents[$fd_key][self::EV_EXCEPT])) { + \call_user_func_array($this->_allEvents[$fd_key][self::EV_EXCEPT][0], + array($this->_allEvents[$fd_key][self::EV_EXCEPT][1])); + } + } + } + } + } + + /** + * Destroy loop. + * + * @return void + */ + public function destroy() + { + + } + + /** + * Get timer count. + * + * @return integer + */ + public function getTimerCount() + { + return \count($this->_eventTimer); + } +} diff --git a/GatewayWorker/vendor/workerman/workerman/Events/Swoole.php b/GatewayWorker/vendor/workerman/workerman/Events/Swoole.php new file mode 100644 index 0000000..1778ecd --- /dev/null +++ b/GatewayWorker/vendor/workerman/workerman/Events/Swoole.php @@ -0,0 +1,221 @@ + + * @link http://www.workerman.net/ + * @link https://github.com/ares333/Workerman + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events; + +use Swoole\Event; +use Swoole\Timer; + +class Swoole implements EventInterface +{ + + protected $_timer = array(); + + protected $_timerOnceMap = array(); + + protected $mapId = 0; + + protected $_fd = array(); + + // milisecond + public static $signalDispatchInterval = 200; + + protected $_hasSignal = false; + + /** + * + * {@inheritdoc} + * + * @see \Workerman\Events\EventInterface::add() + */ + public function add($fd, $flag, $func, $args = null) + { + if (! isset($args)) { + $args = array(); + } + switch ($flag) { + case self::EV_SIGNAL: + $res = \pcntl_signal($fd, $func, false); + if (! $this->_hasSignal && $res) { + Timer::tick(static::$signalDispatchInterval, + function () { + \pcntl_signal_dispatch(); + }); + $this->_hasSignal = true; + } + return $res; + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + $method = self::EV_TIMER === $flag ? 'tick' : 'after'; + if ($this->mapId > \PHP_INT_MAX) { + $this->mapId = 0; + } + $mapId = $this->mapId++; + $timer_id = Timer::$method($fd * 1000, + function ($timer_id = null) use ($func, $args, $mapId) { + \call_user_func_array($func, $args); + // EV_TIMER_ONCE + if (! isset($timer_id)) { + // may be deleted in $func + if (\array_key_exists($mapId, $this->_timerOnceMap)) { + $timer_id = $this->_timerOnceMap[$mapId]; + unset($this->_timer[$timer_id], + $this->_timerOnceMap[$mapId]); + } + } + }); + if ($flag === self::EV_TIMER_ONCE) { + $this->_timerOnceMap[$mapId] = $timer_id; + $this->_timer[$timer_id] = $mapId; + } else { + $this->_timer[$timer_id] = null; + } + return $timer_id; + case self::EV_READ: + case self::EV_WRITE: + $fd_key = (int) $fd; + if (! isset($this->_fd[$fd_key])) { + if ($flag === self::EV_READ) { + $res = Event::add($fd, $func, null, SWOOLE_EVENT_READ); + $fd_type = SWOOLE_EVENT_READ; + } else { + $res = Event::add($fd, null, $func, SWOOLE_EVENT_WRITE); + $fd_type = SWOOLE_EVENT_WRITE; + } + if ($res) { + $this->_fd[$fd_key] = $fd_type; + } + } else { + $fd_val = $this->_fd[$fd_key]; + $res = true; + if ($flag === self::EV_READ) { + if (($fd_val & SWOOLE_EVENT_READ) !== SWOOLE_EVENT_READ) { + $res = Event::set($fd, $func, null, + SWOOLE_EVENT_READ | SWOOLE_EVENT_WRITE); + $this->_fd[$fd_key] |= SWOOLE_EVENT_READ; + } + } else { + if (($fd_val & SWOOLE_EVENT_WRITE) !== SWOOLE_EVENT_WRITE) { + $res = Event::set($fd, null, $func, + SWOOLE_EVENT_READ | SWOOLE_EVENT_WRITE); + $this->_fd[$fd_key] |= SWOOLE_EVENT_WRITE; + } + } + } + return $res; + } + } + + /** + * + * {@inheritdoc} + * + * @see \Workerman\Events\EventInterface::del() + */ + public function del($fd, $flag) + { + switch ($flag) { + case self::EV_SIGNAL: + return \pcntl_signal($fd, SIG_IGN, false); + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + // already remove in EV_TIMER_ONCE callback. + if (! \array_key_exists($fd, $this->_timer)) { + return true; + } + $res = Timer::clear($fd); + if ($res) { + $mapId = $this->_timer[$fd]; + if (isset($mapId)) { + unset($this->_timerOnceMap[$mapId]); + } + unset($this->_timer[$fd]); + } + return $res; + case self::EV_READ: + case self::EV_WRITE: + $fd_key = (int) $fd; + if (isset($this->_fd[$fd_key])) { + $fd_val = $this->_fd[$fd_key]; + if ($flag === self::EV_READ) { + $flag_remove = ~ SWOOLE_EVENT_READ; + } else { + $flag_remove = ~ SWOOLE_EVENT_WRITE; + } + $fd_val &= $flag_remove; + if (0 === $fd_val) { + $res = Event::del($fd); + if ($res) { + unset($this->_fd[$fd_key]); + } + } else { + $res = Event::set($fd, null, null, $fd_val); + if ($res) { + $this->_fd[$fd_key] = $fd_val; + } + } + } else { + $res = true; + } + return $res; + } + } + + /** + * + * {@inheritdoc} + * + * @see \Workerman\Events\EventInterface::clearAllTimer() + */ + public function clearAllTimer() + { + foreach (array_keys($this->_timer) as $v) { + Timer::clear($v); + } + $this->_timer = array(); + $this->_timerOnceMap = array(); + } + + /** + * + * {@inheritdoc} + * + * @see \Workerman\Events\EventInterface::loop() + */ + public function loop() + { + Event::wait(); + } + + /** + * + * {@inheritdoc} + * + * @see \Workerman\Events\EventInterface::destroy() + */ + public function destroy() + { + //Event::exit(); + } + + /** + * + * {@inheritdoc} + * + * @see \Workerman\Events\EventInterface::getTimerCount() + */ + public function getTimerCount() + { + return \count($this->_timer); + } +} diff --git a/GatewayWorker/vendor/workerman/workerman/Lib/Constants.php b/GatewayWorker/vendor/workerman/workerman/Lib/Constants.php new file mode 100644 index 0000000..20821cd --- /dev/null +++ b/GatewayWorker/vendor/workerman/workerman/Lib/Constants.php @@ -0,0 +1,42 @@ + + * @copyright walkor + * @license http://www.opensource.org/licenses/mit-license.php MIT License + * + * @link http://www.workerman.net/ + */ + +// Display errors. +ini_set('display_errors', 'on'); +// Reporting all. +error_reporting(E_ALL); +// JIT is not stable, temporarily disabled. +ini_set('pcre.jit', 0); + +// Reset opcache. +if (function_exists('opcache_reset')) { + opcache_reset(); +} + +// For onError callback. +const WORKERMAN_CONNECT_FAIL = 1; +// For onError callback. +const WORKERMAN_SEND_FAIL = 2; + +// Define OS Type +const OS_TYPE_LINUX = 'linux'; +const OS_TYPE_WINDOWS = 'windows'; + +// Compatible with php7 +if ( ! class_exists('Error')) { + class Error extends Exception + { + } +} diff --git a/GatewayWorker/vendor/workerman/workerman/Lib/Timer.php b/GatewayWorker/vendor/workerman/workerman/Lib/Timer.php new file mode 100644 index 0000000..1bd3419 --- /dev/null +++ b/GatewayWorker/vendor/workerman/workerman/Lib/Timer.php @@ -0,0 +1,182 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Lib; + +use Workerman\Events\EventInterface; +use Workerman\Worker; +use \Exception; + +/** + * Timer. + * + * example: + * Workerman\Lib\Timer::add($time_interval, callback, array($arg1, $arg2..)); + */ +class Timer +{ + /** + * Tasks that based on ALARM signal. + * [ + * run_time => [[$func, $args, $persistent, time_interval],[$func, $args, $persistent, time_interval],..]], + * run_time => [[$func, $args, $persistent, time_interval],[$func, $args, $persistent, time_interval],..]], + * .. + * ] + * + * @var array + */ + protected static $_tasks = array(); + + /** + * event + * + * @var EventInterface + */ + protected static $_event = null; + + /** + * Init. + * + * @param EventInterface $event + * @return void + */ + public static function init($event = null) + { + if ($event) { + self::$_event = $event; + return; + } + if (\function_exists('pcntl_signal')) { + \pcntl_signal(\SIGALRM, array('\Workerman\Lib\Timer', 'signalHandle'), false); + } + } + + /** + * ALARM signal handler. + * + * @return void + */ + public static function signalHandle() + { + if (!self::$_event) { + \pcntl_alarm(1); + self::tick(); + } + } + + /** + * Add a timer. + * + * @param float $time_interval + * @param callable $func + * @param mixed $args + * @param bool $persistent + * @return int|false + */ + public static function add($time_interval, $func, $args = array(), $persistent = true) + { + if ($time_interval <= 0) { + Worker::safeEcho(new Exception("bad time_interval")); + return false; + } + + if ($args === null) { + $args = array(); + } + + if (self::$_event) { + return self::$_event->add($time_interval, + $persistent ? EventInterface::EV_TIMER : EventInterface::EV_TIMER_ONCE, $func, $args); + } + + if (!\is_callable($func)) { + Worker::safeEcho(new Exception("not callable")); + return false; + } + + if (empty(self::$_tasks)) { + \pcntl_alarm(1); + } + + $run_time = \time() + $time_interval; + if (!isset(self::$_tasks[$run_time])) { + self::$_tasks[$run_time] = array(); + } + self::$_tasks[$run_time][] = array($func, (array)$args, $persistent, $time_interval); + return 1; + } + + + /** + * Tick. + * + * @return void + */ + public static function tick() + { + if (empty(self::$_tasks)) { + \pcntl_alarm(0); + return; + } + + $time_now = \time(); + foreach (self::$_tasks as $run_time => $task_data) { + if ($time_now >= $run_time) { + foreach ($task_data as $index => $one_task) { + $task_func = $one_task[0]; + $task_args = $one_task[1]; + $persistent = $one_task[2]; + $time_interval = $one_task[3]; + try { + \call_user_func_array($task_func, $task_args); + } catch (\Exception $e) { + Worker::safeEcho($e); + } + if ($persistent) { + self::add($time_interval, $task_func, $task_args); + } + } + unset(self::$_tasks[$run_time]); + } + } + } + + /** + * Remove a timer. + * + * @param mixed $timer_id + * @return bool + */ + public static function del($timer_id) + { + if (self::$_event) { + return self::$_event->del($timer_id, EventInterface::EV_TIMER); + } + + return false; + } + + /** + * Remove all timers. + * + * @return void + */ + public static function delAll() + { + self::$_tasks = array(); + \pcntl_alarm(0); + if (self::$_event) { + self::$_event->clearAllTimer(); + } + } +} diff --git a/GatewayWorker/vendor/workerman/workerman/MIT-LICENSE.txt b/GatewayWorker/vendor/workerman/workerman/MIT-LICENSE.txt new file mode 100644 index 0000000..fd6b1c8 --- /dev/null +++ b/GatewayWorker/vendor/workerman/workerman/MIT-LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2009-2015 walkor and contributors (see https://github.com/walkor/workerman/contributors) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/GatewayWorker/vendor/workerman/workerman/Protocols/Frame.php b/GatewayWorker/vendor/workerman/workerman/Protocols/Frame.php new file mode 100644 index 0000000..26b04de --- /dev/null +++ b/GatewayWorker/vendor/workerman/workerman/Protocols/Frame.php @@ -0,0 +1,61 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Protocols; + +use Workerman\Connection\TcpConnection; + +/** + * Frame Protocol. + */ +class Frame +{ + /** + * Check the integrity of the package. + * + * @param string $buffer + * @param TcpConnection $connection + * @return int + */ + public static function input($buffer, TcpConnection $connection) + { + if (\strlen($buffer) < 4) { + return 0; + } + $unpack_data = \unpack('Ntotal_length', $buffer); + return $unpack_data['total_length']; + } + + /** + * Decode. + * + * @param string $buffer + * @return string + */ + public static function decode($buffer) + { + return \substr($buffer, 4); + } + + /** + * Encode. + * + * @param string $buffer + * @return string + */ + public static function encode($buffer) + { + $total_length = 4 + \strlen($buffer); + return \pack('N', $total_length) . $buffer; + } +} diff --git a/GatewayWorker/vendor/workerman/workerman/Protocols/Http.php b/GatewayWorker/vendor/workerman/workerman/Protocols/Http.php new file mode 100644 index 0000000..4fabe3a --- /dev/null +++ b/GatewayWorker/vendor/workerman/workerman/Protocols/Http.php @@ -0,0 +1,755 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Protocols; + +use Workerman\Connection\TcpConnection; +use Workerman\Protocols\Websocket; +use Workerman\Worker; + +/** + * http protocol + */ +class Http +{ + /** + * The supported HTTP methods + * @var array + */ + public static $methods = array('GET'=>'GET', 'POST'=>'POST', 'PUT'=>'PUT', 'DELETE'=>'DELETE', 'HEAD'=>'HEAD', 'OPTIONS'=>'OPTIONS'); + + /** + * Cache. + * @var array + */ + protected static $_cache = []; + + /** + * Check the integrity of the package. + * + * @param string $recv_buffer + * @param TcpConnection $connection + * @return int + */ + public static function input($recv_buffer, TcpConnection $connection) + { + if (isset(static::$_cache[$recv_buffer]['input'])) { + return static::$_cache[$recv_buffer]['input']; + } + $recv_len = \strlen($recv_buffer); + $crlf_post = \strpos($recv_buffer, "\r\n\r\n"); + if (!$crlf_post) { + // Judge whether the package length exceeds the limit. + if ($recv_len >= $connection->maxPackageSize) { + $connection->close(); + } + return 0; + } + + $method = \substr($recv_buffer, 0, \strpos($recv_buffer, ' ')); + if (!isset(static::$methods[$method])) { + $connection->send("HTTP/1.1 400 Bad Request\r\n\r\n", true); + $connection->consumeRecvBuffer($recv_len); + return 0; + } + + if ($method === 'GET' || $method === 'OPTIONS' || $method === 'HEAD') { + static::$_cache[$recv_buffer]['input'] = $recv_len; + return $recv_len; + } + + $match = array(); + if (\preg_match("/\r\nContent-Length: ?(\d+)/i", $recv_buffer, $match)) { + $content_length = isset($match[1]) ? $match[1] : 0; + $total_length = $content_length + $crlf_post + 4; + static::$_cache[$recv_buffer]['input'] = $total_length; + return $total_length; + } + + return $method === 'DELETE' ? $recv_len : 0; + } + + /** + * Parse $_POST、$_GET、$_COOKIE. + * + * @param string $recv_buffer + * @param TcpConnection $connection + * @return array + */ + public static function decode($recv_buffer, TcpConnection $connection) + { + if (isset(static::$_cache[$recv_buffer]['decode'])) { + HttpCache::reset(); + $cache = static::$_cache[$recv_buffer]['decode']; + //$cache['server']['REQUEST_TIME_FLOAT'] = \microtime(true); + //$cache['server']['REQUEST_TIME'] = (int)$cache['server']['REQUEST_TIME_FLOAT']; + $_SERVER = $cache['server']; + $_POST = $cache['post']; + $_GET = $cache['get']; + $_COOKIE = $cache['cookie']; + $_REQUEST = $cache['request']; + $GLOBALS['HTTP_RAW_POST_DATA'] = $GLOBALS['HTTP_RAW_REQUEST_DATA'] = ''; + return static::$_cache[$recv_buffer]['decode']; + } + // Init. + $_POST = $_GET = $_COOKIE = $_REQUEST = $_SESSION = $_FILES = array(); + $GLOBALS['HTTP_RAW_POST_DATA'] = ''; + // Clear cache. + HttpCache::reset(); + //$microtime = \microtime(true); + // $_SERVER + $_SERVER = array( + 'QUERY_STRING' => '', + 'REQUEST_METHOD' => '', + 'REQUEST_URI' => '', + 'SERVER_PROTOCOL' => '', + 'SERVER_SOFTWARE' => 'workerman/'.Worker::VERSION, + 'SERVER_NAME' => '', + 'HTTP_HOST' => '', + 'HTTP_USER_AGENT' => '', + 'HTTP_ACCEPT' => '', + 'HTTP_ACCEPT_LANGUAGE' => '', + 'HTTP_ACCEPT_ENCODING' => '', + 'HTTP_COOKIE' => '', + 'HTTP_CONNECTION' => '', + 'CONTENT_TYPE' => '', + 'REMOTE_ADDR' => '', + 'REMOTE_PORT' => '0', + //'REQUEST_TIME' => (int)$microtime, + //'REQUEST_TIME_FLOAT' => $microtime //compatible php5.4 + ); + + // Parse headers. + list($http_header, $http_body) = \explode("\r\n\r\n", $recv_buffer, 2); + $header_data = \explode("\r\n", $http_header); + + list($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI'], $_SERVER['SERVER_PROTOCOL']) = \explode(' ', + $header_data[0]); + + $http_post_boundary = ''; + unset($header_data[0]); + foreach ($header_data as $content) { + // \r\n\r\n + if (empty($content)) { + continue; + } + list($key, $value) = \explode(':', $content, 2); + $key = \str_replace('-', '_', strtoupper($key)); + $value = \trim($value); + $_SERVER['HTTP_' . $key] = $value; + switch ($key) { + // HTTP_HOST + case 'HOST': + $tmp = \explode(':', $value); + $_SERVER['SERVER_NAME'] = $tmp[0]; + if (isset($tmp[1])) { + $_SERVER['SERVER_PORT'] = $tmp[1]; + } + break; + // cookie + case 'COOKIE': + \parse_str(\str_replace('; ', '&', $_SERVER['HTTP_COOKIE']), $_COOKIE); + break; + // content-type + case 'CONTENT_TYPE': + if (!\preg_match('/boundary="?(\S+)"?/', $value, $match)) { + if ($pos = \strpos($value, ';')) { + $_SERVER['CONTENT_TYPE'] = \substr($value, 0, $pos); + } else { + $_SERVER['CONTENT_TYPE'] = $value; + } + } else { + $_SERVER['CONTENT_TYPE'] = 'multipart/form-data'; + $http_post_boundary = '--' . $match[1]; + } + break; + case 'CONTENT_LENGTH': + $_SERVER['CONTENT_LENGTH'] = $value; + break; + case 'UPGRADE': + if($value === 'websocket'){ + $connection->protocol = '\Workerman\Protocols\Websocket'; + return Websocket::input($recv_buffer,$connection); + } + break; + } + } + + // Parse $_POST. + if ($_SERVER['REQUEST_METHOD'] === 'POST' && $_SERVER['CONTENT_TYPE']) { + switch ($_SERVER['CONTENT_TYPE']) { + case 'multipart/form-data': + self::parseUploadFiles($http_body, $http_post_boundary); + break; + case 'application/json': + $_POST = \json_decode($http_body, true); + break; + case 'application/x-www-form-urlencoded': + \parse_str($http_body, $_POST); + break; + } + } + + // Parse other HTTP action parameters + if ($_SERVER['REQUEST_METHOD'] !== 'GET' && $_SERVER['REQUEST_METHOD'] !== "POST") { + $data = array(); + if ($_SERVER['CONTENT_TYPE'] === "application/x-www-form-urlencoded") { + \parse_str($http_body, $data); + } elseif ($_SERVER['CONTENT_TYPE'] === "application/json") { + $data = \json_decode($http_body, true); + } + $_REQUEST = \array_merge($_REQUEST, $data); + } + + // HTTP_RAW_REQUEST_DATA HTTP_RAW_POST_DATA + $GLOBALS['HTTP_RAW_REQUEST_DATA'] = $GLOBALS['HTTP_RAW_POST_DATA'] = $http_body; + + // QUERY_STRING + $_SERVER['QUERY_STRING'] = \parse_url($_SERVER['REQUEST_URI'], \PHP_URL_QUERY); + if ($_SERVER['QUERY_STRING']) { + // $GET + \parse_str($_SERVER['QUERY_STRING'], $_GET); + } else { + $_SERVER['QUERY_STRING'] = ''; + } + + if (\is_array($_POST)) { + // REQUEST + $_REQUEST = \array_merge($_GET, $_POST, $_REQUEST); + } else { + // REQUEST + $_REQUEST = \array_merge($_GET, $_REQUEST); + } + + // REMOTE_ADDR REMOTE_PORT + $_SERVER['REMOTE_ADDR'] = $connection->getRemoteIp(); + $_SERVER['REMOTE_PORT'] = $connection->getRemotePort(); + $ret = array('get' => $_GET, 'post' => $_POST, 'cookie' => $_COOKIE, 'server' => $_SERVER, 'files' => $_FILES, 'request'=>$_REQUEST); + if ($_SERVER['REQUEST_METHOD'] === 'GET') { + static::$_cache[$recv_buffer]['decode'] = $ret; + if (\count(static::$_cache) > 256) { + unset(static::$_cache[key(static::$_cache)]); + } + } + + return $ret; + } + + /** + * Http encode. + * + * @param string $content + * @param TcpConnection $connection + * @return string + */ + public static function encode($content, TcpConnection $connection) + { + // http-code status line. + $header = HttpCache::$status . "\r\n"; + + // Cookie headers + if(HttpCache::$cookie) { + $header .= \implode("\r\n", HttpCache::$cookie) . "\r\n"; + } + + // other headers + if (HttpCache::$header) { + $header .= \implode("\r\n", HttpCache::$header) . "\r\n"; + } + + if(!empty($connection->gzip)) { + $header .= "Content-Encoding: gzip\r\n"; + $content = \gzencode($content,$connection->gzip); + } + // header + $header .= 'Content-Length: ' . \strlen($content) . "\r\n\r\n"; + + // save session + self::sessionWriteClose(); + + // the whole http package + return $header . $content; + } + + /** + * Send a raw HTTP header + * + * @param string $content + * @param bool $replace + * @param int $http_response_code + * + * @return bool|void + */ + public static function header($content, $replace = true, $http_response_code = null) + { + if (NO_CLI) { + \header($content, $replace, $http_response_code); + return; + } + + if (\strpos($content, 'HTTP') === 0) { + HttpCache::$status = $content; + return true; + } + + $key = \strstr($content, ':', true); + if (empty($key)) { + return false; + } + + if ('location' === \strtolower($key)) { + if (!$http_response_code) { + $http_response_code = 302; + } + self::responseCode($http_response_code); + } + + if ($key === 'Set-Cookie') { + HttpCache::$cookie[] = $content; + } else { + HttpCache::$header[$key] = $content; + } + + return true; + } + + /** + * Remove previously set headers + * + * @param string $name + * @return void + */ + public static function headerRemove($name) + { + if (NO_CLI) { + \header_remove($name); + return; + } + unset(HttpCache::$header[$name]); + } + + /** + * Sets the HTTP response status code. + * + * @param int $code The response code + * @return boolean|int The valid status code or FALSE if code is not provided and it is not invoked in a web server environment + */ + public static function responseCode($code) + { + if (NO_CLI) { + return \http_response_code($code); + } + if (isset(HttpCache::$codes[$code])) { + HttpCache::$status = "HTTP/1.1 $code " . HttpCache::$codes[$code]; + return $code; + } + return false; + } + + /** + * Set cookie. + * + * @param string $name + * @param string $value + * @param integer $maxage + * @param string $path + * @param string $domain + * @param bool $secure + * @param bool $HTTPOnly + * @return bool|void + */ + public static function setcookie( + $name, + $value = '', + $maxage = 0, + $path = '', + $domain = '', + $secure = false, + $HTTPOnly = false + ) { + if (NO_CLI) { + return \setcookie($name, $value, $maxage, $path, $domain, $secure, $HTTPOnly); + } + + HttpCache::$cookie[] = 'Set-Cookie: ' . $name . '=' . rawurlencode($value) + . (empty($domain) ? '' : '; Domain=' . $domain) + . (empty($maxage) ? '' : '; Max-Age=' . $maxage) + . (empty($path) ? '' : '; Path=' . $path) + . (!$secure ? '' : '; Secure') + . (!$HTTPOnly ? '' : '; HttpOnly'); + + return true; + } + + /** + * sessionCreateId + * + * @return string + */ + public static function sessionCreateId() + { + \mt_srand(); + return bin2hex(\pack('d', \microtime(true)) . \pack('N',\mt_rand(0, 2147483647))); + } + + /** + * Get and/or set the current session id + * + * @param string $id + * + * @return string|null + */ + public static function sessionId($id = null) + { + if (NO_CLI) { + return $id ? \session_id($id) : \session_id(); + } + if (static::sessionStarted() && HttpCache::$instance->sessionFile) { + return \str_replace('ses_', '', \basename(HttpCache::$instance->sessionFile)); + } + return ''; + } + + /** + * Get and/or set the current session name + * + * @param string $name + * + * @return string + */ + public static function sessionName($name = null) + { + if (NO_CLI) { + return $name ? \session_name($name) : \session_name(); + } + $session_name = HttpCache::$sessionName; + if ($name && ! static::sessionStarted()) { + HttpCache::$sessionName = $name; + } + return $session_name; + } + + /** + * Get and/or set the current session save path + * + * @param string $path + * + * @return string + */ + public static function sessionSavePath($path = null) + { + if (NO_CLI) { + return $path ? \session_save_path($path) : \session_save_path(); + } + if ($path && \is_dir($path) && \is_writable($path) && !static::sessionStarted()) { + HttpCache::$sessionPath = $path; + } + return HttpCache::$sessionPath; + } + + /** + * sessionStarted + * + * @return bool + */ + public static function sessionStarted() + { + if (!HttpCache::$instance) return false; + + return HttpCache::$instance->sessionStarted; + } + + /** + * sessionStart + * + * @return bool + */ + public static function sessionStart() + { + if (NO_CLI) { + return \session_start(); + } + + self::tryGcSessions(); + + if (HttpCache::$instance->sessionStarted) { + Worker::safeEcho("already sessionStarted\n"); + return true; + } + HttpCache::$instance->sessionStarted = true; + // Generate a SID. + if (!isset($_COOKIE[HttpCache::$sessionName]) || !\is_file(HttpCache::$sessionPath . '/ses_' . $_COOKIE[HttpCache::$sessionName])) { + // Create a unique session_id and the associated file name. + while (true) { + $session_id = static::sessionCreateId(); + if (!\is_file($file_name = HttpCache::$sessionPath . '/ses_' . $session_id)) break; + } + HttpCache::$instance->sessionFile = $file_name; + return self::setcookie( + HttpCache::$sessionName + , $session_id + , \ini_get('session.cookie_lifetime') + , \ini_get('session.cookie_path') + , \ini_get('session.cookie_domain') + , \ini_get('session.cookie_secure') + , \ini_get('session.cookie_httponly') + ); + } + if (!HttpCache::$instance->sessionFile) { + HttpCache::$instance->sessionFile = HttpCache::$sessionPath . '/ses_' . $_COOKIE[HttpCache::$sessionName]; + } + // Read session from session file. + if (HttpCache::$instance->sessionFile) { + $raw = \file_get_contents(HttpCache::$instance->sessionFile); + if ($raw) { + $_SESSION = \unserialize($raw); + } + } + return true; + } + + /** + * Save session. + * + * @return bool + */ + public static function sessionWriteClose() + { + if (NO_CLI) { + \session_write_close(); + return true; + } + if (!empty(HttpCache::$instance->sessionStarted) && !empty($_SESSION)) { + $session_str = \serialize($_SESSION); + if ($session_str && HttpCache::$instance->sessionFile) { + return (bool) \file_put_contents(HttpCache::$instance->sessionFile, $session_str); + } + } + return empty($_SESSION); + } + + /** + * End, like call exit in php-fpm. + * + * @param string $msg + * @throws \Exception + */ + public static function end($msg = '') + { + if (NO_CLI) { + exit($msg); + } + if ($msg) { + echo $msg; + } + throw new \Exception('jump_exit'); + } + + /** + * Get mime types. + * + * @return string + */ + public static function getMimeTypesFile() + { + return __DIR__ . '/Http/mime.types'; + } + + /** + * Parse $_FILES. + * + * @param string $http_body + * @param string $http_post_boundary + * @return void + */ + protected static function parseUploadFiles($http_body, $http_post_boundary) + { + $http_body = \substr($http_body, 0, \strlen($http_body) - (\strlen($http_post_boundary) + 4)); + $boundary_data_array = \explode($http_post_boundary . "\r\n", $http_body); + if ($boundary_data_array[0] === '') { + unset($boundary_data_array[0]); + } + $key = -1; + foreach ($boundary_data_array as $boundary_data_buffer) { + list($boundary_header_buffer, $boundary_value) = \explode("\r\n\r\n", $boundary_data_buffer, 2); + // Remove \r\n from the end of buffer. + $boundary_value = \substr($boundary_value, 0, -2); + $key ++; + foreach (\explode("\r\n", $boundary_header_buffer) as $item) { + list($header_key, $header_value) = \explode(": ", $item); + $header_key = \strtolower($header_key); + switch ($header_key) { + case "content-disposition": + // Is file data. + if (\preg_match('/name="(.*?)"; filename="(.*?)"$/', $header_value, $match)) { + // Parse $_FILES. + $_FILES[$key] = array( + 'name' => $match[1], + 'file_name' => $match[2], + 'file_data' => $boundary_value, + 'file_size' => \strlen($boundary_value), + ); + break; + } // Is post field. + else { + // Parse $_POST. + if (\preg_match('/name="(.*?)"$/', $header_value, $match)) { + $_POST[$match[1]] = $boundary_value; + } + } + break; + case "content-type": + // add file_type + $_FILES[$key]['file_type'] = \trim($header_value); + break; + } + } + } + } + + /** + * Try GC sessions. + * + * @return void + */ + public static function tryGcSessions() + { + if (HttpCache::$sessionGcProbability <= 0 || + HttpCache::$sessionGcDivisor <= 0 || + \rand(1, HttpCache::$sessionGcDivisor) > HttpCache::$sessionGcProbability) { + return; + } + + $time_now = \time(); + foreach(glob(HttpCache::$sessionPath.'/ses*') as $file) { + if(\is_file($file) && $time_now - \filemtime($file) > HttpCache::$sessionGcMaxLifeTime) { + \unlink($file); + } + } + } +} + +/** + * Http cache for the current http response. + */ +class HttpCache +{ + public static $codes = array( + 100 => 'Continue', + 101 => 'Switching Protocols', + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 306 => '(Unused)', + 307 => 'Temporary Redirect', + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Timeout', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Long', + 415 => 'Unsupported Media Type', + 416 => 'Requested Range Not Satisfiable', + 417 => 'Expectation Failed', + 422 => 'Unprocessable Entity', + 423 => 'Locked', + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Timeout', + 505 => 'HTTP Version Not Supported', + ); + + public static $default = array( + 'Content-Type' => 'Content-Type: text/html;charset=utf-8', + 'Connection' => 'Connection: keep-alive', + 'Server' => 'Server: workerman' + ); + + /** + * @var HttpCache + */ + public static $instance = null; + public static $status = ''; + public static $header = array(); + public static $cookie = array(); + public static $sessionPath = ''; + public static $sessionName = ''; + public static $sessionGcProbability = 1; + public static $sessionGcDivisor = 1000; + public static $sessionGcMaxLifeTime = 1440; + public $sessionStarted = false; + public $sessionFile = ''; + + public static function reset() + { + self::$status = 'HTTP/1.1 200 OK'; + self::$header = self::$default; + self::$cookie = array(); + self::$instance->sessionFile = ''; + self::$instance->sessionStarted = false; + } + + public static function init() + { + if (!self::$sessionName) { + self::$sessionName = \ini_get('session.name'); + } + + if (!self::$sessionPath) { + self::$sessionPath = @\session_save_path(); + } + + if (!self::$sessionPath || \strpos(self::$sessionPath, 'tcp://') === 0) { + self::$sessionPath = \sys_get_temp_dir(); + } + + if ($gc_probability = \ini_get('session.gc_probability')) { + self::$sessionGcProbability = $gc_probability; + } + + if ($gc_divisor = \ini_get('session.gc_divisor')) { + self::$sessionGcDivisor = $gc_divisor; + } + + if ($gc_max_life_time = \ini_get('session.gc_maxlifetime')) { + self::$sessionGcMaxLifeTime = $gc_max_life_time; + } + + self::$instance = new HttpCache(); + } +} + +HttpCache::init(); + +define('NO_CLI', \PHP_SAPI !== 'cli'); diff --git a/GatewayWorker/vendor/workerman/workerman/Protocols/Http/mime.types b/GatewayWorker/vendor/workerman/workerman/Protocols/Http/mime.types new file mode 100644 index 0000000..e6ccf0a --- /dev/null +++ b/GatewayWorker/vendor/workerman/workerman/Protocols/Http/mime.types @@ -0,0 +1,90 @@ + +types { + text/html html htm shtml; + text/css css; + text/xml xml; + image/gif gif; + image/jpeg jpeg jpg; + application/javascript js; + application/atom+xml atom; + application/rss+xml rss; + + text/mathml mml; + text/plain txt; + text/vnd.sun.j2me.app-descriptor jad; + text/vnd.wap.wml wml; + text/x-component htc; + + image/png png; + image/tiff tif tiff; + image/vnd.wap.wbmp wbmp; + image/x-icon ico; + image/x-jng jng; + image/x-ms-bmp bmp; + image/svg+xml svg svgz; + image/webp webp; + + application/font-woff woff; + application/java-archive jar war ear; + application/json json; + application/mac-binhex40 hqx; + application/msword doc; + application/pdf pdf; + application/postscript ps eps ai; + application/rtf rtf; + application/vnd.apple.mpegurl m3u8; + application/vnd.ms-excel xls; + application/vnd.ms-fontobject eot; + application/vnd.ms-powerpoint ppt; + application/vnd.wap.wmlc wmlc; + application/vnd.google-earth.kml+xml kml; + application/vnd.google-earth.kmz kmz; + application/x-7z-compressed 7z; + application/x-cocoa cco; + application/x-java-archive-diff jardiff; + application/x-java-jnlp-file jnlp; + application/x-makeself run; + application/x-perl pl pm; + application/x-pilot prc pdb; + application/x-rar-compressed rar; + application/x-redhat-package-manager rpm; + application/x-sea sea; + application/x-shockwave-flash swf; + application/x-stuffit sit; + application/x-tcl tcl tk; + application/x-x509-ca-cert der pem crt; + application/x-xpinstall xpi; + application/xhtml+xml xhtml; + application/xspf+xml xspf; + application/zip zip; + + application/octet-stream bin exe dll; + application/octet-stream deb; + application/octet-stream dmg; + application/octet-stream iso img; + application/octet-stream msi msp msm; + + application/vnd.openxmlformats-officedocument.wordprocessingml.document docx; + application/vnd.openxmlformats-officedocument.spreadsheetml.sheet xlsx; + application/vnd.openxmlformats-officedocument.presentationml.presentation pptx; + + audio/midi mid midi kar; + audio/mpeg mp3; + audio/ogg ogg; + audio/x-m4a m4a; + audio/x-realaudio ra; + + video/3gpp 3gpp 3gp; + video/mp2t ts; + video/mp4 mp4; + video/mpeg mpeg mpg; + video/quicktime mov; + video/webm webm; + video/x-flv flv; + video/x-m4v m4v; + video/x-mng mng; + video/x-ms-asf asx asf; + video/x-ms-wmv wmv; + video/x-msvideo avi; + font/ttf ttf; +} diff --git a/GatewayWorker/vendor/workerman/workerman/Protocols/ProtocolInterface.php b/GatewayWorker/vendor/workerman/workerman/Protocols/ProtocolInterface.php new file mode 100644 index 0000000..4fea87d --- /dev/null +++ b/GatewayWorker/vendor/workerman/workerman/Protocols/ProtocolInterface.php @@ -0,0 +1,52 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Protocols; + +use Workerman\Connection\ConnectionInterface; + +/** + * Protocol interface + */ +interface ProtocolInterface +{ + /** + * Check the integrity of the package. + * Please return the length of package. + * If length is unknow please return 0 that mean wating more data. + * If the package has something wrong please return false the connection will be closed. + * + * @param string $recv_buffer + * @param ConnectionInterface $connection + * @return int|false + */ + public static function input($recv_buffer, ConnectionInterface $connection); + + /** + * Decode package and emit onMessage($message) callback, $message is the result that decode returned. + * + * @param string $recv_buffer + * @param ConnectionInterface $connection + * @return mixed + */ + public static function decode($recv_buffer, ConnectionInterface $connection); + + /** + * Encode package brefore sending to client. + * + * @param mixed $data + * @param ConnectionInterface $connection + * @return string + */ + public static function encode($data, ConnectionInterface $connection); +} diff --git a/GatewayWorker/vendor/workerman/workerman/Protocols/Text.php b/GatewayWorker/vendor/workerman/workerman/Protocols/Text.php new file mode 100644 index 0000000..407ea2d --- /dev/null +++ b/GatewayWorker/vendor/workerman/workerman/Protocols/Text.php @@ -0,0 +1,70 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Protocols; + +use Workerman\Connection\ConnectionInterface; + +/** + * Text Protocol. + */ +class Text +{ + /** + * Check the integrity of the package. + * + * @param string $buffer + * @param ConnectionInterface $connection + * @return int + */ + public static function input($buffer, ConnectionInterface $connection) + { + // Judge whether the package length exceeds the limit. + if (isset($connection->maxPackageSize) && \strlen($buffer) >= $connection->maxPackageSize) { + $connection->close(); + return 0; + } + // Find the position of "\n". + $pos = \strpos($buffer, "\n"); + // No "\n", packet length is unknown, continue to wait for the data so return 0. + if ($pos === false) { + return 0; + } + // Return the current package length. + return $pos + 1; + } + + /** + * Encode. + * + * @param string $buffer + * @return string + */ + public static function encode($buffer) + { + // Add "\n" + return $buffer . "\n"; + } + + /** + * Decode. + * + * @param string $buffer + * @return string + */ + public static function decode($buffer) + { + // Remove "\n" + return \rtrim($buffer, "\r\n"); + } +} \ No newline at end of file diff --git a/GatewayWorker/vendor/workerman/workerman/Protocols/Websocket.php b/GatewayWorker/vendor/workerman/workerman/Protocols/Websocket.php new file mode 100644 index 0000000..ad9d0ac --- /dev/null +++ b/GatewayWorker/vendor/workerman/workerman/Protocols/Websocket.php @@ -0,0 +1,503 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Protocols; + +use Workerman\Connection\ConnectionInterface; +use Workerman\Connection\TcpConnection; +use Workerman\Worker; + +/** + * WebSocket protocol. + */ +class Websocket implements \Workerman\Protocols\ProtocolInterface +{ + /** + * Websocket blob type. + * + * @var string + */ + const BINARY_TYPE_BLOB = "\x81"; + + /** + * Websocket arraybuffer type. + * + * @var string + */ + const BINARY_TYPE_ARRAYBUFFER = "\x82"; + + /** + * Check the integrity of the package. + * + * @param string $buffer + * @param ConnectionInterface $connection + * @return int + */ + public static function input($buffer, ConnectionInterface $connection) + { + // Receive length. + $recv_len = \strlen($buffer); + // We need more data. + if ($recv_len < 6) { + return 0; + } + + // Has not yet completed the handshake. + if (empty($connection->websocketHandshake)) { + return static::dealHandshake($buffer, $connection); + } + + // Buffer websocket frame data. + if ($connection->websocketCurrentFrameLength) { + // We need more frame data. + if ($connection->websocketCurrentFrameLength > $recv_len) { + // Return 0, because it is not clear the full packet length, waiting for the frame of fin=1. + return 0; + } + } else { + $firstbyte = \ord($buffer[0]); + $secondbyte = \ord($buffer[1]); + $data_len = $secondbyte & 127; + $is_fin_frame = $firstbyte >> 7; + $masked = $secondbyte >> 7; + + if (!$masked) { + Worker::safeEcho("frame not masked so close the connection\n"); + $connection->close(); + return 0; + } + + $opcode = $firstbyte & 0xf; + switch ($opcode) { + case 0x0: + break; + // Blob type. + case 0x1: + break; + // Arraybuffer type. + case 0x2: + break; + // Close package. + case 0x8: + // Try to emit onWebSocketClose callback. + if (isset($connection->onWebSocketClose) || isset($connection->worker->onWebSocketClose)) { + try { + \call_user_func(isset($connection->onWebSocketClose)?$connection->onWebSocketClose:$connection->worker->onWebSocketClose, $connection); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } // Close connection. + else { + $connection->close("\x88\x02\x03\xe8", true); + } + return 0; + // Ping package. + case 0x9: + break; + // Pong package. + case 0xa: + break; + // Wrong opcode. + default : + Worker::safeEcho("error opcode $opcode and close websocket connection. Buffer:" . bin2hex($buffer) . "\n"); + $connection->close(); + return 0; + } + + // Calculate packet length. + $head_len = 6; + if ($data_len === 126) { + $head_len = 8; + if ($head_len > $recv_len) { + return 0; + } + $pack = \unpack('nn/ntotal_len', $buffer); + $data_len = $pack['total_len']; + } else { + if ($data_len === 127) { + $head_len = 14; + if ($head_len > $recv_len) { + return 0; + } + $arr = \unpack('n/N2c', $buffer); + $data_len = $arr['c1']*4294967296 + $arr['c2']; + } + } + $current_frame_length = $head_len + $data_len; + + $total_package_size = \strlen($connection->websocketDataBuffer) + $current_frame_length; + if ($total_package_size > $connection->maxPackageSize) { + Worker::safeEcho("error package. package_length=$total_package_size\n"); + $connection->close(); + return 0; + } + + if ($is_fin_frame) { + if ($opcode === 0x9) { + if ($recv_len >= $current_frame_length) { + $ping_data = static::decode(\substr($buffer, 0, $current_frame_length), $connection); + $connection->consumeRecvBuffer($current_frame_length); + $tmp_connection_type = isset($connection->websocketType) ? $connection->websocketType : static::BINARY_TYPE_BLOB; + $connection->websocketType = "\x8a"; + if (isset($connection->onWebSocketPing) || isset($connection->worker->onWebSocketPing)) { + try { + \call_user_func(isset($connection->onWebSocketPing)?$connection->onWebSocketPing:$connection->worker->onWebSocketPing, $connection, $ping_data); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } else { + $connection->send($ping_data); + } + $connection->websocketType = $tmp_connection_type; + if ($recv_len > $current_frame_length) { + return static::input(\substr($buffer, $current_frame_length), $connection); + } + } + return 0; + } else if ($opcode === 0xa) { + if ($recv_len >= $current_frame_length) { + $pong_data = static::decode(\substr($buffer, 0, $current_frame_length), $connection); + $connection->consumeRecvBuffer($current_frame_length); + $tmp_connection_type = isset($connection->websocketType) ? $connection->websocketType : static::BINARY_TYPE_BLOB; + $connection->websocketType = "\x8a"; + // Try to emit onWebSocketPong callback. + if (isset($connection->onWebSocketPong) || isset($connection->worker->onWebSocketPong)) { + try { + \call_user_func(isset($connection->onWebSocketPong)?$connection->onWebSocketPong:$connection->worker->onWebSocketPong, $connection, $pong_data); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + $connection->websocketType = $tmp_connection_type; + if ($recv_len > $current_frame_length) { + return static::input(\substr($buffer, $current_frame_length), $connection); + } + } + return 0; + } + return $current_frame_length; + } else { + $connection->websocketCurrentFrameLength = $current_frame_length; + } + } + + // Received just a frame length data. + if ($connection->websocketCurrentFrameLength === $recv_len) { + static::decode($buffer, $connection); + $connection->consumeRecvBuffer($connection->websocketCurrentFrameLength); + $connection->websocketCurrentFrameLength = 0; + return 0; + } // The length of the received data is greater than the length of a frame. + elseif ($connection->websocketCurrentFrameLength < $recv_len) { + static::decode(\substr($buffer, 0, $connection->websocketCurrentFrameLength), $connection); + $connection->consumeRecvBuffer($connection->websocketCurrentFrameLength); + $current_frame_length = $connection->websocketCurrentFrameLength; + $connection->websocketCurrentFrameLength = 0; + // Continue to read next frame. + return static::input(\substr($buffer, $current_frame_length), $connection); + } // The length of the received data is less than the length of a frame. + else { + return 0; + } + } + + /** + * Websocket encode. + * + * @param string $buffer + * @param ConnectionInterface $connection + * @return string + */ + public static function encode($buffer, ConnectionInterface $connection) + { + if (!is_scalar($buffer)) { + throw new \Exception("You can't send(" . \gettype($buffer) . ") to client, you need to convert it to a string. "); + } + $len = \strlen($buffer); + if (empty($connection->websocketType)) { + $connection->websocketType = static::BINARY_TYPE_BLOB; + } + + $first_byte = $connection->websocketType; + + if ($len <= 125) { + $encode_buffer = $first_byte . \chr($len) . $buffer; + } else { + if ($len <= 65535) { + $encode_buffer = $first_byte . \chr(126) . \pack("n", $len) . $buffer; + } else { + $encode_buffer = $first_byte . \chr(127) . \pack("xxxxN", $len) . $buffer; + } + } + + // Handshake not completed so temporary buffer websocket data waiting for send. + if (empty($connection->websocketHandshake)) { + if (empty($connection->tmpWebsocketData)) { + $connection->tmpWebsocketData = ''; + } + // If buffer has already full then discard the current package. + if (\strlen($connection->tmpWebsocketData) > $connection->maxSendBufferSize) { + if ($connection->onError) { + try { + \call_user_func($connection->onError, $connection, \WORKERMAN_SEND_FAIL, 'send buffer full and drop package'); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + return ''; + } + $connection->tmpWebsocketData .= $encode_buffer; + // Check buffer is full. + if ($connection->maxSendBufferSize <= \strlen($connection->tmpWebsocketData)) { + if ($connection->onBufferFull) { + try { + \call_user_func($connection->onBufferFull, $connection); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + } + + // Return empty string. + return ''; + } + + return $encode_buffer; + } + + /** + * Websocket decode. + * + * @param string $buffer + * @param ConnectionInterface $connection + * @return string + */ + public static function decode($buffer, ConnectionInterface $connection) + { + $len = \ord($buffer[1]) & 127; + if ($len === 126) { + $masks = \substr($buffer, 4, 4); + $data = \substr($buffer, 8); + } else { + if ($len === 127) { + $masks = \substr($buffer, 10, 4); + $data = \substr($buffer, 14); + } else { + $masks = \substr($buffer, 2, 4); + $data = \substr($buffer, 6); + } + } + $dataLength = \strlen($data); + $masks = \str_repeat($masks, \floor($dataLength / 4)) . \substr($masks, 0, $dataLength % 4); + $decoded = $data ^ $masks; + if ($connection->websocketCurrentFrameLength) { + $connection->websocketDataBuffer .= $decoded; + return $connection->websocketDataBuffer; + } else { + if ($connection->websocketDataBuffer !== '') { + $decoded = $connection->websocketDataBuffer . $decoded; + $connection->websocketDataBuffer = ''; + } + return $decoded; + } + } + + /** + * Websocket handshake. + * + * @param string $buffer + * @param TcpConnection $connection + * @return int + */ + protected static function dealHandshake($buffer, TcpConnection $connection) + { + // HTTP protocol. + if (0 === \strpos($buffer, 'GET')) { + // Find \r\n\r\n. + $heder_end_pos = \strpos($buffer, "\r\n\r\n"); + if (!$heder_end_pos) { + return 0; + } + $header_length = $heder_end_pos + 4; + + // Get Sec-WebSocket-Key. + $Sec_WebSocket_Key = ''; + if (\preg_match("/Sec-WebSocket-Key: *(.*?)\r\n/i", $buffer, $match)) { + $Sec_WebSocket_Key = $match[1]; + } else { + $connection->send("HTTP/1.1 200 Websocket\r\nServer: workerman/".Worker::VERSION."\r\n\r\n

Websocket


powered by workerman ".Worker::VERSION."
", + true); + $connection->close(); + return 0; + } + // Calculation websocket key. + $new_key = \base64_encode(\sha1($Sec_WebSocket_Key . "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", true)); + // Handshake response data. + $handshake_message = "HTTP/1.1 101 Switching Protocols\r\n" + ."Upgrade: websocket\r\n" + ."Sec-WebSocket-Version: 13\r\n" + ."Connection: Upgrade\r\n" + ."Sec-WebSocket-Accept: " . $new_key . "\r\n"; + + // Websocket data buffer. + $connection->websocketDataBuffer = ''; + // Current websocket frame length. + $connection->websocketCurrentFrameLength = 0; + // Current websocket frame data. + $connection->websocketCurrentFrameBuffer = ''; + // Consume handshake data. + $connection->consumeRecvBuffer($header_length); + + // blob or arraybuffer + if (empty($connection->websocketType)) { + $connection->websocketType = static::BINARY_TYPE_BLOB; + } + + $has_server_header = false; + + // Try to emit onWebSocketConnect callback. + if (isset($connection->onWebSocketConnect) || isset($connection->worker->onWebSocketConnect)) { + static::parseHttpHeader($buffer); + try { + \call_user_func(isset($connection->onWebSocketConnect)?$connection->onWebSocketConnect:$connection->worker->onWebSocketConnect, $connection, $buffer); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + if (!empty($_SESSION) && \class_exists('\GatewayWorker\Lib\Context')) { + $connection->session = \GatewayWorker\Lib\Context::sessionEncode($_SESSION); + } + $_GET = $_SERVER = $_SESSION = $_COOKIE = array(); + + if (isset($connection->headers)) { + if (\is_array($connection->headers)) { + foreach ($connection->headers as $header) { + if (\strpos($header, 'Server:') === 0) { + $has_server_header = true; + } + $handshake_message .= "$header\r\n"; + } + } else { + $handshake_message .= "$connection->headers\r\n"; + } + } + } + if (!$has_server_header) { + $handshake_message .= "Server: workerman/".Worker::VERSION."\r\n"; + } + $handshake_message .= "\r\n"; + // Send handshake response. + $connection->send($handshake_message, true); + // Mark handshake complete.. + $connection->websocketHandshake = true; + // There are data waiting to be sent. + if (!empty($connection->tmpWebsocketData)) { + $connection->send($connection->tmpWebsocketData, true); + $connection->tmpWebsocketData = ''; + } + if (\strlen($buffer) > $header_length) { + return static::input(\substr($buffer, $header_length), $connection); + } + return 0; + } // Is flash policy-file-request. + elseif (0 === \strpos($buffer, 'send($policy_xml, true); + $connection->consumeRecvBuffer(\strlen($buffer)); + return 0; + } + // Bad websocket handshake request. + $connection->send("HTTP/1.1 200 Websocket\r\nServer: workerman/".Worker::VERSION."\r\n\r\n

Websocket


powered by workerman ".Worker::VERSION."
", + true); + $connection->close(); + return 0; + } + + /** + * Parse http header. + * + * @param string $buffer + * @return void + */ + protected static function parseHttpHeader($buffer) + { + // Parse headers. + list($http_header, ) = \explode("\r\n\r\n", $buffer, 2); + $header_data = \explode("\r\n", $http_header); + + if ($_SERVER) { + $_SERVER = array(); + } + + list($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI'], $_SERVER['SERVER_PROTOCOL']) = \explode(' ', + $header_data[0]); + + unset($header_data[0]); + foreach ($header_data as $content) { + // \r\n\r\n + if (empty($content)) { + continue; + } + list($key, $value) = \explode(':', $content, 2); + $key = \str_replace('-', '_', \strtoupper($key)); + $value = \trim($value); + $_SERVER['HTTP_' . $key] = $value; + switch ($key) { + // HTTP_HOST + case 'HOST': + $tmp = \explode(':', $value); + $_SERVER['SERVER_NAME'] = $tmp[0]; + if (isset($tmp[1])) { + $_SERVER['SERVER_PORT'] = $tmp[1]; + } + break; + // cookie + case 'COOKIE': + \parse_str(\str_replace('; ', '&', $_SERVER['HTTP_COOKIE']), $_COOKIE); + break; + } + } + + // QUERY_STRING + $_SERVER['QUERY_STRING'] = \parse_url($_SERVER['REQUEST_URI'], \PHP_URL_QUERY); + if ($_SERVER['QUERY_STRING']) { + // $GET + \parse_str($_SERVER['QUERY_STRING'], $_GET); + } else { + $_SERVER['QUERY_STRING'] = ''; + } + } +} diff --git a/GatewayWorker/vendor/workerman/workerman/Protocols/Ws.php b/GatewayWorker/vendor/workerman/workerman/Protocols/Ws.php new file mode 100644 index 0000000..7372187 --- /dev/null +++ b/GatewayWorker/vendor/workerman/workerman/Protocols/Ws.php @@ -0,0 +1,472 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Protocols; + +use Workerman\Worker; +use Workerman\Lib\Timer; +use Workerman\Connection\TcpConnection; +use Workerman\Connection\ConnectionInterface; + +/** + * Websocket protocol for client. + */ +class Ws +{ + /** + * Websocket blob type. + * + * @var string + */ + const BINARY_TYPE_BLOB = "\x81"; + + /** + * Websocket arraybuffer type. + * + * @var string + */ + const BINARY_TYPE_ARRAYBUFFER = "\x82"; + + /** + * Check the integrity of the package. + * + * @param string $buffer + * @param ConnectionInterface $connection + * @return int + */ + public static function input($buffer, ConnectionInterface $connection) + { + if (empty($connection->handshakeStep)) { + Worker::safeEcho("recv data before handshake. Buffer:" . \bin2hex($buffer) . "\n"); + return false; + } + // Recv handshake response + if ($connection->handshakeStep === 1) { + return self::dealHandshake($buffer, $connection); + } + $recv_len = \strlen($buffer); + if ($recv_len < 2) { + return 0; + } + // Buffer websocket frame data. + if ($connection->websocketCurrentFrameLength) { + // We need more frame data. + if ($connection->websocketCurrentFrameLength > $recv_len) { + // Return 0, because it is not clear the full packet length, waiting for the frame of fin=1. + return 0; + } + } else { + + $firstbyte = \ord($buffer[0]); + $secondbyte = \ord($buffer[1]); + $data_len = $secondbyte & 127; + $is_fin_frame = $firstbyte >> 7; + $masked = $secondbyte >> 7; + + if ($masked) { + Worker::safeEcho("frame masked so close the connection\n"); + $connection->close(); + return 0; + } + + $opcode = $firstbyte & 0xf; + + switch ($opcode) { + case 0x0: + break; + // Blob type. + case 0x1: + break; + // Arraybuffer type. + case 0x2: + break; + // Close package. + case 0x8: + // Try to emit onWebSocketClose callback. + if (isset($connection->onWebSocketClose)) { + try { + \call_user_func($connection->onWebSocketClose, $connection); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } // Close connection. + else { + $connection->close(); + } + return 0; + // Ping package. + case 0x9: + break; + // Pong package. + case 0xa: + break; + // Wrong opcode. + default : + Worker::safeEcho("error opcode $opcode and close websocket connection. Buffer:" . $buffer . "\n"); + $connection->close(); + return 0; + } + // Calculate packet length. + if ($data_len === 126) { + if (\strlen($buffer) < 4) { + return 0; + } + $pack = \unpack('nn/ntotal_len', $buffer); + $current_frame_length = $pack['total_len'] + 4; + } else if ($data_len === 127) { + if (\strlen($buffer) < 10) { + return 0; + } + $arr = \unpack('n/N2c', $buffer); + $current_frame_length = $arr['c1']*4294967296 + $arr['c2'] + 10; + } else { + $current_frame_length = $data_len + 2; + } + + $total_package_size = \strlen($connection->websocketDataBuffer) + $current_frame_length; + if ($total_package_size > $connection->maxPackageSize) { + Worker::safeEcho("error package. package_length=$total_package_size\n"); + $connection->close(); + return 0; + } + + if ($is_fin_frame) { + if ($opcode === 0x9) { + if ($recv_len >= $current_frame_length) { + $ping_data = static::decode(\substr($buffer, 0, $current_frame_length), $connection); + $connection->consumeRecvBuffer($current_frame_length); + $tmp_connection_type = isset($connection->websocketType) ? $connection->websocketType : static::BINARY_TYPE_BLOB; + $connection->websocketType = "\x8a"; + if (isset($connection->onWebSocketPing)) { + try { + \call_user_func($connection->onWebSocketPing, $connection, $ping_data); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } else { + $connection->send($ping_data); + } + $connection->websocketType = $tmp_connection_type; + if ($recv_len > $current_frame_length) { + return static::input(\substr($buffer, $current_frame_length), $connection); + } + } + return 0; + + } else if ($opcode === 0xa) { + if ($recv_len >= $current_frame_length) { + $pong_data = static::decode(\substr($buffer, 0, $current_frame_length), $connection); + $connection->consumeRecvBuffer($current_frame_length); + $tmp_connection_type = isset($connection->websocketType) ? $connection->websocketType : static::BINARY_TYPE_BLOB; + $connection->websocketType = "\x8a"; + // Try to emit onWebSocketPong callback. + if (isset($connection->onWebSocketPong)) { + try { + \call_user_func($connection->onWebSocketPong, $connection, $pong_data); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + $connection->websocketType = $tmp_connection_type; + if ($recv_len > $current_frame_length) { + return static::input(\substr($buffer, $current_frame_length), $connection); + } + } + return 0; + } + return $current_frame_length; + } else { + $connection->websocketCurrentFrameLength = $current_frame_length; + } + } + // Received just a frame length data. + if ($connection->websocketCurrentFrameLength === $recv_len) { + self::decode($buffer, $connection); + $connection->consumeRecvBuffer($connection->websocketCurrentFrameLength); + $connection->websocketCurrentFrameLength = 0; + return 0; + } // The length of the received data is greater than the length of a frame. + elseif ($connection->websocketCurrentFrameLength < $recv_len) { + self::decode(\substr($buffer, 0, $connection->websocketCurrentFrameLength), $connection); + $connection->consumeRecvBuffer($connection->websocketCurrentFrameLength); + $current_frame_length = $connection->websocketCurrentFrameLength; + $connection->websocketCurrentFrameLength = 0; + // Continue to read next frame. + return self::input(\substr($buffer, $current_frame_length), $connection); + } // The length of the received data is less than the length of a frame. + else { + return 0; + } + } + + /** + * Websocket encode. + * + * @param string $buffer + * @param ConnectionInterface $connection + * @return string + */ + public static function encode($payload, ConnectionInterface $connection) + { + if (empty($connection->websocketType)) { + $connection->websocketType = self::BINARY_TYPE_BLOB; + } + $payload = (string)$payload; + if (empty($connection->handshakeStep)) { + static::sendHandshake($connection); + } + $mask = 1; + $mask_key = "\x00\x00\x00\x00"; + + $pack = ''; + $length = $length_flag = \strlen($payload); + if (65535 < $length) { + $pack = \pack('NN', ($length & 0xFFFFFFFF00000000) >> 32, $length & 0x00000000FFFFFFFF); + $length_flag = 127; + } else if (125 < $length) { + $pack = \pack('n*', $length); + $length_flag = 126; + } + + $head = ($mask << 7) | $length_flag; + $head = $connection->websocketType . \chr($head) . $pack; + + $frame = $head . $mask_key; + // append payload to frame: + $mask_key = \str_repeat($mask_key, \floor($length / 4)) . \substr($mask_key, 0, $length % 4); + $frame .= $payload ^ $mask_key; + if ($connection->handshakeStep === 1) { + // If buffer has already full then discard the current package. + if (\strlen($connection->tmpWebsocketData) > $connection->maxSendBufferSize) { + if ($connection->onError) { + try { + \call_user_func($connection->onError, $connection, \WORKERMAN_SEND_FAIL, 'send buffer full and drop package'); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + return ''; + } + $connection->tmpWebsocketData = $connection->tmpWebsocketData . $frame; + // Check buffer is full. + if ($connection->maxSendBufferSize <= \strlen($connection->tmpWebsocketData)) { + if ($connection->onBufferFull) { + try { + \call_user_func($connection->onBufferFull, $connection); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + } + return ''; + } + return $frame; + } + + /** + * Websocket decode. + * + * @param string $buffer + * @param ConnectionInterface $connection + * @return string + */ + public static function decode($bytes, ConnectionInterface $connection) + { + $data_length = \ord($bytes[1]); + + if ($data_length === 126) { + $decoded_data = \substr($bytes, 4); + } else if ($data_length === 127) { + $decoded_data = \substr($bytes, 10); + } else { + $decoded_data = \substr($bytes, 2); + } + if ($connection->websocketCurrentFrameLength) { + $connection->websocketDataBuffer .= $decoded_data; + return $connection->websocketDataBuffer; + } else { + if ($connection->websocketDataBuffer !== '') { + $decoded_data = $connection->websocketDataBuffer . $decoded_data; + $connection->websocketDataBuffer = ''; + } + return $decoded_data; + } + } + + /** + * Send websocket handshake data. + * + * @return void + */ + public static function onConnect($connection) + { + static::sendHandshake($connection); + } + + /** + * Clean + * + * @param $connection + */ + public static function onClose($connection) + { + $connection->handshakeStep = null; + $connection->websocketCurrentFrameLength = 0; + $connection->tmpWebsocketData = ''; + $connection->websocketDataBuffer = ''; + if (!empty($connection->websocketPingTimer)) { + Timer::del($connection->websocketPingTimer); + $connection->websocketPingTimer = null; + } + } + + /** + * Send websocket handshake. + * + * @param TcpConnection $connection + * @return void + */ + public static function sendHandshake(TcpConnection $connection) + { + if (!empty($connection->handshakeStep)) { + return; + } + // Get Host. + $port = $connection->getRemotePort(); + $host = $port === 80 ? $connection->getRemoteHost() : $connection->getRemoteHost() . ':' . $port; + // Handshake header. + $connection->websocketSecKey = \base64_encode(\md5(\mt_rand(), true)); + $user_header = isset($connection->headers) ? $connection->headers : + (isset($connection->wsHttpHeader) ? $connection->wsHttpHeader : null); + $user_header_str = ''; + if (!empty($user_header)) { + if (\is_array($user_header)){ + foreach($user_header as $k=>$v){ + $user_header_str .= "$k: $v\r\n"; + } + } else { + $user_header_str .= $user_header; + } + $user_header_str = "\r\n".\trim($user_header_str); + } + $header = 'GET ' . $connection->getRemoteURI() . " HTTP/1.1\r\n". + (!\preg_match("/\nHost:/i", $user_header_str) ? "Host: $host\r\n" : ''). + "Connection: Upgrade\r\n". + "Upgrade: websocket\r\n". + (isset($connection->websocketOrigin) ? "Origin: ".$connection->websocketOrigin."\r\n":''). + (isset($connection->WSClientProtocol)?"Sec-WebSocket-Protocol: ".$connection->WSClientProtocol."\r\n":''). + "Sec-WebSocket-Version: 13\r\n". + "Sec-WebSocket-Key: " . $connection->websocketSecKey . $user_header_str . "\r\n\r\n"; + $connection->send($header, true); + $connection->handshakeStep = 1; + $connection->websocketCurrentFrameLength = 0; + $connection->websocketDataBuffer = ''; + $connection->tmpWebsocketData = ''; + } + + /** + * Websocket handshake. + * + * @param string $buffer + * @param TcpConnection $connection + * @return int + */ + public static function dealHandshake($buffer, TcpConnection $connection) + { + $pos = \strpos($buffer, "\r\n\r\n"); + if ($pos) { + //checking Sec-WebSocket-Accept + if (\preg_match("/Sec-WebSocket-Accept: *(.*?)\r\n/i", $buffer, $match)) { + if ($match[1] !== \base64_encode(\sha1($connection->websocketSecKey . "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", true))) { + Worker::safeEcho("Sec-WebSocket-Accept not match. Header:\n" . \substr($buffer, 0, $pos) . "\n"); + $connection->close(); + return 0; + } + } else { + Worker::safeEcho("Sec-WebSocket-Accept not found. Header:\n" . \substr($buffer, 0, $pos) . "\n"); + $connection->close(); + return 0; + } + + // handshake complete + + // Get WebSocket subprotocol (if specified by server) + if (\preg_match("/Sec-WebSocket-Protocol: *(.*?)\r\n/i", $buffer, $match)) { + $connection->WSServerProtocol = \trim($match[1]); + } + + $connection->handshakeStep = 2; + $handshake_response_length = $pos + 4; + // Try to emit onWebSocketConnect callback. + if (isset($connection->onWebSocketConnect)) { + try { + \call_user_func($connection->onWebSocketConnect, $connection, \substr($buffer, 0, $handshake_response_length)); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + // Headbeat. + if (!empty($connection->websocketPingInterval)) { + $connection->websocketPingTimer = Timer::add($connection->websocketPingInterval, function() use ($connection){ + if (false === $connection->send(\pack('H*', '898000000000'), true)) { + Timer::del($connection->websocketPingTimer); + $connection->websocketPingTimer = null; + } + }); + } + + $connection->consumeRecvBuffer($handshake_response_length); + if (!empty($connection->tmpWebsocketData)) { + $connection->send($connection->tmpWebsocketData, true); + $connection->tmpWebsocketData = ''; + } + if (\strlen($buffer) > $handshake_response_length) { + return self::input(\substr($buffer, $handshake_response_length), $connection); + } + } + return 0; + } + + public static function WSSetProtocol($connection, $params) { + $connection->WSClientProtocol = $params[0]; + } + + public static function WSGetServerProtocol($connection) { + return (\property_exists($connection, 'WSServerProtocol') ? $connection->WSServerProtocol : null); + } + +} diff --git a/GatewayWorker/vendor/workerman/workerman/README.md b/GatewayWorker/vendor/workerman/workerman/README.md new file mode 100644 index 0000000..2aac0f2 --- /dev/null +++ b/GatewayWorker/vendor/workerman/workerman/README.md @@ -0,0 +1,606 @@ +# Workerman +[![Gitter](https://badges.gitter.im/walkor/Workerman.svg)](https://gitter.im/walkor/Workerman?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=body_badge) +[![Latest Stable Version](https://poser.pugx.org/workerman/workerman/v/stable)](https://packagist.org/packages/workerman/workerman) +[![Total Downloads](https://poser.pugx.org/workerman/workerman/downloads)](https://packagist.org/packages/workerman/workerman) +[![Monthly Downloads](https://poser.pugx.org/workerman/workerman/d/monthly)](https://packagist.org/packages/workerman/workerman) +[![Daily Downloads](https://poser.pugx.org/workerman/workerman/d/daily)](https://packagist.org/packages/workerman/workerman) +[![License](https://poser.pugx.org/workerman/workerman/license)](https://packagist.org/packages/workerman/workerman) + +## What is it +Workerman is an asynchronous event-driven PHP framework with high performance to build fast and scalable network applications. +Workerman supports HTTP, Websocket, SSL and other custom protocols. +Workerman supports libevent/event extension, [HHVM](https://github.com/facebook/hhvm) , [ReactPHP](https://github.com/reactphp/react). + +## Requires +PHP 5.3 or Higher +A POSIX compatible operating system (Linux, OSX, BSD) +POSIX and PCNTL extensions required +Event extension recommended for better performance + +## Installation + +``` +composer require workerman/workerman +``` + +## Basic Usage + +### A websocket server +```php +count = 4; + +// Emitted when new connection come +$ws_worker->onConnect = function($connection) +{ + echo "New connection\n"; + }; + +// Emitted when data received +$ws_worker->onMessage = function($connection, $data) +{ + // Send hello $data + $connection->send('hello ' . $data); +}; + +// Emitted when connection closed +$ws_worker->onClose = function($connection) +{ + echo "Connection closed\n"; +}; + +// Run worker +Worker::runAll(); +``` + +### An http server +```php +require_once __DIR__ . '/vendor/autoload.php'; +use Workerman\Worker; + +// #### http worker #### +$http_worker = new Worker("http://0.0.0.0:2345"); + +// 4 processes +$http_worker->count = 4; + +// Emitted when data received +$http_worker->onMessage = function($connection, $data) +{ + // $_GET, $_POST, $_COOKIE, $_SESSION, $_SERVER, $_FILES are available + var_dump($_GET, $_POST, $_COOKIE, $_SESSION, $_SERVER, $_FILES); + // send data to client + $connection->send("hello world \n"); +}; + +// run all workers +Worker::runAll(); +``` + +### A WebServer +```php +require_once __DIR__ . '/vendor/autoload.php'; +use Workerman\WebServer; +use Workerman\Worker; + +// WebServer +$web = new WebServer("http://0.0.0.0:80"); + +// 4 processes +$web->count = 4; + +// Set the root of domains +$web->addRoot('www.your_domain.com', '/your/path/Web'); +$web->addRoot('www.another_domain.com', '/another/path/Web'); +// run all workers +Worker::runAll(); +``` + +### A tcp server +```php +require_once __DIR__ . '/vendor/autoload.php'; +use Workerman\Worker; + +// #### create socket and listen 1234 port #### +$tcp_worker = new Worker("tcp://0.0.0.0:1234"); + +// 4 processes +$tcp_worker->count = 4; + +// Emitted when new connection come +$tcp_worker->onConnect = function($connection) +{ + echo "New Connection\n"; +}; + +// Emitted when data received +$tcp_worker->onMessage = function($connection, $data) +{ + // send data to client + $connection->send("hello $data \n"); +}; + +// Emitted when new connection come +$tcp_worker->onClose = function($connection) +{ + echo "Connection closed\n"; +}; + +Worker::runAll(); +``` + +### Enable SSL +```php + array( + 'local_cert' => '/your/path/of/server.pem', + 'local_pk' => '/your/path/of/server.key', + 'verify_peer' => false, + ) +); + +// Create a Websocket server with ssl context. +$ws_worker = new Worker("websocket://0.0.0.0:2346", $context); + +// Enable SSL. WebSocket+SSL means that Secure WebSocket (wss://). +// The similar approaches for Https etc. +$ws_worker->transport = 'ssl'; + +$ws_worker->onMessage = function($connection, $data) +{ + // Send hello $data + $connection->send('hello ' . $data); +}; + +Worker::runAll(); +``` + +### Custom protocol +Protocols/MyTextProtocol.php +```php +namespace Protocols; +/** + * User defined protocol + * Format Text+"\n" + */ +class MyTextProtocol +{ + public static function input($recv_buffer) + { + // Find the position of the first occurrence of "\n" + $pos = strpos($recv_buffer, "\n"); + // Not a complete package. Return 0 because the length of package can not be calculated + if($pos === false) + { + return 0; + } + // Return length of the package + return $pos+1; + } + + public static function decode($recv_buffer) + { + return trim($recv_buffer); + } + + public static function encode($data) + { + return $data."\n"; + } +} +``` + +```php +require_once __DIR__ . '/vendor/autoload.php'; +use Workerman\Worker; + +// #### MyTextProtocol worker #### +$text_worker = new Worker("MyTextProtocol://0.0.0.0:5678"); + +$text_worker->onConnect = function($connection) +{ + echo "New connection\n"; +}; + +$text_worker->onMessage = function($connection, $data) +{ + // send data to client + $connection->send("hello world \n"); +}; + +$text_worker->onClose = function($connection) +{ + echo "Connection closed\n"; +}; + +// run all workers +Worker::runAll(); +``` + +### Timer +```php +require_once __DIR__ . '/vendor/autoload.php'; +use Workerman\Worker; +use Workerman\Lib\Timer; + +$task = new Worker(); +$task->onWorkerStart = function($task) +{ + // 2.5 seconds + $time_interval = 2.5; + $timer_id = Timer::add($time_interval, + function() + { + echo "Timer run\n"; + } + ); +}; + +// run all workers +Worker::runAll(); +``` + +### AsyncTcpConnection (tcp/ws/text/frame etc...) +```php +require_once __DIR__ . '/vendor/autoload.php'; +use Workerman\Worker; +use Workerman\Connection\AsyncTcpConnection; + +$worker = new Worker(); +$worker->onWorkerStart = function() +{ + // Websocket protocol for client. + $ws_connection = new AsyncTcpConnection("ws://echo.websocket.org:80"); + $ws_connection->onConnect = function($connection){ + $connection->send('hello'); + }; + $ws_connection->onMessage = function($connection, $data){ + echo "recv: $data\n"; + }; + $ws_connection->onError = function($connection, $code, $msg){ + echo "error: $msg\n"; + }; + $ws_connection->onClose = function($connection){ + echo "connection closed\n"; + }; + $ws_connection->connect(); +}; +Worker::runAll(); +``` + +### Async Mysql of ReactPHP +``` +composer require react/mysql +``` + +```php +onWorkerStart = function() { + global $mysql; + $loop = Worker::getEventLoop(); + $mysql = new React\MySQL\Connection($loop, array( + 'host' => '127.0.0.1', + 'dbname' => 'dbname', + 'user' => 'user', + 'passwd' => 'passwd', + )); + $mysql->on('error', function($e){ + echo $e; + }); + $mysql->connect(function ($e) { + if($e) { + echo $e; + } else { + echo "connect success\n"; + } + }); +}; +$worker->onMessage = function($connection, $data) { + global $mysql; + $mysql->query('show databases' /*trim($data)*/, function ($command, $mysql) use ($connection) { + if ($command->hasError()) { + $error = $command->getError(); + } else { + $results = $command->resultRows; + $fields = $command->resultFields; + $connection->send(json_encode($results)); + } + }); +}; +Worker::runAll(); +``` + +### Async Redis of ReactPHP +``` +composer require clue/redis-react +``` + +```php +onWorkerStart = function() { + global $factory; + $loop = Worker::getEventLoop(); + $factory = new Factory($loop); +}; + +$worker->onMessage = function($connection, $data) { + global $factory; + $factory->createClient('localhost:6379')->then(function (Client $client) use ($connection) { + $client->set('greeting', 'Hello world'); + $client->append('greeting', '!'); + + $client->get('greeting')->then(function ($greeting) use ($connection){ + // Hello world! + echo $greeting . PHP_EOL; + $connection->send($greeting); + }); + + $client->incr('invocation')->then(function ($n) use ($connection){ + echo 'This is invocation #' . $n . PHP_EOL; + $connection->send($n); + }); + }); +}; + +Worker::runAll(); +``` + +### Aysnc dns of ReactPHP +``` +composer require react/dns +``` + +```php +require_once __DIR__ . '/vendor/autoload.php'; +use Workerman\Worker; +$worker = new Worker('tcp://0.0.0.0:6161'); +$worker->onWorkerStart = function() { + global $dns; + // Get event-loop. + $loop = Worker::getEventLoop(); + $factory = new React\Dns\Resolver\Factory(); + $dns = $factory->create('8.8.8.8', $loop); +}; +$worker->onMessage = function($connection, $host) { + global $dns; + $host = trim($host); + $dns->resolve($host)->then(function($ip) use($host, $connection) { + $connection->send("$host: $ip"); + },function($e) use($host, $connection){ + $connection->send("$host: {$e->getMessage()}"); + }); +}; + +Worker::runAll(); +``` + +### Http client of ReactPHP +``` +composer require react/http-client +``` + +```php +onMessage = function($connection, $host) { + $loop = Worker::getEventLoop(); + $client = new \React\HttpClient\Client($loop); + $request = $client->request('GET', trim($host)); + $request->on('error', function(Exception $e) use ($connection) { + $connection->send($e); + }); + $request->on('response', function ($response) use ($connection) { + $response->on('data', function ($data) use ($connection) { + $connection->send($data); + }); + }); + $request->end(); +}; + +Worker::runAll(); +``` + +### ZMQ of ReactPHP +``` +composer require react/zmq +``` + +```php +onWorkerStart = function() { + global $pull; + $loop = Worker::getEventLoop(); + $context = new React\ZMQ\Context($loop); + $pull = $context->getSocket(ZMQ::SOCKET_PULL); + $pull->bind('tcp://127.0.0.1:5555'); + + $pull->on('error', function ($e) { + var_dump($e->getMessage()); + }); + + $pull->on('message', function ($msg) { + echo "Received: $msg\n"; + }); +}; + +Worker::runAll(); +``` + +### STOMP of ReactPHP +``` +composer require react/stomp +``` + +```php +onWorkerStart = function() { + global $client; + $loop = Worker::getEventLoop(); + $factory = new React\Stomp\Factory($loop); + $client = $factory->createClient(array('vhost' => '/', 'login' => 'guest', 'passcode' => 'guest')); + + $client + ->connect() + ->then(function ($client) use ($loop) { + $client->subscribe('/topic/foo', function ($frame) { + echo "Message received: {$frame->body}\n"; + }); + }); +}; + +Worker::runAll(); +``` + + + +## Available commands +```php start.php start ``` +```php start.php start -d ``` +![workerman start](http://www.workerman.net/img/workerman-start.png) +```php start.php status ``` +![workerman satus](http://www.workerman.net/img/workerman-status.png?a=123) +```php start.php connections``` +```php start.php stop ``` +```php start.php restart ``` +```php start.php reload ``` + +## Documentation + +中文主页:[http://www.workerman.net](http://www.workerman.net) + +中文文档: [http://doc.workerman.net](http://doc.workerman.net) + +Documentation:[https://github.com/walkor/workerman-manual](https://github.com/walkor/workerman-manual/blob/master/english/src/SUMMARY.md) + +# Benchmarks +``` +CPU: Intel(R) Core(TM) i3-3220 CPU @ 3.30GHz and 4 processors totally +Memory: 8G +OS: Ubuntu 14.04 LTS +Software: ab +PHP: 5.5.9 +``` + +**Codes** +```php +count=3; +$worker->onMessage = function($connection, $data) +{ + $connection->send("HTTP/1.1 200 OK\r\nConnection: keep-alive\r\nServer: workerman\r\nContent-Length: 5\r\n\r\nhello"); +}; +Worker::runAll(); +``` +**Result** + +```shell +ab -n1000000 -c100 -k http://127.0.0.1:1234/ +This is ApacheBench, Version 2.3 <$Revision: 1528965 $> +Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ +Licensed to The Apache Software Foundation, http://www.apache.org/ + +Benchmarking 127.0.0.1 (be patient) +Completed 100000 requests +Completed 200000 requests +Completed 300000 requests +Completed 400000 requests +Completed 500000 requests +Completed 600000 requests +Completed 700000 requests +Completed 800000 requests +Completed 900000 requests +Completed 1000000 requests +Finished 1000000 requests + + +Server Software: workerman/3.1.4 +Server Hostname: 127.0.0.1 +Server Port: 1234 + +Document Path: / +Document Length: 5 bytes + +Concurrency Level: 100 +Time taken for tests: 7.240 seconds +Complete requests: 1000000 +Failed requests: 0 +Keep-Alive requests: 1000000 +Total transferred: 73000000 bytes +HTML transferred: 5000000 bytes +Requests per second: 138124.14 [#/sec] (mean) +Time per request: 0.724 [ms] (mean) +Time per request: 0.007 [ms] (mean, across all concurrent requests) +Transfer rate: 9846.74 [Kbytes/sec] received + +Connection Times (ms) + min mean[+/-sd] median max +Connect: 0 0 0.0 0 5 +Processing: 0 1 0.2 1 9 +Waiting: 0 1 0.2 1 9 +Total: 0 1 0.2 1 9 + +Percentage of the requests served within a certain time (ms) + 50% 1 + 66% 1 + 75% 1 + 80% 1 + 90% 1 + 95% 1 + 98% 1 + 99% 1 + 100% 9 (longest request) + +``` + + +## Other links with workerman + +[PHPSocket.IO](https://github.com/walkor/phpsocket.io) +[php-socks5](https://github.com/walkor/php-socks5) +[php-http-proxy](https://github.com/walkor/php-http-proxy) + +## Donate + + +## LICENSE + +Workerman is released under the [MIT license](https://github.com/walkor/workerman/blob/master/MIT-LICENSE.txt). diff --git a/GatewayWorker/vendor/workerman/workerman/WebServer.php b/GatewayWorker/vendor/workerman/workerman/WebServer.php new file mode 100644 index 0000000..a9a750a --- /dev/null +++ b/GatewayWorker/vendor/workerman/workerman/WebServer.php @@ -0,0 +1,328 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman; + +use Workerman\Protocols\Http; +use Workerman\Protocols\HttpCache; +use Workerman\Connection\TcpConnection; + +/** + * WebServer. + */ +class WebServer extends Worker +{ + /** + * Virtual host to path mapping. + * + * @var array ['workerman.net'=>'/home', 'www.workerman.net'=>'home/www'] + */ + protected $serverRoot = array(); + + /** + * Mime mapping. + * + * @var array + */ + protected static $mimeTypeMap = array(); + + + /** + * Used to save user OnWorkerStart callback settings. + * + * @var callable + */ + protected $_onWorkerStart = null; + + /** + * Add virtual host. + * + * @param string $domain + * @param string $config + * @return void + */ + public function addRoot($domain, $config) + { + if (\is_string($config)) { + $config = array('root' => $config); + } + $this->serverRoot[$domain] = $config; + } + + /** + * Construct. + * + * @param string $socket_name + * @param array $context_option + */ + public function __construct($socket_name, array $context_option = array()) + { + list(, $address) = \explode(':', $socket_name, 2); + parent::__construct('http:' . $address, $context_option); + $this->name = 'WebServer'; + } + + /** + * Run webserver instance. + * + * @see Workerman.Worker::run() + */ + public function run() + { + $this->_onWorkerStart = $this->onWorkerStart; + $this->onWorkerStart = array($this, 'onWorkerStart'); + $this->onMessage = array($this, 'onMessage'); + parent::run(); + } + + /** + * Emit when process start. + * + * @throws \Exception + */ + public function onWorkerStart() + { + if (empty($this->serverRoot)) { + Worker::safeEcho(new \Exception('server root not set, please use WebServer::addRoot($domain, $root_path) to set server root path')); + exit(250); + } + + // Init mimeMap. + $this->initMimeTypeMap(); + + // Try to emit onWorkerStart callback. + if ($this->_onWorkerStart) { + try { + \call_user_func($this->_onWorkerStart, $this); + } catch (\Exception $e) { + self::log($e); + exit(250); + } catch (\Error $e) { + self::log($e); + exit(250); + } + } + } + + /** + * Init mime map. + * + * @return void + */ + public function initMimeTypeMap() + { + $mime_file = Http::getMimeTypesFile(); + if (!\is_file($mime_file)) { + $this->log("$mime_file mime.type file not fond"); + return; + } + $items = \file($mime_file, \FILE_IGNORE_NEW_LINES | \FILE_SKIP_EMPTY_LINES); + if (!\is_array($items)) { + $this->log("get $mime_file mime.type content fail"); + return; + } + foreach ($items as $content) { + if (\preg_match("/\s*(\S+)\s+(\S.+)/", $content, $match)) { + $mime_type = $match[1]; + $workerman_file_extension_var = $match[2]; + $workerman_file_extension_array = \explode(' ', \substr($workerman_file_extension_var, 0, -1)); + foreach ($workerman_file_extension_array as $workerman_file_extension) { + self::$mimeTypeMap[$workerman_file_extension] = $mime_type; + } + } + } + } + + /** + * Emit when http message coming. + * + * @param TcpConnection $connection + * @return void + */ + public function onMessage(TcpConnection $connection) + { + // REQUEST_URI. + $workerman_url_info = \parse_url('http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']); + if (!$workerman_url_info) { + Http::header('HTTP/1.1 400 Bad Request'); + if (\strtolower($_SERVER['HTTP_CONNECTION']) === "keep-alive") { + $connection->send('

400 Bad Request

'); + } else { + $connection->close('

400 Bad Request

'); + } + return; + } + + $workerman_path = isset($workerman_url_info['path']) ? $workerman_url_info['path'] : '/'; + + $workerman_path_info = \pathinfo($workerman_path); + $workerman_file_extension = isset($workerman_path_info['extension']) ? $workerman_path_info['extension'] : ''; + if ($workerman_file_extension === '') { + $workerman_path = ($len = \strlen($workerman_path)) && $workerman_path[$len - 1] === '/' ? $workerman_path . 'index.php' : $workerman_path . '/index.php'; + $workerman_file_extension = 'php'; + } + + $workerman_siteConfig = isset($this->serverRoot[$_SERVER['SERVER_NAME']]) ? $this->serverRoot[$_SERVER['SERVER_NAME']] : \current($this->serverRoot); + $workerman_root_dir = $workerman_siteConfig['root']; + $workerman_file = "$workerman_root_dir/$workerman_path"; + if(isset($workerman_siteConfig['additionHeader'])){ + Http::header($workerman_siteConfig['additionHeader']); + } + if ($workerman_file_extension === 'php' && !\is_file($workerman_file)) { + $workerman_file = "$workerman_root_dir/index.php"; + if (!\is_file($workerman_file)) { + $workerman_file = "$workerman_root_dir/index.html"; + $workerman_file_extension = 'html'; + } + } + + // File exsits. + if (\is_file($workerman_file)) { + // Security check. + if ((!($workerman_request_realpath = \realpath($workerman_file)) || !($workerman_root_dir_realpath = \realpath($workerman_root_dir))) || 0 !== \strpos($workerman_request_realpath, + $workerman_root_dir_realpath) + ) { + Http::header('HTTP/1.1 400 Bad Request'); + if (\strtolower($_SERVER['HTTP_CONNECTION']) === "keep-alive") { + $connection->send('

400 Bad Request

'); + } else { + $connection->close('

400 Bad Request

'); + } + return; + } + + $workerman_file = \realpath($workerman_file); + + // Request php file. + if ($workerman_file_extension === 'php') { + $workerman_cwd = \getcwd(); + \chdir($workerman_root_dir); + \ini_set('display_errors', 'off'); + \ob_start(); + // Try to include php file. + try { + // $_SERVER. + $_SERVER['REMOTE_ADDR'] = $connection->getRemoteIp(); + $_SERVER['REMOTE_PORT'] = $connection->getRemotePort(); + include $workerman_file; + } catch (\Exception $e) { + // Jump_exit? + if ($e->getMessage() !== 'jump_exit') { + Worker::safeEcho($e); + } + } + $content = \ob_get_clean(); + \ini_set('display_errors', 'on'); + if (\strtolower($_SERVER['HTTP_CONNECTION']) === "keep-alive") { + $connection->send($content); + } else { + $connection->close($content); + } + \chdir($workerman_cwd); + return; + } + + // Send file to client. + return self::sendFile($connection, $workerman_file); + } else { + // 404 + Http::header("HTTP/1.1 404 Not Found"); + if(isset($workerman_siteConfig['custom404']) && \file_exists($workerman_siteConfig['custom404'])){ + $html404 = \file_get_contents($workerman_siteConfig['custom404']); + }else{ + $html404 = '404 File not found

404 Not Found

'; + } + if (\strtolower($_SERVER['HTTP_CONNECTION']) === "keep-alive") { + $connection->send($html404); + } else { + $connection->close($html404); + } + return; + } + } + + public static function sendFile($connection, $file_path) + { + // Check 304. + $info = \stat($file_path); + $modified_time = $info ? \date('D, d M Y H:i:s', $info['mtime']) . ' ' . \date_default_timezone_get() : ''; + if (!empty($_SERVER['HTTP_IF_MODIFIED_SINCE']) && $info) { + // Http 304. + if ($modified_time === $_SERVER['HTTP_IF_MODIFIED_SINCE']) { + // 304 + Http::header('HTTP/1.1 304 Not Modified'); + // Send nothing but http headers.. + if (\strtolower($_SERVER['HTTP_CONNECTION']) === "keep-alive") { + $connection->send(''); + } else { + $connection->close(''); + } + return; + } + } + + // Http header. + if ($modified_time) { + $modified_time = "Last-Modified: $modified_time\r\n"; + } + $file_size = \filesize($file_path); + $file_info = \pathinfo($file_path); + $extension = isset($file_info['extension']) ? $file_info['extension'] : ''; + $file_name = isset($file_info['filename']) ? $file_info['filename'] : ''; + $header = "HTTP/1.1 200 OK\r\n"; + if (isset(self::$mimeTypeMap[$extension])) { + $header .= "Content-Type: " . self::$mimeTypeMap[$extension] . "\r\n"; + } else { + $header .= "Content-Type: application/octet-stream\r\n" + ."Content-Disposition: attachment; filename=\"$file_name\"\r\n"; + } + $header .= "Connection: keep-alive\r\n" + .$modified_time + ."Content-Length: $file_size\r\n\r\n"; + $trunk_limit_size = 1024*1024; + if ($file_size < $trunk_limit_size) { + return $connection->send($header.\file_get_contents($file_path), true); + } + $connection->send($header, true); + + // Read file content from disk piece by piece and send to client. + $connection->fileHandler = \fopen($file_path, 'r'); + $do_write = function()use($connection) + { + // Send buffer not full. + while(empty($connection->bufferFull)) + { + // Read from disk. + $buffer = \fread($connection->fileHandler, 8192); + // Read eof. + if($buffer === '' || $buffer === false) + { + return; + } + $connection->send($buffer, true); + } + }; + // Send buffer full. + $connection->onBufferFull = function($connection) + { + $connection->bufferFull = true; + }; + // Send buffer drain. + $connection->onBufferDrain = function($connection)use($do_write) + { + $connection->bufferFull = false; + $do_write(); + }; + $do_write(); + } +} diff --git a/GatewayWorker/vendor/workerman/workerman/Worker.php b/GatewayWorker/vendor/workerman/workerman/Worker.php new file mode 100644 index 0000000..c18754d --- /dev/null +++ b/GatewayWorker/vendor/workerman/workerman/Worker.php @@ -0,0 +1,2534 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman; +require_once __DIR__ . '/Lib/Constants.php'; + +use Workerman\Events\EventInterface; +use Workerman\Connection\ConnectionInterface; +use Workerman\Connection\TcpConnection; +use Workerman\Connection\UdpConnection; +use Workerman\Lib\Timer; +use Workerman\Events\Select; +use \Exception; + +/** + * Worker class + * A container for listening ports + */ +class Worker +{ + /** + * Version. + * + * @var string + */ + const VERSION = '3.5.26'; + + /** + * Status starting. + * + * @var int + */ + const STATUS_STARTING = 1; + + /** + * Status running. + * + * @var int + */ + const STATUS_RUNNING = 2; + + /** + * Status shutdown. + * + * @var int + */ + const STATUS_SHUTDOWN = 4; + + /** + * Status reloading. + * + * @var int + */ + const STATUS_RELOADING = 8; + + /** + * After sending the restart command to the child process KILL_WORKER_TIMER_TIME seconds, + * if the process is still living then forced to kill. + * + * @var int + */ + const KILL_WORKER_TIMER_TIME = 2; + + /** + * Default backlog. Backlog is the maximum length of the queue of pending connections. + * + * @var int + */ + const DEFAULT_BACKLOG = 102400; + /** + * Max udp package size. + * + * @var int + */ + const MAX_UDP_PACKAGE_SIZE = 65535; + + /** + * The safe distance for columns adjacent + * + * @var int + */ + const UI_SAFE_LENGTH = 4; + + /** + * Worker id. + * + * @var int + */ + public $id = 0; + + /** + * Name of the worker processes. + * + * @var string + */ + public $name = 'none'; + + /** + * Number of worker processes. + * + * @var int + */ + public $count = 1; + + /** + * Unix user of processes, needs appropriate privileges (usually root). + * + * @var string + */ + public $user = ''; + + /** + * Unix group of processes, needs appropriate privileges (usually root). + * + * @var string + */ + public $group = ''; + + /** + * reloadable. + * + * @var bool + */ + public $reloadable = true; + + /** + * reuse port. + * + * @var bool + */ + public $reusePort = false; + + /** + * Emitted when worker processes start. + * + * @var callable + */ + public $onWorkerStart = null; + + /** + * Emitted when a socket connection is successfully established. + * + * @var callable + */ + public $onConnect = null; + + /** + * Emitted when data is received. + * + * @var callable + */ + public $onMessage = null; + + /** + * Emitted when the other end of the socket sends a FIN packet. + * + * @var callable + */ + public $onClose = null; + + /** + * Emitted when an error occurs with connection. + * + * @var callable + */ + public $onError = null; + + /** + * Emitted when the send buffer becomes full. + * + * @var callable + */ + public $onBufferFull = null; + + /** + * Emitted when the send buffer becomes empty. + * + * @var callable + */ + public $onBufferDrain = null; + + /** + * Emitted when worker processes stoped. + * + * @var callable + */ + public $onWorkerStop = null; + + /** + * Emitted when worker processes get reload signal. + * + * @var callable + */ + public $onWorkerReload = null; + + /** + * Transport layer protocol. + * + * @var string + */ + public $transport = 'tcp'; + + /** + * Store all connections of clients. + * + * @var array + */ + public $connections = array(); + + /** + * Application layer protocol. + * + * @var string + */ + public $protocol = null; + + /** + * Root path for autoload. + * + * @var string + */ + protected $_autoloadRootPath = ''; + + /** + * Pause accept new connections or not. + * + * @var bool + */ + protected $_pauseAccept = true; + + /** + * Is worker stopping ? + * @var bool + */ + public $stopping = false; + + /** + * Daemonize. + * + * @var bool + */ + public static $daemonize = false; + + /** + * Stdout file. + * + * @var string + */ + public static $stdoutFile = '/dev/null'; + + /** + * The file to store master process PID. + * + * @var string + */ + public static $pidFile = ''; + + /** + * Log file. + * + * @var mixed + */ + public static $logFile = ''; + + /** + * Global event loop. + * + * @var EventInterface + */ + public static $globalEvent = null; + + /** + * Emitted when the master process get reload signal. + * + * @var callable + */ + public static $onMasterReload = null; + + /** + * Emitted when the master process terminated. + * + * @var callable + */ + public static $onMasterStop = null; + + /** + * EventLoopClass + * + * @var string + */ + public static $eventLoopClass = ''; + + /** + * The PID of master process. + * + * @var int + */ + protected static $_masterPid = 0; + + /** + * Listening socket. + * + * @var resource + */ + protected $_mainSocket = null; + + /** + * Socket name. The format is like this http://0.0.0.0:80 . + * + * @var string + */ + protected $_socketName = ''; + + /** parse from _socketName avoid parse again in master or worker + * LocalSocket The format is like tcp://0.0.0.0:8080 + * @var string + */ + + protected $_localSocket=null; + + /** + * Context of socket. + * + * @var resource + */ + protected $_context = null; + + /** + * All worker instances. + * + * @var Worker[] + */ + protected static $_workers = array(); + + /** + * All worker processes pid. + * The format is like this [worker_id=>[pid=>pid, pid=>pid, ..], ..] + * + * @var array + */ + protected static $_pidMap = array(); + + /** + * All worker processes waiting for restart. + * The format is like this [pid=>pid, pid=>pid]. + * + * @var array + */ + protected static $_pidsToRestart = array(); + + /** + * Mapping from PID to worker process ID. + * The format is like this [worker_id=>[0=>$pid, 1=>$pid, ..], ..]. + * + * @var array + */ + protected static $_idMap = array(); + + /** + * Current status. + * + * @var int + */ + protected static $_status = self::STATUS_STARTING; + + /** + * Maximum length of the worker names. + * + * @var int + */ + protected static $_maxWorkerNameLength = 12; + + /** + * Maximum length of the socket names. + * + * @var int + */ + protected static $_maxSocketNameLength = 12; + + /** + * Maximum length of the process user names. + * + * @var int + */ + protected static $_maxUserNameLength = 12; + + /** + * Maximum length of the Proto names. + * + * @var int + */ + protected static $_maxProtoNameLength = 4; + + /** + * Maximum length of the Processes names. + * + * @var int + */ + protected static $_maxProcessesNameLength = 9; + + /** + * Maximum length of the Status names. + * + * @var int + */ + protected static $_maxStatusNameLength = 1; + + /** + * The file to store status info of current worker process. + * + * @var string + */ + protected static $_statisticsFile = ''; + + /** + * Start file. + * + * @var string + */ + protected static $_startFile = ''; + + /** + * OS. + * + * @var string + */ + protected static $_OS = \OS_TYPE_LINUX; + + /** + * Processes for windows. + * + * @var array + */ + protected static $_processForWindows = array(); + + /** + * Status info of current worker process. + * + * @var array + */ + protected static $_globalStatistics = array( + 'start_timestamp' => 0, + 'worker_exit_info' => array() + ); + + /** + * Available event loops. + * + * @var array + */ + protected static $_availableEventLoops = array( + 'libevent' => '\Workerman\Events\Libevent', + 'event' => '\Workerman\Events\Event' + // Temporarily removed swoole because it is not stable enough + //'swoole' => '\Workerman\Events\Swoole' + ); + + /** + * PHP built-in protocols. + * + * @var array + */ + protected static $_builtinTransports = array( + 'tcp' => 'tcp', + 'udp' => 'udp', + 'unix' => 'unix', + 'ssl' => 'tcp' + ); + + /** + * PHP built-in error types. + * + * @var array + */ + protected static $_errorType = array( + \E_ERROR => 'E_ERROR', // 1 + \E_WARNING => 'E_WARNING', // 2 + \E_PARSE => 'E_PARSE', // 4 + \E_NOTICE => 'E_NOTICE', // 8 + \E_CORE_ERROR => 'E_CORE_ERROR', // 16 + \E_CORE_WARNING => 'E_CORE_WARNING', // 32 + \E_COMPILE_ERROR => 'E_COMPILE_ERROR', // 64 + \E_COMPILE_WARNING => 'E_COMPILE_WARNING', // 128 + \E_USER_ERROR => 'E_USER_ERROR', // 256 + \E_USER_WARNING => 'E_USER_WARNING', // 512 + \E_USER_NOTICE => 'E_USER_NOTICE', // 1024 + \E_STRICT => 'E_STRICT', // 2048 + \E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR', // 4096 + \E_DEPRECATED => 'E_DEPRECATED', // 8192 + \E_USER_DEPRECATED => 'E_USER_DEPRECATED' // 16384 + ); + + /** + * Graceful stop or not. + * + * @var bool + */ + protected static $_gracefulStop = false; + + /** + * Standard output stream + * @var resource + */ + protected static $_outputStream = null; + + /** + * If $outputStream support decorated + * @var bool + */ + protected static $_outputDecorated = null; + + /** + * Run all worker instances. + * + * @return void + */ + public static function runAll() + { + static::checkSapiEnv(); + static::init(); + static::lock(); + static::parseCommand(); + static::daemonize(); + static::initWorkers(); + static::installSignal(); + static::saveMasterPid(); + static::unlock(); + static::displayUI(); + static::forkWorkers(); + static::resetStd(); + static::monitorWorkers(); + } + + /** + * Check sapi. + * + * @return void + */ + protected static function checkSapiEnv() + { + // Only for cli. + if (\PHP_SAPI !== 'cli') { + exit("Only run in command line mode \n"); + } + if (\DIRECTORY_SEPARATOR === '\\') { + self::$_OS = \OS_TYPE_WINDOWS; + } + } + + /** + * Init. + * + * @return void + */ + protected static function init() + { + \set_error_handler(function($code, $msg, $file, $line){ + Worker::safeEcho("$msg in file $file on line $line\n"); + }); + + // Start file. + $backtrace = \debug_backtrace(); + static::$_startFile = $backtrace[\count($backtrace) - 1]['file']; + + + $unique_prefix = \str_replace('/', '_', static::$_startFile); + + // Pid file. + if (empty(static::$pidFile)) { + static::$pidFile = __DIR__ . "/../$unique_prefix.pid"; + } + + // Log file. + if (empty(static::$logFile)) { + static::$logFile = __DIR__ . '/../workerman.log'; + } + $log_file = (string)static::$logFile; + if (!\is_file($log_file)) { + \touch($log_file); + \chmod($log_file, 0622); + } + + // State. + static::$_status = static::STATUS_STARTING; + + // For statistics. + static::$_globalStatistics['start_timestamp'] = \time(); + static::$_statisticsFile = \sys_get_temp_dir() . "/$unique_prefix.status"; + + // Process title. + static::setProcessTitle('WorkerMan: master process start_file=' . static::$_startFile); + + // Init data for worker id. + static::initId(); + + // Timer init. + Timer::init(); + } + + /** + * Lock. + * + * @return void + */ + protected static function lock() + { + $fd = \fopen(static::$_startFile, 'r'); + if ($fd && !flock($fd, LOCK_EX)) { + static::log('Workerman['.static::$_startFile.'] already running.'); + exit; + } + } + + /** + * Unlock. + * + * @return void + */ + protected static function unlock() + { + $fd = \fopen(static::$_startFile, 'r'); + $fd && flock($fd, \LOCK_UN); + } + + /** + * Init All worker instances. + * + * @return void + */ + protected static function initWorkers() + { + if (static::$_OS !== \OS_TYPE_LINUX) { + return; + } + foreach (static::$_workers as $worker) { + // Worker name. + if (empty($worker->name)) { + $worker->name = 'none'; + } + + // Get unix user of the worker process. + if (empty($worker->user)) { + $worker->user = static::getCurrentUser(); + } else { + if (\posix_getuid() !== 0 && $worker->user !== static::getCurrentUser()) { + static::log('Warning: You must have the root privileges to change uid and gid.'); + } + } + + // Socket name. + $worker->socket = $worker->getSocketName(); + + // Status name. + $worker->status = ' [OK] '; + + // Get column mapping for UI + foreach(static::getUiColumns() as $column_name => $prop){ + !isset($worker->{$prop}) && $worker->{$prop} = 'NNNN'; + $prop_length = \strlen($worker->{$prop}); + $key = '_max' . \ucfirst(\strtolower($column_name)) . 'NameLength'; + static::$$key = \max(static::$$key, $prop_length); + } + + // Listen. + if (!$worker->reusePort) { + $worker->listen(); + } + } + } + + /** + * Reload all worker instances. + * + * @return void + */ + public static function reloadAllWorkers() + { + static::init(); + static::initWorkers(); + static::displayUI(); + static::$_status = static::STATUS_RELOADING; + } + + /** + * Get all worker instances. + * + * @return array + */ + public static function getAllWorkers() + { + return static::$_workers; + } + + /** + * Get global event-loop instance. + * + * @return EventInterface + */ + public static function getEventLoop() + { + return static::$globalEvent; + } + + /** + * Get main socket resource + * @return resource + */ + public function getMainSocket(){ + return $this->_mainSocket; + } + + /** + * Init idMap. + * return void + */ + protected static function initId() + { + foreach (static::$_workers as $worker_id => $worker) { + $new_id_map = array(); + $worker->count = $worker->count < 1 ? 1 : $worker->count; + for($key = 0; $key < $worker->count; $key++) { + $new_id_map[$key] = isset(static::$_idMap[$worker_id][$key]) ? static::$_idMap[$worker_id][$key] : 0; + } + static::$_idMap[$worker_id] = $new_id_map; + } + } + + /** + * Get unix user of current porcess. + * + * @return string + */ + protected static function getCurrentUser() + { + $user_info = \posix_getpwuid(\posix_getuid()); + return $user_info['name']; + } + + /** + * Display staring UI. + * + * @return void + */ + protected static function displayUI() + { + global $argv; + if (\in_array('-q', $argv)) { + return; + } + if (static::$_OS !== \OS_TYPE_LINUX) { + static::safeEcho("----------------------- WORKERMAN -----------------------------\r\n"); + static::safeEcho('Workerman version:'. static::VERSION. ' PHP version:'. \PHP_VERSION. "\r\n"); + static::safeEcho("------------------------ WORKERS -------------------------------\r\n"); + static::safeEcho("worker listen processes status\r\n"); + return; + } + + //show version + $line_version = 'Workerman version:' . static::VERSION . \str_pad('PHP version:', 22, ' ', \STR_PAD_LEFT) . \PHP_VERSION . \PHP_EOL; + !\defined('LINE_VERSIOIN_LENGTH') && \define('LINE_VERSIOIN_LENGTH', \strlen($line_version)); + $total_length = static::getSingleLineTotalLength(); + $line_one = '' . \str_pad(' WORKERMAN ', $total_length + \strlen(''), '-', \STR_PAD_BOTH) . ''. \PHP_EOL; + $line_two = \str_pad(' WORKERS ' , $total_length + \strlen(''), '-', \STR_PAD_BOTH) . \PHP_EOL; + static::safeEcho($line_one . $line_version . $line_two); + + //Show title + $title = ''; + foreach(static::getUiColumns() as $column_name => $prop){ + $key = '_max' . \ucfirst(\strtolower($column_name)) . 'NameLength'; + //just keep compatible with listen name + $column_name === 'socket' && $column_name = 'listen'; + $title.= "{$column_name}" . \str_pad('', static::$$key + static::UI_SAFE_LENGTH - \strlen($column_name)); + } + $title && static::safeEcho($title . \PHP_EOL); + + //Show content + foreach (static::$_workers as $worker) { + $content = ''; + foreach(static::getUiColumns() as $column_name => $prop){ + $key = '_max' . \ucfirst(\strtolower($column_name)) . 'NameLength'; + \preg_match_all("/(|<\/n>||<\/w>||<\/g>)/is", $worker->{$prop}, $matches); + $place_holder_length = !empty($matches) ? \strlen(\implode('', $matches[0])) : 0; + $content .= \str_pad($worker->{$prop}, static::$$key + static::UI_SAFE_LENGTH + $place_holder_length); + } + $content && static::safeEcho($content . \PHP_EOL); + } + + //Show last line + $line_last = \str_pad('', static::getSingleLineTotalLength(), '-') . \PHP_EOL; + !empty($content) && static::safeEcho($line_last); + + if (static::$daemonize) { + static::safeEcho("Input \"php $argv[0] stop\" to stop. Start success.\n\n"); + } else { + static::safeEcho("Press Ctrl+C to stop. Start success.\n"); + } + } + + /** + * Get UI columns to be shown in terminal + * + * 1. $column_map: array('ui_column_name' => 'clas_property_name') + * 2. Consider move into configuration in future + * + * @return array + */ + public static function getUiColumns() + { + return array( + 'proto' => 'transport', + 'user' => 'user', + 'worker' => 'name', + 'socket' => 'socket', + 'processes' => 'count', + 'status' => 'status', + ); + } + + /** + * Get single line total length for ui + * + * @return int + */ + public static function getSingleLineTotalLength() + { + $total_length = 0; + + foreach(static::getUiColumns() as $column_name => $prop){ + $key = '_max' . \ucfirst(\strtolower($column_name)) . 'NameLength'; + $total_length += static::$$key + static::UI_SAFE_LENGTH; + } + + //keep beauty when show less colums + !\defined('LINE_VERSIOIN_LENGTH') && \define('LINE_VERSIOIN_LENGTH', 0); + $total_length <= LINE_VERSIOIN_LENGTH && $total_length = LINE_VERSIOIN_LENGTH; + + return $total_length; + } + + /** + * Parse command. + * + * @return void + */ + protected static function parseCommand() + { + if (static::$_OS !== \OS_TYPE_LINUX) { + return; + } + global $argv; + // Check argv; + $start_file = $argv[0]; + $available_commands = array( + 'start', + 'stop', + 'restart', + 'reload', + 'status', + 'connections', + ); + $usage = "Usage: php yourfile [mode]\nCommands: \nstart\t\tStart worker in DEBUG mode.\n\t\tUse mode -d to start in DAEMON mode.\nstop\t\tStop worker.\n\t\tUse mode -g to stop gracefully.\nrestart\t\tRestart workers.\n\t\tUse mode -d to start in DAEMON mode.\n\t\tUse mode -g to stop gracefully.\nreload\t\tReload codes.\n\t\tUse mode -g to reload gracefully.\nstatus\t\tGet worker status.\n\t\tUse mode -d to show live status.\nconnections\tGet worker connections.\n"; + if (!isset($argv[1]) || !\in_array($argv[1], $available_commands)) { + if (isset($argv[1])) { + static::safeEcho('Unknown command: ' . $argv[1] . "\n"); + } + exit($usage); + } + + // Get command. + $command = \trim($argv[1]); + $command2 = isset($argv[2]) ? $argv[2] : ''; + + // Start command. + $mode = ''; + if ($command === 'start') { + if ($command2 === '-d' || static::$daemonize) { + $mode = 'in DAEMON mode'; + } else { + $mode = 'in DEBUG mode'; + } + } + static::log("Workerman[$start_file] $command $mode"); + + // Get master process PID. + $master_pid = \is_file(static::$pidFile) ? \file_get_contents(static::$pidFile) : 0; + $master_is_alive = $master_pid && \posix_kill($master_pid, 0) && \posix_getpid() !== $master_pid; + // Master is still alive? + if ($master_is_alive) { + if ($command === 'start') { + static::log("Workerman[$start_file] already running"); + exit; + } + } elseif ($command !== 'start' && $command !== 'restart') { + static::log("Workerman[$start_file] not run"); + exit; + } + + // execute command. + switch ($command) { + case 'start': + if ($command2 === '-d') { + static::$daemonize = true; + } + break; + case 'status': + while (1) { + if (\is_file(static::$_statisticsFile)) { + @\unlink(static::$_statisticsFile); + } + // Master process will send SIGUSR2 signal to all child processes. + \posix_kill($master_pid, SIGUSR2); + // Sleep 1 second. + \sleep(1); + // Clear terminal. + if ($command2 === '-d') { + static::safeEcho("\33[H\33[2J\33(B\33[m", true); + } + // Echo status data. + static::safeEcho(static::formatStatusData()); + if ($command2 !== '-d') { + exit(0); + } + static::safeEcho("\nPress Ctrl+C to quit.\n\n"); + } + exit(0); + case 'connections': + if (\is_file(static::$_statisticsFile) && \is_writable(static::$_statisticsFile)) { + \unlink(static::$_statisticsFile); + } + // Master process will send SIGIO signal to all child processes. + \posix_kill($master_pid, SIGIO); + // Waiting amoment. + \usleep(500000); + // Display statisitcs data from a disk file. + if(\is_readable(static::$_statisticsFile)) { + \readfile(static::$_statisticsFile); + } + exit(0); + case 'restart': + case 'stop': + if ($command2 === '-g') { + static::$_gracefulStop = true; + $sig = \SIGTERM; + static::log("Workerman[$start_file] is gracefully stopping ..."); + } else { + static::$_gracefulStop = false; + $sig = \SIGINT; + static::log("Workerman[$start_file] is stopping ..."); + } + // Send stop signal to master process. + $master_pid && \posix_kill($master_pid, $sig); + // Timeout. + $timeout = 5; + $start_time = \time(); + // Check master process is still alive? + while (1) { + $master_is_alive = $master_pid && \posix_kill($master_pid, 0); + if ($master_is_alive) { + // Timeout? + if (!static::$_gracefulStop && \time() - $start_time >= $timeout) { + static::log("Workerman[$start_file] stop fail"); + exit; + } + // Waiting amoment. + \usleep(10000); + continue; + } + // Stop success. + static::log("Workerman[$start_file] stop success"); + if ($command === 'stop') { + exit(0); + } + if ($command2 === '-d') { + static::$daemonize = true; + } + break; + } + break; + case 'reload': + if($command2 === '-g'){ + $sig = \SIGQUIT; + }else{ + $sig = \SIGUSR1; + } + \posix_kill($master_pid, $sig); + exit; + default : + if (isset($command)) { + static::safeEcho('Unknown command: ' . $command . "\n"); + } + exit($usage); + } + } + + /** + * Format status data. + * + * @return string + */ + protected static function formatStatusData() + { + static $total_request_cache = array(); + if (!\is_readable(static::$_statisticsFile)) { + return ''; + } + $info = \file(static::$_statisticsFile, \FILE_IGNORE_NEW_LINES); + if (!$info) { + return ''; + } + $status_str = ''; + $current_total_request = array(); + $worker_info = \unserialize($info[0]); + \ksort($worker_info, SORT_NUMERIC); + unset($info[0]); + $data_waiting_sort = array(); + $read_process_status = false; + $total_requests = 0; + $total_qps = 0; + $total_connections = 0; + $total_fails = 0; + $total_memory = 0; + $total_timers = 0; + $maxLen1 = static::$_maxSocketNameLength; + $maxLen2 = static::$_maxWorkerNameLength; + foreach($info as $key => $value) { + if (!$read_process_status) { + $status_str .= $value . "\n"; + if (\preg_match('/^pid.*?memory.*?listening/', $value)) { + $read_process_status = true; + } + continue; + } + if(\preg_match('/^[0-9]+/', $value, $pid_math)) { + $pid = $pid_math[0]; + $data_waiting_sort[$pid] = $value; + if(\preg_match('/^\S+?\s+?(\S+?)\s+?(\S+?)\s+?(\S+?)\s+?(\S+?)\s+?(\S+?)\s+?(\S+?)\s+?(\S+?)\s+?/', $value, $match)) { + $total_memory += \intval(\str_ireplace('M','',$match[1])); + $maxLen1 = \max($maxLen1,\strlen($match[2])); + $maxLen2 = \max($maxLen2,\strlen($match[3])); + $total_connections += \intval($match[4]); + $total_fails += \intval($match[5]); + $total_timers += \intval($match[6]); + $current_total_request[$pid] = $match[7]; + $total_requests += \intval($match[7]); + } + } + } + foreach($worker_info as $pid => $info) { + if (!isset($data_waiting_sort[$pid])) { + $status_str .= "$pid\t" . \str_pad('N/A', 7) . " " + . \str_pad($info['listen'], static::$_maxSocketNameLength) . " " + . \str_pad($info['name'], static::$_maxWorkerNameLength) . " " + . \str_pad('N/A', 11) . " " . \str_pad('N/A', 9) . " " + . \str_pad('N/A', 7) . " " . \str_pad('N/A', 13) . " N/A [busy] \n"; + continue; + } + //$qps = isset($total_request_cache[$pid]) ? $current_total_request[$pid] + if (!isset($total_request_cache[$pid]) || !isset($current_total_request[$pid])) { + $qps = 0; + } else { + $qps = $current_total_request[$pid] - $total_request_cache[$pid]; + $total_qps += $qps; + } + $status_str .= $data_waiting_sort[$pid]. " " . \str_pad($qps, 6) ." [idle]\n"; + } + $total_request_cache = $current_total_request; + $status_str .= "----------------------------------------------PROCESS STATUS---------------------------------------------------\n"; + $status_str .= "Summary\t" . \str_pad($total_memory.'M', 7) . " " + . \str_pad('-', $maxLen1) . " " + . \str_pad('-', $maxLen2) . " " + . \str_pad($total_connections, 11) . " " . \str_pad($total_fails, 9) . " " + . \str_pad($total_timers, 7) . " " . \str_pad($total_requests, 13) . " " + . \str_pad($total_qps,6)." [Summary] \n"; + return $status_str; + } + + + /** + * Install signal handler. + * + * @return void + */ + protected static function installSignal() + { + if (static::$_OS !== \OS_TYPE_LINUX) { + return; + } + $signalHandler = '\Workerman\Worker::signalHandler'; + // stop + \pcntl_signal(\SIGINT, $signalHandler, false); + // graceful stop + \pcntl_signal(\SIGTERM, $signalHandler, false); + // reload + \pcntl_signal(\SIGUSR1, $signalHandler, false); + // graceful reload + \pcntl_signal(\SIGQUIT, $signalHandler, false); + // status + \pcntl_signal(\SIGUSR2, $signalHandler, false); + // connection status + \pcntl_signal(\SIGIO, $signalHandler, false); + // ignore + \pcntl_signal(\SIGPIPE, \SIG_IGN, false); + } + + /** + * Reinstall signal handler. + * + * @return void + */ + protected static function reinstallSignal() + { + if (static::$_OS !== \OS_TYPE_LINUX) { + return; + } + $signalHandler = '\Workerman\Worker::signalHandler'; + // uninstall stop signal handler + \pcntl_signal(\SIGINT, \SIG_IGN, false); + // uninstall graceful stop signal handler + \pcntl_signal(\SIGTERM, \SIG_IGN, false); + // uninstall reload signal handler + \pcntl_signal(\SIGUSR1, \SIG_IGN, false); + // uninstall graceful reload signal handler + \pcntl_signal(\SIGQUIT, \SIG_IGN, false); + // uninstall status signal handler + \pcntl_signal(\SIGUSR2, \SIG_IGN, false); + // uninstall connections status signal handler + \pcntl_signal(\SIGIO, \SIG_IGN, false); + // reinstall stop signal handler + static::$globalEvent->add(\SIGINT, EventInterface::EV_SIGNAL, $signalHandler); + // reinstall graceful stop signal handler + static::$globalEvent->add(\SIGTERM, EventInterface::EV_SIGNAL, $signalHandler); + // reinstall reload signal handler + static::$globalEvent->add(\SIGUSR1, EventInterface::EV_SIGNAL, $signalHandler); + // reinstall graceful reload signal handler + static::$globalEvent->add(\SIGQUIT, EventInterface::EV_SIGNAL, $signalHandler); + // reinstall status signal handler + static::$globalEvent->add(\SIGUSR2, EventInterface::EV_SIGNAL, $signalHandler); + // reinstall connection status signal handler + static::$globalEvent->add(\SIGIO, EventInterface::EV_SIGNAL, $signalHandler); + } + + /** + * Signal handler. + * + * @param int $signal + */ + public static function signalHandler($signal) + { + switch ($signal) { + // Stop. + case \SIGINT: + static::$_gracefulStop = false; + static::stopAll(); + break; + // Graceful stop. + case \SIGTERM: + static::$_gracefulStop = true; + static::stopAll(); + break; + // Reload. + case \SIGQUIT: + case \SIGUSR1: + if($signal === \SIGQUIT){ + static::$_gracefulStop = true; + }else{ + static::$_gracefulStop = false; + } + static::$_pidsToRestart = static::getAllWorkerPids(); + static::reload(); + break; + // Show status. + case \SIGUSR2: + static::writeStatisticsToStatusFile(); + break; + // Show connection status. + case \SIGIO: + static::writeConnectionsStatisticsToStatusFile(); + break; + } + } + + /** + * Run as deamon mode. + * + * @throws Exception + */ + protected static function daemonize() + { + if (!static::$daemonize || static::$_OS !== \OS_TYPE_LINUX) { + return; + } + \umask(0); + $pid = \pcntl_fork(); + if (-1 === $pid) { + throw new Exception('Fork fail'); + } elseif ($pid > 0) { + exit(0); + } + if (-1 === \posix_setsid()) { + throw new Exception("Setsid fail"); + } + // Fork again avoid SVR4 system regain the control of terminal. + $pid = \pcntl_fork(); + if (-1 === $pid) { + throw new Exception("Fork fail"); + } elseif (0 !== $pid) { + exit(0); + } + } + + /** + * Redirect standard input and output. + * + * @throws Exception + */ + public static function resetStd() + { + if (!static::$daemonize || static::$_OS !== \OS_TYPE_LINUX) { + return; + } + global $STDOUT, $STDERR; + $handle = \fopen(static::$stdoutFile, "a"); + if ($handle) { + unset($handle); + \set_error_handler(function(){}); + \fclose($STDOUT); + \fclose($STDERR); + \fclose(\STDOUT); + \fclose(\STDERR); + $STDOUT = \fopen(static::$stdoutFile, "a"); + $STDERR = \fopen(static::$stdoutFile, "a"); + // change output stream + static::$_outputStream = null; + static::outputStream($STDOUT); + \restore_error_handler(); + return; + } + + throw new Exception('Can not open stdoutFile ' . static::$stdoutFile); + } + + /** + * Save pid. + * + * @throws Exception + */ + protected static function saveMasterPid() + { + if (static::$_OS !== \OS_TYPE_LINUX) { + return; + } + + static::$_masterPid = \posix_getpid(); + if (false === \file_put_contents(static::$pidFile, static::$_masterPid)) { + throw new Exception('can not save pid to ' . static::$pidFile); + } + } + + /** + * Get event loop name. + * + * @return string + */ + protected static function getEventLoopName() + { + if (static::$eventLoopClass) { + return static::$eventLoopClass; + } + + if (!\class_exists('\Swoole\Event', false)) { + unset(static::$_availableEventLoops['swoole']); + } + + $loop_name = ''; + foreach (static::$_availableEventLoops as $name=>$class) { + if (\extension_loaded($name)) { + $loop_name = $name; + break; + } + } + + if ($loop_name) { + if (\interface_exists('\React\EventLoop\LoopInterface')) { + switch ($loop_name) { + case 'libevent': + static::$eventLoopClass = '\Workerman\Events\React\ExtLibEventLoop'; + break; + case 'event': + static::$eventLoopClass = '\Workerman\Events\React\ExtEventLoop'; + break; + default : + static::$eventLoopClass = '\Workerman\Events\React\StreamSelectLoop'; + break; + } + } else { + static::$eventLoopClass = static::$_availableEventLoops[$loop_name]; + } + } else { + static::$eventLoopClass = \interface_exists('\React\EventLoop\LoopInterface') ? '\Workerman\Events\React\StreamSelectLoop' : '\Workerman\Events\Select'; + } + return static::$eventLoopClass; + } + + /** + * Get all pids of worker processes. + * + * @return array + */ + protected static function getAllWorkerPids() + { + $pid_array = array(); + foreach (static::$_pidMap as $worker_pid_array) { + foreach ($worker_pid_array as $worker_pid) { + $pid_array[$worker_pid] = $worker_pid; + } + } + return $pid_array; + } + + /** + * Fork some worker processes. + * + * @return void + */ + protected static function forkWorkers() + { + if (static::$_OS === \OS_TYPE_LINUX) { + static::forkWorkersForLinux(); + } else { + static::forkWorkersForWindows(); + } + } + + /** + * Fork some worker processes. + * + * @return void + */ + protected static function forkWorkersForLinux() + { + + foreach (static::$_workers as $worker) { + if (static::$_status === static::STATUS_STARTING) { + if (empty($worker->name)) { + $worker->name = $worker->getSocketName(); + } + $worker_name_length = \strlen($worker->name); + if (static::$_maxWorkerNameLength < $worker_name_length) { + static::$_maxWorkerNameLength = $worker_name_length; + } + } + + while (\count(static::$_pidMap[$worker->workerId]) < $worker->count) { + static::forkOneWorkerForLinux($worker); + } + } + } + + /** + * Fork some worker processes. + * + * @return void + */ + protected static function forkWorkersForWindows() + { + $files = static::getStartFilesForWindows(); + global $argv; + if(\in_array('-q', $argv) || \count($files) === 1) + { + if(\count(static::$_workers) > 1) + { + static::safeEcho("@@@ Error: multi workers init in one php file are not support @@@\r\n"); + static::safeEcho("@@@ See http://doc.workerman.net/faq/multi-woker-for-windows.html @@@\r\n"); + } + elseif(\count(static::$_workers) <= 0) + { + exit("@@@no worker inited@@@\r\n\r\n"); + } + + \reset(static::$_workers); + /** @var Worker $worker */ + $worker = current(static::$_workers); + + // Display UI. + static::safeEcho(\str_pad($worker->name, 21) . \str_pad($worker->getSocketName(), 36) . \str_pad($worker->count, 10) . "[ok]\n"); + $worker->listen(); + $worker->run(); + exit("@@@child exit@@@\r\n"); + } + else + { + static::$globalEvent = new \Workerman\Events\Select(); + Timer::init(static::$globalEvent); + foreach($files as $start_file) + { + static::forkOneWorkerForWindows($start_file); + } + } + } + + /** + * Get start files for windows. + * + * @return array + */ + public static function getStartFilesForWindows() { + global $argv; + $files = array(); + foreach($argv as $file) + { + if(\is_file($file)) + { + $files[$file] = $file; + } + } + return $files; + } + + /** + * Fork one worker process. + * + * @param string $start_file + */ + public static function forkOneWorkerForWindows($start_file) + { + $start_file = \realpath($start_file); + $std_file = \sys_get_temp_dir() . '/'.\str_replace(array('/', "\\", ':'), '_', $start_file).'.out.txt'; + + $descriptorspec = array( + 0 => array('pipe', 'a'), // stdin + 1 => array('file', $std_file, 'w'), // stdout + 2 => array('file', $std_file, 'w') // stderr + ); + + + $pipes = array(); + $process = \proc_open("php \"$start_file\" -q", $descriptorspec, $pipes); + $std_handler = \fopen($std_file, 'a+'); + \stream_set_blocking($std_handler, false); + + if (empty(static::$globalEvent)) { + static::$globalEvent = new Select(); + Timer::init(static::$globalEvent); + } + $timer_id = Timer::add(0.1, function()use($std_handler) + { + Worker::safeEcho(\fread($std_handler, 65535)); + }); + + // 保存子进程句柄 + static::$_processForWindows[$start_file] = array($process, $start_file, $timer_id); + } + + /** + * check worker status for windows. + * @return void + */ + public static function checkWorkerStatusForWindows() + { + foreach(static::$_processForWindows as $process_data) + { + $process = $process_data[0]; + $start_file = $process_data[1]; + $timer_id = $process_data[2]; + $status = \proc_get_status($process); + if(isset($status['running'])) + { + if(!$status['running']) + { + static::safeEcho("process $start_file terminated and try to restart\n"); + Timer::del($timer_id); + \proc_close($process); + static::forkOneWorkerForWindows($start_file); + } + } + else + { + static::safeEcho("proc_get_status fail\n"); + } + } + } + + + /** + * Fork one worker process. + * + * @param self $worker + * @throws Exception + */ + protected static function forkOneWorkerForLinux(self $worker) + { + // Get available worker id. + $id = static::getId($worker->workerId, 0); + if ($id === false) { + return; + } + $pid = \pcntl_fork(); + // For master process. + if ($pid > 0) { + static::$_pidMap[$worker->workerId][$pid] = $pid; + static::$_idMap[$worker->workerId][$id] = $pid; + } // For child processes. + elseif (0 === $pid) { + \srand(); + \mt_srand(); + if ($worker->reusePort) { + $worker->listen(); + } + if (static::$_status === static::STATUS_STARTING) { + static::resetStd(); + } + static::$_pidMap = array(); + // Remove other listener. + foreach(static::$_workers as $key => $one_worker) { + if ($one_worker->workerId !== $worker->workerId) { + $one_worker->unlisten(); + unset(static::$_workers[$key]); + } + } + Timer::delAll(); + static::setProcessTitle('WorkerMan: worker process ' . $worker->name . ' ' . $worker->getSocketName()); + $worker->setUserAndGroup(); + $worker->id = $id; + $worker->run(); + $err = new Exception('event-loop exited'); + static::log($err); + exit(250); + } else { + throw new Exception("forkOneWorker fail"); + } + } + + /** + * Get worker id. + * + * @param int $worker_id + * @param int $pid + * + * @return integer + */ + protected static function getId($worker_id, $pid) + { + return \array_search($pid, static::$_idMap[$worker_id]); + } + + /** + * Set unix user and group for current process. + * + * @return void + */ + public function setUserAndGroup() + { + // Get uid. + $user_info = \posix_getpwnam($this->user); + if (!$user_info) { + static::log("Warning: User {$this->user} not exsits"); + return; + } + $uid = $user_info['uid']; + // Get gid. + if ($this->group) { + $group_info = \posix_getgrnam($this->group); + if (!$group_info) { + static::log("Warning: Group {$this->group} not exsits"); + return; + } + $gid = $group_info['gid']; + } else { + $gid = $user_info['gid']; + } + + // Set uid and gid. + if ($uid !== \posix_getuid() || $gid !== \posix_getgid()) { + if (!\posix_setgid($gid) || !\posix_initgroups($user_info['name'], $gid) || !\posix_setuid($uid)) { + static::log("Warning: change gid or uid fail."); + } + } + } + + /** + * Set process name. + * + * @param string $title + * @return void + */ + protected static function setProcessTitle($title) + { + \set_error_handler(function(){}); + // >=php 5.5 + if (\function_exists('cli_set_process_title')) { + \cli_set_process_title($title); + } // Need proctitle when php<=5.5 . + elseif (\extension_loaded('proctitle') && \function_exists('setproctitle')) { + \setproctitle($title); + } + \restore_error_handler(); + } + + /** + * Monitor all child processes. + * + * @return void + */ + protected static function monitorWorkers() + { + if (static::$_OS === \OS_TYPE_LINUX) { + static::monitorWorkersForLinux(); + } else { + static::monitorWorkersForWindows(); + } + } + + /** + * Monitor all child processes. + * + * @return void + */ + protected static function monitorWorkersForLinux() + { + static::$_status = static::STATUS_RUNNING; + while (1) { + // Calls signal handlers for pending signals. + \pcntl_signal_dispatch(); + // Suspends execution of the current process until a child has exited, or until a signal is delivered + $status = 0; + $pid = \pcntl_wait($status, \WUNTRACED); + // Calls signal handlers for pending signals again. + \pcntl_signal_dispatch(); + // If a child has already exited. + if ($pid > 0) { + // Find out witch worker process exited. + foreach (static::$_pidMap as $worker_id => $worker_pid_array) { + if (isset($worker_pid_array[$pid])) { + $worker = static::$_workers[$worker_id]; + // Exit status. + if ($status !== 0) { + static::log("worker[" . $worker->name . ":$pid] exit with status $status"); + } + + // For Statistics. + if (!isset(static::$_globalStatistics['worker_exit_info'][$worker_id][$status])) { + static::$_globalStatistics['worker_exit_info'][$worker_id][$status] = 0; + } + ++static::$_globalStatistics['worker_exit_info'][$worker_id][$status]; + + // Clear process data. + unset(static::$_pidMap[$worker_id][$pid]); + + // Mark id is available. + $id = static::getId($worker_id, $pid); + static::$_idMap[$worker_id][$id] = 0; + + break; + } + } + // Is still running state then fork a new worker process. + if (static::$_status !== static::STATUS_SHUTDOWN) { + static::forkWorkers(); + // If reloading continue. + if (isset(static::$_pidsToRestart[$pid])) { + unset(static::$_pidsToRestart[$pid]); + static::reload(); + } + } + } + + // If shutdown state and all child processes exited then master process exit. + if (static::$_status === static::STATUS_SHUTDOWN && !static::getAllWorkerPids()) { + static::exitAndClearAll(); + } + } + } + + /** + * Monitor all child processes. + * + * @return void + */ + protected static function monitorWorkersForWindows() + { + Timer::add(1, "\\Workerman\\Worker::checkWorkerStatusForWindows"); + + static::$globalEvent->loop(); + } + + /** + * Exit current process. + * + * @return void + */ + protected static function exitAndClearAll() + { + foreach (static::$_workers as $worker) { + $socket_name = $worker->getSocketName(); + if ($worker->transport === 'unix' && $socket_name) { + list(, $address) = \explode(':', $socket_name, 2); + @\unlink($address); + } + } + @\unlink(static::$pidFile); + static::log("Workerman[" . \basename(static::$_startFile) . "] has been stopped"); + if (static::$onMasterStop) { + \call_user_func(static::$onMasterStop); + } + exit(0); + } + + /** + * Execute reload. + * + * @return void + */ + protected static function reload() + { + // For master process. + if (static::$_masterPid === \posix_getpid()) { + // Set reloading state. + if (static::$_status !== static::STATUS_RELOADING && static::$_status !== static::STATUS_SHUTDOWN) { + static::log("Workerman[" . \basename(static::$_startFile) . "] reloading"); + static::$_status = static::STATUS_RELOADING; + // Try to emit onMasterReload callback. + if (static::$onMasterReload) { + try { + \call_user_func(static::$onMasterReload); + } catch (\Exception $e) { + static::log($e); + exit(250); + } catch (\Error $e) { + static::log($e); + exit(250); + } + static::initId(); + } + } + + if (static::$_gracefulStop) { + $sig = \SIGQUIT; + } else { + $sig = \SIGUSR1; + } + + // Send reload signal to all child processes. + $reloadable_pid_array = array(); + foreach (static::$_pidMap as $worker_id => $worker_pid_array) { + $worker = static::$_workers[$worker_id]; + if ($worker->reloadable) { + foreach ($worker_pid_array as $pid) { + $reloadable_pid_array[$pid] = $pid; + } + } else { + foreach ($worker_pid_array as $pid) { + // Send reload signal to a worker process which reloadable is false. + \posix_kill($pid, $sig); + } + } + } + + // Get all pids that are waiting reload. + static::$_pidsToRestart = \array_intersect(static::$_pidsToRestart, $reloadable_pid_array); + + // Reload complete. + if (empty(static::$_pidsToRestart)) { + if (static::$_status !== static::STATUS_SHUTDOWN) { + static::$_status = static::STATUS_RUNNING; + } + return; + } + // Continue reload. + $one_worker_pid = \current(static::$_pidsToRestart); + // Send reload signal to a worker process. + \posix_kill($one_worker_pid, $sig); + // If the process does not exit after static::KILL_WORKER_TIMER_TIME seconds try to kill it. + if(!static::$_gracefulStop){ + Timer::add(static::KILL_WORKER_TIMER_TIME, '\posix_kill', array($one_worker_pid, \SIGKILL), false); + } + } // For child processes. + else { + \reset(static::$_workers); + $worker = \current(static::$_workers); + // Try to emit onWorkerReload callback. + if ($worker->onWorkerReload) { + try { + \call_user_func($worker->onWorkerReload, $worker); + } catch (\Exception $e) { + static::log($e); + exit(250); + } catch (\Error $e) { + static::log($e); + exit(250); + } + } + + if ($worker->reloadable) { + static::stopAll(); + } + } + } + + /** + * Stop. + * + * @return void + */ + public static function stopAll() + { + static::$_status = static::STATUS_SHUTDOWN; + // For master process. + if (static::$_masterPid === \posix_getpid()) { + static::log("Workerman[" . \basename(static::$_startFile) . "] stopping ..."); + $worker_pid_array = static::getAllWorkerPids(); + // Send stop signal to all child processes. + if (static::$_gracefulStop) { + $sig = \SIGTERM; + } else { + $sig = \SIGINT; + } + foreach ($worker_pid_array as $worker_pid) { + \posix_kill($worker_pid, $sig); + if(!static::$_gracefulStop){ + Timer::add(static::KILL_WORKER_TIMER_TIME, '\posix_kill', array($worker_pid, \SIGKILL), false); + } + } + Timer::add(1, "\\Workerman\\Worker::checkIfChildRunning"); + // Remove statistics file. + if (\is_file(static::$_statisticsFile)) { + @\unlink(static::$_statisticsFile); + } + } // For child processes. + else { + // Execute exit. + foreach (static::$_workers as $worker) { + if(!$worker->stopping){ + $worker->stop(); + $worker->stopping = true; + } + } + if (!static::$_gracefulStop || ConnectionInterface::$statistics['connection_count'] <= 0) { + static::$_workers = array(); + if (static::$globalEvent) { + static::$globalEvent->destroy(); + } + exit(0); + } + } + } + + /** + * check if child processes is really running + */ + public static function checkIfChildRunning() + { + foreach (static::$_pidMap as $worker_id => $worker_pid_array) { + foreach ($worker_pid_array as $pid => $worker_pid) { + if (!\posix_kill($pid, 0)) { + unset(static::$_pidMap[$worker_id][$pid]); + } + } + } + } + + /** + * Get process status. + * + * @return number + */ + public static function getStatus() + { + return static::$_status; + } + + /** + * If stop gracefully. + * + * @return bool + */ + public static function getGracefulStop() + { + return static::$_gracefulStop; + } + + /** + * Write statistics data to disk. + * + * @return void + */ + protected static function writeStatisticsToStatusFile() + { + // For master process. + if (static::$_masterPid === \posix_getpid()) { + $all_worker_info = array(); + foreach(static::$_pidMap as $worker_id => $pid_array) { + /** @var /Workerman/Worker $worker */ + $worker = static::$_workers[$worker_id]; + foreach($pid_array as $pid) { + $all_worker_info[$pid] = array('name' => $worker->name, 'listen' => $worker->getSocketName()); + } + } + + \file_put_contents(static::$_statisticsFile, \serialize($all_worker_info)."\n", \FILE_APPEND); + $loadavg = \function_exists('sys_getloadavg') ? \array_map('round', \sys_getloadavg(), array(2)) : array('-', '-', '-'); + \file_put_contents(static::$_statisticsFile, + "----------------------------------------------GLOBAL STATUS----------------------------------------------------\n", \FILE_APPEND); + \file_put_contents(static::$_statisticsFile, + 'Workerman version:' . static::VERSION . " PHP version:" . \PHP_VERSION . "\n", \FILE_APPEND); + \file_put_contents(static::$_statisticsFile, 'start time:' . \date('Y-m-d H:i:s', + static::$_globalStatistics['start_timestamp']) . ' run ' . \floor((\time() - static::$_globalStatistics['start_timestamp']) / (24 * 60 * 60)) . ' days ' . \floor(((\time() - static::$_globalStatistics['start_timestamp']) % (24 * 60 * 60)) / (60 * 60)) . " hours \n", + FILE_APPEND); + $load_str = 'load average: ' . \implode(", ", $loadavg); + \file_put_contents(static::$_statisticsFile, + \str_pad($load_str, 33) . 'event-loop:' . static::getEventLoopName() . "\n", \FILE_APPEND); + \file_put_contents(static::$_statisticsFile, + \count(static::$_pidMap) . ' workers ' . \count(static::getAllWorkerPids()) . " processes\n", + \FILE_APPEND); + \file_put_contents(static::$_statisticsFile, + \str_pad('worker_name', static::$_maxWorkerNameLength) . " exit_status exit_count\n", \FILE_APPEND); + foreach (static::$_pidMap as $worker_id => $worker_pid_array) { + $worker = static::$_workers[$worker_id]; + if (isset(static::$_globalStatistics['worker_exit_info'][$worker_id])) { + foreach (static::$_globalStatistics['worker_exit_info'][$worker_id] as $worker_exit_status => $worker_exit_count) { + \file_put_contents(static::$_statisticsFile, + \str_pad($worker->name, static::$_maxWorkerNameLength) . " " . \str_pad($worker_exit_status, + 16) . " $worker_exit_count\n", \FILE_APPEND); + } + } else { + \file_put_contents(static::$_statisticsFile, + \str_pad($worker->name, static::$_maxWorkerNameLength) . " " . \str_pad(0, 16) . " 0\n", + \FILE_APPEND); + } + } + \file_put_contents(static::$_statisticsFile, + "----------------------------------------------PROCESS STATUS---------------------------------------------------\n", + \FILE_APPEND); + \file_put_contents(static::$_statisticsFile, + "pid\tmemory " . \str_pad('listening', static::$_maxSocketNameLength) . " " . \str_pad('worker_name', + static::$_maxWorkerNameLength) . " connections " . \str_pad('send_fail', 9) . " " + . \str_pad('timers', 8) . \str_pad('total_request', 13) ." qps status\n", \FILE_APPEND); + + \chmod(static::$_statisticsFile, 0722); + + foreach (static::getAllWorkerPids() as $worker_pid) { + \posix_kill($worker_pid, \SIGUSR2); + } + return; + } + + // For child processes. + \reset(static::$_workers); + /** @var \Workerman\Worker $worker */ + $worker = current(static::$_workers); + $worker_status_str = \posix_getpid() . "\t" . \str_pad(round(memory_get_usage(true) / (1024 * 1024), 2) . "M", 7) + . " " . \str_pad($worker->getSocketName(), static::$_maxSocketNameLength) . " " + . \str_pad(($worker->name === $worker->getSocketName() ? 'none' : $worker->name), static::$_maxWorkerNameLength) + . " "; + $worker_status_str .= \str_pad(ConnectionInterface::$statistics['connection_count'], 11) + . " " . \str_pad(ConnectionInterface::$statistics['send_fail'], 9) + . " " . \str_pad(static::$globalEvent->getTimerCount(), 7) + . " " . \str_pad(ConnectionInterface::$statistics['total_request'], 13) . "\n"; + \file_put_contents(static::$_statisticsFile, $worker_status_str, \FILE_APPEND); + } + + /** + * Write statistics data to disk. + * + * @return void + */ + protected static function writeConnectionsStatisticsToStatusFile() + { + // For master process. + if (static::$_masterPid === \posix_getpid()) { + \file_put_contents(static::$_statisticsFile, "--------------------------------------------------------------------- WORKERMAN CONNECTION STATUS --------------------------------------------------------------------------------\n", \FILE_APPEND); + \file_put_contents(static::$_statisticsFile, "PID Worker CID Trans Protocol ipv4 ipv6 Recv-Q Send-Q Bytes-R Bytes-W Status Local Address Foreign Address\n", \FILE_APPEND); + \chmod(static::$_statisticsFile, 0722); + foreach (static::getAllWorkerPids() as $worker_pid) { + \posix_kill($worker_pid, \SIGIO); + } + return; + } + + // For child processes. + $bytes_format = function($bytes) + { + if($bytes > 1024*1024*1024*1024) { + return round($bytes/(1024*1024*1024*1024), 1)."TB"; + } + if($bytes > 1024*1024*1024) { + return round($bytes/(1024*1024*1024), 1)."GB"; + } + if($bytes > 1024*1024) { + return round($bytes/(1024*1024), 1)."MB"; + } + if($bytes > 1024) { + return round($bytes/(1024), 1)."KB"; + } + return $bytes."B"; + }; + + $pid = \posix_getpid(); + $str = ''; + \reset(static::$_workers); + $current_worker = current(static::$_workers); + $default_worker_name = $current_worker->name; + + /** @var \Workerman\Worker $worker */ + foreach(TcpConnection::$connections as $connection) { + /** @var \Workerman\Connection\TcpConnection $connection */ + $transport = $connection->transport; + $ipv4 = $connection->isIpV4() ? ' 1' : ' 0'; + $ipv6 = $connection->isIpV6() ? ' 1' : ' 0'; + $recv_q = $bytes_format($connection->getRecvBufferQueueSize()); + $send_q = $bytes_format($connection->getSendBufferQueueSize()); + $local_address = \trim($connection->getLocalAddress()); + $remote_address = \trim($connection->getRemoteAddress()); + $state = $connection->getStatus(false); + $bytes_read = $bytes_format($connection->bytesRead); + $bytes_written = $bytes_format($connection->bytesWritten); + $id = $connection->id; + $protocol = $connection->protocol ? $connection->protocol : $connection->transport; + $pos = \strrpos($protocol, '\\'); + if ($pos) { + $protocol = \substr($protocol, $pos+1); + } + if (\strlen($protocol) > 15) { + $protocol = \substr($protocol, 0, 13) . '..'; + } + $worker_name = isset($connection->worker) ? $connection->worker->name : $default_worker_name; + if (\strlen($worker_name) > 14) { + $worker_name = \substr($worker_name, 0, 12) . '..'; + } + $str .= \str_pad($pid, 9) . \str_pad($worker_name, 16) . \str_pad($id, 10) . \str_pad($transport, 8) + . \str_pad($protocol, 16) . \str_pad($ipv4, 7) . \str_pad($ipv6, 7) . \str_pad($recv_q, 13) + . \str_pad($send_q, 13) . \str_pad($bytes_read, 13) . \str_pad($bytes_written, 13) . ' ' + . \str_pad($state, 14) . ' ' . \str_pad($local_address, 22) . ' ' . \str_pad($remote_address, 22) ."\n"; + } + if ($str) { + \file_put_contents(static::$_statisticsFile, $str, \FILE_APPEND); + } + } + + /** + * Check errors when current process exited. + * + * @return void + */ + public static function checkErrors() + { + if (static::STATUS_SHUTDOWN !== static::$_status) { + $error_msg = static::$_OS === \OS_TYPE_LINUX ? 'Worker['. \posix_getpid() .'] process terminated' : 'Worker process terminated'; + $errors = error_get_last(); + if ($errors && ($errors['type'] === \E_ERROR || + $errors['type'] === \E_PARSE || + $errors['type'] === \E_CORE_ERROR || + $errors['type'] === \E_COMPILE_ERROR || + $errors['type'] === \E_RECOVERABLE_ERROR) + ) { + $error_msg .= ' with ERROR: ' . static::getErrorType($errors['type']) . " \"{$errors['message']} in {$errors['file']} on line {$errors['line']}\""; + } + static::log($error_msg); + } + } + + /** + * Get error message by error code. + * + * @param integer $type + * @return string + */ + protected static function getErrorType($type) + { + if(isset(self::$_errorType[$type])) { + return self::$_errorType[$type]; + } + + return ''; + } + + /** + * Log. + * + * @param string $msg + * @return void + */ + public static function log($msg) + { + $msg = $msg . "\n"; + if (!static::$daemonize) { + static::safeEcho($msg); + } + \file_put_contents((string)static::$logFile, \date('Y-m-d H:i:s') . ' ' . 'pid:' + . (static::$_OS === \OS_TYPE_LINUX ? \posix_getpid() : 1) . ' ' . $msg, \FILE_APPEND | \LOCK_EX); + } + + /** + * Safe Echo. + * @param string $msg + * @param bool $decorated + * @return bool + */ + public static function safeEcho($msg, $decorated = false) + { + $stream = static::outputStream(); + if (!$stream) { + return false; + } + if (!$decorated) { + $line = $white = $green = $end = ''; + if (static::$_outputDecorated) { + $line = "\033[1A\n\033[K"; + $white = "\033[47;30m"; + $green = "\033[32;40m"; + $end = "\033[0m"; + } + $msg = \str_replace(array('', '', ''), array($line, $white, $green), $msg); + $msg = \str_replace(array('', '', ''), $end, $msg); + } elseif (!static::$_outputDecorated) { + return false; + } + \fwrite($stream, $msg); + \fflush($stream); + return true; + } + + /** + * @param null $stream + * @return bool|resource + */ + private static function outputStream($stream = null) + { + if (!$stream) { + $stream = static::$_outputStream ? static::$_outputStream : \STDOUT; + } + if (!$stream || !\is_resource($stream) || 'stream' !== \get_resource_type($stream)) { + return false; + } + $stat = \fstat($stream); + if (!$stat) { + return false; + } + if (($stat['mode'] & 0170000) === 0100000) { + // file + static::$_outputDecorated = false; + } else { + static::$_outputDecorated = + static::$_OS === \OS_TYPE_LINUX && + \function_exists('posix_isatty') && + \posix_isatty($stream); + } + return static::$_outputStream = $stream; + } + + /** + * Construct. + * + * @param string $socket_name + * @param array $context_option + */ + public function __construct($socket_name = '', array $context_option = array()) + { + // Save all worker instances. + $this->workerId = \spl_object_hash($this); + static::$_workers[$this->workerId] = $this; + static::$_pidMap[$this->workerId] = array(); + + // Get autoload root path. + $backtrace = \debug_backtrace(); + $this->_autoloadRootPath = \dirname($backtrace[0]['file']); + Autoloader::setRootPath($this->_autoloadRootPath); + + // Context for socket. + if ($socket_name) { + $this->_socketName = $socket_name; + if (!isset($context_option['socket']['backlog'])) { + $context_option['socket']['backlog'] = static::DEFAULT_BACKLOG; + } + $this->_context = \stream_context_create($context_option); + } + + // Turn reusePort on. + if (static::$_OS === \OS_TYPE_LINUX // if linux + && \version_compare(\PHP_VERSION,'7.0.0', 'ge') // if php >= 7.0.0 + && \strtolower(\php_uname('s')) !== 'darwin' // if not Mac OS + && $this->transport !== 'unix') { // if not unix socket + + $this->reusePort = true; + } + } + + + /** + * Listen. + * + * @throws Exception + */ + public function listen() + { + if (!$this->_socketName) { + return; + } + + // Autoload. + Autoloader::setRootPath($this->_autoloadRootPath); + + if (!$this->_mainSocket) { + + $local_socket = $this->parseSocketAddress(); + + // Flag. + $flags = $this->transport === 'udp' ? \STREAM_SERVER_BIND : \STREAM_SERVER_BIND | \STREAM_SERVER_LISTEN; + $errno = 0; + $errmsg = ''; + // SO_REUSEPORT. + if ($this->reusePort) { + \stream_context_set_option($this->_context, 'socket', 'so_reuseport', 1); + } + + // Create an Internet or Unix domain server socket. + $this->_mainSocket = \stream_socket_server($local_socket, $errno, $errmsg, $flags, $this->_context); + if (!$this->_mainSocket) { + throw new Exception($errmsg); + } + + if ($this->transport === 'ssl') { + \stream_socket_enable_crypto($this->_mainSocket, false); + } elseif ($this->transport === 'unix') { + $socket_file = \substr($local_socket, 7); + if ($this->user) { + \chown($socket_file, $this->user); + } + if ($this->group) { + \chgrp($socket_file, $this->group); + } + } + + // Try to open keepalive for tcp and disable Nagle algorithm. + if (\function_exists('socket_import_stream') && static::$_builtinTransports[$this->transport] === 'tcp') { + \set_error_handler(function(){}); + $socket = \socket_import_stream($this->_mainSocket); + \socket_set_option($socket, \SOL_SOCKET, \SO_KEEPALIVE, 1); + \socket_set_option($socket, \SOL_TCP, \TCP_NODELAY, 1); + \restore_error_handler(); + } + + // Non blocking. + \stream_set_blocking($this->_mainSocket, false); + } + + $this->resumeAccept(); + } + + /** + * Unlisten. + * + * @return void + */ + public function unlisten() { + $this->pauseAccept(); + if ($this->_mainSocket) { + \set_error_handler(function(){}); + \fclose($this->_mainSocket); + \restore_error_handler(); + $this->_mainSocket = null; + } + } + + /** + * Parse local socket address. + * + * @throws Exception + */ + protected function parseSocketAddress() { + if (!$this->_socketName) { + return; + } + // Get the application layer communication protocol and listening address. + list($scheme, $address) = \explode(':', $this->_socketName, 2); + // Check application layer protocol class. + if (!isset(static::$_builtinTransports[$scheme])) { + $scheme = \ucfirst($scheme); + $this->protocol = \substr($scheme,0,1)==='\\' ? $scheme : '\\Protocols\\' . $scheme; + if (!\class_exists($this->protocol)) { + $this->protocol = "\\Workerman\\Protocols\\$scheme"; + if (!\class_exists($this->protocol)) { + throw new Exception("class \\Protocols\\$scheme not exist"); + } + } + + if (!isset(static::$_builtinTransports[$this->transport])) { + throw new Exception('Bad worker->transport ' . \var_export($this->transport, true)); + } + } else { + $this->transport = $scheme; + } + //local socket + return static::$_builtinTransports[$this->transport] . ":" . $address; + } + + /** + * Pause accept new connections. + * + * @return void + */ + public function pauseAccept() + { + if (static::$globalEvent && false === $this->_pauseAccept && $this->_mainSocket) { + static::$globalEvent->del($this->_mainSocket, EventInterface::EV_READ); + $this->_pauseAccept = true; + } + } + + /** + * Resume accept new connections. + * + * @return void + */ + public function resumeAccept() + { + // Register a listener to be notified when server socket is ready to read. + if (static::$globalEvent && true === $this->_pauseAccept && $this->_mainSocket) { + if ($this->transport !== 'udp') { + static::$globalEvent->add($this->_mainSocket, EventInterface::EV_READ, array($this, 'acceptConnection')); + } else { + static::$globalEvent->add($this->_mainSocket, EventInterface::EV_READ, array($this, 'acceptUdpConnection')); + } + $this->_pauseAccept = false; + } + } + + /** + * Get socket name. + * + * @return string + */ + public function getSocketName() + { + return $this->_socketName ? \lcfirst($this->_socketName) : 'none'; + } + + /** + * Run worker instance. + * + * @return void + */ + public function run() + { + //Update process state. + static::$_status = static::STATUS_RUNNING; + + // Register shutdown function for checking errors. + \register_shutdown_function(array("\\Workerman\\Worker", 'checkErrors')); + + // Set autoload root path. + Autoloader::setRootPath($this->_autoloadRootPath); + + // Create a global event loop. + if (!static::$globalEvent) { + $event_loop_class = static::getEventLoopName(); + static::$globalEvent = new $event_loop_class; + $this->resumeAccept(); + } + + // Reinstall signal. + static::reinstallSignal(); + + // Init Timer. + Timer::init(static::$globalEvent); + + // Set an empty onMessage callback. + if (empty($this->onMessage)) { + $this->onMessage = function () {}; + } + + \restore_error_handler(); + + // Try to emit onWorkerStart callback. + if ($this->onWorkerStart) { + try { + \call_user_func($this->onWorkerStart, $this); + } catch (\Exception $e) { + static::log($e); + // Avoid rapid infinite loop exit. + sleep(1); + exit(250); + } catch (\Error $e) { + static::log($e); + // Avoid rapid infinite loop exit. + sleep(1); + exit(250); + } + } + + // Main loop. + static::$globalEvent->loop(); + } + + /** + * Stop current worker instance. + * + * @return void + */ + public function stop() + { + // Try to emit onWorkerStop callback. + if ($this->onWorkerStop) { + try { + \call_user_func($this->onWorkerStop, $this); + } catch (\Exception $e) { + static::log($e); + exit(250); + } catch (\Error $e) { + static::log($e); + exit(250); + } + } + // Remove listener for server socket. + $this->unlisten(); + // Close all connections for the worker. + if (!static::$_gracefulStop) { + foreach ($this->connections as $connection) { + $connection->close(); + } + } + // Clear callback. + $this->onMessage = $this->onClose = $this->onError = $this->onBufferDrain = $this->onBufferFull = null; + } + + /** + * Accept a connection. + * + * @param resource $socket + * @return void + */ + public function acceptConnection($socket) + { + // Accept a connection on server socket. + \set_error_handler(function(){}); + $new_socket = \stream_socket_accept($socket, 0, $remote_address); + \restore_error_handler(); + + // Thundering herd. + if (!$new_socket) { + return; + } + + // TcpConnection. + $connection = new TcpConnection($new_socket, $remote_address); + $this->connections[$connection->id] = $connection; + $connection->worker = $this; + $connection->protocol = $this->protocol; + $connection->transport = $this->transport; + $connection->onMessage = $this->onMessage; + $connection->onClose = $this->onClose; + $connection->onError = $this->onError; + $connection->onBufferDrain = $this->onBufferDrain; + $connection->onBufferFull = $this->onBufferFull; + + // Try to emit onConnect callback. + if ($this->onConnect) { + try { + \call_user_func($this->onConnect, $connection); + } catch (\Exception $e) { + static::log($e); + exit(250); + } catch (\Error $e) { + static::log($e); + exit(250); + } + } + } + + /** + * For udp package. + * + * @param resource $socket + * @return bool + */ + public function acceptUdpConnection($socket) + { + \set_error_handler(function(){}); + $recv_buffer = \stream_socket_recvfrom($socket, static::MAX_UDP_PACKAGE_SIZE, 0, $remote_address); + \restore_error_handler(); + if (false === $recv_buffer || empty($remote_address)) { + return false; + } + // UdpConnection. + $connection = new UdpConnection($socket, $remote_address); + $connection->protocol = $this->protocol; + if ($this->onMessage) { + try { + if ($this->protocol !== null) { + /** @var \Workerman\Protocols\ProtocolInterface $parser */ + $parser = $this->protocol; + if(\method_exists($parser,'input')){ + while($recv_buffer !== ''){ + $len = $parser::input($recv_buffer, $connection); + if($len === 0) + return true; + $package = \substr($recv_buffer,0,$len); + $recv_buffer = \substr($recv_buffer,$len); + $data = $parser::decode($package,$connection); + if ($data === false) + continue; + \call_user_func($this->onMessage, $connection, $data); + } + }else{ + $data = $parser::decode($recv_buffer, $connection); + // Discard bad packets. + if ($data === false) + return true; + \call_user_func($this->onMessage, $connection, $data); + } + }else{ + \call_user_func($this->onMessage, $connection, $recv_buffer); + } + ++ConnectionInterface::$statistics['total_request']; + } catch (\Exception $e) { + static::log($e); + exit(250); + } catch (\Error $e) { + static::log($e); + exit(250); + } + } + return true; + } +} diff --git a/GatewayWorker/vendor/workerman/workerman/composer.json b/GatewayWorker/vendor/workerman/workerman/composer.json new file mode 100644 index 0000000..fdd4808 --- /dev/null +++ b/GatewayWorker/vendor/workerman/workerman/composer.json @@ -0,0 +1,38 @@ +{ + "name": "workerman/workerman", + "type": "library", + "keywords": [ + "event-loop", + "asynchronous" + ], + "homepage": "http://www.workerman.net", + "license": "MIT", + "description": "An asynchronous event driven PHP framework for easily building fast, scalable network applications.", + "authors": [ + { + "name": "walkor", + "email": "walkor@workerman.net", + "homepage": "http://www.workerman.net", + "role": "Developer" + } + ], + "support": { + "email": "walkor@workerman.net", + "issues": "https://github.com/walkor/workerman/issues", + "forum": "http://wenda.workerman.net/", + "wiki": "http://doc.workerman.net/", + "source": "https://github.com/walkor/workerman" + }, + "require": { + "php": ">=5.3" + }, + "suggest": { + "ext-event": "For better performance. " + }, + "autoload": { + "psr-4": { + "Workerman\\": "./" + } + }, + "minimum-stability": "dev" +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..be92e94 --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +**(注:本程序为简单实现,因纯属业余时间编写,开发比较仓促,欢迎完善)**
+1.staticweb目录您需要进行nginx配置为站点。
+2.GatewayWorker下start_gateway中配置wss所需要的证书
+`$context = array(`
+      `'ssl' => array(// 请使用绝对路径`
+          `'local_cert' =>'/xxxxx.pem', //也可以是crt文件`
+          `'local_pk' => 'xxxxxx.key',`
+          `'verify_peer' => false, + // 'allow_self_signed' => true, //如果是自签名证书需要开启此选项`
+      `)`
+ `);`
+3.启动GatewayWorker
+ `cd 您的GatewayWorker路径/GatewayWorker ; php start.php start` +4.start_sync中可根据您的服务器配置来设置:进程数
+task进程数可以根据需要多开一些,默认为10
+`$task_worker->count = 10;`
+5.demo站点:点我跳转
+6.本程序基于workerMan编写
+7.因本人平常工作繁忙,欢迎爱好者帮忙搭建docker,请联系本人放出仓库地址
+8.如有问题,可联系本人qq:9496898 + diff --git a/staticweb/css/style.css b/staticweb/css/style.css new file mode 100644 index 0000000..68b2889 --- /dev/null +++ b/staticweb/css/style.css @@ -0,0 +1,316 @@ + +/* reset */ +html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,dl,dt,dd,ol,nav ul,nav li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline;} +article, aside, details, figcaption, figure,footer, header, hgroup, menu, nav, section {display: block;} +ol,ul{list-style:none;margin:0px;padding:0px;} +blockquote,q{quotes:none;} +blockquote:before,blockquote:after,q:before,q:after{content:'';content:none;} +table{border-collapse:collapse;border-spacing:0;} +/* start editing from here */ +a{text-decoration:none;} +.txt-rt{text-align:right;}/* text align right */ +.txt-lt{text-align:left;}/* text align left */ +.txt-center{text-align:center;}/* text align center */ +.float-rt{float:right;}/* float right */ +.float-lt{float:left;}/* float left */ +.clear{clear:both;}/* clear float */ +.pos-relative{position:relative;}/* Position Relative */ +.pos-absolute{position:absolute;}/* Position Absolute */ +.vertical-base{ vertical-align:baseline;}/* vertical align baseline */ +.vertical-top{ vertical-align:top;}/* vertical align top */ +nav.vertical ul li{ display:block;}/* vertical menu */ +nav.horizontal ul li{ display: inline-block;}/* horizontal menu */ +img{max-width:100%;} +/*end reset*/ +/****-----start-body----****/ +body { +font-family:Verdana; + background: linear-gradient( rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5) ),url(https://api.dujin.org/bing/1920.php)0px 0px no-repeat; + background-size:cover; +} +body a{ + transition: 0.1s all; + -webkit-transition: 0.1s all; + -moz-transition: 0.1s all; + -o-transition: 0.1s all; +} +body h1 { + color:#fff; + text-align: center; + padding: 0.5em 0; + font-size:3em; + 'Lobster', cursive; +} +body h2 { + color:#fff; + text-align: center; + padding: 0.5em 0; + font-size:1em; + 'Lobster', cursive; +} +.app-block{ + width:28%; + margin:0 auto; + text-align: center; + + padding: 3em 5em; + height: 80%; +} +form { + padding: 3% 3% 7% 3%; + border-bottom: 1px solid #999; +} +/*-----*/ +.cube img { + margin: 1em 0; +} +.app-block input[type="text"],.app-block input[type="password"]{ + width: 100%; + padding: 1.25em 1em 1.25em 4em; + color: #fff; + font-size: 16px; + outline: none; + font-weight: 500; + border: none; + + background: url("../images/icons.png") no-repeat 14px 20px #1e1e1d; + margin:0.8em 0; + } +.app-block input[type="password"]{ + background: url("../images/icons.png") no-repeat 13px -53px #1e1e1d; + } +.submit { + margin: 1em 0; +} +.app-block p.sign { + padding-top: 3em; +} +.widora{ + color: #fff; +} +.app-block .sub-btn{ + font-size: 20px; + font-weight: 300; + color: #fff; + cursor: pointer; + outline: none; + padding:20px 20px; + width:100%; + border:none; + moz-transition: all 0.3s; + -o-transition: all 0.3s; + -webkit-transition: all 0.3s; + transition: all 0.3s; + background:#eacb20; + border-radius: 2em; + -webkit-border-radius: 2em; + -o-border-radius: 2em; + -moz-border-radius: 2em; + border: 3px solid #EACB20 +} +.sub-btn:hover{ + color:#EACB20; + background: none; + border: 3px dotted #EACB20; +} +.app-block p a,.app-block p.sign{ + color:#AFAFB0; + font-weight: 400; + font-size: 1em; +} +.app-block p{ + padding-top: 1em; +} +.app-block p a:hover,.app-block p.sign a:hover{ + text-decoration: underline; + color: #EACB20; +} +/*---------------*/ +.copy-right { + padding: 3em 1em; +} +.copy-right p { + color: #ddd; + font-size: 1em; + font-weight:400; + margin: 0 auto; + text-align: center; +} + +.copy-right p a:hover { + text-decoration: underline; +} +/*-----start-responsive-design------*/ + +@media (max-width:1440px){ + .app-block{ + width:40%; + } + .app-block input[type="text"],.app-block input[type="password"]{ + width: 80.2%; + + } +} +@media (max-width:1366px){ + .app-block{ + width: 40%; + } +.app-block input[type="text"], .app-block input[type="password"] { + width: 79.7%; + } +body h1 { + font-size: 2.9em; +}} + +@media (max-width:1280px){ + .app-block { + width: 70%; + } + .app-block input[type="text"], .app-block input[type="password"] { + width: 80.7%; + } + +} +@media (max-width:1024px){ + .app-block { + width: 70%; + } + .app-block input[type="text"], .app-block input[type="password"] { + width: 82.7%; + } + +} +@media (max-width:768px){ + body h1 { + font-size: 2.4em; + } + .cam img { + margin: 2em 0; +} +.app-block { + width: 75%; +} +.copy-right { + padding: 1em 1em; +} +.app-block input[type="text"], .app-block input[type="password"] { + width:79.7%; + } + +} +@media (max-width:640px){ +.app-block input[type="text"], .app-block input[type="password"] { + width: 79.8%; +} +.app-block { + width: 75%; +} +} +@media (max-width:480px){ + .copy-right p { + font-size: 0.9em; + } + .new h3 a, .new h4 a { + font-size: 0.9em; + } + form { + width: 94%; + margin: 0 auto; + } + .app-block .sub-btn { + font-size: 18px; + padding: 14px 15px; + } + .app-block { + width: 68%; + padding: 1em 4em 2em 4em; +} + .app-block input[type="text"], .app-block input[type="password"] { + width: 72.8%; + background: url("../images/icons.png") no-repeat 16px 18px #1e1e1d; + background-size: 7%; + padding: 1.1em 1em 1.1em 4em; + margin-top: 1px; + font-size: 14px; +} +.app-block input[type="password"] { + background: url("../images/icons.png") no-repeat 16px -28px #1e1e1d; + background-size: 7%; + } + .buttons { + margin: 0em 0 1em 0; + padding: 3em 0em 1em 0em; +} +.block h2 { + padding: 0.8em 0; + font-size: 1.4em; +} +.app-block p a, .app-block p.sign a { + font-size: 0.9em; +} +.app-block p { + padding-top: 0em; +} +.app-block p.sign { + padding: 2em 0 2em 0; +} +} +@media (max-width:320px){ +.app-block { + width: 96.5%; + padding: 0em 0em 1em 0em; + margin: 0 auto 0 2%; +} +.app-block p a, .app-block p.sign { + font-size: 0.9em; +} + body h1 { + font-size: 1.7em; +} +.copy-right p { + font-size: 0.8em; + line-height: 1.7em; +} +.app-block p.sign { + padding: 1em 0 1em 0; +} +.submit { + margin: 0.5em 0; +} +.app-block .sub-btn { + font-size: 18px; + padding: 10px 10px; +} +.app-block p { + padding-top: 0.5em; +} + .app-block input[type="text"], .app-block input[type="password"] { + width: 74.8%; + background: url("../images/icons.png") no-repeat 15px 16px #1e1e1d; + background-size: 8%; + padding: 1em 1em 1em 4em; + margin-top: 1px; + font-size: 13px; +} +.app-block input[type="password"] { + background: url("../images/icons.png") no-repeat 16px -28px #1e1e1d; + background-size: 8%; + } +form { + width: 92%; + margin: 0 auto; +} +.cube img { + width: 27%; + } +} +.statusLabel{ + color: #fff; +} +.sub-btn a{ + font-size:18px; + color:#fff; + display: block; + width: 100%; + height:100%; +} diff --git a/staticweb/images/bg.jpg b/staticweb/images/bg.jpg new file mode 100644 index 0000000000000000000000000000000000000000..bc114a28221d8ed6a37b72cf037d33ef019b426c GIT binary patch literal 162874 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®jd+qZjVDwvL*(L&slyyWDZji)Fv>X9wt> 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

iyGl= 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 literal 0 HcmV?d00001 diff --git a/staticweb/images/githublogo.png b/staticweb/images/githublogo.png new file mode 100644 index 0000000000000000000000000000000000000000..78708d7cdc6b4e61ab61dc6b057fd73137e3a402 GIT binary patch literal 59848 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 literal 0 HcmV?d00001 diff --git a/staticweb/images/icons.png b/staticweb/images/icons.png new file mode 100644 index 0000000000000000000000000000000000000000..515aedc1ca241099d709c16a134fd20a0d761ec8 GIT binary patch literal 3444 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 + + + GitHub代下载服务·永久免费 + + + + + + + + + + +

GitHub代下载服务

+

Git克隆慢?外网源码包没速度?试试我,记得收藏哦

+
+
+ +
已成功加速下载次数:
+
+ +
+ +
+

注意:暂不支持超过2GB的仓库ε=ε=ε=┏(゜ロ゜;)┛

+
+

您的支持是我们前进的动力,希望大家多多关注我的B站,不定期更新个人开源项目,谢谢啦~~~~

+
+
+

Copyright © 2019- 歪朵拉  |  Designed by xiaoxin  | 获取source |  免责声明  |  更新日志

+
+ + + diff --git a/staticweb/js/index.js b/staticweb/js/index.js new file mode 100644 index 0000000..d071359 --- /dev/null +++ b/staticweb/js/index.js @@ -0,0 +1,126 @@ +var webSocketClick = { + ws:null, + app_block:null, + githubUrl:null, + init:function(){ + var __this = this; + __this.app_block = $(".app-block"); + __this.ws = new WebSocket("wss://" + document.domain + ":8848"); + __this.ws.onopen = function(event){ + __this.onOpen(event) + } + __this.ws.onmessage = function(event){ + __this.onMessage(event) + } + __this.ws.onclose = function(event){ + __this.onClose(event) + } + __this.app_block.find(".sub-btn").on('click',function(){ + __this.getSource($(this)) + }) + $(".exemption").on("click",function(){ + var html = '
\ + 您浏览并使用本站的代下载服务,视为对以下声明内容的全部认可\ +

1、本站不存储资源,只是中转平台

\ +

2、使用本站下载任何资源需自行承担风险

\ +

3、本站仅代下网上现成的代码,无法对这些代码的可用性,准确性或可靠性作出任何承诺与保证

\ +

4、本站不对任何由于使用或无法使用本站提供的代码所造成的直接的和间接的损失负任何责任

\ +

5、任何单位和个人不得使用本站下载侵权、盗版、违法的任何软件或资料

\ +
'; + layer.alert(html,{title:'免责声明',area:'500px'}) + return false; + }) + }, + onOpen:function(event){ + var __this = this; + sendJson = { + 'type':"2", + }; + __this.ws.send(JSON.stringify(sendJson)+"\n") + console.log("连接成功") + }, + onMessage:function(event){ + var __this = this; + try { + var data = JSON.parse(event.data) + if (data.status == 1) { + layer.msg(data.msg); + return false; + } + switch (data.data.sta) { + case 1: //打包 + case 2: //压缩zip + __this.app_block.find(".sub-btn").text(data.msg) + break; + + case 3: + layer.msg("操作完成,请下载"); + __this.app_block.find(".sub-btn").html("" + data.msg + "").css({ + "background": "green", + "color": "#fff", + }); + $(".download-btn").on("click",function(){ + sendJson = { + 'type':"1", + }; + __this.ws.send(JSON.stringify(sendJson)+"\n") + sendJson.type = "2"; + __this.ws.send(JSON.stringify(sendJson)+"\n") + setTimeout(function(){ + window.location.reload(); + },2000); + }) + + break; + + case 4: + $(".down_num").find("b").text($.trim(data.data.down_num)) + break; + } + }catch (e) { + + } + + + + + }, + onClose:function (event) { + var __this = this; + console.log("连接关闭") + }, + getSource:function($this){ + var __this = this; + var githubUrl = __this.app_block.find(".text"); + var githubUrlReg = new RegExp('^https:\\/\\/github.com\\/([\\w\\/\\d\\-\\_\\.]+)$','ig'); + var githubUrlReg1 = new RegExp('^((https|http):\\/\\/)?([\\w\\.\\/\\-\\_^]+?)\\.([tar\\.gz|gz|tar\\.bz2|bz2|tar|zip|tar\\.xz|tar\\.z|rpm|deb|rar]+?)$','ig'); + if(!githubUrl.val()){ + layer.msg("请输入github地址"); + githubUrl.select(); + return false; + } + var urlVal = $.trim(githubUrl.val()) + if(!githubUrlReg.test(urlVal) && !githubUrlReg1.test(urlVal)){ + layer.msg("请输入正确的github地址:仅支持https协议 或
(gz|tar.gz|bz2|tar|tar.bz2|zip|tar.xz|tar.z|rpm|deb|rar)格式的压缩包下载链接"); + githubUrl.select(); + return false; + } + __this.githubUrl = urlVal + __this.getSourceInit(); + + }, + getSourceInit:function(){ + var __this = this; + var sendJson = { + "url" : __this.githubUrl, + } + __this.app_block.find(".sub-btn").prop("disabled",true).off("click").css({ + "background": 'none', + "color":"#EACB20", + }); + __this.ws.send(JSON.stringify(sendJson)+"\n") + } +} +$(function(){ + webSocketClick.init(); +}) \ No newline at end of file diff --git a/staticweb/js/jquery-2.2.4.min.js b/staticweb/js/jquery-2.2.4.min.js new file mode 100644 index 0000000..d5df667 --- /dev/null +++ b/staticweb/js/jquery-2.2.4.min.js @@ -0,0 +1,5 @@ + +/*! jQuery v2.2.4 | (c) jQuery Foundation | jquery.org/license */ +!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=a.document,e=c.slice,f=c.concat,g=c.push,h=c.indexOf,i={},j=i.toString,k=i.hasOwnProperty,l={},m="2.2.4",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return e.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:e.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a){return n.each(this,a)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(e.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor()},push:g,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(n.isPlainObject(d)||(e=n.isArray(d)))?(e?(e=!1,f=c&&n.isArray(c)?c:[]):f=c&&n.isPlainObject(c)?c:{},g[b]=n.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){var b=a&&a.toString();return!n.isArray(a)&&b-parseFloat(b)+1>=0},isPlainObject:function(a){var b;if("object"!==n.type(a)||a.nodeType||n.isWindow(a))return!1;if(a.constructor&&!k.call(a,"constructor")&&!k.call(a.constructor.prototype||{},"isPrototypeOf"))return!1;for(b in a);return void 0===b||k.call(a,b)},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?i[j.call(a)]||"object":typeof a},globalEval:function(a){var b,c=eval;a=n.trim(a),a&&(1===a.indexOf("use strict")?(b=d.createElement("script"),b.text=a,d.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b){var c,d=0;if(s(a)){for(c=a.length;c>d;d++)if(b.call(a[d],d,a[d])===!1)break}else for(d in a)if(b.call(a[d],d,a[d])===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):g.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:h.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,e,g=0,h=[];if(s(a))for(d=a.length;d>g;g++)e=b(a[g],g,c),null!=e&&h.push(e);else for(g in a)e=b(a[g],g,c),null!=e&&h.push(e);return f.apply([],h)},guid:1,proxy:function(a,b){var c,d,f;return"string"==typeof b&&(c=a[b],b=a,a=c),n.isFunction(a)?(d=e.call(arguments,2),f=function(){return a.apply(b||this,d.concat(e.call(arguments)))},f.guid=a.guid=a.guid||n.guid++,f):void 0},now:Date.now,support:l}),"function"==typeof Symbol&&(n.fn[Symbol.iterator]=c[Symbol.iterator]),n.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(a,b){i["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=!!a&&"length"in a&&a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ga(),z=ga(),A=ga(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+M+"))|)"+L+"*\\]",O=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+N+")*)|.*)\\)|)",P=new RegExp(L+"+","g"),Q=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),R=new RegExp("^"+L+"*,"+L+"*"),S=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),T=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),U=new RegExp(O),V=new RegExp("^"+M+"$"),W={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M+"|[*])"),ATTR:new RegExp("^"+N),PSEUDO:new RegExp("^"+O),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},X=/^(?:input|select|textarea|button)$/i,Y=/^h\d$/i,Z=/^[^{]+\{\s*\[native \w/,$=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,_=/[+~]/,aa=/'|\\/g,ba=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),ca=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},da=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(ea){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function fa(a,b,d,e){var f,h,j,k,l,o,r,s,w=b&&b.ownerDocument,x=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==x&&9!==x&&11!==x)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==x&&(o=$.exec(a)))if(f=o[1]){if(9===x){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(w&&(j=w.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(o[2])return H.apply(d,b.getElementsByTagName(a)),d;if((f=o[3])&&c.getElementsByClassName&&b.getElementsByClassName)return H.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==x)w=b,s=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(aa,"\\$&"):b.setAttribute("id",k=u),r=g(a),h=r.length,l=V.test(k)?"#"+k:"[id='"+k+"']";while(h--)r[h]=l+" "+qa(r[h]);s=r.join(","),w=_.test(a)&&oa(b.parentNode)||b}if(s)try{return H.apply(d,w.querySelectorAll(s)),d}catch(y){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(Q,"$1"),b,d,e)}function ga(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ha(a){return a[u]=!0,a}function ia(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ja(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function ka(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function la(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function na(a){return ha(function(b){return b=+b,ha(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function oa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=fa.support={},f=fa.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=fa.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ia(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ia(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Z.test(n.getElementsByClassName),c.getById=ia(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ba,ca);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ba,ca);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return"undefined"!=typeof b.getElementsByClassName&&p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=Z.test(n.querySelectorAll))&&(ia(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ia(function(a){var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Z.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ia(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",O)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Z.test(o.compareDocumentPosition),t=b||Z.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return ka(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?ka(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},fa.matches=function(a,b){return fa(a,null,null,b)},fa.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(T,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return fa(b,n,null,[a]).length>0},fa.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},fa.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},fa.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},fa.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=fa.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=fa.selectors={cacheLength:50,createPseudo:ha,match:W,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ba,ca),a[3]=(a[3]||a[4]||a[5]||"").replace(ba,ca),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||fa.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&fa.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return W.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&U.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ba,ca).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=fa.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(P," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||fa.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ha(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ha(function(a){var b=[],c=[],d=h(a.replace(Q,"$1"));return d[u]?ha(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ha(function(a){return function(b){return fa(a,b).length>0}}),contains:ha(function(a){return a=a.replace(ba,ca),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ha(function(a){return V.test(a||"")||fa.error("unsupported lang: "+a),a=a.replace(ba,ca).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Y.test(a.nodeName)},input:function(a){return X.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:na(function(){return[0]}),last:na(function(a,b){return[b-1]}),eq:na(function(a,b,c){return[0>c?c+b:c]}),even:na(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:na(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:na(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:na(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function ra(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j,k=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(j=b[u]||(b[u]={}),i=j[b.uniqueID]||(j[b.uniqueID]={}),(h=i[d])&&h[0]===w&&h[1]===f)return k[2]=h[2];if(i[d]=k,k[2]=a(b,c,g))return!0}}}function sa(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ta(a,b,c){for(var d=0,e=b.length;e>d;d++)fa(a,b[d],c);return c}function ua(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(c&&!c(f,d,e)||(g.push(f),j&&b.push(h)));return g}function va(a,b,c,d,e,f){return d&&!d[u]&&(d=va(d)),e&&!e[u]&&(e=va(e,f)),ha(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ta(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:ua(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=ua(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=ua(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function wa(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ra(function(a){return a===b},h,!0),l=ra(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[ra(sa(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return va(i>1&&sa(m),i>1&&qa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(Q,"$1"),c,e>i&&wa(a.slice(i,e)),f>e&&wa(a=a.slice(e)),f>e&&qa(a))}m.push(c)}return sa(m)}function xa(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=F.call(i));u=ua(u)}H.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&fa.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ha(f):f}return h=fa.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=wa(b[c]),f[u]?d.push(f):e.push(f);f=A(a,xa(e,d)),f.selector=a}return f},i=fa.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(ba,ca),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=W.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(ba,ca),_.test(j[0].type)&&oa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&qa(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,!b||_.test(a)&&oa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ia(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ia(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ja("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ia(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ja("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ia(function(a){return null==a.getAttribute("disabled")})||ja(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),fa}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.uniqueSort=n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},v=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},w=n.expr.match.needsContext,x=/^<([\w-]+)\s*\/?>(?:<\/\1>|)$/,y=/^.[^:#\[\.,]*$/;function z(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(y.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return h.call(b,a)>-1!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=this.length,d=[],e=this;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;c>b;b++)if(n.contains(e[b],this))return!0}));for(b=0;c>b;b++)n.find(a,e[b],d);return d=this.pushStack(c>1?n.unique(d):d),d.selector=this.selector?this.selector+" "+a:a,d},filter:function(a){return this.pushStack(z(this,a||[],!1))},not:function(a){return this.pushStack(z(this,a||[],!0))},is:function(a){return!!z(this,"string"==typeof a&&w.test(a)?n(a):a||[],!1).length}});var A,B=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,C=n.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||A,"string"==typeof a){if(e="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:B.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),x.test(e[1])&&n.isPlainObject(b))for(e in b)n.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}return f=d.getElementById(e[2]),f&&f.parentNode&&(this.length=1,this[0]=f),this.context=d,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?void 0!==c.ready?c.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};C.prototype=n.fn,A=n(d);var D=/^(?:parents|prev(?:Until|All))/,E={children:!0,contents:!0,next:!0,prev:!0};n.fn.extend({has:function(a){var b=n(a,this),c=b.length;return this.filter(function(){for(var a=0;c>a;a++)if(n.contains(this,b[a]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=w.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?h.call(n(a),this[0]):h.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.uniqueSort(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function F(a,b){while((a=a[b])&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return u(a,"parentNode")},parentsUntil:function(a,b,c){return u(a,"parentNode",c)},next:function(a){return F(a,"nextSibling")},prev:function(a){return F(a,"previousSibling")},nextAll:function(a){return u(a,"nextSibling")},prevAll:function(a){return u(a,"previousSibling")},nextUntil:function(a,b,c){return u(a,"nextSibling",c)},prevUntil:function(a,b,c){return u(a,"previousSibling",c)},siblings:function(a){return v((a.parentNode||{}).firstChild,a)},children:function(a){return v(a.firstChild)},contents:function(a){return a.contentDocument||n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(E[a]||n.uniqueSort(e),D.test(a)&&e.reverse()),this.pushStack(e)}});var G=/\S+/g;function H(a){var b={};return n.each(a.match(G)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?H(a):n.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h-1)f.splice(c,1),h>=c&&h--}),this},has:function(a){return a?n.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=g=[],c||(f=c=""),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().progress(c.notify).done(c.resolve).fail(c.reject):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=e.call(arguments),d=c.length,f=1!==d||a&&n.isFunction(a.promise)?d:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(d){b[a]=this,c[a]=arguments.length>1?e.call(arguments):d,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(d>1)for(i=new Array(d),j=new Array(d),k=new Array(d);d>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().progress(h(b,j,i)).done(h(b,k,c)).fail(g.reject):--f;return f||g.resolveWith(k,c),g.promise()}});var I;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(I.resolveWith(d,[n]),n.fn.triggerHandler&&(n(d).triggerHandler("ready"),n(d).off("ready"))))}});function J(){d.removeEventListener("DOMContentLoaded",J),a.removeEventListener("load",J),n.ready()}n.ready.promise=function(b){return I||(I=n.Deferred(),"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll?a.setTimeout(n.ready):(d.addEventListener("DOMContentLoaded",J),a.addEventListener("load",J))),I.promise(b)},n.ready.promise();var K=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===n.type(c)){e=!0;for(h in c)K(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,n.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(n(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},L=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function M(){this.expando=n.expando+M.uid++}M.uid=1,M.prototype={register:function(a,b){var c=b||{};return a.nodeType?a[this.expando]=c:Object.defineProperty(a,this.expando,{value:c,writable:!0,configurable:!0}),a[this.expando]},cache:function(a){if(!L(a))return{};var b=a[this.expando];return b||(b={},L(a)&&(a.nodeType?a[this.expando]=b:Object.defineProperty(a,this.expando,{value:b,configurable:!0}))),b},set:function(a,b,c){var d,e=this.cache(a);if("string"==typeof b)e[b]=c;else for(d in b)e[d]=b[d];return e},get:function(a,b){return void 0===b?this.cache(a):a[this.expando]&&a[this.expando][b]},access:function(a,b,c){var d;return void 0===b||b&&"string"==typeof b&&void 0===c?(d=this.get(a,b),void 0!==d?d:this.get(a,n.camelCase(b))):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d,e,f=a[this.expando];if(void 0!==f){if(void 0===b)this.register(a);else{n.isArray(b)?d=b.concat(b.map(n.camelCase)):(e=n.camelCase(b),b in f?d=[b,e]:(d=e,d=d in f?[d]:d.match(G)||[])),c=d.length;while(c--)delete f[d[c]]}(void 0===b||n.isEmptyObject(f))&&(a.nodeType?a[this.expando]=void 0:delete a[this.expando])}},hasData:function(a){var b=a[this.expando];return void 0!==b&&!n.isEmptyObject(b)}};var N=new M,O=new M,P=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,Q=/[A-Z]/g;function R(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(Q,"-$&").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:P.test(c)?n.parseJSON(c):c; +}catch(e){}O.set(a,b,c)}else c=void 0;return c}n.extend({hasData:function(a){return O.hasData(a)||N.hasData(a)},data:function(a,b,c){return O.access(a,b,c)},removeData:function(a,b){O.remove(a,b)},_data:function(a,b,c){return N.access(a,b,c)},_removeData:function(a,b){N.remove(a,b)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=O.get(f),1===f.nodeType&&!N.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),R(f,d,e[d])));N.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){O.set(this,a)}):K(this,function(b){var c,d;if(f&&void 0===b){if(c=O.get(f,a)||O.get(f,a.replace(Q,"-$&").toLowerCase()),void 0!==c)return c;if(d=n.camelCase(a),c=O.get(f,d),void 0!==c)return c;if(c=R(f,d,void 0),void 0!==c)return c}else d=n.camelCase(a),this.each(function(){var c=O.get(this,d);O.set(this,d,b),a.indexOf("-")>-1&&void 0!==c&&O.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){O.remove(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=N.get(a,b),c&&(!d||n.isArray(c)?d=N.access(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return N.get(a,c)||N.access(a,c,{empty:n.Callbacks("once memory").add(function(){N.remove(a,[b+"queue",c])})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length",""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};$.optgroup=$.option,$.tbody=$.tfoot=$.colgroup=$.caption=$.thead,$.th=$.td;function _(a,b){var c="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&n.nodeName(a,b)?n.merge([a],c):c}function aa(a,b){for(var c=0,d=a.length;d>c;c++)N.set(a[c],"globalEval",!b||N.get(b[c],"globalEval"))}var ba=/<|&#?\w+;/;function ca(a,b,c,d,e){for(var f,g,h,i,j,k,l=b.createDocumentFragment(),m=[],o=0,p=a.length;p>o;o++)if(f=a[o],f||0===f)if("object"===n.type(f))n.merge(m,f.nodeType?[f]:f);else if(ba.test(f)){g=g||l.appendChild(b.createElement("div")),h=(Y.exec(f)||["",""])[1].toLowerCase(),i=$[h]||$._default,g.innerHTML=i[1]+n.htmlPrefilter(f)+i[2],k=i[0];while(k--)g=g.lastChild;n.merge(m,g.childNodes),g=l.firstChild,g.textContent=""}else m.push(b.createTextNode(f));l.textContent="",o=0;while(f=m[o++])if(d&&n.inArray(f,d)>-1)e&&e.push(f);else if(j=n.contains(f.ownerDocument,f),g=_(l.appendChild(f),"script"),j&&aa(g),c){k=0;while(f=g[k++])Z.test(f.type||"")&&c.push(f)}return l}!function(){var a=d.createDocumentFragment(),b=a.appendChild(d.createElement("div")),c=d.createElement("input");c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),b.appendChild(c),l.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="",l.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var da=/^key/,ea=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,fa=/^([^.]*)(?:\.(.+)|)/;function ga(){return!0}function ha(){return!1}function ia(){try{return d.activeElement}catch(a){}}function ja(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)ja(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=ha;else if(!e)return a;return 1===f&&(g=e,e=function(a){return n().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=n.guid++)),a.each(function(){n.event.add(this,b,e,d,c)})}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=N.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=n.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return"undefined"!=typeof n&&n.event.triggered!==b.type?n.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(G)||[""],j=b.length;while(j--)h=fa.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o&&(l=n.event.special[o]||{},o=(e?l.delegateType:l.bindType)||o,l=n.event.special[o]||{},k=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},f),(m=i[o])||(m=i[o]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(o,g)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),n.event.global[o]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=N.hasData(a)&&N.get(a);if(r&&(i=r.events)){b=(b||"").match(G)||[""],j=b.length;while(j--)if(h=fa.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=i[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&q!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete i[o])}else for(o in i)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(i)&&N.remove(a,"handle events")}},dispatch:function(a){a=n.event.fix(a);var b,c,d,f,g,h=[],i=e.call(arguments),j=(N.get(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())a.rnamespace&&!a.rnamespace.test(g.namespace)||(a.handleObj=g,a.data=g.data,d=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==d&&(a.result=d)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&("click"!==a.type||isNaN(a.button)||a.button<1))for(;i!==this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>-1:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h]*)\/>/gi,la=/\s*$/g;function pa(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function qa(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function ra(a){var b=na.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function sa(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(N.hasData(a)&&(f=N.access(a),g=N.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;d>c;c++)n.event.add(b,e,j[e][c])}O.hasData(a)&&(h=O.access(a),i=n.extend({},h),O.set(b,i))}}function ta(a,b){var c=b.nodeName.toLowerCase();"input"===c&&X.test(a.type)?b.checked=a.checked:"input"!==c&&"textarea"!==c||(b.defaultValue=a.defaultValue)}function ua(a,b,c,d){b=f.apply([],b);var e,g,h,i,j,k,m=0,o=a.length,p=o-1,q=b[0],r=n.isFunction(q);if(r||o>1&&"string"==typeof q&&!l.checkClone&&ma.test(q))return a.each(function(e){var f=a.eq(e);r&&(b[0]=q.call(this,e,f.html())),ua(f,b,c,d)});if(o&&(e=ca(b,a[0].ownerDocument,!1,a,d),g=e.firstChild,1===e.childNodes.length&&(e=g),g||d)){for(h=n.map(_(e,"script"),qa),i=h.length;o>m;m++)j=e,m!==p&&(j=n.clone(j,!0,!0),i&&n.merge(h,_(j,"script"))),c.call(a[m],j,m);if(i)for(k=h[h.length-1].ownerDocument,n.map(h,ra),m=0;i>m;m++)j=h[m],Z.test(j.type||"")&&!N.access(j,"globalEval")&&n.contains(k,j)&&(j.src?n._evalUrl&&n._evalUrl(j.src):n.globalEval(j.textContent.replace(oa,"")))}return a}function va(a,b,c){for(var d,e=b?n.filter(b,a):a,f=0;null!=(d=e[f]);f++)c||1!==d.nodeType||n.cleanData(_(d)),d.parentNode&&(c&&n.contains(d.ownerDocument,d)&&aa(_(d,"script")),d.parentNode.removeChild(d));return a}n.extend({htmlPrefilter:function(a){return a.replace(ka,"<$1>")},clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=n.contains(a.ownerDocument,a);if(!(l.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(g=_(h),f=_(a),d=0,e=f.length;e>d;d++)ta(f[d],g[d]);if(b)if(c)for(f=f||_(a),g=g||_(h),d=0,e=f.length;e>d;d++)sa(f[d],g[d]);else sa(a,h);return g=_(h,"script"),g.length>0&&aa(g,!i&&_(a,"script")),h},cleanData:function(a){for(var b,c,d,e=n.event.special,f=0;void 0!==(c=a[f]);f++)if(L(c)){if(b=c[N.expando]){if(b.events)for(d in b.events)e[d]?n.event.remove(c,d):n.removeEvent(c,d,b.handle);c[N.expando]=void 0}c[O.expando]&&(c[O.expando]=void 0)}}}),n.fn.extend({domManip:ua,detach:function(a){return va(this,a,!0)},remove:function(a){return va(this,a)},text:function(a){return K(this,function(a){return void 0===a?n.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=a)})},null,a,arguments.length)},append:function(){return ua(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=pa(this,a);b.appendChild(a)}})},prepend:function(){return ua(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=pa(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return ua(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return ua(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(n.cleanData(_(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return K(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!la.test(a)&&!$[(Y.exec(a)||["",""])[1].toLowerCase()]){a=n.htmlPrefilter(a);try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(_(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=[];return ua(this,arguments,function(b){var c=this.parentNode;n.inArray(this,a)<0&&(n.cleanData(_(this)),c&&c.replaceChild(b,this))},a)}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=[],e=n(a),f=e.length-1,h=0;f>=h;h++)c=h===f?this:this.clone(!0),n(e[h])[b](c),g.apply(d,c.get());return this.pushStack(d)}});var wa,xa={HTML:"block",BODY:"block"};function ya(a,b){var c=n(b.createElement(a)).appendTo(b.body),d=n.css(c[0],"display");return c.detach(),d}function za(a){var b=d,c=xa[a];return c||(c=ya(a,b),"none"!==c&&c||(wa=(wa||n("';break;case 3:delete t.title,delete t.closeBtn,t.icon===-1&&0===t.icon,r.closeAll("loading");break;case 4:f||(t.content=[t.content,"body"]),t.follow=t.content[1],t.content=t.content[0]+'',delete t.title,t.tips="object"==typeof t.tips?t.tips:[t.tips,!0],t.tipsMore||r.closeAll("tips")}if(e.vessel(f,function(n,r,u){c.append(n[0]),f?function(){2==t.type||4==t.type?function(){i("body").append(n[1])}():function(){s.parents("."+l[0])[0]||(s.data("display",s.css("display")).show().addClass("layui-layer-wrap").wrap(n[1]),i("#"+l[0]+a).find("."+l[5]).before(r))}()}():c.append(n[1]),i(".layui-layer-move")[0]||c.append(o.moveElem=u),e.layero=i("#"+l[0]+a),t.scrollbar||l.html.css("overflow","hidden").attr("layer-full",a)}).auto(a),i("#layui-layer-shade"+e.index).css({"background-color":t.shade[1]||"#000",opacity:t.shade[0]||t.shade}),2==t.type&&6==r.ie&&e.layero.find("iframe").attr("src",s[0]),4==t.type?e.tips():e.offset(),t.fixed&&n.on("resize",function(){e.offset(),(/^\d+%$/.test(t.area[0])||/^\d+%$/.test(t.area[1]))&&e.auto(a),4==t.type&&e.tips()}),t.time<=0||setTimeout(function(){r.close(e.index)},t.time),e.move().callback(),l.anim[t.anim]){var u="layer-anim "+l.anim[t.anim];e.layero.addClass(u).one("webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend",function(){i(this).removeClass(u)})}t.isOutAnim&&e.layero.data("isOutAnim",!0)}},s.pt.auto=function(e){var t=this,a=t.config,o=i("#"+l[0]+e);""===a.area[0]&&a.maxWidth>0&&(r.ie&&r.ie<8&&a.btn&&o.width(o.innerWidth()),o.outerWidth()>a.maxWidth&&o.width(a.maxWidth));var s=[o.innerWidth(),o.innerHeight()],f=o.find(l[1]).outerHeight()||0,c=o.find("."+l[6]).outerHeight()||0,u=function(e){e=o.find(e),e.height(s[1]-f-c-2*(0|parseFloat(e.css("padding-top"))))};switch(a.type){case 2:u("iframe");break;default:""===a.area[1]?a.maxHeight>0&&o.outerHeight()>a.maxHeight?(s[1]=a.maxHeight,u("."+l[5])):a.fixed&&s[1]>=n.height()&&(s[1]=n.height(),u("."+l[5])):u("."+l[5])}return t},s.pt.offset=function(){var e=this,t=e.config,i=e.layero,a=[i.outerWidth(),i.outerHeight()],o="object"==typeof t.offset;e.offsetTop=(n.height()-a[1])/2,e.offsetLeft=(n.width()-a[0])/2,o?(e.offsetTop=t.offset[0],e.offsetLeft=t.offset[1]||e.offsetLeft):"auto"!==t.offset&&("t"===t.offset?e.offsetTop=0:"r"===t.offset?e.offsetLeft=n.width()-a[0]:"b"===t.offset?e.offsetTop=n.height()-a[1]:"l"===t.offset?e.offsetLeft=0:"lt"===t.offset?(e.offsetTop=0,e.offsetLeft=0):"lb"===t.offset?(e.offsetTop=n.height()-a[1],e.offsetLeft=0):"rt"===t.offset?(e.offsetTop=0,e.offsetLeft=n.width()-a[0]):"rb"===t.offset?(e.offsetTop=n.height()-a[1],e.offsetLeft=n.width()-a[0]):e.offsetTop=t.offset),t.fixed||(e.offsetTop=/%$/.test(e.offsetTop)?n.height()*parseFloat(e.offsetTop)/100:parseFloat(e.offsetTop),e.offsetLeft=/%$/.test(e.offsetLeft)?n.width()*parseFloat(e.offsetLeft)/100:parseFloat(e.offsetLeft),e.offsetTop+=n.scrollTop(),e.offsetLeft+=n.scrollLeft()),i.attr("minLeft")&&(e.offsetTop=n.height()-(i.find(l[1]).outerHeight()||0),e.offsetLeft=i.css("left")),i.css({top:e.offsetTop,left:e.offsetLeft})},s.pt.tips=function(){var e=this,t=e.config,a=e.layero,o=[a.outerWidth(),a.outerHeight()],r=i(t.follow);r[0]||(r=i("body"));var s={width:r.outerWidth(),height:r.outerHeight(),top:r.offset().top,left:r.offset().left},f=a.find(".layui-layer-TipsG"),c=t.tips[0];t.tips[1]||f.remove(),s.autoLeft=function(){s.left+o[0]-n.width()>0?(s.tipLeft=s.left+s.width-o[0],f.css({right:12,left:"auto"})):s.tipLeft=s.left},s.where=[function(){s.autoLeft(),s.tipTop=s.top-o[1]-10,f.removeClass("layui-layer-TipsB").addClass("layui-layer-TipsT").css("border-right-color",t.tips[1])},function(){s.tipLeft=s.left+s.width+10,s.tipTop=s.top,f.removeClass("layui-layer-TipsL").addClass("layui-layer-TipsR").css("border-bottom-color",t.tips[1])},function(){s.autoLeft(),s.tipTop=s.top+s.height+10,f.removeClass("layui-layer-TipsT").addClass("layui-layer-TipsB").css("border-right-color",t.tips[1])},function(){s.tipLeft=s.left-o[0]-10,s.tipTop=s.top,f.removeClass("layui-layer-TipsR").addClass("layui-layer-TipsL").css("border-bottom-color",t.tips[1])}],s.where[c-1](),1===c?s.top-(n.scrollTop()+o[1]+16)<0&&s.where[2]():2===c?n.width()-(s.left+s.width+o[0]+16)>0||s.where[3]():3===c?s.top-n.scrollTop()+s.height+o[1]+16-n.height()>0&&s.where[0]():4===c&&o[0]+16-s.left>0&&s.where[1](),a.find("."+l[5]).css({"background-color":t.tips[1],"padding-right":t.closeBtn?"30px":""}),a.css({left:s.tipLeft-(t.fixed?n.scrollLeft():0),top:s.tipTop-(t.fixed?n.scrollTop():0)})},s.pt.move=function(){var e=this,t=e.config,a=i(document),s=e.layero,l=s.find(t.move),f=s.find(".layui-layer-resize"),c={};return t.move&&l.css("cursor","move"),l.on("mousedown",function(e){e.preventDefault(),t.move&&(c.moveStart=!0,c.offset=[e.clientX-parseFloat(s.css("left")),e.clientY-parseFloat(s.css("top"))],o.moveElem.css("cursor","move").show())}),f.on("mousedown",function(e){e.preventDefault(),c.resizeStart=!0,c.offset=[e.clientX,e.clientY],c.area=[s.outerWidth(),s.outerHeight()],o.moveElem.css("cursor","se-resize").show()}),a.on("mousemove",function(i){if(c.moveStart){var a=i.clientX-c.offset[0],o=i.clientY-c.offset[1],l="fixed"===s.css("position");if(i.preventDefault(),c.stX=l?0:n.scrollLeft(),c.stY=l?0:n.scrollTop(),!t.moveOut){var f=n.width()-s.outerWidth()+c.stX,u=n.height()-s.outerHeight()+c.stY;af&&(a=f),ou&&(o=u)}s.css({left:a,top:o})}if(t.resize&&c.resizeStart){var a=i.clientX-c.offset[0],o=i.clientY-c.offset[1];i.preventDefault(),r.style(e.index,{width:c.area[0]+a,height:c.area[1]+o}),c.isResize=!0,t.resizing&&t.resizing(s)}}).on("mouseup",function(e){c.moveStart&&(delete c.moveStart,o.moveElem.hide(),t.moveEnd&&t.moveEnd(s)),c.resizeStart&&(delete c.resizeStart,o.moveElem.hide())}),e},s.pt.callback=function(){function e(){var e=a.cancel&&a.cancel(t.index,n);e===!1||r.close(t.index)}var t=this,n=t.layero,a=t.config;t.openLayer(),a.success&&(2==a.type?n.find("iframe").on("load",function(){a.success(n,t.index)}):a.success(n,t.index)),6==r.ie&&t.IE6(n),n.find("."+l[6]).children("a").on("click",function(){var e=i(this).index();if(0===e)a.yes?a.yes(t.index,n):a.btn1?a.btn1(t.index,n):r.close(t.index);else{var o=a["btn"+(e+1)]&&a["btn"+(e+1)](t.index,n);o===!1||r.close(t.index)}}),n.find("."+l[7]).on("click",e),a.shadeClose&&i("#layui-layer-shade"+t.index).on("click",function(){r.close(t.index)}),n.find(".layui-layer-min").on("click",function(){var e=a.min&&a.min(n);e===!1||r.min(t.index,a)}),n.find(".layui-layer-max").on("click",function(){i(this).hasClass("layui-layer-maxmin")?(r.restore(t.index),a.restore&&a.restore(n)):(r.full(t.index,a),setTimeout(function(){a.full&&a.full(n)},100))}),a.end&&(o.end[t.index]=a.end)},o.reselect=function(){i.each(i("select"),function(e,t){var n=i(this);n.parents("."+l[0])[0]||1==n.attr("layer")&&i("."+l[0]).length<1&&n.removeAttr("layer").show(),n=null})},s.pt.IE6=function(e){i("select").each(function(e,t){var n=i(this);n.parents("."+l[0])[0]||"none"===n.css("display")||n.attr({layer:"1"}).hide(),n=null})},s.pt.openLayer=function(){var e=this;r.zIndex=e.config.zIndex,r.setTop=function(e){var t=function(){r.zIndex++,e.css("z-index",r.zIndex+1)};return r.zIndex=parseInt(e[0].style.zIndex),e.on("mousedown",t),r.zIndex}},o.record=function(e){var t=[e.width(),e.height(),e.position().top,e.position().left+parseFloat(e.css("margin-left"))];e.find(".layui-layer-max").addClass("layui-layer-maxmin"),e.attr({area:t})},o.rescollbar=function(e){l.html.attr("layer-full")==e&&(l.html[0].style.removeProperty?l.html[0].style.removeProperty("overflow"):l.html[0].style.removeAttribute("overflow"),l.html.removeAttr("layer-full"))},e.layer=r,r.getChildFrame=function(e,t){return t=t||i("."+l[4]).attr("times"),i("#"+l[0]+t).find("iframe").contents().find(e)},r.getFrameIndex=function(e){return i("#"+e).parents("."+l[4]).attr("times")},r.iframeAuto=function(e){if(e){var t=r.getChildFrame("html",e).outerHeight(),n=i("#"+l[0]+e),a=n.find(l[1]).outerHeight()||0,o=n.find("."+l[6]).outerHeight()||0;n.css({height:t+a+o}),n.find("iframe").css({height:t})}},r.iframeSrc=function(e,t){i("#"+l[0]+e).find("iframe").attr("src",t)},r.style=function(e,t,n){var a=i("#"+l[0]+e),r=a.find(".layui-layer-content"),s=a.attr("type"),f=a.find(l[1]).outerHeight()||0,c=a.find("."+l[6]).outerHeight()||0;a.attr("minLeft");s!==o.type[3]&&s!==o.type[4]&&(n||(parseFloat(t.width)<=260&&(t.width=260),parseFloat(t.height)-f-c<=64&&(t.height=64+f+c)),a.css(t),c=a.find("."+l[6]).outerHeight(),s===o.type[2]?a.find("iframe").css({height:parseFloat(t.height)-f-c}):r.css({height:parseFloat(t.height)-f-c-parseFloat(r.css("padding-top"))-parseFloat(r.css("padding-bottom"))}))},r.min=function(e,t){var a=i("#"+l[0]+e),s=a.find(l[1]).outerHeight()||0,f=a.attr("minLeft")||181*o.minIndex+"px",c=a.css("position");o.record(a),o.minLeft[0]&&(f=o.minLeft[0],o.minLeft.shift()),a.attr("position",c),r.style(e,{width:180,height:s,left:f,top:n.height()-s,position:"fixed",overflow:"hidden"},!0),a.find(".layui-layer-min").hide(),"page"===a.attr("type")&&a.find(l[4]).hide(),o.rescollbar(e),a.attr("minLeft")||o.minIndex++,a.attr("minLeft",f)},r.restore=function(e){var t=i("#"+l[0]+e),n=t.attr("area").split(",");t.attr("type");r.style(e,{width:parseFloat(n[0]),height:parseFloat(n[1]),top:parseFloat(n[2]),left:parseFloat(n[3]),position:t.attr("position"),overflow:"visible"},!0),t.find(".layui-layer-max").removeClass("layui-layer-maxmin"),t.find(".layui-layer-min").show(),"page"===t.attr("type")&&t.find(l[4]).show(),o.rescollbar(e)},r.full=function(e){var t,a=i("#"+l[0]+e);o.record(a),l.html.attr("layer-full")||l.html.css("overflow","hidden").attr("layer-full",e),clearTimeout(t),t=setTimeout(function(){var t="fixed"===a.css("position");r.style(e,{top:t?0:n.scrollTop(),left:t?0:n.scrollLeft(),width:n.width(),height:n.height()},!0),a.find(".layui-layer-min").hide()},100)},r.title=function(e,t){var n=i("#"+l[0]+(t||r.index)).find(l[1]);n.html(e)},r.close=function(e){var t=i("#"+l[0]+e),n=t.attr("type"),a="layer-anim-close";if(t[0]){var s="layui-layer-wrap",f=function(){if(n===o.type[1]&&"object"===t.attr("conType")){t.children(":not(."+l[5]+")").remove();for(var a=t.find("."+s),r=0;r<2;r++)a.unwrap();a.css("display",a.data("display")).removeClass(s)}else{if(n===o.type[2])try{var f=i("#"+l[4]+e)[0];f.contentWindow.document.write(""),f.contentWindow.close(),t.find("."+l[5])[0].removeChild(f)}catch(c){}t[0].innerHTML="",t.remove()}"function"==typeof o.end[e]&&o.end[e](),delete o.end[e]};t.data("isOutAnim")&&t.addClass("layer-anim "+a),i("#layui-layer-moves, #layui-layer-shade"+e).remove(),6==r.ie&&o.reselect(),o.rescollbar(e),t.attr("minLeft")&&(o.minIndex--,o.minLeft.push(t.attr("minLeft"))),r.ie&&r.ie<10||!t.data("isOutAnim")?f():setTimeout(function(){f()},200)}},r.closeAll=function(e){i.each(i("."+l[0]),function(){var t=i(this),n=e?t.attr("type")===e:1;n&&r.close(t.attr("times")),n=null})};var f=r.cache||{},c=function(e){return f.skin?" "+f.skin+" "+f.skin+"-"+e:""};r.prompt=function(e,t){var a="";if(e=e||{},"function"==typeof e&&(t=e),e.area){var o=e.area;a='style="width: '+o[0]+"; height: "+o[1]+';"',delete e.area}var s,l=2==e.formType?'":function(){return''}(),f=e.success;return delete e.success,r.open(i.extend({type:1,btn:["确定","取消"],content:l,skin:"layui-layer-prompt"+c("prompt"),maxWidth:n.width(),success:function(e){s=e.find(".layui-layer-input"),s.focus(),"function"==typeof f&&f(e)},resize:!1,yes:function(i){var n=s.val();""===n?s.focus():n.length>(e.maxlength||500)?r.tips("最多输入"+(e.maxlength||500)+"个字数",s,{tips:1}):t&&t(n,i,s)}},e))},r.tab=function(e){e=e||{};var t=e.tab||{},n="layui-this",a=e.success;return delete e.success,r.open(i.extend({type:1,skin:"layui-layer-tab"+c("tab"),resize:!1,title:function(){var e=t.length,i=1,a="";if(e>0)for(a=''+t[0].title+"";i"+t[i].title+"";return a}(),content:'
    '+function(){var e=t.length,i=1,a="";if(e>0)for(a='
  • '+(t[0].content||"no content")+"
  • ";i'+(t[i].content||"no content")+"";return a}()+"
",success:function(t){var o=t.find(".layui-layer-title").children(),r=t.find(".layui-layer-tabmain").children();o.on("mousedown",function(t){t.stopPropagation?t.stopPropagation():t.cancelBubble=!0;var a=i(this),o=a.index();a.addClass(n).siblings().removeClass(n),r.eq(o).show().siblings().hide(),"function"==typeof e.change&&e.change(o)}),"function"==typeof a&&a(t)}},e))},r.photos=function(t,n,a){function o(e,t,i){var n=new Image;return n.src=e,n.complete?t(n):(n.onload=function(){n.onload=null,t(n)},void(n.onerror=function(e){n.onerror=null,i(e)}))}var s={};if(t=t||{},t.photos){var l=t.photos.constructor===Object,f=l?t.photos:{},u=f.data||[],d=f.start||0;s.imgIndex=(0|d)+1,t.img=t.img||"img";var y=t.success;if(delete t.success,l){if(0===u.length)return r.msg("没有图片")}else{var p=i(t.photos),h=function(){u=[],p.find(t.img).each(function(e){var t=i(this);t.attr("layer-index",e),u.push({alt:t.attr("alt"),pid:t.attr("layer-pid"),src:t.attr("layer-src")||t.attr("src"),thumb:t.attr("src")})})};if(h(),0===u.length)return;if(n||p.on("click",t.img,function(){var e=i(this),n=e.attr("layer-index");r.photos(i.extend(t,{photos:{start:n,data:u,tab:t.tab},full:t.full}),!0),h()}),!n)return}s.imgprev=function(e){s.imgIndex--,s.imgIndex<1&&(s.imgIndex=u.length),s.tabimg(e)},s.imgnext=function(e,t){s.imgIndex++,s.imgIndex>u.length&&(s.imgIndex=1,t)||s.tabimg(e)},s.keyup=function(e){if(!s.end){var t=e.keyCode;e.preventDefault(),37===t?s.imgprev(!0):39===t?s.imgnext(!0):27===t&&r.close(s.index)}},s.tabimg=function(e){if(!(u.length<=1))return f.start=s.imgIndex-1,r.close(s.index),r.photos(t,!0,e)},s.event=function(){s.bigimg.hover(function(){s.imgsee.show()},function(){s.imgsee.hide()}),s.bigimg.find(".layui-layer-imgprev").on("click",function(e){e.preventDefault(),s.imgprev()}),s.bigimg.find(".layui-layer-imgnext").on("click",function(e){e.preventDefault(),s.imgnext()}),i(document).on("keyup",s.keyup)},s.loadi=r.load(1,{shade:!("shade"in t)&&.9,scrollbar:!1}),o(u[d].src,function(n){r.close(s.loadi),s.index=r.open(i.extend({type:1,id:"layui-layer-photos",area:function(){var a=[n.width,n.height],o=[i(e).width()-100,i(e).height()-100];if(!t.full&&(a[0]>o[0]||a[1]>o[1])){var r=[a[0]/o[0],a[1]/o[1]];r[0]>r[1]?(a[0]=a[0]/r[0],a[1]=a[1]/r[0]):r[0]'+(u[d].alt||
'+(u.length>1?'':"")+'
'+(u[d].alt||"")+""+s.imgIndex+"/"+u.length+"
",success:function(e,i){s.bigimg=e.find(".layui-layer-phimg"),s.imgsee=e.find(".layui-layer-imguide,.layui-layer-imgbar"),s.event(e),t.tab&&t.tab(u[d],e),"function"==typeof y&&y(e)},end:function(){s.end=!0,i(document).off("keyup",s.keyup)}},t))},function(){r.close(s.loadi),r.msg("当前图片地址异常
是否继续查看下一张?",{time:3e4,btn:["下一张","不看了"],yes:function(){u.length>1&&s.imgnext(!0,!0)}})})}},o.run=function(t){i=t,n=i(e),l.html=i("html"),r.open=function(e){var t=new s(e);return t.index}},e.layui&&layui.define?(r.ready(),layui.define("jquery",function(t){r.path=layui.cache.dir,o.run(layui.$),e.layer=r,t("layer",r)})):"function"==typeof define&&define.amd?define(["jquery"],function(){return o.run(e.jQuery),r}):function(){o.run(e.jQuery),r.ready()}()}(window); \ No newline at end of file diff --git a/staticweb/layer/mobile/layer.js b/staticweb/layer/mobile/layer.js new file mode 100755 index 0000000..f9cf693 --- /dev/null +++ b/staticweb/layer/mobile/layer.js @@ -0,0 +1,2 @@ +/*! layer mobile-v2.0.0 Web弹层组件 MIT License http://layer.layui.com/mobile By 贤心 */ + ;!function(e){"use strict";var t=document,n="querySelectorAll",i="getElementsByClassName",a=function(e){return t[n](e)},s={type:0,shade:!0,shadeClose:!0,fixed:!0,anim:"scale"},l={extend:function(e){var t=JSON.parse(JSON.stringify(s));for(var n in e)t[n]=e[n];return t},timer:{},end:{}};l.touch=function(e,t){e.addEventListener("click",function(e){t.call(this,e)},!1)};var r=0,o=["layui-m-layer"],c=function(e){var t=this;t.config=l.extend(e),t.view()};c.prototype.view=function(){var e=this,n=e.config,s=t.createElement("div");e.id=s.id=o[0]+r,s.setAttribute("class",o[0]+" "+o[0]+(n.type||0)),s.setAttribute("index",r);var l=function(){var e="object"==typeof n.title;return n.title?'

'+(e?n.title[0]:n.title)+"

":""}(),c=function(){"string"==typeof n.btn&&(n.btn=[n.btn]);var e,t=(n.btn||[]).length;return 0!==t&&n.btn?(e=''+n.btn[0]+"",2===t&&(e=''+n.btn[1]+""+e),'
'+e+"
"):""}();if(n.fixed||(n.top=n.hasOwnProperty("top")?n.top:100,n.style=n.style||"",n.style+=" top:"+(t.body.scrollTop+n.top)+"px"),2===n.type&&(n.content='

'+(n.content||"")+"

"),n.skin&&(n.anim="up"),"msg"===n.skin&&(n.shade=!1),s.innerHTML=(n.shade?"
':"")+'
"+l+'
'+n.content+"
"+c+"
",!n.type||2===n.type){var d=t[i](o[0]+n.type),y=d.length;y>=1&&layer.close(d[0].getAttribute("index"))}document.body.appendChild(s);var u=e.elem=a("#"+e.id)[0];n.success&&n.success(u),e.index=r++,e.action(n,u)},c.prototype.action=function(e,t){var n=this;e.time&&(l.timer[n.index]=setTimeout(function(){layer.close(n.index)},1e3*e.time));var a=function(){var t=this.getAttribute("type");0==t?(e.no&&e.no(),layer.close(n.index)):e.yes?e.yes(n.index):layer.close(n.index)};if(e.btn)for(var s=t[i]("layui-m-layerbtn")[0].children,r=s.length,o=0;odiv{line-height:22px;padding-top:7px;margin-bottom:20px;font-size:14px}.layui-m-layerbtn{display:box;display:-moz-box;display:-webkit-box;width:100%;height:50px;line-height:50px;font-size:0;border-top:1px solid #D0D0D0;background-color:#F2F2F2}.layui-m-layerbtn span{display:block;-moz-box-flex:1;box-flex:1;-webkit-box-flex:1;font-size:14px;cursor:pointer}.layui-m-layerbtn span[yes]{color:#40AFFE}.layui-m-layerbtn span[no]{border-right:1px solid #D0D0D0;border-radius:0 0 0 5px}.layui-m-layerbtn span:active{background-color:#F6F6F6}.layui-m-layerend{position:absolute;right:7px;top:10px;width:30px;height:30px;border:0;font-weight:400;background:0 0;cursor:pointer;-webkit-appearance:none;font-size:30px}.layui-m-layerend::after,.layui-m-layerend::before{position:absolute;left:5px;top:15px;content:'';width:18px;height:1px;background-color:#999;transform:rotate(45deg);-webkit-transform:rotate(45deg);border-radius:3px}.layui-m-layerend::after{transform:rotate(-45deg);-webkit-transform:rotate(-45deg)}body .layui-m-layer .layui-m-layer-footer{position:fixed;width:95%;max-width:100%;margin:0 auto;left:0;right:0;bottom:10px;background:0 0}.layui-m-layer-footer .layui-m-layercont{padding:20px;border-radius:5px 5px 0 0;background-color:rgba(255,255,255,.8)}.layui-m-layer-footer .layui-m-layerbtn{display:block;height:auto;background:0 0;border-top:none}.layui-m-layer-footer .layui-m-layerbtn span{background-color:rgba(255,255,255,.8)}.layui-m-layer-footer .layui-m-layerbtn span[no]{color:#FD482C;border-top:1px solid #c2c2c2;border-radius:0 0 5px 5px}.layui-m-layer-footer .layui-m-layerbtn span[yes]{margin-top:10px;border-radius:5px}body .layui-m-layer .layui-m-layer-msg{width:auto;max-width:90%;margin:0 auto;bottom:-150px;background-color:rgba(0,0,0,.7);color:#fff}.layui-m-layer-msg .layui-m-layercont{padding:10px 20px} \ No newline at end of file diff --git a/staticweb/layer/theme/default/icon-ext.png b/staticweb/layer/theme/default/icon-ext.png new file mode 100755 index 0000000000000000000000000000000000000000..bbbb669bb311514baa5db3a6a00b4644d0e280f1 GIT binary patch literal 5911 zcmY+I2Q(bf_s2JgAUe^aMOKL(VwGqSy<0@0i{8cRqDzD%ST(B#i!4FHDp8XlI?-*k z=$*)lUVhK-{LcTJ|C}>3XXea%^WJ^;-tXtWSbbeJ3NjWl2n0f*p{@)EcPu#VNQl8z z1kb_-ZbS$r4I>h8JSVYx1)fR0)Sn&qHr}8y{y+4^AUz zcYBDagvi~yB6shN>mfA37p#|G7`9y&Ggi_)mcoDUevwZ%`QQ+u`Spkp9gx zTYuuo_8p5IL4SGDE=2#lxUGErKvu^NZ*;4Tj}QBeHs#sycwNE47h{3wpZ|9emH((u z9sRflNhSr++WU1KOOW>%Hbg-aK-&p%Q&ht?^+2LRNG+S62f~|#IHbK7^Ddkcx)J1Q z0S7-})`HegD(zyqd3ie^Xb3L+7UdQyoXc9w+U)bw_5iL6R1v||XHI%*wrz$^Hxo(q z4GqONss`jwc1leu&Ie}C_iF{Y#ELuWnzl6x0$Yn+EWq{3{85roZ0UUaYXG0b)L=y?`*9JA#80I z3P(##E(C&bEKxAud)k68*!7p?g7>p#8~i=*Q(G^3Q}7`S4GptXIHeC{8;MWMNzpPwJM({dpXnId*kn{Y5EiD@N@df+QF z=ydO?XqznoUo&{Dudh#pk{Zx!=;*Y&!4i%`+VW%iA)5@ZRhS}sZ!`B~ge$$|!57kC z871jaeGcN{4!xWL0L6rzKKTQ{CGhEnft!6{hpBOL@H)dt#qvkFpkh)jIe7!-rRUdp>qgmJfFq zu+`PvIwEDAvWR8v{he98pdc9`A)$|^)nqNRdM+;OA7%#BqsQ#odE$E4*4F56+(4$K zsq)ctF_F`f6JI+gX1PU8^4qTgCGJRhvcGj(PEM?EXEz`bdS^_aKk8|n(uNonokkJ~ zag?3Cy}{$huW)WWtdtA*BPsuF*6i$TQs!XF8--%I1#}uhDYUHLC5;re$(42JWcdZfurd&*Jj(-wE3U z8p;?N6=YEnPf2Mh(w;fF3mu3Gk>_Afh;hsbd^z3VUpfT4cTeBcw1gC8&%6JByc1M_PomP9JdP7ad#I|Ex0?^gtOKU zS}xQ|ue9x;{3qE}?K*yG^rj{Yaj}ONmn%l7{4PRP*70t&`|8*tWxo=;xaG7+xv%q#ha*J2qI9~PFF+Y+mbgD ziF_c%s!C1d;_7;|oarfw($1iLFOrgTw4!h!ZC2}HY+qhlT7bpU=MJQQ!hAVj-Qaa4 ztn-@to@J1PBefH;Y?PA2+51Vcg88_?ZdMB3?h#8Dw#WxwQZV?AUM#rDa>_%p<#@Cr zV5@q3qN+M?E-Q5(z`GHQiIYXd@6&1Q{x96RE4Gcd^@@Dp0H{!lq1#bD?~a_Dm*Q zij@+o@!eV!xX}0P`~K7_22})mJWS+b4!ulcRWin!Wt8cVpc;Hqr*d2DTvsfl4fCH8L@O* z?nN!Gtd!cil@-W#fZt&-m@Ayz+%L8!Ypb3gd4tultdRWXkCO}`6}r;*rhLQ~`gtUh z^TTT>n8{S#Gs38Eic+i&zp&2q3=9N&QrY<`$_8z7Ucd220cZclG3DjNTmvSSmb%ZL z-Sw!=EH5u7nq6yM^W@bgu~@%V;3it{vqlSY`a^mZyC)7qXbs>g$_68iBg9c4k?3+# z|2}BBkXz}`Hr#-D&h+936cRcX2GJvg?ps5J?8M#X_*4Oty5~n?k(``8VmKU5(7cYi zbToq=exH@{G*rQ?#%-=Gmd<6mNGCI3x1CYq&OhsY{&hGNVRBb=m)-nEMa%N{7uQP~ zQ7BYzu0rm}h!H^qq>{Dt5A?Gdb0|sV*Lb%3LFyK8`1cay(mw&R0kS!v%{{AP6MePy zBdv;0=9_&t7)D1&qm^!bpA*$BPJVHnao$H}ltSB71!x2*{M8g?;F&95F1&b`Cm7%Owcs1q(qa=-&BynT$mBqLgRMzppzZQ zGpuq!MrCHzE;oR~WvpUi5Ho7&K}>wXxs#KV(!T5TKo&?M!v~$vK&S2)7Jc9~!^Vl7 zQmY`@?)!NycG6UPEOn>4O?eCu9p8-9HGN1`1B_(zKJM591)}l1I*9%D>vpSF`}YH6*luWP;=xh;*vXvvYM3cw6r2N6?VyfqweC zfh_5V4<8az<7zNVGhgm&>XoUV4XSZqd|M9NMLIh>)jO-&=6f53|B33O8Hgg**Ijh8 zW!k%vdDm7~)#K!b|0u4fq|ncV99U4Y%Xa$DhIDjrglU_ZnJMWmwegd*d7;^zi7xUq zv+sZ3pO37BAa-Wtp37Uoi89vWIY~f15M;O>L&^4Zy55&n$_rA3%NkK?~ zLzzoi1qd~pLeGvJ^V2ivO?my=3hu9(tjEVw+AqtcWk#K();BkwpRA_GT6GV_3hV}* z=%f4p8|`IfWfA}qbC&T(k%fhYR%!}#uUQ4AF@%4Dnhd=`@Bw_d##&9OY5} zR9^HdO;zWY_f6W76RDI=7RVIyX#8^5m?u|dpj78Jds8)n1 z2Yq>*5YkWp&Gx5WYfnYv3z`{DKb)3?8s*r2+LP$9A^t%)24vIF(lRIZ)dWtKT6T<{ zT0?B-6;F08jfRqyGBmCwzCV1Adygr+KrKO6I_&&(9=|dmat>q&BlyaWCKxjuL3(s_ zw10B2bFtP+rEuyR9DEYtah>aE6}~|p*&MA4GWho-ZY>8AgV4XpxxI&{_<>@z4O<~! z;;+piCu#A_;tpitt#j`JE_v7&&LVq>^sr#*uU^?>CKPT1Su>Q9`dg0>cwn_8G04XC= z&i-1sT32C@kxV;iDb-}V`QrSfx~b3-=;a=h)->roY)#Eeb72#EK)@CU-Isqkm8Tg@?m5|+yDr&~&N`L+;d>8ic!Cez8F!MA3&2Do0)UCg>? zsdO6Tl910D8zAxP*g076k+}?dkZM3wglA=Cp^-tK^1c$M)R&a-^9D(~z+3i)wCEx( zly1YX0R;|K$kQh&9_~6l!fWX1je|jKgJcBNaM?`k?Y$)AfsaqBRyQ}be;xj8V%A^3 zdY$1k09z>U^;@y<5gG~;%Dy6lV#=zvhOv&M?DRSlb$4w{O4YL163^TSdF?3{td2j`{98*`gzmLzKc1Ek8 zgM)d*Nq6}8tbr$hR2Xi0zRqwY^amgL%V6=Mv4Y+bRCkc=tLp{0nUX*w;*Ge3hFUWepyi@hQ*CCmG zKg>Lv+8YD$K%6p?gP?g|vBJJrNRv!szktd`I^-CeL3-V~KTBHnXfYY6RNsKH09;a1 z693D!;@Qc*J4AwfVpvb%?c~;v6+HK$E{EulzBQp{2pFhA>hbSyQNdWQYMh&DnmsUb z84oR4OzYy}Vq$uFF%Ruf{fJ*fHXvn~$5f}}>~lip843U~kFie3qM-H1(F7YN>%cz^ zQh&Kr7rCmq1SBE~i;7+z9|uXuwPT%!-${D1=kvKV7lTyn)F(u z|Bhvv;FEk*j?AOHuRfTQ2VGo~a!7rE8}n_kV2!A%a37DZGO4TsSMTobK3p%Y2=Bb# zT5i#BxTY5t*Rh~cH}aYMD$EF@#^U7g0Y1QH6MS1K_KnZKb>sE*b!wsrFDdOuj~GBI zF`*;njv6`GnO*U3Ibj182QgP`=_LcX;VPrG*fuULGA%^^?l!Uee&TV%PIJT0CO9%^ zcfNg1IX*$!_UG~^gQW3UK!Dd7g*i27D+QC0$Zz>7uP;$B-4s>4AJmkRnrdLe_=E+> zs{3ROx2&|ItWw0k#QKA4%YB)}ZN0CI`9zJ^kMJuy&K@4;{s)=>V=Ny%s^JSlF&DsM z-X^Jk$jiG_u|`XgNY>WVzQ~&Yfo0Xhk%7l*O zL`+veGywua{JNb>@JS`K!M|{P!`L#$wwf}F);$@pldcY+-Df*g_h2x7n&f-P;c;tG z&Nwa|9UUwd3p5>+&c(yA!)qfxRAuiM@A@=MpYGSTEd6+UQ&D-{cVi60+^m}U_! zdvLnEuPNsIh~-`zK>X@S(SuHl`&*OuBqX?Xh~P^qez;0|?RTONgf9N}hyZ$kINu40YZOS$tn2wQJX^7$k4DA;4ji%`qluAKwb<#ej4=0in_3s zRmcF_LB4M0j~{oUHIj``o>O%XEG)7!!c;c+)+R&GHms^ZTvs>N*Jl96qa`64aeGpr zBN*LJCWF01G{;y322+FzG_WL~^x6j>KjAX0HC9n~~2pkZca2HkLym^VL1 zUBc0tT_}LtJ9q9F^yp9%)wX|B7yzhcq1yJgo*E`Uk z_r{ozHjg13O8PfI*2mZPv&$$ypw!~DT&ZV~0Q{Vk9GIH_+q`qrN9NfVb97-LW?>aX z%kad+2jN&(HkIW|paoF+VW}g5!x2zABqNdeB`;PO58=aEcf_-4fy$mi%Z{RJ=K!eM zLoF?>q0UXe2C$6tsV0^-qb0^JM}TZ6s$J9TSJ-Najxu514T!?RG!kbk4>Vqt(|H)mToz#peQ#y6|Tp}<1aBrlW#nk?aP zxRaC9Zy4f*msc+bDkP*c zt&&cDoo5<=IM`F#-RzqQgC<_9Kl9Lu%*PBeZwFJExsI+T!yQ(co4 z*NNxQl&YkNJ{{IxohMt4Xj2wBt&54T| zEcW>k&M}v52(;l3DO6>670t4m?eP8DsiK?xBPK#weB$4C-5+@?#$mgfmK;1u@!!8i z4dX)J+d|(`DBko+QYSX!UOQz|4K>nQxuBui%JcO}N?pvg9U5GFDU9vE{o?;$+ApsB YZmOLxGt_1UThtH@6k?11;06>$MlhS}5=b&FE!8cRn$r(cw*CLxiM=BH4${Ax7y;K}kc|Yw?S8cxq>*aTjQ8v6{l9 zH@7H!N#68nTt6@Ke%^biXL^`i@jn0X<)XWz$A8Lq$~~VEnG#-}VqFJzNf^EZy%>C= zMyiaYN(V?`C+9Cg<@d(R?s~NOh)Eo9=rHo+pjFHxhYXrg^73Z%^+_lwD9|%9Qd3i#YxykZI|Z~vLdZp9dfJo4{E6+H zF#v8l=-CkIBL%vW9G!&UW-M+~AocB*r{|SjyFIaBPFZ1V{{8cEP2_y%-%EWo{d{Bb zIG`27vEtj&PbVCyOJ8WQQ|z3@Z2eHm9*q|AOTLhn=4vLi-pVvvwozD5%Rv^X)R&#D zHDz&f1ap3R-j!NtejVLjdeOLGqBl?Hf9~@6u{4i*wh`TChcR|sp61YuGtR~Ylmhpa z*|28&7zZ;!n`0mKzF~Q?i9k9Kc9B?vYgx?nazH;7eI3-XHR5u7=;W`I6|woD+IX zlV2>vWkhg~SJMcY_iWH^>5a36RP2nrsz~zA&Kl=t$Q{@ZEccpPZ9d=QPs=6aV!}?h zdP4%PbYGO|X7PR$GS-XnS|Wg>Ep4t*lIA(pjL>28Na-tbt_mFf1UKWA)qVgNt$vCd zclrZ*kxA09#G@w-9@uImTl7R)<$~ik|B`+CVWj+HX)_0nBf7+~I4W0BhdnZ=N{v)d zeFLrcG*<+}s8_%F!+k|iUU*?uRg9|WYg%h7&-KmC7e>aC($X*}oSJ_9V$V_nZ8)8I z3F=h;fMPB?JNxJiwKYjvTH4TS)shL=0QjFIQsPM~R<@Qu{JB?PeC!?g z`0?LRSgT!q-rM_T*z-B>jB>sV7+3cz(1$j=YhakTiS*$?5<%ntP)PFUR5FVlu!@Z8iJWo#ozHZdhwx z5MuDrOHzfP7u&K{pX2JyqsE;f%N$)R%Bs`J>U7RsD2W#$c_s#);iUI|_^yKdq>QAVh{H@LGf_q?EJd3oxYoh(YbOawAerPz0_A zMR`+*CXc^7Z}D)uaR^RmQDYbme{v5pn&G1OCe_sZl;$(fEl@YYVCt)aB~sM1H9NVv ziJl81nqhU2TsJ$|tm%Ia;^_`M>}JmV?Sgacy%GAg7kA`fWthRyL9^JfU1QeM@2*z&1n&>irCh-+N(t--^jFyZ2gW1TAo%{WL@L4?4XQW+ zS4li@%6{Q&krye&OglNvx7H)O2yapNt5nTMpQ3ZVM3vu}bmhhh;wd^bWKEt3P6WE& zRhHBimj^e0tAx?G8ab(Zm@~oGEgPGe4!=_d?r)R^`=YrWJjT~rxC=!1q9irzztAOa ziw1qdBw%1on0>{3n0^TpTShrz_4^b!iX+!?Lu@YxcHmm&r5F`hcw^8SHco=it~rhB zn38C4T;sXB+?sB(90xXe@u8mNWfeMl!K3#(zERwQ1FWSI+$2ka3id7 z?mQeBR_;P6hsoE8(z44*qe+(SdPAk~3Q>X+6?r85Z`jCxOcH+30daI z?fo?T-%uINCKCKO&2^=vK)Q95^}LW?!l$S(AyTh`TH{)SwkC&Fj=J`P?1f=&2#_|q zsp)TFPh(>;)ChBMaL}``B+wrQT{0U68z=79`LowQog5h+uDU0|KiHxFb>{n?n-}VI zG7)7q?R~io`E_|c@^I=4y6VW>&BmIga+Q9vfNvQ0&7FSA8C|wyo7RFw{V3nU`*-b~ z4?M?e2D(*Gc?H#3yF*9=u(x{YW>tQZeqnyYkk>^_>y|JEK| zcY~ZJS@)xVW*A>FbAM87LOH(mU%5OwsHra)Tn*pDX!TGywS6)P{MI~iT)oWGDoAmb*dC6oqE)-fc z9aDYYcxCQz3d=W@f#ehc=W@21NqZ|Bbjm~6Y69v;&scB2Y?xw`J$hY@Wn~c!+MF!! z&Q@!HjZ{TZ>rT7|tq$9-_gfW3MKHfsm7JUc?t^S?zr4P_=JLBEMD~l@+S-E3H1)Gx zUVVjaQR)Y-|2?xBN=X(%DH?b=_FW3jE|HlJjeVuWhM?j4VbvSNUY#-=@bnB43gp;rJ{|!m%o|YH&-~aL4;Q73l$6kY#B|#<)G}~Pvd7F3$exko zz`)B${AeE-%vyjhKuq%&5r?szhBFaLB60+#+J7P5UBK%NJ%r~_1sS$1CqRA9QSdG< z?74NywQn`X%saRM;t*UQ=6(40SRvENINIr&3(6lt4MWu&a>V8enSGL^micFX5l(Nr z1t@PxH@+diuZsQ^ZbyJtzy_}E0_BfBzW`RrA1v+6K9jR!Cr8LNQrloNK@)t zg4ffQPx!aIHOv@MyPlJy{?`ku^-CuiPyR?8^WU%IN99ukTRuV~+)-_3h{?%%oKB;a zF-YH;=i5-~EbD%T5#19)i5k2Zo)e?OP3O_)jhI|vPI>M_#8nQgjZy;`wd^fvP;KtN zj@9+miK_4N6Dp!TjiYJ{9cSx0uP|*o&gLd2SLH06`ao?qZbK5|~@(H&%pJXSB=tJ^U1}L2ZCf z^<5#@v3GPSf6~TXmomp{xK{UEbV98E9I1>IB)$|%;*pMYNr-TwTj+OU4pxZZXl}0# zDI^HLWI~S&?dT9Vn8-@?*tG7CKr{4Q)DAc`*xHF8cKUnKd3hi!`h?Ze(z38Td;mW( zI{I}gmmjdOxVY`Yr{>>5xbp1kvT40jNkg0qI3iio?I&nZVaX zhx6*#m3pKf&ILi?u88mXxuIKM9~-x3YC%+EN(+Z>26q7f=i zz8zO#o*NyM8$+2te2xFgs{LSnRSgqg&uB{#-&u2G(}5(>lfUhK$Kze2JO1khL^Jd2 zRPoYk|CBM~?+zk0SOPp_!oWC7O*X?;0)WZCpxkK@Tur6l9d^0X!r@SJP&#kkb>MgF z&Kgah>b2iu6RV)6!n<8vP5E~Pxi68&+p^Cc((=YBjvp8I`xZa*fcI;5@JAyEbqIVF zdGKk^K+E#MM!ZAzH?WD~pT^Yk^3Sl}0Jylg$i9i>qAC=arjOVASZm6kaiHAk>sqTL z^7-Lpj%-kn$ocs>7dJN)6sR!a&4aqbBGcJ$P-E^3+sg7ncjDT8OSkam&Ra7Fjys(` zMa~rtg+A-e^r^ajPRR+o@#}|Xd}S$HrvY96OyN2isH@IsI+Ssb0i1St&5>Wh{zdii zsk-Tp;y{Bt?{Zj+RB+Kbg2q~x|DQl`W$7q>Opzzzy<-#1i)$DP- z)uXXc-um}Fb}e08x1qU#8>uS%#eal=>@&-w&qCiz3qnd+WlXM7EX=Qpa9l284Z=$q zrKW&HHB;Ksii#+fmX`8|(H)(g?8C0l`1ts{UY$5#1E%zboB!z1JEY{udOB8c3Dv`! z^5uJBZtI|*xWpH7w z-KOyDbb74>0gU1tA3IQ?*I>SzrD_|Hy1l(*(g}i*AeF5Gc7{B776bXWLVu4AGCNsq z1G($SF6y4?NfwjpW+6(CW^Ya}X;E&J`9v9LWo5`4X%9t;ZeHl={$v#A*R+2MGxLKH z%4%`9W{Gl%^JQ!uW#*3AH++Z>1mDr+$=6^ochvQ>)i$_o=J08R$ct_%0yY5Z-*FT$ zBpT_OF(?O0I_w+tYtz!wN-eyLkRZTXotMdYb&QOnnd$skr@4hI@BV$onZ3MIKRdeg zlvbC~_E&t92(L^;t}x6*XmnnZ|7(IbV~DBZhsE1(Q~nR3jqcJ<4rDAZ5>i>mwjW*s zx0@P#(ygb=q^-^{YcQvwcxkRGL00ziUB@9)4)f8H#i3(HXNZW8hi{m7+OGqj$ITP_ zl(-SjD>c%E+8YY52kudyKDc~DN>AF^`J*TbEb0-V_j4To9Z8M1XP$KlGVGa`?^gG zQ$CCX^T#)ZcX!33sva+KBC}ak&I~hu?b!}jKz--4+fQHK+1Msx7ANmKGg)CYg?x|8{Y;{u53cE zLa^9&L;p|}_`Hkb*=}sImu~oLMZ7lm8o$WOzww0=JyCmP#+%)((th8)+l6$P5&m5^ z!w$^pi*rmQ`03tU74W`dQru{U1L|RNGj-0auJG^`cOdJFQO>>Pt)(iZEzY}fzpqmH zz%Y1obE-9&wt%0uUDpG&^O`4Llvd_<8@lf{IrkLIbr3B80+ z3VT67cQV-^aigg6(v>MhDTsIXqf+)?iU#o4-3w|#zI|7Xt*!ABFzHt;OB>G`MpY&% ziiy;EUMg$Lq7D+|@yNf`)#brA)nBn-DusCr>tC}%xP1wNqGYGWB&-Kt+%&LYzLLpjBo3O%pU@}KMEl+xgug?5#eeMZZ*M5pTIz@L2p=Xq6sTNQ zHJ{b+VnaDVZM~mV-(sqZU2q9KORaAy{J}YfIfYl+Jgb+Rj?_mO_g_V(*;L`^u0<|O zhyQIn@;nmKk6@dXSXnfek*~Y0*%&U2AL$UJEoP5=tPXZS8|_6l*YK>jpWG3$`>3Gu z&Pe*eH_&hDNLLZTqn#yUHkMA9#ns_Ib>}{!8*o9Q>Ha<8I$0LHyYn?!6%}+km0Y=3AWWz5 zL*c~aq%`O6D6wI^y|@L~e99GWO(PYxPcz3!oE)idDu8bZe@-EU zZlq(U5&l3W!DhD$CfK`@5#Pp~Q=r^?#CcZ~+}+BA;rhB)h;>TS(gqq4ZXI735S-`(JQw*2UNO>Ib&~cA z&9_@wsS$+!-g7oM<8Mk9Q0Bj4aQudxgUmiMqc5bVQRW0xUVtkJKw+3;?bF{D3NESy zL40aF+8RJ$)S_K{%s0ib)4I+CG-4jMz^B_ZM~b7`(877~NW`*7EiF{Tg+_sAf|Xx5 zCjVg4H0jj*{V^pdMmerQU4K(z!xd+ydr;+x{b%aA3Sh-_1+v_B;i0P2HUX&UKgM6Q zOK*RZF4Nw-Tg3Wz+naO^Xp`UPnU$>4E}-h7U%Ji*qnFA{-g0BA@WU7iY^Yw$G%`^# zHVcIixcKt~xBB^iGp z3@t5fb8~ZClsCB97AlOotvR;EkFX4AYG{0S()V2v$3dwYkMYja~K%b>bjg5E{lkmt*IRvYMM zkDPx`B|zB_hPD1KU?|4CXa9Z)<00tuvx7pgR2Js+;DJUnl)uo&=U~+>rO{a$P3NB? zWa|XQGuB`}#3CsBPT+HGN!>%7i25SUqvez#$UimFG}+EiH;B`Z8sT@{-8U5LHx z30FMSuqs?xDRPaaj()s1WCuHD`eQI$Rddg;EG-Sxy0W3D!sD7jJ8nP5pM?tw5aBLtT>Ezo~F{N9z31aC$`tOwx&-_siZR-;He}OGw1aExggDS?qn>kae!^ocJ;%-cARcbAHYopeQTFY z^t;gqb_t*}c{tr*pgzCZCN+y7v7ib&D|LooBp>Y@9!uGKtu8dspoZ1`hl_8n_w1-; zz*~OW^GQ>Razt;nG}sc&&5106|7LcQ4?n7^nTPXgRQ71BJgL>i)~A!_UggEQK+Ka| zo&ZF2AeR%9rUUye$U@WOY{jMWf||ZHe&qOO=3tX>(^yY$RF*tYN>)~O;?LqX#FJVY zDxWMaE`GwrRoC;q@K!KzLwl`%{jh)gGwP4_YYqS2%dPH+9>0wu9bK&n=WnF(z^z`; z*;ABX+I$7UN{etNmFXz|?0SlD!IoZ|`lW5+N5k#~!!di!3+u${lm5a)X$>ms8 zdK=m-CZo^4;&4Db=AXwo$FfrJNCP!5Z4Z^7#Tk?b*EtBfVhFWwNlXCy#~Az~{T@$sAr3&$MY> zZS6+i8!N~Nbz&5>TKfDl_+EMTzM$vHu+Due_)VdC3nX{j^K7+naoFJ%9cjSxk$cor zC!FC-p~r^k2+z99i@oqEH9Z()Su!GW=`ua zfic<-4J8bxat(%{#u1VF`w7bYxVKhb6q?tch9)4|d-w!er=Z!MnN!4!@Ihdzw5szln>D%zv?t-xGXgvX`#n(Ul#65nmJxQ%| zv%d$g4nJn_PhBu(RVTHReCHNpZj5spF#O?OGgA5~k~Qi%;R5^I*!!i4r9w>@a-SFn z#I55s#v}FKVtQJ7Re?VfhQvOdOK9ho^fPL03*KB7zt4NH&-fIm1)jSqc5FCp|MQcL z)Ycx1BoCqwdf6q9<=ym=u*x?;+Pz*c< z?V!kOU-3GGgs}eW@~YEGD`$RCbwuE1+ksEUG%R|!%|ZyUquB5Asz5aCTACdgO3;K4 z6S#&?27CiRkr7&4n!C&b^?BZx!>9q*Sd}NfG*R$j-+Oj#)dGJWw%p{)G%Cu^QyQ<>l(az?qz3h|Ff=628kWoA70-80dtfW@Nohfe5T094KY_5vh(gnzNrm!6B)AKSUR z^O~EV*BooEnzfN(wrrg9z~&d^g?4Mx4DIJ3sv(YB5Q0;An7nb(m+Ej`m?Iz6A(2G5H1EdN}>6QyuvI$ccCsaH63n1 zWS=y2G6o%;@j)?(iQ%Ff;V{OGOMq5D@q8`8gp!yVOylyT*)L%($%t#YPk0l{Q zAwP#PU13Z)U=HtmQg_qvYS=y;#ucG1o2z4!g}K8mpF&lv+1b%iSzBAy)`p)k9!?{l zIceqQ=2lr#LqkVr?IV2t$4)O@ROO$~j;r^r?_;xcFAs%#x#|Z~FkhBX>fdW@(sHhi zE59ALL}Xjkl7wvu4efvSDKy<|9tO(FXygG`;tzwC5Vo&<@O7OR0#*}Ixe8bk>rCU< zYoiIYc=r{Pa6+A1s@jSk*?X8u0@3#6Cyzt40%Jqc2&}_9WH@sKGNh8>rBx^RH1d9; zw;RVE*Vp5$^XuzE#R3&7d1FT28@S1)(nyV5>f>CwofRhAvyNn5c`>Yo+cUO*PcsPG z-`(K!C)>;3`~Uaqa&lEw=`bxRG+8iUFxUq&i0{g$p(w&h(%#+63aCtsIOa=Sf9HXJKW@F?1IC=PouT1bSJ9FJl;*>pZyYEU&P6|D8 z_q!B)??CFb@Vz0QK{Aaz&NoDC7XyI9E6nef!N5%_*p>OS!^lftq)MvK_nFZ7Y!^dv zZKRPGMGAZ$H+gdtr~24y^{df#zPu?=Nl>o+AkTf`TLkw3&m* z{Kms|qi=DPNGrAz+YCmOQroX>1)bj8UyY?3oC+d?^{f*9F2N=O5_NT$((UcM--VW> zf7SqBRR8p#1;u0MGQ4=!*E7WG@A$1%TMv)A74kIIaNtwD-VnuC3cBuyr3LEg?eo>@ zm)PyEzo7B zlFNrhOBDi5HP-0Z)4K+jS5=`{=+zO88Ew?fI3zlGqPKkUxUXiSpM@+iA7hzir&WJ_ z9Ybx3$M#o!rk48D_dFaRRyX4ZTS$rtiz?NC~=Yz_9>6yGfZ~2U59G!il{NtqaE^36RP%Z%n9Y&j0us#DtDaM_`QdB zd~??$$u5UlXcF4WGmUoXc@`14^X9mI9;0=IieGM=dK}cV(F%x>4 zC=QfLgncKfZ&8R?GZ7Zl9Qo$0^}|_?qn=H|`MFd+V=7I33rlVb08wbVhE7JEN^|gj z`M_m_qk~Y=Ob~bh%R=FGE7oPI8Ca{1#FG;beO}0pm*Kx5vYh zjsQvanv3K9w|SdbQ3L@?l!iM`y3@;shC->~jy2}A0~=_=D`jh~3}gWpuwUg~OmDkk-Iup>==y_L^Mt8Vg< zm7B}Pp%YnS_dKCxA1~W6joJqyQQ{)@LvWslNl?B>q?BHL=gMHb(-el!nH+lhcdFOv z#avca2KW+9FS8Ne-|qMtE$k^d_z7E@feN;vlll%{#^)SiaACTgWnFY>;X>twBcBiW z=r>*y!qCl(APrBU)yTKF*gIcgBBY3R#S=;eJ41hM#x<*&#g5qjz6D)WeK!o_C7g^n ze2GA~Nni9H)`uv>+X&kFEj^-rdd#XWkIE>(CeUb(KpXu$B_hH`HneI?F){|Ju;?el zSP*J3RGtXiGR@1+=R1@!HYT%Qt{XIKoLOIwn?EjXZcgL2Un*2gp|AvQEN!pvPDDFj z&N-k#$Cho1DUuZPCZ@Tja|7FrW;DrFlmdPV+FE6#bkPE%CDLU56P z8dF^2j6BoOBqqt4ibnKEhv}xPTph#9%OpI=-YEO@)Ea2daCsSjj!(sc%I zbVj~fZx5_6gMDI2XsPO{`pnDK#&4fl%czHKGEw!TAEmJzXPnxb%v^2q>Bkz`9{6d> zgNZz3q{Isyd>yqRL(4k2&RW`@))NxQ6!weEbgDXzLcUFQB{2kzuLN(cP>+8%bC{Bn zQpqh1fNq8YGT{*s6PJ*nP_pxrperaUnjdU3GB6N5r5uasrB_F|PiGaD(R1pDaiTQG zagJ-XJpA9bo*h~TxW{s_vxA*qBZ6#}>s8bD8JK&W)_Y*yNyZ0-k3t9tm`XVM+Dm)5 zOF1&qkj&lXM=9ks#IzT?Q)rmeEVC@f)6o(WOX}(pwq6c+U6-jnJv|{%ABSBsOYqB{ zmkZK^xf7nbjxE$YG9dcOJ?9R3Elo5qIos7DxqtX#5W?zoM9rxja{@l*a=HBOUhj?U zYX6N620I*SapB_4*K*K|Tg!_NwyF2Zn>ZU407s-ZY#QcZv!({J2o*0u_g1t+la`J} z12kP74HP2MH8W!eNwUcfK2oj!oySQAMSV89iKv60wJ=LL&nyM8QI&K7H54^5`fYmNm)BkdfCJcsL>ioSU8*&gp#q zHZx_n_-6{{#`LE8W7xnc0M5#Bp&HU`2cWUF_X$m!LP-0glFII5MwJ(-+O<4Xv!j9)hvSyF*$AI>L_ z*Z(zzUeMeqjQ(e&Bx9-Y@FLpZWtq+l+J8E#G;u`h7mud0s@RcN(>@iroQq(f>aFl1 zR;JL%WAugxLcOL0-{YQK@{b>b-S?Y{K|ySJ&6s@%6UmnCjy|j67pk=K8~~DYwTC-{ zx*NRtE-L1p?p29a^_bCDQ3*Nohqbi><80$&Q2!n-e%C;@RKScA2=DPrmMVk^A}g9{7`yI zcJP1PuyM}yXO$mZgLDejR`uM{($H*)tgAqthnE-CILGc#JT)h0s@^XvG*4GJim;;} zG`3|l^5ms{LJ-jO-IoA20kObCesMxi>|ZEchC0QxYoBNfLI0mbf0xtN9!EaV;Hz&7 zLZQr0qB$m%Pmy^6_fF^dE6JG?c$Owz`cwAOg1zNZ($17!0wtoB5uJD7@ckSL@c-XS z5{$+UBj`4>dWLIz0)&0r|DVJRw*1lb}G2peR}lqQ5=SV;(BqpU!Hu=ge)A|fDV zMnRUdimSG+R*hC$tpn_M)!Noy@U8Xr_u=>c@ykEC&%Mt%=lMS8oadZ--8@{aZCG}I z7BGebe);m?iyNnY9jWG}WkHcomKiq!H0N%y|Z(D%p z&!6kQMelS83UsFXxtN(!p&$SN%3Gm6eq;tq#8Up-Ib;Y}>;#U89L?Di$J?Q|JUypJ zj*Ho$7h>T<^$|F8xN+`TLQY`T&LDPH0^24=&%vH-mtmWcBuL_jSmcbPL|%$*#tuDJ z3_qGbmPg=R{n2;a?F<$EvXb!`@oxrOAC@qIqx7OHD=~%h?tzbwt*>(NaXeNcgU?P);_yTs zj2(JP5!0Bs7+botGlM~Q-9~dUqgq7#Gp{&N*f5hJVTgrf5z5FMWl!JeAk;7BEPN>W@@a$hKj`T51l^7Cg;pZxUr(T_hoeDMAKdy2bvZh!aft(!NlU;F0kt6yEY zeCgtarSs>$JbUK!sgoxb7v|??kIzgWJ1U==oERS){bJ+si%9kF8k6S;vF#VRPfpm*grT3@NznT%W8z&^0I1iq1 zj*rNPMweK>GA`*DO`eBg%K8@tCk?c27+bQEO&Y!{TcDfPVYTzqE~xuK?(#{@;q5>Z z(6*O&vWbUBr=^*b5ZMVnctozJ6vr0Fa!Ih#a2TP|i=bK;tE$nQ(AK`4AWPlf8`gJ_ zc4&a3(KOVGw;7>8JIB;|Btw|$ifL~`?LY(ngd6+}b_tvdCWJJz@PaeJ=fq39OK)AD zet^GQzaD}NN9L2P4?>GcKo>_f!+`{P`T9s1vR|?ip}2 z$a)n$tFoXul~e= zC^aO}=v3E2A(9YXvSti?^8ub|RLLgm__XSM=_GX2;V;VWJg%Mzb)q_wS?QlB1Vz10 zEaZwoiHuk>0!uvOy0Gv}h@Ckh%ITMlLXX@6$>8agNrP&iq3fppMEfDn^00{XD5fqt zN07P4tuS6yk6#iflv-EBSJ&3z8?2h5^uX4Dow$xIU6ECK`{@LDYC%(xZuEXmN-Fly z0bS6c7IqvL>Nc7_t_Nv_kbq-n#J+QaMNQN(=owYvReQSgiHoP)ptpVP(5MHSQTHGD zn$a>>e*4$USENtYa`T;!NPCG$x7Ll0M^WxNo9L4UKEqGkpX`D_*zP)d8cohTKdx=+ z>|K4o!F49`cch34`m-tZ@WT*K^3-p~Qc!XC6Aj6R^8D=OvlU*kB9}NkGbfuVHV7&R zMsBo`@Kmq}Q=p;}53p*Z>KhH4TNqWi_N>lGnQ3`1rY`|IaJa!Czoo2rywm}41e5D| zsB@nKC>JFY+QU1U-42izH!_|JG~xx+405hzOs% z%hYX0mWLKAKtaab9jC7){q~Tt#G1=0)2F^K?#}XKqo3P}>_~mj%(0n;$=C=(FwX^A zm|29N0A&^#70hh^YQx$(OhaQ-vqP&vX-By%s>-PYQ*cPFNMe}U(N)poU{f*#mkHRt z6h@hQQs%t>-dIR(=omQLRD_(4rG?UM?UE5eu^WN=z}@vA3h@|Wta$c(dF6#-O|PE& z2r;CZY_!EVMyi6;zm!tj;=JF=882^C$#?ypM0AIG)!wj4w^SIo){}H@7;CJk+s$F~ z$0HiB<6Hz8k*3x-%$lg#IW>1hL$)NpHj$Wa3w1?Eg#Yu$AbR9K=GVqv6CN#j6$+2 z?7TJHQrPyUkkIUeC>TLlz=k@|pd|@>d~_vSpij%Hj|d6GHMjWo7<6>WFg?8oum{^Q z%EKuncKAS>UUAq!S@{uvhYU<-y}KtKT*NYKB=u2)M4toDY5h~!Gm%&K5z$9u#6ge{ z*!XUD3^I)bFSrpz1Zn2x4;@kYSm@SYjpt=_h2vx%lCUN6?8rSaa;>aYe6#`KWU_aw zo`>UXh*Q7F(|{9=JcjlK3!VIpFtHX53cR&>=jGE2FU_onZ#*F%>haVl?9Uj<07toQ zh36fE)bGo)$K4O!#0-!xeqJ`178Vh?568-3>#OF6sU)VLOLC$C;}FZn6n80ddh z`pS6lW@7iZK7JY(zqp~G^)5pvpKrH0`_OY8I$dm%MfC)8g}n8EI2|jY212B4s7_jS zSZ$qY1-yyf+OG6D9<@JOr>ZZcv#X+U&|jx;M6KTxz?V%THgMY$W{AkiS^3BeW|6?! ze|bZ&Sk<-Pg9J$yB8+0&Lg7Z%U4bO@KDKDbPd5``=d~Pwm&@A5yUkwzg@dRgiOuB8 zbIOXeWpHi!Up~+)+YuLBY-vT}0R#(? zwh0aX2%gkHf0v2;X~(vLgmW);_=IDqm;SX{uxM)+tQ#L(uTUbZi;d(+W#EfPdLyZ~ zQZ&Z-%lJ*L98jQDrRFj+s(76xKFw+k?IWHYJh6pt*IhAU-7eD}ztzrIDXO@O1>^It ztuwkv(Yg4L_#}(~COKsDl`qhF?sSwGQ_P3zmPuVJs%rT4jc(22R<`b*j);VoZfqU7 zw}Svw`FEFmChABhWcnW3t22}rkE;}q7LRXf-~!&q>`(FK=DA_23k4VK`H1sQcm`Yn zKTbeLiILZCo1;-TP>+AQ4MF{i625r#`u8_FYo6^(A7GWO*Ml)6qGw<+AU>~qsSM;o z7M|L5%{1mM(v9(?e6OA}Wb<`9Z{v)@FcqOiQph8lF2yFgjr3)V(In+W$AjptiLAU$L)s!3F*;_q#rUVzQ0r%Z5$?`=3M&BB*c)sUz@#oimLOwh(AIeXOAN*j&Lv}5r#(cnGsoYp1ek4OY?XBBYe1%6G$ zg^zp~%7o-k0mh!f{Ci8|Y%XN+sh#eOmSfT*KL9Uzp!Q<{wA)i>?#;N@c>qU6UtI+ z4@9S;52Zx<59MLu#I77)e>~V8glKh&4Uaob2n@2MjCwmG0nE&*w?!2aRlKXTR1X(OR#DL`yw3Ai)jgd>n zg^GeLeSt29hc4*J0;peX0qf5{y&mF2^itzL1Kkn6BbnZ?oV^S}ez>^ELQ>*gCf$}> z=~+lksY%1dRPLe1Ns}S<7zZl4X4`IjduFGjlhl;}mcWunipGfOA#dbiKO88MYuL;| z78Y@6BWIJPudaUm&9&=VYl2++0HPq3$8ZbKiowDCTIV;j=?OTkU7U7fZn+FoMa~ZHJE)d>7*qHu zC>Y&Jgvnr=j)aUh;NzQzHp1KLJV=NN1RX$(v`@v&M)}h<(5k|V+7Q%36z?_(4G*I} zUOo-~8UAQR`Revs^Bt4RI&qgylU+xZi{6bqmwDql9u-#z}NQsG@MJ^i638u*#| bWW^%>x7XlHV^#Q1uOX^B?ki3(VEg|7!QHuF literal 0 HcmV?d00001 diff --git a/staticweb/layer/theme/default/loading-1.gif b/staticweb/layer/theme/default/loading-1.gif new file mode 100755 index 0000000000000000000000000000000000000000..db3a483e4b74971fbfb1cc0fb6499852cedfe650 GIT binary patch literal 701 zcmZ?wbhEHbRAo?Qn8?Ji_w)@mZNLBj|1&T!DE{a6a}5c0b_{Se(lcOY1PT3QVdY|A zV$cDyff`g97?`@J^shYqmS1s(LX_+4yGox$4el*+Jm?ved2^25GBru=T^dGm#<906 za&AQCZ08H8P;Bd&{NT;vl&}c_^L4%p?g_hjBu{YB29{c>Ob}p@z~Ks3xCw+@!HClp xtZ<(QPf3`00FNu+VbOvoEE+h73k#4LIKl$IE8t;)<_eUs!0QU6uz&iJhvXcHF*h)T1OnEW1i^?zgDfop1p?usL*#PMGT;HQkSO{q6FlJyb$PWkPf|h*eTST}7h8z$}MF(XD(aQ)ZLZ zM?v0rT<1C4XHn<6PbNA{XL@>1^)apdD_@tcYDrW#m`k#MmslI7p^P;Az74wGs`!SI zLs$GEZHsafXsu1i-WleMzAL(yw$-LK{0hv;6hrx8kx!!4$``dAyBnY9Jz&DqJo2$A z!(L$H=KqBeY~CF_viHPz^tTglc?D97CqEBjzUwH}7GI zapg8YZM~>2Wk%E$d&r@9ly9b4Q zJpM7T@}r63I(OExUlG%Xcjz3MU+9U^r!SkpjNThDtaP)7>j6L5z%o5|^hlVOyI*uY zt^UU6NTuY?(Lb4ZIU2Zb5Vz}Pb7KF%ivf&j^CL>$cDz?rMNTQQ|NqDVD7mhghUp%h zhIA{gi{S8y9YhIIbSv$`B!JiPi!0#4#Jge0)p&YVPHchWcyAn zQhvb8ggXGXs9;k`u9Uq*YB>O+Q3Rq=2hlLFcG{Q3ORH_}JnY8C+r%@}6|%ySP%bWG zV~mA;?P`Q2L_Ss})nrJ{$TmeA9Tt*4=}X5x%RioM@_?ZsKSEST-f+GBv~Ya)xX3O{ z8!d=YthI-13OI;RN~`>|6u5L{z20oBp%9MIj)n$!Aw{Wpq&Rtr4~*_74Gjo@3el>B zz(Rk;;>2lp73<2;d=r*8z%WkdsG=vRuG_fvxO#uN^El|+5Qoz^X!2MfxJ3m}vyi?> zMLLDi8+${Z6YbUg?8GNR>-+SwHKdFyr%HqWcs|X_l*-DAC^bG&KCqWg7-_`UlwQ`EdOp_LJkr`L$mHHs75uP?fSgVfsDjuE#ft2b8HDt0yFt!+;C zEgL=)G9ZFt4wa+N3Xg7FGc0~`&EEt6_%7tyzmnb9B_h1~7~GD4V-Bhx7~QKRkF>&aT>(-!Us@aJxAY@8E?HW$G8g zSz@7Jcp>iCp;lU1ieF6n7!oAa-1E!rS0 zF1lBFVS%G#ZO}b@*+bIk+7@Q|iG60vIDVpV%4tW8rKyzwRo_<25;8*Ky@n z-sX>W*b;M){5lB_Edc@m1`VHy0@dg$PTR9uE$O2&a?KAe?xRlCj&Z$iZYw