first commit
@@ -0,0 +1,2 @@
|
||||
.idea
|
||||
.idea/*
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
use Workerman\Worker;
|
||||
use Workerman\Lib\Timer;
|
||||
|
||||
// watch Applications catalogue
|
||||
|
||||
// worker
|
||||
$webDirSource = dirname(dirname(dirname(__FILE__)));
|
||||
$worker = new Worker();
|
||||
$worker->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;
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<[email protected]>
|
||||
* @copyright walkor<[email protected]>
|
||||
* @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");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<[email protected]>
|
||||
* @copyright walkor<[email protected]>
|
||||
* @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();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<[email protected]>
|
||||
* @copyright walkor<[email protected]>
|
||||
* @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();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<[email protected]>
|
||||
* @copyright walkor<[email protected]>
|
||||
* @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();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
use \Workerman\Worker;
|
||||
|
||||
$task_worker = new Worker('text://127.0.0.1:16345');
|
||||
// task进程数可以根据需要多开一些
|
||||
$task_worker->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协议 或<br> (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();
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2009-2015 walkor<[email protected]> 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.
|
||||
@@ -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)
|
||||

|
||||
|
||||
## [chat room](http://chat.workerman.net/)
|
||||
[Live demo](http://chat.workerman.net/)
|
||||
[Source code](https://github.com/walkor/workerman-chat)
|
||||

|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
/**
|
||||
* run with command
|
||||
* php start.php start
|
||||
*/
|
||||
|
||||
ini_set('display_errors', 'on');
|
||||
use Workerman\Worker;
|
||||
|
||||
if(strpos(strtolower(PHP_OS), 'win') === 0)
|
||||
{
|
||||
exit("start.php not support windows, please use start_for_win.bat\n");
|
||||
}
|
||||
|
||||
// 检查扩展
|
||||
if(!extension_loaded('pcntl'))
|
||||
{
|
||||
exit("Please install pcntl extension. See http://doc3.workerman.net/appendices/install-extension.html\n");
|
||||
}
|
||||
|
||||
if(!extension_loaded('posix'))
|
||||
{
|
||||
exit("Please install posix extension. See http://doc3.workerman.net/appendices/install-extension.html\n");
|
||||
}
|
||||
|
||||
// 标记是全局启动
|
||||
define('GLOBAL_START', 1);
|
||||
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
// 加载所有Applications/*/start.php,以便启动所有服务
|
||||
foreach(glob(__DIR__.'/Applications/*/start*.php') as $start_file)
|
||||
{
|
||||
require_once $start_file;
|
||||
}
|
||||
// 运行所有服务
|
||||
Worker::runAll();
|
||||
@@ -0,0 +1,2 @@
|
||||
php Applications\YourApp\start_register.php Applications\YourApp\start_gateway.php Applications\YourApp\start_businessworker.php
|
||||
pause
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
// autoload.php @generated by Composer
|
||||
|
||||
require_once __DIR__ . '/composer/autoload_real.php';
|
||||
|
||||
return ComposerAutoloaderInite69c95f9a095e47d6d5b943de8a25aa3::getLoader();
|
||||
@@ -0,0 +1,445 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Composer.
|
||||
*
|
||||
* (c) Nils Adermann <[email protected]>
|
||||
* Jordi Boggiano <[email protected]>
|
||||
*
|
||||
* 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 <[email protected]>
|
||||
* @author Jordi Boggiano <[email protected]>
|
||||
* @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;
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
// autoload_classmap.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(dirname(__FILE__));
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
);
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
// autoload_namespaces.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(dirname(__FILE__));
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
);
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
// autoload_psr4.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(dirname(__FILE__));
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'Workerman\\' => array($vendorDir . '/workerman/workerman'),
|
||||
'GatewayWorker\\' => array($vendorDir . '/workerman/gateway-worker/src'),
|
||||
);
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
// autoload_real.php @generated by Composer
|
||||
|
||||
class ComposerAutoloaderInite69c95f9a095e47d6d5b943de8a25aa3
|
||||
{
|
||||
private static $loader;
|
||||
|
||||
public static function loadClassLoader($class)
|
||||
{
|
||||
if ('Composer\Autoload\ClassLoader' === $class) {
|
||||
require __DIR__ . '/ClassLoader.php';
|
||||
}
|
||||
}
|
||||
|
||||
public static function getLoader()
|
||||
{
|
||||
if (null !== self::$loader) {
|
||||
return self::$loader;
|
||||
}
|
||||
|
||||
spl_autoload_register(array('ComposerAutoloaderInite69c95f9a095e47d6d5b943de8a25aa3', 'loadClassLoader'), true, true);
|
||||
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
|
||||
spl_autoload_unregister(array('ComposerAutoloaderInite69c95f9a095e47d6d5b943de8a25aa3', 'loadClassLoader'));
|
||||
|
||||
$useStaticLoader = PHP_VERSION_ID >= 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
// autoload_static.php @generated by Composer
|
||||
|
||||
namespace Composer\Autoload;
|
||||
|
||||
class ComposerStaticInite69c95f9a095e47d6d5b943de8a25aa3
|
||||
{
|
||||
public static $prefixLengthsPsr4 = array (
|
||||
'W' =>
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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": "[email protected]",
|
||||
"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"
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1 @@
|
||||
23078
|
||||
@@ -0,0 +1,4 @@
|
||||
.buildpath
|
||||
.project
|
||||
.settings
|
||||
.idea
|
||||
@@ -0,0 +1,21 @@
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2009-2015 walkor<[email protected]> 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.
|
||||
@@ -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等启动入口文件)<br>
|
||||
[点击这里下载demo](http://www.workerman.net/download/GatewayWorker.zip)。<br>
|
||||
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)
|
||||

|
||||
|
||||
## [chat room](http://chat.workerman.net/)
|
||||
[Live demo](http://chat.workerman.net/)
|
||||
[Source code](https://github.com/walkor/workerman-chat)
|
||||

|
||||
@@ -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"}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,560 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<[email protected]>
|
||||
* @copyright walkor<[email protected]>
|
||||
* @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<[email protected]>
|
||||
*
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,1019 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<[email protected]>
|
||||
* @copyright walkor<[email protected]>
|
||||
* @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<[email protected]>
|
||||
*
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<[email protected]>
|
||||
* @copyright walkor<[email protected]>
|
||||
* @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));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<[email protected]>
|
||||
* @copyright walkor<[email protected]>
|
||||
* @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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,1976 @@
|
||||
<?php
|
||||
namespace GatewayWorker\Lib;
|
||||
|
||||
use Exception;
|
||||
use PDO;
|
||||
use PDOException;
|
||||
|
||||
/**
|
||||
* 数据库连接类,依赖 PDO_MYSQL 扩展
|
||||
* 在 https://github.com/auraphp/Aura.SqlQuery 的基础上修改而成
|
||||
*/
|
||||
class DbConnection
|
||||
{
|
||||
/**
|
||||
* SELECT
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $union = array();
|
||||
|
||||
/**
|
||||
* 是否是更新
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $for_update = false;
|
||||
|
||||
/**
|
||||
* 选择的列
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $cols = array();
|
||||
|
||||
/**
|
||||
* 从哪些表里面 SELECT
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $from = array();
|
||||
|
||||
/**
|
||||
* $from 当前的 key
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $from_key = -1;
|
||||
|
||||
/**
|
||||
* GROUP BY 的列
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $group_by = array();
|
||||
|
||||
/**
|
||||
* HAVING 条件数组.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $having = array();
|
||||
|
||||
/**
|
||||
* HAVING 语句中绑定的值.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $bind_having = array();
|
||||
|
||||
/**
|
||||
* 每页多少条记录
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $paging = 10;
|
||||
|
||||
/**
|
||||
* sql 中绑定的值
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $bind_values = array();
|
||||
|
||||
/**
|
||||
* WHERE 条件.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $where = array();
|
||||
|
||||
/**
|
||||
* WHERE 语句绑定的值
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $bind_where = array();
|
||||
|
||||
/**
|
||||
* ORDER BY 的列
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $order_by = array();
|
||||
|
||||
/**
|
||||
* ORDER BY 的排序方式,默认为升序
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $order_asc = true;
|
||||
/**
|
||||
* SELECT 多少记录
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $limit = 0;
|
||||
|
||||
/**
|
||||
* 返回记录的游标
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $offset = 0;
|
||||
|
||||
/**
|
||||
* flags 列表
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $flags = array();
|
||||
|
||||
/**
|
||||
* 操作哪个表
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table;
|
||||
|
||||
/**
|
||||
* 表.列 和 last-insert-id 映射
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $last_insert_id_names = array();
|
||||
|
||||
/**
|
||||
* INSERT 或者 UPDATE 的列
|
||||
*
|
||||
* @param array
|
||||
*/
|
||||
protected $col_values;
|
||||
|
||||
/**
|
||||
* 返回的列
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $returning = array();
|
||||
|
||||
/**
|
||||
* sql 的类型 SELECT INSERT DELETE UPDATE
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type = '';
|
||||
|
||||
/**
|
||||
* pdo 实例
|
||||
*
|
||||
* @var PDO
|
||||
*/
|
||||
protected $pdo;
|
||||
|
||||
/**
|
||||
* PDOStatement 实例
|
||||
*
|
||||
* @var \PDOStatement
|
||||
*/
|
||||
protected $sQuery;
|
||||
|
||||
/**
|
||||
* 数据库用户名密码等配置
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $settings = array();
|
||||
|
||||
/**
|
||||
* sql 的参数
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $parameters = array();
|
||||
|
||||
/**
|
||||
* 最后一条直行的 sql
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $lastSql = '';
|
||||
|
||||
/**
|
||||
* 是否执行成功
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $success = false;
|
||||
|
||||
/**
|
||||
* 选择哪些列
|
||||
*
|
||||
* @param string|array $cols
|
||||
* @return self
|
||||
*/
|
||||
public function select($cols = '*')
|
||||
{
|
||||
$this->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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,1361 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<[email protected]>
|
||||
* @copyright walkor<[email protected]>
|
||||
* @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');
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<[email protected]>
|
||||
* @copyright walkor<[email protected]>
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<[email protected]>
|
||||
* @copyright walkor<[email protected]>
|
||||
* @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<[email protected]>
|
||||
*
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,6 @@
|
||||
logs
|
||||
.buildpath
|
||||
.project
|
||||
.settings
|
||||
.idea
|
||||
.DS_Store
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<[email protected]>
|
||||
* @copyright walkor<[email protected]>
|
||||
* @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');
|
||||
@@ -0,0 +1,376 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<[email protected]>
|
||||
* @copyright walkor<[email protected]>
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<[email protected]>
|
||||
* @copyright walkor<[email protected]>
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<[email protected]>
|
||||
* @copyright walkor<[email protected]>
|
||||
* @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);
|
||||
}
|
||||
@@ -0,0 +1,1007 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<[email protected]>
|
||||
* @copyright walkor<[email protected]>
|
||||
* @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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<[email protected]>
|
||||
* @copyright walkor<[email protected]>
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author 有个鬼<[email protected]>
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author 有个鬼<[email protected]>
|
||||
* @copyright 有个鬼<[email protected]>
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<[email protected]>
|
||||
* @copyright walkor<[email protected]>
|
||||
* @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();
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<[email protected]>
|
||||
* @copyright walkor<[email protected]>
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,264 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<[email protected]>
|
||||
* @copyright walkor<[email protected]>
|
||||
* @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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<[email protected]>
|
||||
* @copyright walkor<[email protected]>
|
||||
* @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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<[email protected]>
|
||||
* @copyright walkor<[email protected]>
|
||||
* @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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<[email protected]>
|
||||
* @copyright walkor<[email protected]>
|
||||
* @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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,338 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<[email protected]>
|
||||
* @copyright walkor<[email protected]>
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author Ares<aresrr#qq.com>
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<[email protected]>
|
||||
* @copyright walkor<[email protected]>
|
||||
* @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
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<[email protected]>
|
||||
* @copyright walkor<[email protected]>
|
||||
* @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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2009-2015 walkor<[email protected]> 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.
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<[email protected]>
|
||||
* @copyright walkor<[email protected]>
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,755 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<[email protected]>
|
||||
* @copyright walkor<[email protected]>
|
||||
* @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');
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<[email protected]>
|
||||
* @copyright walkor<[email protected]>
|
||||
* @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);
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<[email protected]>
|
||||
* @copyright walkor<[email protected]>
|
||||
* @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");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,503 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<[email protected]>
|
||||
* @copyright walkor<[email protected]>
|
||||
* @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<div style=\"text-align:center\"><h1>Websocket</h1><hr>powered by <a href=\"https://www.workerman.net\">workerman ".Worker::VERSION."</a></div>",
|
||||
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, '<polic')) {
|
||||
$policy_xml = '<?xml version="1.0"?><cross-domain-policy><site-control permitted-cross-domain-policies="all"/><allow-access-from domain="*" to-ports="*"/></cross-domain-policy>' . "\0";
|
||||
$connection->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<div style=\"text-align:center\"><h1>Websocket</h1><hr>powered by <a href=\"https://www.workerman.net\">workerman ".Worker::VERSION."</a></div>",
|
||||
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'] = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,472 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<[email protected]>
|
||||
* @copyright walkor<[email protected]>
|
||||
* @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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,606 @@
|
||||
# Workerman
|
||||
[](https://gitter.im/walkor/Workerman?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=body_badge)
|
||||
[](https://packagist.org/packages/workerman/workerman)
|
||||
[](https://packagist.org/packages/workerman/workerman)
|
||||
[](https://packagist.org/packages/workerman/workerman)
|
||||
[](https://packagist.org/packages/workerman/workerman)
|
||||
[](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
|
||||
<?php
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
use Workerman\Worker;
|
||||
|
||||
// Create a Websocket server
|
||||
$ws_worker = new Worker("websocket://0.0.0.0:2346");
|
||||
|
||||
// 4 processes
|
||||
$ws_worker->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
|
||||
<?php
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
use Workerman\Worker;
|
||||
|
||||
// SSL context.
|
||||
$context = array(
|
||||
'ssl' => 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
|
||||
<?php
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
use Workerman\Worker;
|
||||
|
||||
$worker = new Worker('tcp://0.0.0.0:6161');
|
||||
$worker->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
|
||||
<?php
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
use Clue\React\Redis\Factory;
|
||||
use Clue\React\Redis\Client;
|
||||
use Workerman\Worker;
|
||||
|
||||
$worker = new Worker('tcp://0.0.0.0:6161');
|
||||
|
||||
$worker->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
|
||||
<?php
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
use Workerman\Worker;
|
||||
|
||||
$worker = new Worker('tcp://0.0.0.0:6161');
|
||||
|
||||
$worker->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
|
||||
<?php
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
use Workerman\Worker;
|
||||
|
||||
$worker = new Worker('text://0.0.0.0:6161');
|
||||
|
||||
$worker->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
|
||||
<?php
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
use Workerman\Worker;
|
||||
|
||||
$worker = new Worker('text://0.0.0.0:6161');
|
||||
|
||||
$worker->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 ```
|
||||

|
||||
```php start.php status ```
|
||||

|
||||
```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
|
||||
<?php
|
||||
use Workerman\Worker;
|
||||
$worker = new Worker('tcp://0.0.0.0:1234');
|
||||
$worker->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
|
||||
<a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=UQGGS9UB35WWG"><img src="http://donate.workerman.net/img/donate.png"></a>
|
||||
|
||||
## LICENSE
|
||||
|
||||
Workerman is released under the [MIT license](https://github.com/walkor/workerman/blob/master/MIT-LICENSE.txt).
|
||||
@@ -0,0 +1,328 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<[email protected]>
|
||||
* @copyright walkor<[email protected]>
|
||||
* @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('<h1>400 Bad Request</h1>');
|
||||
} else {
|
||||
$connection->close('<h1>400 Bad Request</h1>');
|
||||
}
|
||||
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('<h1>400 Bad Request</h1>');
|
||||
} else {
|
||||
$connection->close('<h1>400 Bad Request</h1>');
|
||||
}
|
||||
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 = '<html><head><title>404 File not found</title></head><body><center><h3>404 Not Found</h3></center></body></html>';
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2534 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<[email protected]>
|
||||
* @copyright walkor<[email protected]>
|
||||
* @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 = '<g> [OK] </g>';
|
||||
|
||||
// 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 = '<n>' . \str_pad('<w> WORKERMAN </w>', $total_length + \strlen('<w></w>'), '-', \STR_PAD_BOTH) . '</n>'. \PHP_EOL;
|
||||
$line_two = \str_pad('<w> WORKERS </w>' , $total_length + \strlen('<w></w>'), '-', \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.= "<w>{$column_name}</w>" . \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>|<\/n>|<w>|<\/w>|<g>|<\/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 <command> [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('<n>', '<w>', '<g>'), array($line, $white, $green), $msg);
|
||||
$msg = \str_replace(array('</n>', '</w>', '</g>'), $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;
|
||||
}
|
||||
}
|
||||
@@ -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": "[email protected]",
|
||||
"homepage": "http://www.workerman.net",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"support": {
|
||||
"email": "[email protected]",
|
||||
"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"
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
**(注:本程序为简单实现,因纯属业余时间编写,开发比较仓促,欢迎完善)**<br>
|
||||
1.staticweb目录您需要进行nginx配置为站点。<br>
|
||||
2.GatewayWorker下start_gateway中配置wss所需要的证书<br>
|
||||
`$context = array(`<br>
|
||||
`'ssl' => array(// 请使用绝对路径`<br>
|
||||
`'local_cert' =>'/xxxxx.pem', //也可以是crt文件`<br>
|
||||
`'local_pk' => 'xxxxxx.key',`<br>
|
||||
`'verify_peer' => false,
|
||||
// 'allow_self_signed' => true, //如果是自签名证书需要开启此选项`<br>
|
||||
`)`<br>
|
||||
`);`<br>
|
||||
3.启动GatewayWorker<br>
|
||||
`cd 您的GatewayWorker路径/GatewayWorker ; php start.php start`
|
||||
4.start_sync中可根据您的服务器配置来设置:进程数<br>
|
||||
task进程数可以根据需要多开一些,默认为10<br>
|
||||
`$task_worker->count = 10;`<br>
|
||||
5.demo站点:<a href="https://g.widora.cn">点我跳转</a><br>
|
||||
6.本程序基于<a href="https://www.workerman.net/">workerMan</a>编写<br>
|
||||
7.因本人平常工作繁忙,欢迎爱好者帮忙搭建docker,请联系本人放出仓库地址<br>
|
||||
8.如有问题,可联系本人qq:9496898
|
||||
|
||||
@@ -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%;
|
||||
}
|
||||
|
After Width: | Height: | Size: 159 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
@@ -0,0 +1,41 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>GitHub代下载服务·永久免费</title>
|
||||
<link href="/css/style.css" rel='stylesheet' type='text/css' />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<script src="/js/jquery-2.2.4.min.js" type="application/javascript"></script>
|
||||
<script src="/layer/layer.js" type="application/javascript"></script>
|
||||
<!-- Global site tag (gtag.js) - Google Analytics -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-109508401-13"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
|
||||
gtag('config', 'UA-109508401-13');
|
||||
</script>
|
||||
|
||||
<body>
|
||||
<h1>GitHub代下载服务</h1>
|
||||
<h2>Git克隆慢?外网源码包没速度?试试我,记得收藏哦</h2>
|
||||
<div class="app-block">
|
||||
<div class="cube">
|
||||
<img src="images/githublogo.png" width="120" class="img-responsive" alt="" />
|
||||
<div style="color:#fff;width: 100%;height: 30px;text-align: center;display: block;" class="down_num">已成功加速下载次数:<b></b></div>
|
||||
</div>
|
||||
<input type="text" class="text" placeholder="https开头的github地址 或需下载包地址的链接" value="">
|
||||
<div class="submit">
|
||||
<button class="sub-btn">获取链接</button>
|
||||
</div>
|
||||
<div style="color:#f00"><p>注意:暂不支持超过2GB的仓库ε=ε=ε=┏(゜ロ゜;)┛ </p></div>
|
||||
</br>
|
||||
<div style="color:#ccc"><p>您的支持是我们前进的动力,希望大家多多关注<a href="https://space.bilibili.com/84013907" title="bilibili" class="bilibili" style="color:#00a1d6">我的B站</a>,不定期更新个人开源项目,谢谢啦~~~~</p></div>
|
||||
</br>
|
||||
<div class="copy-right">
|
||||
<p>Copyright © 2019-<script>document.write((new Date).getFullYear())</script> <a href="http://www.widora.cn/" title="widora" class="widora" target="_blank">歪朵拉</a> | Designed by xiaoxin | <a href="https://github.com/xiaoxinda/github-proxy" title="source" class="widora" target="_blank">获取source</a> | <a href="javascript;" title="copyright" class="widora exemption">免责声明</a> | 更新日志 </p>
|
||||
</div>
|
||||
<script src="/js/index.js" type="application/javascript"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -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 = '<div style="font-size:1rem;margin:15px;">\
|
||||
<span style="font-size:1.5rem;">您浏览并使用本站的代下载服务,视为对以下声明内容的全部认可</span>\
|
||||
<p>1、本站不存储资源,只是中转平台</p>\
|
||||
<p>2、使用本站下载任何资源需自行承担风险</p>\
|
||||
<p>3、本站仅代下网上现成的代码,无法对这些代码的可用性,准确性或可靠性作出任何承诺与保证</p>\
|
||||
<p>4、本站不对任何由于使用或无法使用本站提供的代码所造成的直接的和间接的损失负任何责任</p>\
|
||||
<p>5、任何单位和个人不得使用本站下载侵权、盗版、违法的任何软件或资料</p>\
|
||||
</div>';
|
||||
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("<a class='download-btn' href=\"" + data.data.url + "\" target='_blank'>" + data.msg + "</a>").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协议 或<br> (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();
|
||||
})
|
||||
@@ -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 id='"+u+"'></a><select id='"+u+"-\r\\' msallowcapture=''><option selected=''></option></select>",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;++d<b;)a.push(d);return a})}},d.pseudos.nth=d.pseudos.eq;for(b in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})d.pseudos[b]=la(b);for(b in{submit:!0,reset:!0})d.pseudos[b]=ma(b);function pa(){}pa.prototype=d.filters=d.pseudos,d.setFilters=new pa,g=fa.tokenize=function(a,b){var c,e,f,g,h,i,j,k=z[a+" "];if(k)return b?0:k.slice(0);h=a,i=[],j=d.preFilter;while(h){c&&!(e=R.exec(h))||(e&&(h=h.slice(e[0].length)||h),i.push(f=[])),c=!1,(e=S.exec(h))&&(c=e.shift(),f.push({value:c,type:e[0].replace(Q," ")}),h=h.slice(c.length));for(g in d.filter)!(e=W[g].exec(h))||j[g]&&!(e=j[g](e))||(c=e.shift(),f.push({value:c,type:g,matches:e}),h=h.slice(c.length));if(!c)break}return b?h.length:h?fa.error(a):z(a,i).slice(0)};function qa(a){for(var b=0,c=a.length,d="";c>b;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 href='#'></a>","#"===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="<input/>",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<f.length)f[h].apply(c[0],c[1])===!1&&a.stopOnFalse&&(h=f.length,c=!1)}a.memory||(c=!1),b=!1,e&&(f=c?[]:"")},j={add:function(){return f&&(c&&!b&&(h=f.length-1,g.push(c)),function d(b){n.each(b,function(b,c){n.isFunction(c)?a.unique&&j.has(c)||f.push(c):c&&c.length&&"string"!==n.type(c)&&d(c)})}(arguments),c&&!b&&i()),this},remove:function(){return n.each(arguments,function(a,b){var c;while((c=n.inArray(b,f,c))>-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<c?n.queue(this[0],a):void 0===b?this:this.each(function(){var c=n.queue(this,a,b);n._queueHooks(this,a),"fx"===a&&"inprogress"!==c[0]&&n.dequeue(this,a)})},dequeue:function(a){return this.each(function(){n.dequeue(this,a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,b){var c,d=1,e=n.Deferred(),f=this,g=this.length,h=function(){--d||e.resolveWith(f,[f])};"string"!=typeof a&&(b=a,a=void 0),a=a||"fx";while(g--)c=N.get(f[g],a+"queueHooks"),c&&c.empty&&(d++,c.empty.add(h));return h(),e.promise(b)}});var S=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,T=new RegExp("^(?:([+-])=|)("+S+")([a-z%]*)$","i"),U=["Top","Right","Bottom","Left"],V=function(a,b){return a=b||a,"none"===n.css(a,"display")||!n.contains(a.ownerDocument,a)};function W(a,b,c,d){var e,f=1,g=20,h=d?function(){return d.cur()}:function(){return n.css(a,b,"")},i=h(),j=c&&c[3]||(n.cssNumber[b]?"":"px"),k=(n.cssNumber[b]||"px"!==j&&+i)&&T.exec(n.css(a,b));if(k&&k[3]!==j){j=j||k[3],c=c||[],k=+i||1;do f=f||".5",k/=f,n.style(a,b,k+j);while(f!==(f=h()/i)&&1!==f&&--g)}return c&&(k=+k||+i||0,e=c[1]?k+(c[1]+1)*c[2]:+c[2],d&&(d.unit=j,d.start=k,d.end=e)),e}var X=/^(?:checkbox|radio)$/i,Y=/<([\w:-]+)/,Z=/^$|\/(?:java|ecma)script/i,$={option:[1,"<select multiple='multiple'>","</select>"],thead:[1,"<table>","</table>"],col:[2,"<table><colgroup>","</colgroup></table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_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="<textarea>x</textarea>",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<b.length&&g.push({elem:this,handlers:b.slice(h)}),g},props:"altKey bubbles cancelable ctrlKey currentTarget detail eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(a,b){return null==a.which&&(a.which=null!=b.charCode?b.charCode:b.keyCode),a}},mouseHooks:{props:"button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(a,b){var c,e,f,g=b.button;return null==a.pageX&&null!=b.clientX&&(c=a.target.ownerDocument||d,e=c.documentElement,f=c.body,a.pageX=b.clientX+(e&&e.scrollLeft||f&&f.scrollLeft||0)-(e&&e.clientLeft||f&&f.clientLeft||0),a.pageY=b.clientY+(e&&e.scrollTop||f&&f.scrollTop||0)-(e&&e.clientTop||f&&f.clientTop||0)),a.which||void 0===g||(a.which=1&g?1:2&g?3:4&g?2:0),a}},fix:function(a){if(a[n.expando])return a;var b,c,e,f=a.type,g=a,h=this.fixHooks[f];h||(this.fixHooks[f]=h=ea.test(f)?this.mouseHooks:da.test(f)?this.keyHooks:{}),e=h.props?this.props.concat(h.props):this.props,a=new n.Event(g),b=e.length;while(b--)c=e[b],a[c]=g[c];return a.target||(a.target=d),3===a.target.nodeType&&(a.target=a.target.parentNode),h.filter?h.filter(a,g):a},special:{load:{noBubble:!0},focus:{trigger:function(){return this!==ia()&&this.focus?(this.focus(),!1):void 0},delegateType:"focusin"},blur:{trigger:function(){return this===ia()&&this.blur?(this.blur(),!1):void 0},delegateType:"focusout"},click:{trigger:function(){return"checkbox"===this.type&&this.click&&n.nodeName(this,"input")?(this.click(),!1):void 0},_default:function(a){return n.nodeName(a.target,"a")}},beforeunload:{postDispatch:function(a){void 0!==a.result&&a.originalEvent&&(a.originalEvent.returnValue=a.result)}}}},n.removeEvent=function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c)},n.Event=function(a,b){return this instanceof n.Event?(a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||void 0===a.defaultPrevented&&a.returnValue===!1?ga:ha):this.type=a,b&&n.extend(this,b),this.timeStamp=a&&a.timeStamp||n.now(),void(this[n.expando]=!0)):new n.Event(a,b)},n.Event.prototype={constructor:n.Event,isDefaultPrevented:ha,isPropagationStopped:ha,isImmediatePropagationStopped:ha,isSimulated:!1,preventDefault:function(){var a=this.originalEvent;this.isDefaultPrevented=ga,a&&!this.isSimulated&&a.preventDefault()},stopPropagation:function(){var a=this.originalEvent;this.isPropagationStopped=ga,a&&!this.isSimulated&&a.stopPropagation()},stopImmediatePropagation:function(){var a=this.originalEvent;this.isImmediatePropagationStopped=ga,a&&!this.isSimulated&&a.stopImmediatePropagation(),this.stopPropagation()}},n.each({mouseenter:"mouseover",mouseleave:"mouseout",pointerenter:"pointerover",pointerleave:"pointerout"},function(a,b){n.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c,d=this,e=a.relatedTarget,f=a.handleObj;return e&&(e===d||n.contains(d,e))||(a.type=f.origType,c=f.handler.apply(this,arguments),a.type=b),c}}}),n.fn.extend({on:function(a,b,c,d){return ja(this,a,b,c,d)},one:function(a,b,c,d){return ja(this,a,b,c,d,1)},off:function(a,b,c){var d,e;if(a&&a.preventDefault&&a.handleObj)return d=a.handleObj,n(a.delegateTarget).off(d.namespace?d.origType+"."+d.namespace:d.origType,d.selector,d.handler),this;if("object"==typeof a){for(e in a)this.off(e,b,a[e]);return this}return b!==!1&&"function"!=typeof b||(c=b,b=void 0),c===!1&&(c=ha),this.each(function(){n.event.remove(this,a,c,b)})}});var ka=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi,la=/<script|<style|<link/i,ma=/checked\s*(?:[^=]|=\s*.checked.)/i,na=/^true\/(.*)/,oa=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\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></$2>")},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("<iframe frameborder='0' width='0' height='0'/>")).appendTo(b.documentElement),b=wa[0].contentDocument,b.write(),b.close(),c=ya(a,b),wa.detach()),xa[a]=c),c}var Aa=/^margin/,Ba=new RegExp("^("+S+")(?!px)[a-z%]+$","i"),Ca=function(b){var c=b.ownerDocument.defaultView;return c&&c.opener||(c=a),c.getComputedStyle(b)},Da=function(a,b,c,d){var e,f,g={};for(f in b)g[f]=a.style[f],a.style[f]=b[f];e=c.apply(a,d||[]);for(f in b)a.style[f]=g[f];return e},Ea=d.documentElement;!function(){var b,c,e,f,g=d.createElement("div"),h=d.createElement("div");if(h.style){h.style.backgroundClip="content-box",h.cloneNode(!0).style.backgroundClip="",l.clearCloneStyle="content-box"===h.style.backgroundClip,g.style.cssText="border:0;width:8px;height:0;top:0;left:-9999px;padding:0;margin-top:1px;position:absolute",g.appendChild(h);function i(){h.style.cssText="-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;position:relative;display:block;margin:auto;border:1px;padding:1px;top:1%;width:50%",h.innerHTML="",Ea.appendChild(g);var d=a.getComputedStyle(h);b="1%"!==d.top,f="2px"===d.marginLeft,c="4px"===d.width,h.style.marginRight="50%",e="4px"===d.marginRight,Ea.removeChild(g)}n.extend(l,{pixelPosition:function(){return i(),b},boxSizingReliable:function(){return null==c&&i(),c},pixelMarginRight:function(){return null==c&&i(),e},reliableMarginLeft:function(){return null==c&&i(),f},reliableMarginRight:function(){var b,c=h.appendChild(d.createElement("div"));return c.style.cssText=h.style.cssText="-webkit-box-sizing:content-box;box-sizing:content-box;display:block;margin:0;border:0;padding:0",c.style.marginRight=c.style.width="0",h.style.width="1px",Ea.appendChild(g),b=!parseFloat(a.getComputedStyle(c).marginRight),Ea.removeChild(g),h.removeChild(c),b}})}}();function Fa(a,b,c){var d,e,f,g,h=a.style;return c=c||Ca(a),g=c?c.getPropertyValue(b)||c[b]:void 0,""!==g&&void 0!==g||n.contains(a.ownerDocument,a)||(g=n.style(a,b)),c&&!l.pixelMarginRight()&&Ba.test(g)&&Aa.test(b)&&(d=h.width,e=h.minWidth,f=h.maxWidth,h.minWidth=h.maxWidth=h.width=g,g=c.width,h.width=d,h.minWidth=e,h.maxWidth=f),void 0!==g?g+"":g}function Ga(a,b){return{get:function(){return a()?void delete this.get:(this.get=b).apply(this,arguments)}}}var Ha=/^(none|table(?!-c[ea]).+)/,Ia={position:"absolute",visibility:"hidden",display:"block"},Ja={letterSpacing:"0",fontWeight:"400"},Ka=["Webkit","O","Moz","ms"],La=d.createElement("div").style;function Ma(a){if(a in La)return a;var b=a[0].toUpperCase()+a.slice(1),c=Ka.length;while(c--)if(a=Ka[c]+b,a in La)return a}function Na(a,b,c){var d=T.exec(b);return d?Math.max(0,d[2]-(c||0))+(d[3]||"px"):b}function Oa(a,b,c,d,e){for(var f=c===(d?"border":"content")?4:"width"===b?1:0,g=0;4>f;f+=2)"margin"===c&&(g+=n.css(a,c+U[f],!0,e)),d?("content"===c&&(g-=n.css(a,"padding"+U[f],!0,e)),"margin"!==c&&(g-=n.css(a,"border"+U[f]+"Width",!0,e))):(g+=n.css(a,"padding"+U[f],!0,e),"padding"!==c&&(g+=n.css(a,"border"+U[f]+"Width",!0,e)));return g}function Pa(a,b,c){var d=!0,e="width"===b?a.offsetWidth:a.offsetHeight,f=Ca(a),g="border-box"===n.css(a,"boxSizing",!1,f);if(0>=e||null==e){if(e=Fa(a,b,f),(0>e||null==e)&&(e=a.style[b]),Ba.test(e))return e;d=g&&(l.boxSizingReliable()||e===a.style[b]),e=parseFloat(e)||0}return e+Oa(a,b,c||(g?"border":"content"),d,f)+"px"}function Qa(a,b){for(var c,d,e,f=[],g=0,h=a.length;h>g;g++)d=a[g],d.style&&(f[g]=N.get(d,"olddisplay"),c=d.style.display,b?(f[g]||"none"!==c||(d.style.display=""),""===d.style.display&&V(d)&&(f[g]=N.access(d,"olddisplay",za(d.nodeName)))):(e=V(d),"none"===c&&e||N.set(d,"olddisplay",e?c:n.css(d,"display"))));for(g=0;h>g;g++)d=a[g],d.style&&(b&&"none"!==d.style.display&&""!==d.style.display||(d.style.display=b?f[g]||"":"none"));return a}n.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=Fa(a,"opacity");return""===c?"1":c}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":"cssFloat"},style:function(a,b,c,d){if(a&&3!==a.nodeType&&8!==a.nodeType&&a.style){var e,f,g,h=n.camelCase(b),i=a.style;return b=n.cssProps[h]||(n.cssProps[h]=Ma(h)||h),g=n.cssHooks[b]||n.cssHooks[h],void 0===c?g&&"get"in g&&void 0!==(e=g.get(a,!1,d))?e:i[b]:(f=typeof c,"string"===f&&(e=T.exec(c))&&e[1]&&(c=W(a,b,e),f="number"),null!=c&&c===c&&("number"===f&&(c+=e&&e[3]||(n.cssNumber[h]?"":"px")),l.clearCloneStyle||""!==c||0!==b.indexOf("background")||(i[b]="inherit"),g&&"set"in g&&void 0===(c=g.set(a,c,d))||(i[b]=c)),void 0)}},css:function(a,b,c,d){var e,f,g,h=n.camelCase(b);return b=n.cssProps[h]||(n.cssProps[h]=Ma(h)||h),g=n.cssHooks[b]||n.cssHooks[h],g&&"get"in g&&(e=g.get(a,!0,c)),void 0===e&&(e=Fa(a,b,d)),"normal"===e&&b in Ja&&(e=Ja[b]),""===c||c?(f=parseFloat(e),c===!0||isFinite(f)?f||0:e):e}}),n.each(["height","width"],function(a,b){n.cssHooks[b]={get:function(a,c,d){return c?Ha.test(n.css(a,"display"))&&0===a.offsetWidth?Da(a,Ia,function(){return Pa(a,b,d)}):Pa(a,b,d):void 0},set:function(a,c,d){var e,f=d&&Ca(a),g=d&&Oa(a,b,d,"border-box"===n.css(a,"boxSizing",!1,f),f);return g&&(e=T.exec(c))&&"px"!==(e[3]||"px")&&(a.style[b]=c,c=n.css(a,b)),Na(a,c,g)}}}),n.cssHooks.marginLeft=Ga(l.reliableMarginLeft,function(a,b){return b?(parseFloat(Fa(a,"marginLeft"))||a.getBoundingClientRect().left-Da(a,{marginLeft:0},function(){return a.getBoundingClientRect().left}))+"px":void 0}),n.cssHooks.marginRight=Ga(l.reliableMarginRight,function(a,b){return b?Da(a,{display:"inline-block"},Fa,[a,"marginRight"]):void 0}),n.each({margin:"",padding:"",border:"Width"},function(a,b){n.cssHooks[a+b]={expand:function(c){for(var d=0,e={},f="string"==typeof c?c.split(" "):[c];4>d;d++)e[a+U[d]+b]=f[d]||f[d-2]||f[0];return e}},Aa.test(a)||(n.cssHooks[a+b].set=Na)}),n.fn.extend({css:function(a,b){return K(this,function(a,b,c){var d,e,f={},g=0;if(n.isArray(b)){for(d=Ca(a),e=b.length;e>g;g++)f[b[g]]=n.css(a,b[g],!1,d);return f}return void 0!==c?n.style(a,b,c):n.css(a,b)},a,b,arguments.length>1)},show:function(){return Qa(this,!0)},hide:function(){return Qa(this)},toggle:function(a){return"boolean"==typeof a?a?this.show():this.hide():this.each(function(){V(this)?n(this).show():n(this).hide()})}});function Ra(a,b,c,d,e){return new Ra.prototype.init(a,b,c,d,e)}n.Tween=Ra,Ra.prototype={constructor:Ra,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||n.easing._default,this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(n.cssNumber[c]?"":"px")},cur:function(){var a=Ra.propHooks[this.prop];return a&&a.get?a.get(this):Ra.propHooks._default.get(this)},run:function(a){var b,c=Ra.propHooks[this.prop];return this.options.duration?this.pos=b=n.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):Ra.propHooks._default.set(this),this}},Ra.prototype.init.prototype=Ra.prototype,Ra.propHooks={_default:{get:function(a){var b;return 1!==a.elem.nodeType||null!=a.elem[a.prop]&&null==a.elem.style[a.prop]?a.elem[a.prop]:(b=n.css(a.elem,a.prop,""),b&&"auto"!==b?b:0)},set:function(a){n.fx.step[a.prop]?n.fx.step[a.prop](a):1!==a.elem.nodeType||null==a.elem.style[n.cssProps[a.prop]]&&!n.cssHooks[a.prop]?a.elem[a.prop]=a.now:n.style(a.elem,a.prop,a.now+a.unit)}}},Ra.propHooks.scrollTop=Ra.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},n.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2},_default:"swing"},n.fx=Ra.prototype.init,n.fx.step={};var Sa,Ta,Ua=/^(?:toggle|show|hide)$/,Va=/queueHooks$/;function Wa(){return a.setTimeout(function(){Sa=void 0}),Sa=n.now()}function Xa(a,b){var c,d=0,e={height:a};for(b=b?1:0;4>d;d+=2-b)c=U[d],e["margin"+c]=e["padding"+c]=a;return b&&(e.opacity=e.width=a),e}function Ya(a,b,c){for(var d,e=(_a.tweeners[b]||[]).concat(_a.tweeners["*"]),f=0,g=e.length;g>f;f++)if(d=e[f].call(c,b,a))return d}function Za(a,b,c){var d,e,f,g,h,i,j,k,l=this,m={},o=a.style,p=a.nodeType&&V(a),q=N.get(a,"fxshow");c.queue||(h=n._queueHooks(a,"fx"),null==h.unqueued&&(h.unqueued=0,i=h.empty.fire,h.empty.fire=function(){h.unqueued||i()}),h.unqueued++,l.always(function(){l.always(function(){h.unqueued--,n.queue(a,"fx").length||h.empty.fire()})})),1===a.nodeType&&("height"in b||"width"in b)&&(c.overflow=[o.overflow,o.overflowX,o.overflowY],j=n.css(a,"display"),k="none"===j?N.get(a,"olddisplay")||za(a.nodeName):j,"inline"===k&&"none"===n.css(a,"float")&&(o.display="inline-block")),c.overflow&&(o.overflow="hidden",l.always(function(){o.overflow=c.overflow[0],o.overflowX=c.overflow[1],o.overflowY=c.overflow[2]}));for(d in b)if(e=b[d],Ua.exec(e)){if(delete b[d],f=f||"toggle"===e,e===(p?"hide":"show")){if("show"!==e||!q||void 0===q[d])continue;p=!0}m[d]=q&&q[d]||n.style(a,d)}else j=void 0;if(n.isEmptyObject(m))"inline"===("none"===j?za(a.nodeName):j)&&(o.display=j);else{q?"hidden"in q&&(p=q.hidden):q=N.access(a,"fxshow",{}),f&&(q.hidden=!p),p?n(a).show():l.done(function(){n(a).hide()}),l.done(function(){var b;N.remove(a,"fxshow");for(b in m)n.style(a,b,m[b])});for(d in m)g=Ya(p?q[d]:0,d,l),d in q||(q[d]=g.start,p&&(g.end=g.start,g.start="width"===d||"height"===d?1:0))}}function $a(a,b){var c,d,e,f,g;for(c in a)if(d=n.camelCase(c),e=b[d],f=a[c],n.isArray(f)&&(e=f[1],f=a[c]=f[0]),c!==d&&(a[d]=f,delete a[c]),g=n.cssHooks[d],g&&"expand"in g){f=g.expand(f),delete a[d];for(c in f)c in a||(a[c]=f[c],b[c]=e)}else b[d]=e}function _a(a,b,c){var d,e,f=0,g=_a.prefilters.length,h=n.Deferred().always(function(){delete i.elem}),i=function(){if(e)return!1;for(var b=Sa||Wa(),c=Math.max(0,j.startTime+j.duration-b),d=c/j.duration||0,f=1-d,g=0,i=j.tweens.length;i>g;g++)j.tweens[g].run(f);return h.notifyWith(a,[j,f,c]),1>f&&i?c:(h.resolveWith(a,[j]),!1)},j=h.promise({elem:a,props:n.extend({},b),opts:n.extend(!0,{specialEasing:{},easing:n.easing._default},c),originalProperties:b,originalOptions:c,startTime:Sa||Wa(),duration:c.duration,tweens:[],createTween:function(b,c){var d=n.Tween(a,j.opts,b,c,j.opts.specialEasing[b]||j.opts.easing);return j.tweens.push(d),d},stop:function(b){var c=0,d=b?j.tweens.length:0;if(e)return this;for(e=!0;d>c;c++)j.tweens[c].run(1);return b?(h.notifyWith(a,[j,1,0]),h.resolveWith(a,[j,b])):h.rejectWith(a,[j,b]),this}}),k=j.props;for($a(k,j.opts.specialEasing);g>f;f++)if(d=_a.prefilters[f].call(j,a,k,j.opts))return n.isFunction(d.stop)&&(n._queueHooks(j.elem,j.opts.queue).stop=n.proxy(d.stop,d)),d;return n.map(k,Ya,j),n.isFunction(j.opts.start)&&j.opts.start.call(a,j),n.fx.timer(n.extend(i,{elem:a,anim:j,queue:j.opts.queue})),j.progress(j.opts.progress).done(j.opts.done,j.opts.complete).fail(j.opts.fail).always(j.opts.always)}n.Animation=n.extend(_a,{tweeners:{"*":[function(a,b){var c=this.createTween(a,b);return W(c.elem,a,T.exec(b),c),c}]},tweener:function(a,b){n.isFunction(a)?(b=a,a=["*"]):a=a.match(G);for(var c,d=0,e=a.length;e>d;d++)c=a[d],_a.tweeners[c]=_a.tweeners[c]||[],_a.tweeners[c].unshift(b)},prefilters:[Za],prefilter:function(a,b){b?_a.prefilters.unshift(a):_a.prefilters.push(a)}}),n.speed=function(a,b,c){var d=a&&"object"==typeof a?n.extend({},a):{complete:c||!c&&b||n.isFunction(a)&&a,duration:a,easing:c&&b||b&&!n.isFunction(b)&&b};return d.duration=n.fx.off?0:"number"==typeof d.duration?d.duration:d.duration in n.fx.speeds?n.fx.speeds[d.duration]:n.fx.speeds._default,null!=d.queue&&d.queue!==!0||(d.queue="fx"),d.old=d.complete,d.complete=function(){n.isFunction(d.old)&&d.old.call(this),d.queue&&n.dequeue(this,d.queue)},d},n.fn.extend({fadeTo:function(a,b,c,d){return this.filter(V).css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=n.isEmptyObject(a),f=n.speed(b,c,d),g=function(){var b=_a(this,n.extend({},a),f);(e||N.get(this,"finish"))&&b.stop(!0)};return g.finish=g,e||f.queue===!1?this.each(g):this.queue(f.queue,g)},stop:function(a,b,c){var d=function(a){var b=a.stop;delete a.stop,b(c)};return"string"!=typeof a&&(c=b,b=a,a=void 0),b&&a!==!1&&this.queue(a||"fx",[]),this.each(function(){var b=!0,e=null!=a&&a+"queueHooks",f=n.timers,g=N.get(this);if(e)g[e]&&g[e].stop&&d(g[e]);else for(e in g)g[e]&&g[e].stop&&Va.test(e)&&d(g[e]);for(e=f.length;e--;)f[e].elem!==this||null!=a&&f[e].queue!==a||(f[e].anim.stop(c),b=!1,f.splice(e,1));!b&&c||n.dequeue(this,a)})},finish:function(a){return a!==!1&&(a=a||"fx"),this.each(function(){var b,c=N.get(this),d=c[a+"queue"],e=c[a+"queueHooks"],f=n.timers,g=d?d.length:0;for(c.finish=!0,n.queue(this,a,[]),e&&e.stop&&e.stop.call(this,!0),b=f.length;b--;)f[b].elem===this&&f[b].queue===a&&(f[b].anim.stop(!0),f.splice(b,1));for(b=0;g>b;b++)d[b]&&d[b].finish&&d[b].finish.call(this);delete c.finish})}}),n.each(["toggle","show","hide"],function(a,b){var c=n.fn[b];n.fn[b]=function(a,d,e){return null==a||"boolean"==typeof a?c.apply(this,arguments):this.animate(Xa(b,!0),a,d,e)}}),n.each({slideDown:Xa("show"),slideUp:Xa("hide"),slideToggle:Xa("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){n.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),n.timers=[],n.fx.tick=function(){var a,b=0,c=n.timers;for(Sa=n.now();b<c.length;b++)a=c[b],a()||c[b]!==a||c.splice(b--,1);c.length||n.fx.stop(),Sa=void 0},n.fx.timer=function(a){n.timers.push(a),a()?n.fx.start():n.timers.pop()},n.fx.interval=13,n.fx.start=function(){Ta||(Ta=a.setInterval(n.fx.tick,n.fx.interval))},n.fx.stop=function(){a.clearInterval(Ta),Ta=null},n.fx.speeds={slow:600,fast:200,_default:400},n.fn.delay=function(b,c){return b=n.fx?n.fx.speeds[b]||b:b,c=c||"fx",this.queue(c,function(c,d){var e=a.setTimeout(c,b);d.stop=function(){a.clearTimeout(e)}})},function(){var a=d.createElement("input"),b=d.createElement("select"),c=b.appendChild(d.createElement("option"));a.type="checkbox",l.checkOn=""!==a.value,l.optSelected=c.selected,b.disabled=!0,l.optDisabled=!c.disabled,a=d.createElement("input"),a.value="t",a.type="radio",l.radioValue="t"===a.value}();var ab,bb=n.expr.attrHandle;n.fn.extend({attr:function(a,b){return K(this,n.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){n.removeAttr(this,a)})}}),n.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return"undefined"==typeof a.getAttribute?n.prop(a,b,c):(1===f&&n.isXMLDoc(a)||(b=b.toLowerCase(),e=n.attrHooks[b]||(n.expr.match.bool.test(b)?ab:void 0)),void 0!==c?null===c?void n.removeAttr(a,b):e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:(a.setAttribute(b,c+""),c):e&&"get"in e&&null!==(d=e.get(a,b))?d:(d=n.find.attr(a,b),null==d?void 0:d))},attrHooks:{type:{set:function(a,b){if(!l.radioValue&&"radio"===b&&n.nodeName(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}},removeAttr:function(a,b){var c,d,e=0,f=b&&b.match(G);if(f&&1===a.nodeType)while(c=f[e++])d=n.propFix[c]||c,n.expr.match.bool.test(c)&&(a[d]=!1),a.removeAttribute(c)}}),ab={set:function(a,b,c){return b===!1?n.removeAttr(a,c):a.setAttribute(c,c),c}},n.each(n.expr.match.bool.source.match(/\w+/g),function(a,b){var c=bb[b]||n.find.attr;bb[b]=function(a,b,d){var e,f;return d||(f=bb[b],bb[b]=e,e=null!=c(a,b,d)?b.toLowerCase():null,bb[b]=f),e}});var cb=/^(?:input|select|textarea|button)$/i,db=/^(?:a|area)$/i;n.fn.extend({prop:function(a,b){return K(this,n.prop,a,b,arguments.length>1)},removeProp:function(a){return this.each(function(){delete this[n.propFix[a]||a]})}}),n.extend({prop:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return 1===f&&n.isXMLDoc(a)||(b=n.propFix[b]||b,e=n.propHooks[b]),
|
||||
void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){var b=n.find.attr(a,"tabindex");return b?parseInt(b,10):cb.test(a.nodeName)||db.test(a.nodeName)&&a.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),l.optSelected||(n.propHooks.selected={get:function(a){var b=a.parentNode;return b&&b.parentNode&&b.parentNode.selectedIndex,null},set:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}}),n.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){n.propFix[this.toLowerCase()]=this});var eb=/[\t\r\n\f]/g;function fb(a){return a.getAttribute&&a.getAttribute("class")||""}n.fn.extend({addClass:function(a){var b,c,d,e,f,g,h,i=0;if(n.isFunction(a))return this.each(function(b){n(this).addClass(a.call(this,b,fb(this)))});if("string"==typeof a&&a){b=a.match(G)||[];while(c=this[i++])if(e=fb(c),d=1===c.nodeType&&(" "+e+" ").replace(eb," ")){g=0;while(f=b[g++])d.indexOf(" "+f+" ")<0&&(d+=f+" ");h=n.trim(d),e!==h&&c.setAttribute("class",h)}}return this},removeClass:function(a){var b,c,d,e,f,g,h,i=0;if(n.isFunction(a))return this.each(function(b){n(this).removeClass(a.call(this,b,fb(this)))});if(!arguments.length)return this.attr("class","");if("string"==typeof a&&a){b=a.match(G)||[];while(c=this[i++])if(e=fb(c),d=1===c.nodeType&&(" "+e+" ").replace(eb," ")){g=0;while(f=b[g++])while(d.indexOf(" "+f+" ")>-1)d=d.replace(" "+f+" "," ");h=n.trim(d),e!==h&&c.setAttribute("class",h)}}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):n.isFunction(a)?this.each(function(c){n(this).toggleClass(a.call(this,c,fb(this),b),b)}):this.each(function(){var b,d,e,f;if("string"===c){d=0,e=n(this),f=a.match(G)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else void 0!==a&&"boolean"!==c||(b=fb(this),b&&N.set(this,"__className__",b),this.setAttribute&&this.setAttribute("class",b||a===!1?"":N.get(this,"__className__")||""))})},hasClass:function(a){var b,c,d=0;b=" "+a+" ";while(c=this[d++])if(1===c.nodeType&&(" "+fb(c)+" ").replace(eb," ").indexOf(b)>-1)return!0;return!1}});var gb=/\r/g,hb=/[\x20\t\r\n\f]+/g;n.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=n.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,n(this).val()):a,null==e?e="":"number"==typeof e?e+="":n.isArray(e)&&(e=n.map(e,function(a){return null==a?"":a+""})),b=n.valHooks[this.type]||n.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=n.valHooks[e.type]||n.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(gb,""):null==c?"":c)}}}),n.extend({valHooks:{option:{get:function(a){var b=n.find.attr(a,"value");return null!=b?b:n.trim(n.text(a)).replace(hb," ")}},select:{get:function(a){for(var b,c,d=a.options,e=a.selectedIndex,f="select-one"===a.type||0>e,g=f?null:[],h=f?e+1:d.length,i=0>e?h:f?e:0;h>i;i++)if(c=d[i],(c.selected||i===e)&&(l.optDisabled?!c.disabled:null===c.getAttribute("disabled"))&&(!c.parentNode.disabled||!n.nodeName(c.parentNode,"optgroup"))){if(b=n(c).val(),f)return b;g.push(b)}return g},set:function(a,b){var c,d,e=a.options,f=n.makeArray(b),g=e.length;while(g--)d=e[g],(d.selected=n.inArray(n.valHooks.option.get(d),f)>-1)&&(c=!0);return c||(a.selectedIndex=-1),f}}}}),n.each(["radio","checkbox"],function(){n.valHooks[this]={set:function(a,b){return n.isArray(b)?a.checked=n.inArray(n(a).val(),b)>-1:void 0}},l.checkOn||(n.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})});var ib=/^(?:focusinfocus|focusoutblur)$/;n.extend(n.event,{trigger:function(b,c,e,f){var g,h,i,j,l,m,o,p=[e||d],q=k.call(b,"type")?b.type:b,r=k.call(b,"namespace")?b.namespace.split("."):[];if(h=i=e=e||d,3!==e.nodeType&&8!==e.nodeType&&!ib.test(q+n.event.triggered)&&(q.indexOf(".")>-1&&(r=q.split("."),q=r.shift(),r.sort()),l=q.indexOf(":")<0&&"on"+q,b=b[n.expando]?b:new n.Event(q,"object"==typeof b&&b),b.isTrigger=f?2:3,b.namespace=r.join("."),b.rnamespace=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=e),c=null==c?[b]:n.makeArray(c,[b]),o=n.event.special[q]||{},f||!o.trigger||o.trigger.apply(e,c)!==!1)){if(!f&&!o.noBubble&&!n.isWindow(e)){for(j=o.delegateType||q,ib.test(j+q)||(h=h.parentNode);h;h=h.parentNode)p.push(h),i=h;i===(e.ownerDocument||d)&&p.push(i.defaultView||i.parentWindow||a)}g=0;while((h=p[g++])&&!b.isPropagationStopped())b.type=g>1?j:o.bindType||q,m=(N.get(h,"events")||{})[b.type]&&N.get(h,"handle"),m&&m.apply(h,c),m=l&&h[l],m&&m.apply&&L(h)&&(b.result=m.apply(h,c),b.result===!1&&b.preventDefault());return b.type=q,f||b.isDefaultPrevented()||o._default&&o._default.apply(p.pop(),c)!==!1||!L(e)||l&&n.isFunction(e[q])&&!n.isWindow(e)&&(i=e[l],i&&(e[l]=null),n.event.triggered=q,e[q](),n.event.triggered=void 0,i&&(e[l]=i)),b.result}},simulate:function(a,b,c){var d=n.extend(new n.Event,c,{type:a,isSimulated:!0});n.event.trigger(d,null,b)}}),n.fn.extend({trigger:function(a,b){return this.each(function(){n.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];return c?n.event.trigger(a,b,c,!0):void 0}}),n.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){n.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),n.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),l.focusin="onfocusin"in a,l.focusin||n.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){n.event.simulate(b,a.target,n.event.fix(a))};n.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=N.access(d,b);e||d.addEventListener(a,c,!0),N.access(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=N.access(d,b)-1;e?N.access(d,b,e):(d.removeEventListener(a,c,!0),N.remove(d,b))}}});var jb=a.location,kb=n.now(),lb=/\?/;n.parseJSON=function(a){return JSON.parse(a+"")},n.parseXML=function(b){var c;if(!b||"string"!=typeof b)return null;try{c=(new a.DOMParser).parseFromString(b,"text/xml")}catch(d){c=void 0}return c&&!c.getElementsByTagName("parsererror").length||n.error("Invalid XML: "+b),c};var mb=/#.*$/,nb=/([?&])_=[^&]*/,ob=/^(.*?):[ \t]*([^\r\n]*)$/gm,pb=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,qb=/^(?:GET|HEAD)$/,rb=/^\/\//,sb={},tb={},ub="*/".concat("*"),vb=d.createElement("a");vb.href=jb.href;function wb(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(G)||[];if(n.isFunction(c))while(d=f[e++])"+"===d[0]?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function xb(a,b,c,d){var e={},f=a===tb;function g(h){var i;return e[h]=!0,n.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function yb(a,b){var c,d,e=n.ajaxSettings.flatOptions||{};for(c in b)void 0!==b[c]&&((e[c]?a:d||(d={}))[c]=b[c]);return d&&n.extend(!0,a,d),a}function zb(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===d&&(d=a.mimeType||b.getResponseHeader("Content-Type"));if(d)for(e in h)if(h[e]&&h[e].test(d)){i.unshift(e);break}if(i[0]in c)f=i[0];else{for(e in c){if(!i[0]||a.converters[e+" "+i[0]]){f=e;break}g||(g=e)}f=f||g}return f?(f!==i[0]&&i.unshift(f),c[f]):void 0}function Ab(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}n.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:jb.href,type:"GET",isLocal:pb.test(jb.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":ub,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":n.parseJSON,"text xml":n.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?yb(yb(a,n.ajaxSettings),b):yb(n.ajaxSettings,a)},ajaxPrefilter:wb(sb),ajaxTransport:wb(tb),ajax:function(b,c){"object"==typeof b&&(c=b,b=void 0),c=c||{};var e,f,g,h,i,j,k,l,m=n.ajaxSetup({},c),o=m.context||m,p=m.context&&(o.nodeType||o.jquery)?n(o):n.event,q=n.Deferred(),r=n.Callbacks("once memory"),s=m.statusCode||{},t={},u={},v=0,w="canceled",x={readyState:0,getResponseHeader:function(a){var b;if(2===v){if(!h){h={};while(b=ob.exec(g))h[b[1].toLowerCase()]=b[2]}b=h[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return 2===v?g:null},setRequestHeader:function(a,b){var c=a.toLowerCase();return v||(a=u[c]=u[c]||a,t[a]=b),this},overrideMimeType:function(a){return v||(m.mimeType=a),this},statusCode:function(a){var b;if(a)if(2>v)for(b in a)s[b]=[s[b],a[b]];else x.always(a[x.status]);return this},abort:function(a){var b=a||w;return e&&e.abort(b),z(0,b),this}};if(q.promise(x).complete=r.add,x.success=x.done,x.error=x.fail,m.url=((b||m.url||jb.href)+"").replace(mb,"").replace(rb,jb.protocol+"//"),m.type=c.method||c.type||m.method||m.type,m.dataTypes=n.trim(m.dataType||"*").toLowerCase().match(G)||[""],null==m.crossDomain){j=d.createElement("a");try{j.href=m.url,j.href=j.href,m.crossDomain=vb.protocol+"//"+vb.host!=j.protocol+"//"+j.host}catch(y){m.crossDomain=!0}}if(m.data&&m.processData&&"string"!=typeof m.data&&(m.data=n.param(m.data,m.traditional)),xb(sb,m,c,x),2===v)return x;k=n.event&&m.global,k&&0===n.active++&&n.event.trigger("ajaxStart"),m.type=m.type.toUpperCase(),m.hasContent=!qb.test(m.type),f=m.url,m.hasContent||(m.data&&(f=m.url+=(lb.test(f)?"&":"?")+m.data,delete m.data),m.cache===!1&&(m.url=nb.test(f)?f.replace(nb,"$1_="+kb++):f+(lb.test(f)?"&":"?")+"_="+kb++)),m.ifModified&&(n.lastModified[f]&&x.setRequestHeader("If-Modified-Since",n.lastModified[f]),n.etag[f]&&x.setRequestHeader("If-None-Match",n.etag[f])),(m.data&&m.hasContent&&m.contentType!==!1||c.contentType)&&x.setRequestHeader("Content-Type",m.contentType),x.setRequestHeader("Accept",m.dataTypes[0]&&m.accepts[m.dataTypes[0]]?m.accepts[m.dataTypes[0]]+("*"!==m.dataTypes[0]?", "+ub+"; q=0.01":""):m.accepts["*"]);for(l in m.headers)x.setRequestHeader(l,m.headers[l]);if(m.beforeSend&&(m.beforeSend.call(o,x,m)===!1||2===v))return x.abort();w="abort";for(l in{success:1,error:1,complete:1})x[l](m[l]);if(e=xb(tb,m,c,x)){if(x.readyState=1,k&&p.trigger("ajaxSend",[x,m]),2===v)return x;m.async&&m.timeout>0&&(i=a.setTimeout(function(){x.abort("timeout")},m.timeout));try{v=1,e.send(t,z)}catch(y){if(!(2>v))throw y;z(-1,y)}}else z(-1,"No Transport");function z(b,c,d,h){var j,l,t,u,w,y=c;2!==v&&(v=2,i&&a.clearTimeout(i),e=void 0,g=h||"",x.readyState=b>0?4:0,j=b>=200&&300>b||304===b,d&&(u=zb(m,x,d)),u=Ab(m,u,x,j),j?(m.ifModified&&(w=x.getResponseHeader("Last-Modified"),w&&(n.lastModified[f]=w),w=x.getResponseHeader("etag"),w&&(n.etag[f]=w)),204===b||"HEAD"===m.type?y="nocontent":304===b?y="notmodified":(y=u.state,l=u.data,t=u.error,j=!t)):(t=y,!b&&y||(y="error",0>b&&(b=0))),x.status=b,x.statusText=(c||y)+"",j?q.resolveWith(o,[l,y,x]):q.rejectWith(o,[x,y,t]),x.statusCode(s),s=void 0,k&&p.trigger(j?"ajaxSuccess":"ajaxError",[x,m,j?l:t]),r.fireWith(o,[x,y]),k&&(p.trigger("ajaxComplete",[x,m]),--n.active||n.event.trigger("ajaxStop")))}return x},getJSON:function(a,b,c){return n.get(a,b,c,"json")},getScript:function(a,b){return n.get(a,void 0,b,"script")}}),n.each(["get","post"],function(a,b){n[b]=function(a,c,d,e){return n.isFunction(c)&&(e=e||d,d=c,c=void 0),n.ajax(n.extend({url:a,type:b,dataType:e,data:c,success:d},n.isPlainObject(a)&&a))}}),n._evalUrl=function(a){return n.ajax({url:a,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})},n.fn.extend({wrapAll:function(a){var b;return n.isFunction(a)?this.each(function(b){n(this).wrapAll(a.call(this,b))}):(this[0]&&(b=n(a,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstElementChild)a=a.firstElementChild;return a}).append(this)),this)},wrapInner:function(a){return n.isFunction(a)?this.each(function(b){n(this).wrapInner(a.call(this,b))}):this.each(function(){var b=n(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=n.isFunction(a);return this.each(function(c){n(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){n.nodeName(this,"body")||n(this).replaceWith(this.childNodes)}).end()}}),n.expr.filters.hidden=function(a){return!n.expr.filters.visible(a)},n.expr.filters.visible=function(a){return a.offsetWidth>0||a.offsetHeight>0||a.getClientRects().length>0};var Bb=/%20/g,Cb=/\[\]$/,Db=/\r?\n/g,Eb=/^(?:submit|button|image|reset|file)$/i,Fb=/^(?:input|select|textarea|keygen)/i;function Gb(a,b,c,d){var e;if(n.isArray(b))n.each(b,function(b,e){c||Cb.test(a)?d(a,e):Gb(a+"["+("object"==typeof e&&null!=e?b:"")+"]",e,c,d)});else if(c||"object"!==n.type(b))d(a,b);else for(e in b)Gb(a+"["+e+"]",b[e],c,d)}n.param=function(a,b){var c,d=[],e=function(a,b){b=n.isFunction(b)?b():null==b?"":b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};if(void 0===b&&(b=n.ajaxSettings&&n.ajaxSettings.traditional),n.isArray(a)||a.jquery&&!n.isPlainObject(a))n.each(a,function(){e(this.name,this.value)});else for(c in a)Gb(c,a[c],b,e);return d.join("&").replace(Bb,"+")},n.fn.extend({serialize:function(){return n.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=n.prop(this,"elements");return a?n.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!n(this).is(":disabled")&&Fb.test(this.nodeName)&&!Eb.test(a)&&(this.checked||!X.test(a))}).map(function(a,b){var c=n(this).val();return null==c?null:n.isArray(c)?n.map(c,function(a){return{name:b.name,value:a.replace(Db,"\r\n")}}):{name:b.name,value:c.replace(Db,"\r\n")}}).get()}}),n.ajaxSettings.xhr=function(){try{return new a.XMLHttpRequest}catch(b){}};var Hb={0:200,1223:204},Ib=n.ajaxSettings.xhr();l.cors=!!Ib&&"withCredentials"in Ib,l.ajax=Ib=!!Ib,n.ajaxTransport(function(b){var c,d;return l.cors||Ib&&!b.crossDomain?{send:function(e,f){var g,h=b.xhr();if(h.open(b.type,b.url,b.async,b.username,b.password),b.xhrFields)for(g in b.xhrFields)h[g]=b.xhrFields[g];b.mimeType&&h.overrideMimeType&&h.overrideMimeType(b.mimeType),b.crossDomain||e["X-Requested-With"]||(e["X-Requested-With"]="XMLHttpRequest");for(g in e)h.setRequestHeader(g,e[g]);c=function(a){return function(){c&&(c=d=h.onload=h.onerror=h.onabort=h.onreadystatechange=null,"abort"===a?h.abort():"error"===a?"number"!=typeof h.status?f(0,"error"):f(h.status,h.statusText):f(Hb[h.status]||h.status,h.statusText,"text"!==(h.responseType||"text")||"string"!=typeof h.responseText?{binary:h.response}:{text:h.responseText},h.getAllResponseHeaders()))}},h.onload=c(),d=h.onerror=c("error"),void 0!==h.onabort?h.onabort=d:h.onreadystatechange=function(){4===h.readyState&&a.setTimeout(function(){c&&d()})},c=c("abort");try{h.send(b.hasContent&&b.data||null)}catch(i){if(c)throw i}},abort:function(){c&&c()}}:void 0}),n.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(a){return n.globalEval(a),a}}}),n.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET")}),n.ajaxTransport("script",function(a){if(a.crossDomain){var b,c;return{send:function(e,f){b=n("<script>").prop({charset:a.scriptCharset,src:a.url}).on("load error",c=function(a){b.remove(),c=null,a&&f("error"===a.type?404:200,a.type)}),d.head.appendChild(b[0])},abort:function(){c&&c()}}}});var Jb=[],Kb=/(=)\?(?=&|$)|\?\?/;n.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var a=Jb.pop()||n.expando+"_"+kb++;return this[a]=!0,a}}),n.ajaxPrefilter("json jsonp",function(b,c,d){var e,f,g,h=b.jsonp!==!1&&(Kb.test(b.url)?"url":"string"==typeof b.data&&0===(b.contentType||"").indexOf("application/x-www-form-urlencoded")&&Kb.test(b.data)&&"data");return h||"jsonp"===b.dataTypes[0]?(e=b.jsonpCallback=n.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,h?b[h]=b[h].replace(Kb,"$1"+e):b.jsonp!==!1&&(b.url+=(lb.test(b.url)?"&":"?")+b.jsonp+"="+e),b.converters["script json"]=function(){return g||n.error(e+" was not called"),g[0]},b.dataTypes[0]="json",f=a[e],a[e]=function(){g=arguments},d.always(function(){void 0===f?n(a).removeProp(e):a[e]=f,b[e]&&(b.jsonpCallback=c.jsonpCallback,Jb.push(e)),g&&n.isFunction(f)&&f(g[0]),g=f=void 0}),"script"):void 0}),n.parseHTML=function(a,b,c){if(!a||"string"!=typeof a)return null;"boolean"==typeof b&&(c=b,b=!1),b=b||d;var e=x.exec(a),f=!c&&[];return e?[b.createElement(e[1])]:(e=ca([a],b,f),f&&f.length&&n(f).remove(),n.merge([],e.childNodes))};var Lb=n.fn.load;n.fn.load=function(a,b,c){if("string"!=typeof a&&Lb)return Lb.apply(this,arguments);var d,e,f,g=this,h=a.indexOf(" ");return h>-1&&(d=n.trim(a.slice(h)),a=a.slice(0,h)),n.isFunction(b)?(c=b,b=void 0):b&&"object"==typeof b&&(e="POST"),g.length>0&&n.ajax({url:a,type:e||"GET",dataType:"html",data:b}).done(function(a){f=arguments,g.html(d?n("<div>").append(n.parseHTML(a)).find(d):a)}).always(c&&function(a,b){g.each(function(){c.apply(this,f||[a.responseText,b,a])})}),this},n.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(a,b){n.fn[b]=function(a){return this.on(b,a)}}),n.expr.filters.animated=function(a){return n.grep(n.timers,function(b){return a===b.elem}).length};function Mb(a){return n.isWindow(a)?a:9===a.nodeType&&a.defaultView}n.offset={setOffset:function(a,b,c){var d,e,f,g,h,i,j,k=n.css(a,"position"),l=n(a),m={};"static"===k&&(a.style.position="relative"),h=l.offset(),f=n.css(a,"top"),i=n.css(a,"left"),j=("absolute"===k||"fixed"===k)&&(f+i).indexOf("auto")>-1,j?(d=l.position(),g=d.top,e=d.left):(g=parseFloat(f)||0,e=parseFloat(i)||0),n.isFunction(b)&&(b=b.call(a,c,n.extend({},h))),null!=b.top&&(m.top=b.top-h.top+g),null!=b.left&&(m.left=b.left-h.left+e),"using"in b?b.using.call(a,m):l.css(m)}},n.fn.extend({offset:function(a){if(arguments.length)return void 0===a?this:this.each(function(b){n.offset.setOffset(this,a,b)});var b,c,d=this[0],e={top:0,left:0},f=d&&d.ownerDocument;if(f)return b=f.documentElement,n.contains(b,d)?(e=d.getBoundingClientRect(),c=Mb(f),{top:e.top+c.pageYOffset-b.clientTop,left:e.left+c.pageXOffset-b.clientLeft}):e},position:function(){if(this[0]){var a,b,c=this[0],d={top:0,left:0};return"fixed"===n.css(c,"position")?b=c.getBoundingClientRect():(a=this.offsetParent(),b=this.offset(),n.nodeName(a[0],"html")||(d=a.offset()),d.top+=n.css(a[0],"borderTopWidth",!0),d.left+=n.css(a[0],"borderLeftWidth",!0)),{top:b.top-d.top-n.css(c,"marginTop",!0),left:b.left-d.left-n.css(c,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var a=this.offsetParent;while(a&&"static"===n.css(a,"position"))a=a.offsetParent;return a||Ea})}}),n.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,b){var c="pageYOffset"===b;n.fn[a]=function(d){return K(this,function(a,d,e){var f=Mb(a);return void 0===e?f?f[b]:a[d]:void(f?f.scrollTo(c?f.pageXOffset:e,c?e:f.pageYOffset):a[d]=e)},a,d,arguments.length)}}),n.each(["top","left"],function(a,b){n.cssHooks[b]=Ga(l.pixelPosition,function(a,c){return c?(c=Fa(a,b),Ba.test(c)?n(a).position()[b]+"px":c):void 0})}),n.each({Height:"height",Width:"width"},function(a,b){n.each({padding:"inner"+a,content:b,"":"outer"+a},function(c,d){n.fn[d]=function(d,e){var f=arguments.length&&(c||"boolean"!=typeof d),g=c||(d===!0||e===!0?"margin":"border");return K(this,function(b,c,d){var e;return n.isWindow(b)?b.document.documentElement["client"+a]:9===b.nodeType?(e=b.documentElement,Math.max(b.body["scroll"+a],e["scroll"+a],b.body["offset"+a],e["offset"+a],e["client"+a])):void 0===d?n.css(b,c,g):n.style(b,c,d,g)},b,f?d:void 0,f,null)}})}),n.fn.extend({bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return 1===arguments.length?this.off(a,"**"):this.off(b,a||"**",c)},size:function(){return this.length}}),n.fn.andSelf=n.fn.addBack,"function"==typeof define&&define.amd&&define("jquery",[],function(){return n});var Nb=a.jQuery,Ob=a.$;return n.noConflict=function(b){return a.$===n&&(a.$=Ob),b&&a.jQuery===n&&(a.jQuery=Nb),n},b||(a.jQuery=a.$=n),n});
|
||||
@@ -0,0 +1,2 @@
|
||||
/*! layer-v3.1.1 Web弹层组件 MIT License http://layer.layui.com/ By 贤心 */
|
||||
;!function(e,t){"use strict";var i,n,a=e.layui&&layui.define,o={getPath:function(){var e=document.currentScript?document.currentScript.src:function(){for(var e,t=document.scripts,i=t.length-1,n=i;n>0;n--)if("interactive"===t[n].readyState){e=t[n].src;break}return e||t[i].src}();return e.substring(0,e.lastIndexOf("/")+1)}(),config:{},end:{},minIndex:0,minLeft:[],btn:["确定","取消"],type:["dialog","page","iframe","loading","tips"],getStyle:function(t,i){var n=t.currentStyle?t.currentStyle:e.getComputedStyle(t,null);return n[n.getPropertyValue?"getPropertyValue":"getAttribute"](i)},link:function(t,i,n){if(r.path){var a=document.getElementsByTagName("head")[0],s=document.createElement("link");"string"==typeof i&&(n=i);var l=(n||t).replace(/\.|\//g,""),f="layuicss-"+l,c=0;s.rel="stylesheet",s.href=r.path+t,s.id=f,document.getElementById(f)||a.appendChild(s),"function"==typeof i&&!function u(){return++c>80?e.console&&console.error("layer.css: Invalid"):void(1989===parseInt(o.getStyle(document.getElementById(f),"width"))?i():setTimeout(u,100))}()}}},r={v:"3.1.1",ie:function(){var t=navigator.userAgent.toLowerCase();return!!(e.ActiveXObject||"ActiveXObject"in e)&&((t.match(/msie\s(\d+)/)||[])[1]||"11")}(),index:e.layer&&e.layer.v?1e5:0,path:o.getPath,config:function(e,t){return e=e||{},r.cache=o.config=i.extend({},o.config,e),r.path=o.config.path||r.path,"string"==typeof e.extend&&(e.extend=[e.extend]),o.config.path&&r.ready(),e.extend?(a?layui.addcss("modules/layer/"+e.extend):o.link("theme/"+e.extend),this):this},ready:function(e){var t="layer",i="",n=(a?"modules/layer/":"theme/")+"default/layer.css?v="+r.v+i;return a?layui.addcss(n,e,t):o.link(n,e,t),this},alert:function(e,t,n){var a="function"==typeof t;return a&&(n=t),r.open(i.extend({content:e,yes:n},a?{}:t))},confirm:function(e,t,n,a){var s="function"==typeof t;return s&&(a=n,n=t),r.open(i.extend({content:e,btn:o.btn,yes:n,btn2:a},s?{}:t))},msg:function(e,n,a){var s="function"==typeof n,f=o.config.skin,c=(f?f+" "+f+"-msg":"")||"layui-layer-msg",u=l.anim.length-1;return s&&(a=n),r.open(i.extend({content:e,time:3e3,shade:!1,skin:c,title:!1,closeBtn:!1,btn:!1,resize:!1,end:a},s&&!o.config.skin?{skin:c+" layui-layer-hui",anim:u}:function(){return n=n||{},(n.icon===-1||n.icon===t&&!o.config.skin)&&(n.skin=c+" "+(n.skin||"layui-layer-hui")),n}()))},load:function(e,t){return r.open(i.extend({type:3,icon:e||0,resize:!1,shade:.01},t))},tips:function(e,t,n){return r.open(i.extend({type:4,content:[e,t],closeBtn:!1,time:3e3,shade:!1,resize:!1,fixed:!1,maxWidth:210},n))}},s=function(e){var t=this;t.index=++r.index,t.config=i.extend({},t.config,o.config,e),document.body?t.creat():setTimeout(function(){t.creat()},30)};s.pt=s.prototype;var l=["layui-layer",".layui-layer-title",".layui-layer-main",".layui-layer-dialog","layui-layer-iframe","layui-layer-content","layui-layer-btn","layui-layer-close"];l.anim=["layer-anim-00","layer-anim-01","layer-anim-02","layer-anim-03","layer-anim-04","layer-anim-05","layer-anim-06"],s.pt.config={type:0,shade:.3,fixed:!0,move:l[1],title:"信息",offset:"auto",area:"auto",closeBtn:1,time:0,zIndex:19891014,maxWidth:360,anim:0,isOutAnim:!0,icon:-1,moveType:1,resize:!0,scrollbar:!0,tips:2},s.pt.vessel=function(e,t){var n=this,a=n.index,r=n.config,s=r.zIndex+a,f="object"==typeof r.title,c=r.maxmin&&(1===r.type||2===r.type),u=r.title?'<div class="layui-layer-title" style="'+(f?r.title[1]:"")+'">'+(f?r.title[0]:r.title)+"</div>":"";return r.zIndex=s,t([r.shade?'<div class="layui-layer-shade" id="layui-layer-shade'+a+'" times="'+a+'" style="'+("z-index:"+(s-1)+"; ")+'"></div>':"",'<div class="'+l[0]+(" layui-layer-"+o.type[r.type])+(0!=r.type&&2!=r.type||r.shade?"":" layui-layer-border")+" "+(r.skin||"")+'" id="'+l[0]+a+'" type="'+o.type[r.type]+'" times="'+a+'" showtime="'+r.time+'" conType="'+(e?"object":"string")+'" style="z-index: '+s+"; width:"+r.area[0]+";height:"+r.area[1]+(r.fixed?"":";position:absolute;")+'">'+(e&&2!=r.type?"":u)+'<div id="'+(r.id||"")+'" class="layui-layer-content'+(0==r.type&&r.icon!==-1?" layui-layer-padding":"")+(3==r.type?" layui-layer-loading"+r.icon:"")+'">'+(0==r.type&&r.icon!==-1?'<i class="layui-layer-ico layui-layer-ico'+r.icon+'"></i>':"")+(1==r.type&&e?"":r.content||"")+'</div><span class="layui-layer-setwin">'+function(){var e=c?'<a class="layui-layer-min" href="javascript:;"><cite></cite></a><a class="layui-layer-ico layui-layer-max" href="javascript:;"></a>':"";return r.closeBtn&&(e+='<a class="layui-layer-ico '+l[7]+" "+l[7]+(r.title?r.closeBtn:4==r.type?"1":"2")+'" href="javascript:;"></a>'),e}()+"</span>"+(r.btn?function(){var e="";"string"==typeof r.btn&&(r.btn=[r.btn]);for(var t=0,i=r.btn.length;t<i;t++)e+='<a class="'+l[6]+t+'">'+r.btn[t]+"</a>";return'<div class="'+l[6]+" layui-layer-btn-"+(r.btnAlign||"")+'">'+e+"</div>"}():"")+(r.resize?'<span class="layui-layer-resize"></span>':"")+"</div>"],u,i('<div class="layui-layer-move"></div>')),n},s.pt.creat=function(){var e=this,t=e.config,a=e.index,s=t.content,f="object"==typeof s,c=i("body");if(!t.id||!i("#"+t.id)[0]){switch("string"==typeof t.area&&(t.area="auto"===t.area?["",""]:[t.area,""]),t.shift&&(t.anim=t.shift),6==r.ie&&(t.fixed=!1),t.type){case 0:t.btn="btn"in t?t.btn:o.btn[0],r.closeAll("dialog");break;case 2:var s=t.content=f?t.content:[t.content||"http://layer.layui.com","auto"];t.content='<iframe scrolling="'+(t.content[1]||"auto")+'" allowtransparency="true" id="'+l[4]+a+'" name="'+l[4]+a+'" onload="this.className=\'\';" class="layui-layer-load" frameborder="0" src="'+t.content[0]+'"></iframe>';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]+'<i class="layui-layer-TipsG"></i>',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;a<c.stX&&(a=c.stX),a>f&&(a=f),o<c.stY&&(o=c.stY),o>u&&(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?'<textarea class="layui-layer-input"'+a+">"+(e.value||"")+"</textarea>":function(){return'<input type="'+(1==e.formType?"password":"text")+'" class="layui-layer-input" value="'+(e.value||"")+'">'}(),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='<span class="'+n+'">'+t[0].title+"</span>";i<e;i++)a+="<span>"+t[i].title+"</span>";return a}(),content:'<ul class="layui-layer-tabmain">'+function(){var e=t.length,i=1,a="";if(e>0)for(a='<li class="layui-layer-tabli '+n+'">'+(t[0].content||"no content")+"</li>";i<e;i++)a+='<li class="layui-layer-tabli">'+(t[i].content||"no content")+"</li>";return a}()+"</ul>",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]<r[1]&&(a[0]=a[0]/r[1],a[1]=a[1]/r[1])}return[a[0]+"px",a[1]+"px"]}(),title:!1,shade:.9,shadeClose:!0,closeBtn:!1,move:".layui-layer-phimg img",moveType:1,scrollbar:!1,moveOut:!0,isOutAnim:!1,skin:"layui-layer-photos"+c("photos"),content:'<div class="layui-layer-phimg"><img src="'+u[d].src+'" alt="'+(u[d].alt||"")+'" layer-pid="'+u[d].pid+'"><div class="layui-layer-imgsee">'+(u.length>1?'<span class="layui-layer-imguide"><a href="javascript:;" class="layui-layer-iconext layui-layer-imgprev"></a><a href="javascript:;" class="layui-layer-iconext layui-layer-imgnext"></a></span>':"")+'<div class="layui-layer-imgbar" style="display:'+(a?"block":"")+'"><span class="layui-layer-imgtit"><a href="javascript:;">'+(u[d].alt||"")+"</a><em>"+s.imgIndex+"/"+u.length+"</em></span></div></div></div>",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("当前图片地址异常<br>是否继续查看下一张?",{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);
|
||||
@@ -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?'<h3 style="'+(e?n.title[1]:"")+'">'+(e?n.title[0]:n.title)+"</h3>":""}(),c=function(){"string"==typeof n.btn&&(n.btn=[n.btn]);var e,t=(n.btn||[]).length;return 0!==t&&n.btn?(e='<span yes type="1">'+n.btn[0]+"</span>",2===t&&(e='<span no type="0">'+n.btn[1]+"</span>"+e),'<div class="layui-m-layerbtn">'+e+"</div>"):""}();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='<i></i><i class="layui-m-layerload"></i><i></i><p>'+(n.content||"")+"</p>"),n.skin&&(n.anim="up"),"msg"===n.skin&&(n.shade=!1),s.innerHTML=(n.shade?"<div "+("string"==typeof n.shade?'style="'+n.shade+'"':"")+' class="layui-m-layershade"></div>':"")+'<div class="layui-m-layermain" '+(n.fixed?"":'style="position:static;"')+'><div class="layui-m-layersection"><div class="layui-m-layerchild '+(n.skin?"layui-m-layer-"+n.skin+" ":"")+(n.className?n.className:"")+" "+(n.anim?"layui-m-anim-"+n.anim:"")+'" '+(n.style?'style="'+n.style+'"':"")+">"+l+'<div class="layui-m-layercont">'+n.content+"</div>"+c+"</div></div></div>",!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;o<r;o++)l.touch(s[o],a);if(e.shade&&e.shadeClose){var c=t[i]("layui-m-layershade")[0];l.touch(c,function(){layer.close(n.index,e.end)})}e.end&&(l.end[n.index]=e.end)},e.layer={v:"2.0",index:r,open:function(e){var t=new c(e||{});return t.index},close:function(e){var n=a("#"+o[0]+e)[0];n&&(n.innerHTML="",t.body.removeChild(n),clearTimeout(l.timer[e]),delete l.timer[e],"function"==typeof l.end[e]&&l.end[e](),delete l.end[e])},closeAll:function(){for(var e=t[i](o[0]),n=0,a=e.length;n<a;n++)layer.close(0|e[0].getAttribute("index"))}},"function"==typeof define?define(function(){return layer}):function(){var e=document.scripts,n=e[e.length-1],i=n.src,a=i.substring(0,i.lastIndexOf("/")+1);n.getAttribute("merge")||document.head.appendChild(function(){var e=t.createElement("link");return e.href=a+"need/layer.css?2.0",e.type="text/css",e.rel="styleSheet",e.id="layermcss",e}())}()}(window);
|
||||
@@ -0,0 +1 @@
|
||||
.layui-m-layer{position:relative;z-index:19891014}.layui-m-layer *{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}.layui-m-layermain,.layui-m-layershade{position:fixed;left:0;top:0;width:100%;height:100%}.layui-m-layershade{background-color:rgba(0,0,0,.7);pointer-events:auto}.layui-m-layermain{display:table;font-family:Helvetica,arial,sans-serif;pointer-events:none}.layui-m-layermain .layui-m-layersection{display:table-cell;vertical-align:middle;text-align:center}.layui-m-layerchild{position:relative;display:inline-block;text-align:left;background-color:#fff;font-size:14px;border-radius:5px;box-shadow:0 0 8px rgba(0,0,0,.1);pointer-events:auto;-webkit-overflow-scrolling:touch;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-duration:.2s;animation-duration:.2s}@-webkit-keyframes layui-m-anim-scale{0%{opacity:0;-webkit-transform:scale(.5);transform:scale(.5)}100%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}}@keyframes layui-m-anim-scale{0%{opacity:0;-webkit-transform:scale(.5);transform:scale(.5)}100%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}}.layui-m-anim-scale{animation-name:layui-m-anim-scale;-webkit-animation-name:layui-m-anim-scale}@-webkit-keyframes layui-m-anim-up{0%{opacity:0;-webkit-transform:translateY(800px);transform:translateY(800px)}100%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@keyframes layui-m-anim-up{0%{opacity:0;-webkit-transform:translateY(800px);transform:translateY(800px)}100%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}.layui-m-anim-up{-webkit-animation-name:layui-m-anim-up;animation-name:layui-m-anim-up}.layui-m-layer0 .layui-m-layerchild{width:90%;max-width:640px}.layui-m-layer1 .layui-m-layerchild{border:none;border-radius:0}.layui-m-layer2 .layui-m-layerchild{width:auto;max-width:260px;min-width:40px;border:none;background:0 0;box-shadow:none;color:#fff}.layui-m-layerchild h3{padding:0 10px;height:60px;line-height:60px;font-size:16px;font-weight:400;border-radius:5px 5px 0 0;text-align:center}.layui-m-layerbtn span,.layui-m-layerchild h3{text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.layui-m-layercont{padding:50px 30px;line-height:22px;text-align:center}.layui-m-layer1 .layui-m-layercont{padding:0;text-align:left}.layui-m-layer2 .layui-m-layercont{text-align:center;padding:0;line-height:0}.layui-m-layer2 .layui-m-layercont i{width:25px;height:25px;margin-left:8px;display:inline-block;background-color:#fff;border-radius:100%;-webkit-animation:layui-m-anim-loading 1.4s infinite ease-in-out;animation:layui-m-anim-loading 1.4s infinite ease-in-out;-webkit-animation-fill-mode:both;animation-fill-mode:both}.layui-m-layerbtn,.layui-m-layerbtn span{position:relative;text-align:center;border-radius:0 0 5px 5px}.layui-m-layer2 .layui-m-layercont p{margin-top:20px}@-webkit-keyframes layui-m-anim-loading{0%,100%,80%{transform:scale(0);-webkit-transform:scale(0)}40%{transform:scale(1);-webkit-transform:scale(1)}}@keyframes layui-m-anim-loading{0%,100%,80%{transform:scale(0);-webkit-transform:scale(0)}40%{transform:scale(1);-webkit-transform:scale(1)}}.layui-m-layer2 .layui-m-layercont i:first-child{margin-left:0;-webkit-animation-delay:-.32s;animation-delay:-.32s}.layui-m-layer2 .layui-m-layercont i.layui-m-layerload{-webkit-animation-delay:-.16s;animation-delay:-.16s}.layui-m-layer2 .layui-m-layercont>div{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}
|
||||
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 11 KiB |
@@ -0,0 +1 @@
|
||||
.layui-layer-imgbar,.layui-layer-imgtit a,.layui-layer-tab .layui-layer-title span,.layui-layer-title{text-overflow:ellipsis;white-space:nowrap}html #layuicss-layer{display:none;position:absolute;width:1989px}.layui-layer,.layui-layer-shade{position:fixed;_position:absolute;pointer-events:auto}.layui-layer-shade{top:0;left:0;width:100%;height:100%;_height:expression(document.body.offsetHeight+"px")}.layui-layer{-webkit-overflow-scrolling:touch;top:150px;left:0;margin:0;padding:0;background-color:#fff;-webkit-background-clip:content;border-radius:2px;box-shadow:1px 1px 50px rgba(0,0,0,.3)}.layui-layer-close{position:absolute}.layui-layer-content{position:relative}.layui-layer-border{border:1px solid #B2B2B2;border:1px solid rgba(0,0,0,.1);box-shadow:1px 1px 5px rgba(0,0,0,.2)}.layui-layer-load{background:url(loading-1.gif) center center no-repeat #eee}.layui-layer-ico{background:url(icon.png) no-repeat}.layui-layer-btn a,.layui-layer-dialog .layui-layer-ico,.layui-layer-setwin a{display:inline-block;*display:inline;*zoom:1;vertical-align:top}.layui-layer-move{display:none;position:fixed;*position:absolute;left:0;top:0;width:100%;height:100%;cursor:move;opacity:0;filter:alpha(opacity=0);background-color:#fff;z-index:2147483647}.layui-layer-resize{position:absolute;width:15px;height:15px;right:0;bottom:0;cursor:se-resize}.layer-anim{-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-duration:.3s;animation-duration:.3s}@-webkit-keyframes layer-bounceIn{0%{opacity:0;-webkit-transform:scale(.5);transform:scale(.5)}100%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}}@keyframes layer-bounceIn{0%{opacity:0;-webkit-transform:scale(.5);-ms-transform:scale(.5);transform:scale(.5)}100%{opacity:1;-webkit-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}}.layer-anim-00{-webkit-animation-name:layer-bounceIn;animation-name:layer-bounceIn}@-webkit-keyframes layer-zoomInDown{0%{opacity:0;-webkit-transform:scale(.1) translateY(-2000px);transform:scale(.1) translateY(-2000px);-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}60%{opacity:1;-webkit-transform:scale(.475) translateY(60px);transform:scale(.475) translateY(60px);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}}@keyframes layer-zoomInDown{0%{opacity:0;-webkit-transform:scale(.1) translateY(-2000px);-ms-transform:scale(.1) translateY(-2000px);transform:scale(.1) translateY(-2000px);-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}60%{opacity:1;-webkit-transform:scale(.475) translateY(60px);-ms-transform:scale(.475) translateY(60px);transform:scale(.475) translateY(60px);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}}.layer-anim-01{-webkit-animation-name:layer-zoomInDown;animation-name:layer-zoomInDown}@-webkit-keyframes layer-fadeInUpBig{0%{opacity:0;-webkit-transform:translateY(2000px);transform:translateY(2000px)}100%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@keyframes layer-fadeInUpBig{0%{opacity:0;-webkit-transform:translateY(2000px);-ms-transform:translateY(2000px);transform:translateY(2000px)}100%{opacity:1;-webkit-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}}.layer-anim-02{-webkit-animation-name:layer-fadeInUpBig;animation-name:layer-fadeInUpBig}@-webkit-keyframes layer-zoomInLeft{0%{opacity:0;-webkit-transform:scale(.1) translateX(-2000px);transform:scale(.1) translateX(-2000px);-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}60%{opacity:1;-webkit-transform:scale(.475) translateX(48px);transform:scale(.475) translateX(48px);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}}@keyframes layer-zoomInLeft{0%{opacity:0;-webkit-transform:scale(.1) translateX(-2000px);-ms-transform:scale(.1) translateX(-2000px);transform:scale(.1) translateX(-2000px);-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}60%{opacity:1;-webkit-transform:scale(.475) translateX(48px);-ms-transform:scale(.475) translateX(48px);transform:scale(.475) translateX(48px);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}}.layer-anim-03{-webkit-animation-name:layer-zoomInLeft;animation-name:layer-zoomInLeft}@-webkit-keyframes layer-rollIn{0%{opacity:0;-webkit-transform:translateX(-100%) rotate(-120deg);transform:translateX(-100%) rotate(-120deg)}100%{opacity:1;-webkit-transform:translateX(0) rotate(0);transform:translateX(0) rotate(0)}}@keyframes layer-rollIn{0%{opacity:0;-webkit-transform:translateX(-100%) rotate(-120deg);-ms-transform:translateX(-100%) rotate(-120deg);transform:translateX(-100%) rotate(-120deg)}100%{opacity:1;-webkit-transform:translateX(0) rotate(0);-ms-transform:translateX(0) rotate(0);transform:translateX(0) rotate(0)}}.layer-anim-04{-webkit-animation-name:layer-rollIn;animation-name:layer-rollIn}@keyframes layer-fadeIn{0%{opacity:0}100%{opacity:1}}.layer-anim-05{-webkit-animation-name:layer-fadeIn;animation-name:layer-fadeIn}@-webkit-keyframes layer-shake{0%,100%{-webkit-transform:translateX(0);transform:translateX(0)}10%,30%,50%,70%,90%{-webkit-transform:translateX(-10px);transform:translateX(-10px)}20%,40%,60%,80%{-webkit-transform:translateX(10px);transform:translateX(10px)}}@keyframes layer-shake{0%,100%{-webkit-transform:translateX(0);-ms-transform:translateX(0);transform:translateX(0)}10%,30%,50%,70%,90%{-webkit-transform:translateX(-10px);-ms-transform:translateX(-10px);transform:translateX(-10px)}20%,40%,60%,80%{-webkit-transform:translateX(10px);-ms-transform:translateX(10px);transform:translateX(10px)}}.layer-anim-06{-webkit-animation-name:layer-shake;animation-name:layer-shake}@-webkit-keyframes fadeIn{0%{opacity:0}100%{opacity:1}}.layui-layer-title{padding:0 80px 0 20px;height:42px;line-height:42px;border-bottom:1px solid #eee;font-size:14px;color:#333;overflow:hidden;background-color:#F8F8F8;border-radius:2px 2px 0 0}.layui-layer-setwin{position:absolute;right:15px;*right:0;top:15px;font-size:0;line-height:initial}.layui-layer-setwin a{position:relative;width:16px;height:16px;margin-left:10px;font-size:12px;_overflow:hidden}.layui-layer-setwin .layui-layer-min cite{position:absolute;width:14px;height:2px;left:0;top:50%;margin-top:-1px;background-color:#2E2D3C;cursor:pointer;_overflow:hidden}.layui-layer-setwin .layui-layer-min:hover cite{background-color:#2D93CA}.layui-layer-setwin .layui-layer-max{background-position:-32px -40px}.layui-layer-setwin .layui-layer-max:hover{background-position:-16px -40px}.layui-layer-setwin .layui-layer-maxmin{background-position:-65px -40px}.layui-layer-setwin .layui-layer-maxmin:hover{background-position:-49px -40px}.layui-layer-setwin .layui-layer-close1{background-position:1px -40px;cursor:pointer}.layui-layer-setwin .layui-layer-close1:hover{opacity:.7}.layui-layer-setwin .layui-layer-close2{position:absolute;right:-28px;top:-28px;width:30px;height:30px;margin-left:0;background-position:-149px -31px;*right:-18px;_display:none}.layui-layer-setwin .layui-layer-close2:hover{background-position:-180px -31px}.layui-layer-btn{text-align:right;padding:0 15px 12px;pointer-events:auto;user-select:none;-webkit-user-select:none}.layui-layer-btn a{height:28px;line-height:28px;margin:5px 5px 0;padding:0 15px;border:1px solid #dedede;background-color:#fff;color:#333;border-radius:2px;font-weight:400;cursor:pointer;text-decoration:none}.layui-layer-btn a:hover{opacity:.9;text-decoration:none}.layui-layer-btn a:active{opacity:.8}.layui-layer-btn .layui-layer-btn0{border-color:#1E9FFF;background-color:#1E9FFF;color:#fff}.layui-layer-btn-l{text-align:left}.layui-layer-btn-c{text-align:center}.layui-layer-dialog{min-width:260px}.layui-layer-dialog .layui-layer-content{position:relative;padding:20px;line-height:24px;word-break:break-all;overflow:hidden;font-size:14px;overflow-x:hidden;overflow-y:auto}.layui-layer-dialog .layui-layer-content .layui-layer-ico{position:absolute;top:16px;left:15px;_left:-40px;width:30px;height:30px}.layui-layer-ico1{background-position:-30px 0}.layui-layer-ico2{background-position:-60px 0}.layui-layer-ico3{background-position:-90px 0}.layui-layer-ico4{background-position:-120px 0}.layui-layer-ico5{background-position:-150px 0}.layui-layer-ico6{background-position:-180px 0}.layui-layer-rim{border:6px solid #8D8D8D;border:6px solid rgba(0,0,0,.3);border-radius:5px;box-shadow:none}.layui-layer-msg{min-width:180px;border:1px solid #D3D4D3;box-shadow:none}.layui-layer-hui{min-width:100px;background-color:#000;filter:alpha(opacity=60);background-color:rgba(0,0,0,.6);color:#fff;border:none}.layui-layer-hui .layui-layer-content{padding:12px 25px;text-align:center}.layui-layer-dialog .layui-layer-padding{padding:20px 20px 20px 55px;text-align:left}.layui-layer-page .layui-layer-content{position:relative;overflow:auto}.layui-layer-iframe .layui-layer-btn,.layui-layer-page .layui-layer-btn{padding-top:10px}.layui-layer-nobg{background:0 0}.layui-layer-iframe iframe{display:block;width:100%}.layui-layer-loading{border-radius:100%;background:0 0;box-shadow:none;border:none}.layui-layer-loading .layui-layer-content{width:60px;height:24px;background:url(loading-0.gif) no-repeat}.layui-layer-loading .layui-layer-loading1{width:37px;height:37px;background:url(loading-1.gif) no-repeat}.layui-layer-ico16,.layui-layer-loading .layui-layer-loading2{width:32px;height:32px;background:url(loading-2.gif) no-repeat}.layui-layer-tips{background:0 0;box-shadow:none;border:none}.layui-layer-tips .layui-layer-content{position:relative;line-height:22px;min-width:12px;padding:8px 15px;font-size:12px;_float:left;border-radius:2px;box-shadow:1px 1px 3px rgba(0,0,0,.2);background-color:#000;color:#fff}.layui-layer-tips .layui-layer-close{right:-2px;top:-1px}.layui-layer-tips i.layui-layer-TipsG{position:absolute;width:0;height:0;border-width:8px;border-color:transparent;border-style:dashed;*overflow:hidden}.layui-layer-tips i.layui-layer-TipsB,.layui-layer-tips i.layui-layer-TipsT{left:5px;border-right-style:solid;border-right-color:#000}.layui-layer-tips i.layui-layer-TipsT{bottom:-8px}.layui-layer-tips i.layui-layer-TipsB{top:-8px}.layui-layer-tips i.layui-layer-TipsL,.layui-layer-tips i.layui-layer-TipsR{top:5px;border-bottom-style:solid;border-bottom-color:#000}.layui-layer-tips i.layui-layer-TipsR{left:-8px}.layui-layer-tips i.layui-layer-TipsL{right:-8px}.layui-layer-lan[type=dialog]{min-width:280px}.layui-layer-lan .layui-layer-title{background:#4476A7;color:#fff;border:none}.layui-layer-lan .layui-layer-btn{padding:5px 10px 10px;text-align:right;border-top:1px solid #E9E7E7}.layui-layer-lan .layui-layer-btn a{background:#fff;border-color:#E9E7E7;color:#333}.layui-layer-lan .layui-layer-btn .layui-layer-btn1{background:#C9C5C5}.layui-layer-molv .layui-layer-title{background:#009f95;color:#fff;border:none}.layui-layer-molv .layui-layer-btn a{background:#009f95;border-color:#009f95}.layui-layer-molv .layui-layer-btn .layui-layer-btn1{background:#92B8B1}.layui-layer-iconext{background:url(icon-ext.png) no-repeat}.layui-layer-prompt .layui-layer-input{display:block;width:230px;height:36px;margin:0 auto;line-height:30px;padding-left:10px;border:1px solid #e6e6e6;color:#333}.layui-layer-prompt textarea.layui-layer-input{width:300px;height:100px;line-height:20px;padding:6px 10px}.layui-layer-prompt .layui-layer-content{padding:20px}.layui-layer-prompt .layui-layer-btn{padding-top:0}.layui-layer-tab{box-shadow:1px 1px 50px rgba(0,0,0,.4)}.layui-layer-tab .layui-layer-title{padding-left:0;overflow:visible}.layui-layer-tab .layui-layer-title span{position:relative;float:left;min-width:80px;max-width:260px;padding:0 20px;text-align:center;overflow:hidden;cursor:pointer}.layui-layer-tab .layui-layer-title span.layui-this{height:43px;border-left:1px solid #eee;border-right:1px solid #eee;background-color:#fff;z-index:10}.layui-layer-tab .layui-layer-title span:first-child{border-left:none}.layui-layer-tabmain{line-height:24px;clear:both}.layui-layer-tabmain .layui-layer-tabli{display:none}.layui-layer-tabmain .layui-layer-tabli.layui-this{display:block}.layui-layer-photos{-webkit-animation-duration:.8s;animation-duration:.8s}.layui-layer-photos .layui-layer-content{overflow:hidden;text-align:center}.layui-layer-photos .layui-layer-phimg img{position:relative;width:100%;display:inline-block;*display:inline;*zoom:1;vertical-align:top}.layui-layer-imgbar,.layui-layer-imguide{display:none}.layui-layer-imgnext,.layui-layer-imgprev{position:absolute;top:50%;width:27px;_width:44px;height:44px;margin-top:-22px;outline:0;blr:expression(this.onFocus=this.blur())}.layui-layer-imgprev{left:10px;background-position:-5px -5px;_background-position:-70px -5px}.layui-layer-imgprev:hover{background-position:-33px -5px;_background-position:-120px -5px}.layui-layer-imgnext{right:10px;_right:8px;background-position:-5px -50px;_background-position:-70px -50px}.layui-layer-imgnext:hover{background-position:-33px -50px;_background-position:-120px -50px}.layui-layer-imgbar{position:absolute;left:0;bottom:0;width:100%;height:32px;line-height:32px;background-color:rgba(0,0,0,.8);background-color:#000\9;filter:Alpha(opacity=80);color:#fff;overflow:hidden;font-size:0}.layui-layer-imgtit *{display:inline-block;*display:inline;*zoom:1;vertical-align:top;font-size:12px}.layui-layer-imgtit a{max-width:65%;overflow:hidden;color:#fff}.layui-layer-imgtit a:hover{color:#fff;text-decoration:underline}.layui-layer-imgtit em{padding-left:10px;font-style:normal}@-webkit-keyframes layer-bounceOut{100%{opacity:0;-webkit-transform:scale(.7);transform:scale(.7)}30%{-webkit-transform:scale(1.05);transform:scale(1.05)}0%{-webkit-transform:scale(1);transform:scale(1)}}@keyframes layer-bounceOut{100%{opacity:0;-webkit-transform:scale(.7);-ms-transform:scale(.7);transform:scale(.7)}30%{-webkit-transform:scale(1.05);-ms-transform:scale(1.05);transform:scale(1.05)}0%{-webkit-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}}.layer-anim-close{-webkit-animation-name:layer-bounceOut;animation-name:layer-bounceOut;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-duration:.2s;animation-duration:.2s}@media screen and (max-width:1100px){.layui-layer-iframe{overflow-y:auto;-webkit-overflow-scrolling:touch}}
|
||||
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 701 B |
|
After Width: | Height: | Size: 1.7 KiB |