环境部署
- 版本要求 php >= 7.1 swoole >= 4.4 尽量使用高版本 系统代码不用调整 实现方式
- 除sockets与swoole必须的扩展外, 尽量减少扩展降低可能的冲突, 并留意扩展是否可协程化
- 若使用HTTP协议, 建议通过代理方式, 用Ngnix转发php到Swoole
- 通过修改框架配置文件, 来保持程序始终运行在Swoole环境中
- 启动命令"[sudo -u 用户名 [空格分隔的"key=val"环境变量]] php swoole.php [open:指定服务(优于conf.system.open)] [conf:启动配置绝对路径]", []项可选
- 安全停止和重启命令 "kill -[15|10] pid"
配置文件
框架配置
//强制使用网络方式 _of.com.net.isCli = false; //异步请求网络, 注意端口正确 _of.com.net.async = 'http://127.0.0.1:8888/'; //swoole异步任务方式 _of.com.timer.fork.adapter = 'swoole';
启动配置如"conf:/var/www/xxx.php"
<?php //以下为默认配置 return array( //系统配置 'system' => array( //开启服务, 默认支持http的WebSocket协议服务, 绝对路径则为自定义服务(如: /var/www/html/test.php 参考下方demo) 'open' => 'WebSocket', //监听端口, 默认8888 'port' => 8888, //工作进程超过该时间会安全重启, 单位秒, 默认0不限制 'cycle' => 0, //工作进程超过该内存会安全重启, 支持K M G单位(如: 1G), 默认0不限制 'memory' => 0, //健康检查网址, 可用于初始化, 约10s一次(如: http://127.0.0.1:8888/include/of/index.php), 默认"" 'health' => '', //路由规则, 格式不含GET的路径后再与GET合并, 参考preg_replace前两参数, 如: {"@^(/[^/]+)/.*$@" : "$1.php?a=1", ...} 'routes' => array(), //自定义初始化列表, 会在每个协程加载框架完成时执行, [框架回调结构, ...] 'reinit' => array(), //自定义常量 'define' => array( //系统根路径, 默认为swoole.php所在的目录 'ROOT_DIR' => __DIR__, //框架根路径 'OF_DIR' => __DIR__ . '/include/of', ) ), //服务配置 'server' => array( //启动协程化(无法修改) 'enable_coroutine' => true, //安全重启最大等待时间(s), 超时强杀 'max_wait_time' => 86400, //协程函数范围, 默认全覆盖, 选项参考 'hook_flags' => SWOOLE_HOOK_ALL | SWOOLE_HOOK_CURL, //服务启动进程数, 默认CPU核数, 框架限制每个进程同时处理1000个请求 'worker_num' => swoole_cpu_num(), //错误日志记录等级 'log_level' => SWOOLE_LOG_WARNING, //协程抢占式调度, 默认false关闭, 若为true对密集运算的协程更友好(需更多CPU资源) 'enable_preemptive_scheduler' => false, //最大数据包 'package_max_length' => ini_get('post_max_size')的字节数, //更多参考TCP/UDP与HTTP配置 ... ), //替代PHP配置, 如max_execution_time在cli模式下永远是0, 因此设置到这里 'phpIni' => array( //请求响应超时时间, 默认30s, 等同set_time_limit(30), 此值为包含网络等操作的实际执行时间 'max_execution_time' => 30, //协程静态变量最大内存空间 'memory_limit' => ini_get('memory_limit'), //日期时间函数使用的默认时区 'date.timezone' => date_default_timezone_get() ) );
解释说明
Swoole的优势不在于单个请求而在于通信协议与并发任务
单个请求的效率提升基本无感知, 甚至可能因为过多协程的调度导致执行速度更慢 每个Swoole进程可同时响应多个请求(框架限制为1000), Ngnix与其相比需要启动更多php进程 Swoole支持的通信协议与并发任务是切实改变开发思维的壮举, 当然这些也需要资源的加持
脚本超时的设置值为实际时间, 包含数据库, 网络等操作时间, 可能需部分优化
php官网说明如下 set_time_limit()函数和配置指令max_execution_time只影响脚本本身执行的时间。 任何发生在诸如使用system()的系统调用,流操作,数据库操作等脚本的执行时间不包括其中。 在Windows上为实际执行时间。 Swoole与Windows行为一致, 默认情况下是无需设置的 1. 计划任务永不超时 2. 消息队列每条是2小时超时, 超过分秒级应该改造队列 3. 网络请求若调整执行时间, 应设置在ctrl层或入口文件 可以通过两种方式优化 1. 调整启动配置 phpIni.max_execution_time 的默认值 2. 通过 set_time_limit(int) 或 ini_set('max_execution_time', int) 动态设置
因常驻内存的原因, 自动寻类加载的文件仅会运行一次
若文件包含全局调用, 需改到"前后包含若干'_'的init方法中"(不可有参数不可有输出) 或 配置reinit参数
<?php //全局的, 无参数 => 正常运行 class::init(); //全局的, 无参数 => 正常运行 class::__init_(); //非全局(在方法或类中), 有参数 => 正常运行 function () {class::init(1);} //全局的, 有参数 => 运行报错 class::init(1); //全局的, 无参数 => 正常运行, 但不正确, 每次请求都不会判断而执行init, 应把条件放在init中 if (true) {class::init();} //同时也意味着其它初始化作用的方法名无效 => 无法每次运行, 这类情况可配置reinit参数 class::abcd();
仅实现pcntl_signal和pcntl_signal_dispatch两个方法且仅处理SIGTERM信号
<?php //安装SIGTERM信号处理器 pcntl_signal(15, function ($signo) {}) : //恢复linux进程对SIGTERM信号处理 pcntl_signal(15, SIG_DFL); //检查信号 pcntl_signal_dispatch();
&swoole::data($name, $data = array()) 进程内共享数据
-
namestring数据名称
-
dataarray初始数据, 默认空数组
<?php //引用返回, 多协程初始化数据 $data = &swoole::data('test', array('init' => 0)); //多协程初始化动作(无锁方式实现) ++$data['init'] === 1 ? 初始化动作 : $data['init'] = 1;
swoole::fork($call, $mode = 1, $data = array()) 创建一个"加载框架且不超时"的协程回调建议应用层使用任务方式编程保持兼容性
-
callcallback符合框架回调结构, 包含协程逻辑, 可以是匿名函数
-
modeint
协程模式 1=工作协程, 启动快, 可共享变量, 过多影响调度速度, 适合跨协程操作的任务 2=独立进程, 启动慢, 支持更多"工作协程", 适合运行长时间的任务 4=共享进程, 启动快, 支持少量"工作协程", 适合运行短时间的任务
-
dataarray
mode为1时生效 { "space" :&当协程运行时被设置为协程ID, 引用传值, 默认无引用 "requ" : 指定请求信息, 符合Swoole\Http\Request属性的对象, 默认父协程的请求信息 "argv" : 指定call的触发参数, 参考of::callFunc, 默认无参数 }
<?php /** * 运行的协程在收到SIGTERM信号后 * 1. 定时器会立刻退出 * 2. SIG_DFL默认处理方式的go协程会等到sleep时退出 * 3. 其它处理方式会在调用pcntl_signal_dispatch时安全退出 */ swoole::fork(array( //符合框架的回调结构 'asCall' => $call, 'params' => array($data) )); //模拟一个含两个必要属性的requ对象, 还可以添加get post等参数 $requ = new stdClass; $requ->server = array( 'path_info' => '/index.php', 'remote_addr' => $serv->getClientInfo($fd)['remote_ip'] ); //指定请求信息回调演示 swoole::fork($call, 1, array( 'requ' => $requ ));
应用开发
HTTP应用开发
1. 兼容原开发方式, 部署同Apache和Ngnix相同, 启动Swoole后便可应用 2. 建议Swoole仅作为应用服务器, 用Ngnix做代理, 转发动态的php请求
WebSocket应用开发
1. WebSocket连接时先加载入口文件, 需返回至少包含"info"的回调数组, {"init" : 初始回调, "info" : 信息回调, "done" : 结束回调} 2. 回调符合框架回调结构, 每个回调触发时接受一个数组参数, 结构如下 init({"link" : 连接ID, "resp" : 响应对象}) info({"link" : 连接ID, "resp" : 响应对象, "info" : 信息对象}) done({"link" : 连接ID}) 3. 响应对象包含两个方法 push(string | array | 信息对象 $data) 推送信息, 数组参数转会为json, 成功返回true, 失败返回false clase() 发送关闭帧并关闭连接, 成功返回true, 失败返回false 4. 支持文本ping pong帧处理, 受open_websocket_ping_frame或open_websocket_pong_frame参数影响, 自动响应数据 {"ping" : "pong", "Ping" : "Pong", "PING" : "PONG"}
<?php /** * info是必须的, 否则会直接关闭连接, init和done是可选的 * 假设文件路径 /test.php, 先进行登录验证等操作 */ return array( //连接成功回调 'init' => function ($params) { $params['resp']->push(print_r($params, true)); }, //接受信息回调 'info' => function ($params) { //发送信息 $params['resp']->push(print_r($params, true)); //关闭连接 $params['resp']->close(); } ); ?> <script> //创建连接, 注意端口 var websocket = new WebSocket('ws://127.0.0.1:8888/test.php'); //连接成功回调 websocket.onopen = function (evt) { console.log("Connected to WebSocket server."); //连接成功后发送信息 websocket.send('hello'); }; //接受信息回调 websocket.onmessage = function (evt) { console.log('Retrieved data from server: ' + evt.data); }; //关闭连接回调 websocket.onclose = function (evt) { console.log("Disconnected"); }; //出现错误回调 websocket.onerror = function (evt, e) { console.log('Error occured: ' + evt.data); }; </script>
自定义服务
1. 自定义服务完整的Swoole服务代码写在独立的文件中并返回服务对象, 如: /var/www/html/demo.php 2. 服务代码中调用$GLOBALS中的"system server phpIni"分别对应配置文件中的数据, 键名可扩展 3. 可以调用swoole类的私有方法和属性, 如: self::request($requ, null); 4. 参考swoole.php中"webSocket"方法的代码能更容易实现大部分自定义服务的逻辑 5. 通过命令行启动自定义服务, 如: php swoole.php "open:/var/www/html/demo.php"
<?php /** * 演示http服务 * 假设文件路径 /var/www/html/demo.php */ $serv = new Swoole\Http\Server('0.0.0.0', $GLOBALS['system']['port'], SWOOLE_PROCESS); //请求回调 $serv->on('request', function ($requ, $resp) { //调用私有方法, 处理请求信息 self::request($requ, $resp); }); //返回服务 return $serv; ############################################################################################################## ########服务开发的具体实现思路总体分五步, 客户端连接执行前三步, 接到消息执行第四步, 关闭连接执行第五步######## ############################################################################################################## //第一步拼接一个requ请求对象, 如: $requ = new stdClass; $requ->server = array( //入口文件 'path_info' => '/index.php', //客户端IP 'remote_addr' => $serv->getClientInfo($fd)['remote_ip'] ); //第二步调用swoole::request加载入口文件并保存入口文件返回的回调函数, 如: $call = self::request($requ, null); //第三步封装一个响应对象, 为应用层提供交互功能, 并把上述数据保存到静态变量中, 如: $resp = new stdClass; //第四步在接到客户端消息时, 通过指定请求信息和触发参数的方式创建协程回调, 如: self::fork($call, 1, array( //指定请求头 'requ' => $requ, //触发参数, 内容自定义, 如: {"link" : 连接标识, "resp" : 响应对象, "info" : 客户消息} 'argv' => array('link' => $frame->fd, 'resp' => $resp, 'info' => $frame) )); //第五步在关闭连接时清理连接标识对应的数据, 如: unset($requ, $call, $resp);