关于swoole:Swoole-51-增加更多数据库协程客户端支持

在 5.1 版本中减少了多种数据库协程客户端的反对,并且全副以 PDO 接口的形式提供,旧的业务代码无需做任何更改即可一键切换为协程模式,异步非阻塞地并发执行。 包含: pdo_pgsqlpdo_odbcpdo_sqlitepdo_oci (Oracle 数据库)开启办法减少了 4 个编译参数和 Runtime Hook 选项,开启这些协程客户端。 编译选项--with-swoole-odbc,依赖 unixodbc-dev--with-swoole-pgsql,依赖 libpq-dev--with-swoole-sqlite,依赖 libsqlite3-dev--with-swoole-oracle,依赖 oracle-instantclientRuntime Hook 选项SWOOLE_HOOK_PDO_PGSQLSWOOLE_HOOK_PDO_ODBCSWOOLE_HOOK_PDO_SQLITESWOOLE_HOOK_PDO_ORACLE这些选项已蕴含在 SWOOLE_HOOK_ALL 中,也可独自启用其中一个客户端。 PDO_PGSQL在之前的版本中,提供了 Swoole\Coroutine\PostgreSQL 客户,因为是全新的 API,用户须要兼容 PHP-FPM 和 Swoole,应用并不宽泛。 go(function () { $pg = new Swoole\Coroutine\PostgreSql(); $conn = $pg->connect("host=127.0.0.1 port=5432 dbname=test user=test password="); $result = $pg->query($conn, 'SELECT * FROM test;'); $arr = $pg->fetchAll($result); var_dump($arr);});5.1 版本中减少了 Runtime Hook PDO_PGSQL,能够间接应用 FPM 下的历史代码。 go(function () { $pdo = new PDO("pgsql:host={$host};port={$port};dbname={$dbname}", $user, $password); $statement = $pdo->query('select * from user where id =1 limit 1'); var_dump($statement->fetch(PDO::FETCH_ASSOC));});PDO_ODBCODBC(Open Database Connectivity)是一种为多种数据库管理系统提供对立接口的标准化技术。它是由微软公司提出的,目标是为了实现异构性,使得应用程序可能拜访多种不同类型的数据源。 ODBC 的规范定义了应用程序发出请求的 API,以及由驱动程序提供的响应申请的 API。ODBC 是一种凋谢的、跨平台的技术,能够在多种操作系统和编程语言中应用。 ...

June 9, 2023 · 2 min · jiezi

关于swoole:比df更好用的命令

大家好,我是良许。 对于剖析磁盘应用状况,有两个十分好用的命令:du 和 df 。简略来说,这两个命令的作用是这样的: du 命令:它是英文单词 disk usage 的简写,次要用于查看文件与目录占用多少磁盘空间;df 命令:它是英文单词 disk free 的简写,次要用于查看磁盘被应用了多少空间、残余多少空间,等等。特地是你在磁盘爆满的状况下,这两个命令联合起来十分好用。 然而,这两个命令毕竟是离开的,要是能整合起来该多好,毕竟都是同类型的命令。 别说,还真有人把这两个命令整合起来,它就是:duf 命令! duf 是一个用 Golang 编写的跨磁盘应用状况离别工具,它能够以表格(自适应)的模式输入磁盘应用状况,而且还能够依据需要对后果进行排序,应用十分不便! 1. duf命令的装置以 Ubuntu 为例,咱们不能间接应用 apt-get 命令装置,须要手动装置。 首先,从 GitHub 上下载 duf 命令的安装包: $ wget https://github.com/muesli/duf/releases/download/v0.8.1/duf_0.8.1_linux_amd64.deb而后,再应用 dpkg 命令装置: $ dpkg -i duf_0.8.1_linux_amd64.debmacOS 平台装置: $ brew install duf或者$ sudo port selfupdate && sudo port install dufWindows 平台装置: choco install duf或者scoop install duf2. duf命令的应用最根本的应用,就是间接 duf 三个字母,不加任何参数,非常简单。这种用法会输入所有本地设施、已挂载的任何云存储设备以及任何其余非凡设施(包含长期存储地位等)的详细信息。 $ duf 能够看到,它是以黑白及可视化符号模式显示磁盘应用状况(在 Ubuntu 自带终端工具下),十分直观,也十分养眼。 这里信息毕竟有点多,如果你只想查看本地设施磁盘应用信息,能够加上 --only local 选项: ...

March 14, 2023 · 1 min · jiezi

关于swoole:Swoole-的异步-Task-任务详解

本文将从上面两方面讲述 Swoole Task 工作: 1.如何在 Swoole 中实现异步 Task 工作? 2.Swoole 的异步 Task 工作在 CRMEB 电商零碎中的应用场景有哪些? 一、如何在 Swoole 中实现异步 Task 工作?如果一些耗时的操作要在服务器端程序中执行 (例如,在 Web 服务器中发送电子邮件和短消息等。),如果间接按程序执行这些操作,程序会阻塞以后过程,导致服务器响应迟缓。通常的做法是应用异步脚本或音讯队列来实现这些操作。如何通过 Swoole 实现异步工作解决? Swoole 提供了异步解决的性能,能够将一个异步工作公布到 TaskWorker 过程池中执行,而不影响以后申请的处理速度。 新建文件,命名为 task_server.php,代码如下: 在命令行执行如下命令即可运行程序: php task_server.php 下面的代码创立了一个 TCP 服务,同时设置了四个 taskWorker 过程,实现了两个事件回调函数 onTask 和 onfinish。当客户端与服务器建设连贯时,客户端发送的音讯将触发下面代码中的 receive 事件。在 receive 事件中,会调用 $serv->task () 函数来执行工作,程序会立刻返回,持续向下执行代码。OnTask 回调函数在 TaskWorker 过程中异步执行。执行后调用 $serv->finish () 函数返回后果 (finish 回调函数是可选事件,能够不设置)。 二、Swoole 的 Task 工作适宜解决一些耗时的操作,如发送邮件、发送短信、推送音讯等。例如在下面代码中,onReceive 回调事件被触发后,就能够执行 $serv->task () 来执行一个异步工作。 注意事项: 如果要投递工作,须要在配置文件中 task_worker_num 必须要设置,否则会报错 ...

September 29, 2022 · 1 min · jiezi

关于swoole:基于-Swoole-搭建-WebSocket-服务详解

本节将会详解以下 4 个问题: 什么是 swoole? 什么是 Websocket? 如何基于 Swoole 构建 WebSocket 服务? 基于 Swoole 的 WebSocket 服务和 Http 服务是什么关系? 一、 Swoole 简介Swoole 是一个面向生产环境的 PHP 异步网络通信引擎,使 PHP 开发人员可能编写高性能的异步并发 TCP、UDP、Unix Socket、HTTP 和 WebSocket 服务。Swoole 可广泛应用于互联网、挪动通信、企业软件、云计算、网络游戏、物联网 (IOT)、车联网、智能家居等畛域。应用 PHP+Swoole 作为网络通信框架,能够大大提高企业 IT R&D 团队的工作效率。 Swoole 反对用于构建各种服务器,包含 HTTP 服务器、websocket 服务器、tcp 服务器、redis 服务器等等。咱们在这里应用 websocket 服务器。 二、WebSocket 介绍WebSocket 是通过繁多 TCP 连贯进行全双工通信的协定。WebSocket 通信协议在 2011 年被 IETF 指定为规范 RFC 6455,并由 RFC7936 进行了补充。WebSocket API 也被 W3C 指定为规范。WebSocket 使得客户端和服务器之间的数据交换更加容易,并容许服务器被动将数据推送到客户端。在 WebSocket API 中,浏览器和服务器只须要握手一次,就能够间接创立长久连贯,进行双向数据传输。 ...

September 26, 2022 · 1 min · jiezi

关于swoole:Swoole-进程模型分析

在这边文章中咱们将介绍以下内容: 1、Swoole Server 的运行模式 2、Swoole 过程模型剖析 上图是 Swoole 官网提供的各个过程互相关系图,能够说了解了这张图,你就了解了 Swoole 的过程模型。 1、Swoole Server 的运行模式 Swoole 服务常见的运行模式有单线程模式和过程模式两种,两种形式介绍如下: 单线程模式 (SWOOLE_BASE) 这种模式就是传统的异步非阻塞 Server,与 Nginx 和 Node.js 等异步服务的原理是一样的。在这种模式下,事件循环会间接回调 PHP 的函数,而不是通过线程 dispatch 投递工作。如果在回调函数中有阻塞操作,就会导致 Server 进化为同步模式。就像在上一篇文章中 Nginx+PHP-FPM 架构中介绍的那样,Nginx 只做转发,具体业务由 PHP-FPM 来实现。如果 Nginx 也能够解决业务逻辑,一旦呈现阻塞的业务逻辑,Nginx 的性能会急剧下降。正是因为 Nginx 和 PHP-FPM 所起到的作用不同,才会造就 Nginx 的高性能。Base 模式的特点:没有 Master 过程的角色;每个 Worker 过程同时承当了 Process 模式下 Reactor 线程和 Worker 过程两局部职责;在这种模式下,Manage 过程是可选的,当设置了 worker_num=1,并且么有应用 Task 和 MaxRequest 个性时,底层将间接创立一个独自的 Worker 过程,不创立 Manager 过程 Base 模式的长处:没有 IPC 开销,性能更好;代码简略,不容易出错。Base 模式的毛病:TCP 连贯时在 Worker 过程中维持的,所以当某个 Worker 过程挂掉时,此 Worker 内的所有连贯都将被敞开;大量 TCP 长连贯无奈利用到所有的 Worker 过程;TCP 连贯与 Worker 过程是绑定的,在长连贯利用中,不同的 Worker 过程无奈实现负载平衡。例如:某些连贯的数据量很大,这些连贯所在的 Worker 过程的负载会十分高。然而某些连贯数据量很小,所在 Worker 过程的负载会非常低。Base 模式实用场景:如果客户端连贯之间不须要交互,能够应用 Base 模式。如 Memcache、Http 服务等。 ...

September 21, 2022 · 1 min · jiezi

关于swoole:每个程序员都应该知道的-Swoole-知识-定时器

本节将讲述如下三个问题: PHP 自身的定时器介绍 Swoole 中定时器的应用办法 Swoole 定时器的底层原理 1、PHP 自身的定时器介绍先说说原生 PHP 如何实现定时器,PHP 的定时器是通过 pcntl_alarm 实现的: pcntl_alarm ( int $seconds ) : int pcntl_alarm 函数的作用是为过程设置一个 alarm 闹钟信号。调用这个办法后会创立一个计数器,在指定的秒数后向过程发送一个 SIGALRM 信号。每次对 pcntl_alarm 的调用都会勾销之前设置的 alarm 信号。 其中,$seconds 为期待的秒数,如果 $seconds 设置为 0,将不会创立 alarm 信号。调用这个函数后返回上次 alarm 调度残余的秒数,或者之前没有 alarm 调度时返回 0。 上面咱们看一个例子,pcntl_signal () 函数装置信号处理器,pcntl_signal_dispatch () 调用期待信号的处理器。 pcntl_alarm () 函数是基于时钟信号 + tick 函数实现的,存在一些缺点: 最大进反对到秒,而 Swoole Timer 能够到毫秒级别不反对同时设定多个定时器程序 pcntl_alarm () 依赖 declare (ticks = 1),性能很差 2、Swoole 中定时器的应用办法Swoole 中的定时器能够达到毫秒精度,同时能够反对增加大量定时器。 ...

September 5, 2022 · 1 min · jiezi

关于swoole:PHP-基于-SWX-框架搭建WebSocket服务器二

前言官网地址:SW-X框架-专一高性能便捷开发而生的PHP-SwooleX框架 心愿各大佬举起小手,给小弟一个star:https://github.com/swoolex/swoolex 1、前端模板最终要实现的成果,如下图: 该模板能够间接下载:练习WebSocket应用的前端html模板 也能够间接应用上面的前端代码,命名为:index.html <!DOCTYPE HTML><html><head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="renderer" content="webkit"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> <title>SW-X | WebSocket客户端示例</title> <script src="https://blog.junphp.com/public/js/jquery.min.js"></script> <script src="jquery.md5.js"></script> <script src="tim.js"></script> <style>body,html{margin: 0;padding: 10px; height: calc(100% - 30px);font-size: 13px;}ul,li{list-style: none;padding: 0;margin: 0;}.user_list{width: 200px; height: 100%; overflow: hidden; overflow-y: auto; padding: 10px;border: 1px solid #ccc;float: left;}.user_list li{width: 100%;padding: 5px 0;cursor: pointer;}.user_list li:hover{color: #0077d6;}.main{width: calc(100% - 550px); height: 70%; overflow: hidden; overflow-y: auto; padding: 10px;border: 1px solid #ccc;float: left;border-left: 0;background: #e9f8ff;}.content{width: calc(100% - 530px); height: calc(30% - 1px);border: 1px solid #ccc;float: left;border-left: 0;border-top: 0;position: relative;}#content{width: calc(100% - 20px);;border: 0;height:calc(100% - 25px);padding: 10px;}#content:focus{outline: none;}code{padding: 3px 5px;border-radius: 30%; color: #fff;}.online{background: #35b700;}.offline{background: red;}.record{float: left;width: 100%;padding: 5px 0;}.record span{font-size: 12px; background: #ccc; border-radius: 5px; color: #0037ff;padding: 1px 3px;}.other p{text-indent: 30px;padding: 0px;}.own{text-align: right;}.tips{text-align: center;font-size: 12px; color: #e80000;}.drift{position: absolute;bottom: 10px; right: 10px; }#send{background: #009e3f;border: 1px solid #009020;font-size: 14px;padding: 3px 10px;color: #fff;border-radius: 5px;cursor: pointer;}#send:hover{background: #008234;border: 1px solid #005613;}#open{background: #009e97;border: 1px solid #007974;font-size: 14px;padding: 3px 10px;color: #fff;border-radius: 5px;cursor: pointer;}#open:hover{background: #008a84;border: 1px solid #00736e;}#close{background: #ef0000;border: 1px solid #c30000;font-size: 14px;padding: 3px 10px;color: #fff;border-radius: 5px;cursor: pointer;}#close:hover{background: #c50000;border: 1px solid #a00000;}input{padding: 4px;}.log{width: 326px;height: calc(100% - 40px);border: 1px solid #ccc;float: right;border-left: 0;position: absolute;right: 0;overflow: hidden;overflow-y: auto;}.log div{width: calc(100% - 20px);padding:10px 10px 0 10px;} </style> </head><body> <!--用户列表--> <div class="user_list"> <ul></ul> </div> <!--聊天窗口--> <div class="main"></div> <!--输出窗口--> <div class="content"> <textarea id="content"></textarea> <div class="drift"> <input id="host" type="text" placeholder="WS地址" style="width: 700px;"> <input id="user_id" type="text" placeholder="输出user_id"> <input id="username" type="text" placeholder="输出用户名"> <button id="open">连贯</button> <button id="close">断开</button> <button id="send">发送</button> </div> </div> <!--交互记录--> <div class="log"></div></body></html>留神:最下面有一个tim.js文件须要你自行创立,后续的教程都只对该文件进行变更阐明而已。 ...

May 30, 2022 · 7 min · jiezi

关于swoole:Swoole-定时器能实现毫秒级任务调度你敢相信吗

简介 Timer 毫秒精度的定时器,底层基于 epoll_wait 和 setitimer 实现,数据结构应用 最小堆 ,可反对增加大量定时器,应用最小堆数据结构实现的定时器,相似 JavaScript 的 setInterval,Swoole 定时器的增加和删除,全副为内存操作,因而性能是十分高的。 Swoole 中的 Timer 与 PHP 自身的 pcntl_alarm 是不同的。pcntl_alarm 是基于 时钟信号 + tick 函数实现,一个是最大仅反对到秒,另一个是不反对同时设定多个定时器程序,性能相对来说会比拟差。 距离时钟定时器 咱们能够通过 Timer::tick 来实现距离时钟定时器,定时器会继续触发,每隔指定工夫主动触发执行回调函数, 直到调用 Timer::clear 来革除指定的定时器。 $i = 0;Swoole\Timer::tick(1000, function($id) use ($i) { global $i; echo "tick id:$id i:$i \n"; $i++;});每隔 1 秒工夫触发一次回调函数,回调函数会主动打印一行信息到控制台。一次性定时器 须要执行一次定时器的时候能够应用 Timer::after , 此函数是一个一次性定时器,与距离时钟定时器不同,执行实现后就会销毁,须要留神的是 Timer::after 是非阻塞的。 Swoole\Timer::after(2000, function () { echo "执行一次的after\n";});2 秒后执行回调函数,执行实现后主动退出。革除定时器 Timer::after 执行实现后会主动退出,不须要革除,而 Timer::tick 没有革除定时器操作,会始终执行,直到程序退出。 当不须要定时器的时候,咱们能够应用 Timer::clear 来达到进行定时器的目标,将对应定时器 id 传入该办法即可。 ...

May 9, 2022 · 1 min · jiezi

关于swoole:Swoole中的协程使用相关说明快来围观

什么是协程 协程能够简略了解为线程,只不过这个线程是用户态的,不须要操作系统参加,创立销毁和切换的老本非常低,和线程不同的是协程没法利用多核 cpu 的,想利用多核 cpu 须要依赖 Swoole 的多过程模型。 协程特点开发者能够无感知的用同步的代码编写形式达到异步 IO 的成果和性能,防止了传统异步回调所带来的离散的代码逻辑和陷入多层回调中导致代码无奈保护。同时因为底层封装了协程,所以比照传统的 PHP 层协程框架,开发者不须要应用 yield 关键词来标识一个协程 IO 操作,所以不再须要对 yield 的语义进行深刻了解以及对每一级的调用都批改为 yield,这极大的进步了开发效率。协程适宜 IO 密集型利用,因为协程在 IO 阻塞 时会主动调度,缩小 IO 阻塞导致的工夫损失。睡眠 1 万次,读取,写入,检查和删除文件 1 万次,应用 PDO 和 MySQLi 与数据库通信 1 万次,创立 TCP 服务器和多个客户端互相通信 1 万次,创立 UDP 服务器和多个客户端到互相通信 1 万次...... 一切都在一个过程一秒内完满实现!实用场景 高并发服务,如秒杀零碎、高性能API接口、RPC服务器,连接池,IM聊天、游戏服务器、物联网、音讯服务器等。 示例1:用户能够通过go函数创立一个协程,以达到并发执行的成果,如上面代码所示: go(function () { echo "one" . PHP_EOL;});go(function () { echo "two" . PHP_EOL;});go(function () { echo "three" . PHP_EOL;});每当呈现一个go,底层会主动创立一个协程,协程输入内容后,而后主动退出示例2:通过协程能够并发执行客户端申请,应用到协程调度带来的 IO 阻塞时的调度,来实现高性能服务,上面是通过 defer 机制实现申请的并发执行: ...

April 26, 2022 · 1 min · jiezi

关于swoole:PHP-基于-SWX-框架搭建高性能API架构四

前言官网地址:SW-X框架-专一高性能便捷开发而生的PHP-SwooleX框架 心愿各大佬举起小手,给小弟一个star:https://github.com/swoolex/swoolex 1、什么是中间件中间件属于AOP切面编程的衍生,SW-X中的中间件能够通过绑定路由地址,实现控制器无切入的关联绑定。 在中间件中,能够进行申请拦挡(前置操作)、或者申请缓存销毁(后置操作)等业务。 2、通过路由绑定中间件接回上章案例,咱们当初要对/api/*前缀的所有接口,对立绑定一个名为Auth的中间件。 须要先再/config/middleware.php中间件配置文件中注册绑定规定: <?phpreturn [ // 匹配中间件,路由前半段是/api/结尾的都会绑定到 '/api/*' => [ \box\middleware\Auth::class, ],];所有中间件不强制继承\x\Middleware基类,但个别倡议继承,\x\Middleware类提供了一个error()办法,当开发者想中断利用持续向下执行时,能够调用该办法,抛出自定义的提醒内容到客户端,该办法兼容了框架中4种不同的服务。 中间件倡议(但不强制)对立寄存在/box/middleware目录下, 便于项目管理。 接下来,咱们在/box/middleware/目录下,创立一个Auth.php类,并写入代码: <?php/** * +---------------------------------------------------------------------- * 权限中间件 * +---------------------------------------------------------------------- * 官网:https://www.sw-x.cn * +---------------------------------------------------------------------- * 作者:小黄牛 <1731223728@qq.com> * +---------------------------------------------------------------------- * 开源协定:http://www.apache.org/licenses/LICENSE-2.0 * +----------------------------------------------------------------------*/namespace box\middleware;use x\Middleware;use x\Restful;class Auth extends Middleware{ // 须要跳过的路由 private $_skip = [ 'shop/delete', ]; // 须要跳过的前置路由 private $_group_skip = [ 'login/', ]; /** * 前置操作 * @todo 无 * @author 小黄牛 * @version v2.5.0 + 2021.07.20 * @deprecated 暂不启用 * @global 无 * @return void */ public function handle() { // 取得以后路由地址 $route = \x\Config::get('route'); $url = str_replace($route['suffix'], '', ltrim(\x\Request::url(), $route['cutting'])); $url = substr_replace($url, '', 0, (strpos($url, $route['cutting'])+1)); // 跳过校验 if (in_array($url, $this->_skip)) { return true; } // 跳过校验 foreach ($this->_group_skip as $v) { if (stripos($url, $v) === 0) { return true; } } // 通过上下文,取得申请实例 $Request = \x\context\Request::get(); $get = $Request->get;// get表单 $post = $Request->post;// post表单 $header = $Request->header;// post表单 // 没有拜访权限 if (!isset($get['test'])) { Restful::code(Restful::ACTION_ERROR())->callback(); // 返回false示意中断执行 return false; } // 返回true示意持续向下执行 return true; }}同时,因为下面咱们应用了一个ACTION_ERROR状态码,所以要在Restful状态码文件中退出响应的配置。 ...

March 2, 2022 · 2 min · jiezi

关于swoole:2022112thinkswoole使用教程

think-swoole应用教程核心思想是swoole只是作为一个音讯转发器,业务逻辑还是通过接口来实现,发送音讯也是应用接口,客户端websocket只负责创立和监听承受音讯即可。环境centos8PHP7.4thinkphp6.0.10think-swoole4.0.6开发过程装置think-swoole扩大为了不便咱们装置think-view扩大配置swoole.php文件 server.host 服务器IPserver.port 服务器端口server.options.daemonize 是否过程websocket.enable 关上websocketwebsocket.handle 本人接管或者应用默认(默认的会给咱们发送socket音讯,不理睬即可)websocket.subscribe 创立事件订阅,我这里的文件名是WebSocketEvent(也能够应用监听,只不过须要多个文件)因为是多过程,咱们须要共享变量,能够用MySQL、redis等,咱们这里应用swoole的共享内容Table,因为同一个用户可能是多端登录,咱们创立俩个Table,一个是用户映射fd,一个是fd映射用户,Table的映射是一对一的,然而一个用户可能有多个fd,所以用户映射fd的Table的值应用逗号分隔的多个值,例如用户1->fd1,fd2配置tables俩个table,别离是m2fd、fd2m,thinkphp实现的Table如何应用请本人看代码 'tables' => [ 'm2fd' => [ 'size' => 102400, 'columns' => [ ['name' => 'fd', 'type' => \Swoole\Table::TYPE_STRING, 'size' => 50] ] ], 'fd2m' => [ 'size' => 102400, 'columns' => [ ['name' => 'member_id', 'type' => \Swoole\Table::TYPE_INT] ] ],],通过订阅实现websocket逻辑把咱们须要应用的类通过构造函数依赖注入,方便使用咱们须要WebSocket类实现通信逻辑,Table类实现用户fd映射如果咱们应用了type为11的绑定形式,则订阅open事件,发送给客户端message事件办法体留空或者不写即可,咱们应用接口来实现逻辑close事件移除用户和fd的映射关系咱们定义一个事件,用于接口触发,从而实现发送音讯逻辑,事件名称叫做ApiEvent,代码如下 <?phpdeclare (strict_types = 1);namespace app\subscribe;use app\Request;use Swoole\Server;use think\swoole\Table;use think\swoole\Websocket;class WebSocketEvent{ private $websocket = null; private $m2fd = null; private $fd2m = null; public function __construct(Websocket $websocket, Table $table) { $this->websocket = $websocket; $this->m2fd = $table->get('m2fd'); $this->fd2m = $table->get('fd2m'); } // 这里之所以注入一个申请,是因为如果咱们不必type=11这种形式绑定,则能够通过new WebSocket的时候把用户ID传递过去,而后间接实现绑定 public function onOpen(Request $request) { $currentFd = $this->websocket->getSender(); $data = [ 'type' => 11, 'fd' => $currentFd ]; $this->websocket->push(json_encode($data)); } public function onClose() { $currentFd = $this->websocket->getSender(); // 通过fd找到用户ID $memberId = $this->fd2m->get((string)$currentFd, 'member_id'); // 如果没有找到映射,就阐明没有绑定过,就什么不做,找到的话就解除绑定 if ($memberId) { $this->fd2m->del((string)$currentFd); // 依据用户ID找到映射的所有fd,而后把存在的以后fd移除掉 $fds = $this->m2fd->get((string)$memberId, 'fd'); if ($fds) { $fdArray = explode(',', $fds); $key = array_search($currentFd, $fdArray); unset($fdArray[$key]); if ($fdArray) { $resFds = implode(',', $fdArray); $this->m2fd->set((string)$memberId, $resFds); } else { $this->m2fd->del((string)$memberId); } } } } public function onApiEvent($data) { // $data是接口传递过去的参数,如果是11则实现绑定,是5就转发给from_id和to_id if ($data['type'] == 11) { // m2fd、fd2m俩个Table的映射 $this->fd2m->set((string)$data['fd'], ['member_id' => $data['member_id']]); // 先查找该用户ID是否曾经绑定过其它fd了 $fds = $this->m2fd->get((string)$data['member_id'], 'fd'); if (!$fds) { $this->m2fd->set((string)$data['member_id'], ['fd' => $data['fd']]); } else { // 看看fd是否在曾经映射的fd中,如果在就什么都不做,如果不在就追加到前面 $fdArray = explode(',', $fds); if (!in_array($data['fd'], $fdArray)) { $this->m2fd->set((string)$data['member_id'], ['fd' => $fds . ',' . $data['fd']]); } } } if ($data['type'] == 1) { // 依据from_id和to_id俩个用户ID找到对应的fd,而后发送音讯 $fromFds = $this->m2fd->get((string)$data['from_id'], 'fd'); $toFds = $this->m2fd->get((string)$data['to_id'], 'fd'); $fromFdArray = $toFdArray = []; if ($fromFds) { $fromFdArray = explode(',', $fromFds); } if ($toFds) { $toFdArray = explode(',', $toFds); } // 合并所有发送者fd和接受者fd,之所以发送给发送者,一方面是简化前端工作,前端只须要承受websocket音讯即可,另一方面,多端的话其它端能够能够即时看到聊天记录 $allFdArray = array_unique(array_merge($fromFdArray, $toFdArray)); // 发送音讯 $this->websocket->to($allFdArray)->push(json_encode($data)); } }}接口实现代码如下 ...

January 12, 2022 · 3 min · jiezi

关于swoole:swoole客户端分析与讲解作为TCP链接webscoket链接等客户端

swoole客户端文章目录1.swoole客户端能解决什么样的问题?2.如何应用?3.注意事项1.首先明确swoole客户端能帮忙咱们解决什么样的问题?业务场景当业务中须要链接TCP,UDP,socket,websocket 服务时,咱们须要编写一个客户端去链接对应的服务(比方链接某些数据源)。 此时有很多种的抉择,workerman的AsyncTcpConnection,或者应用php自带的socket函数(socket_create,socket_read等函数),swoole的客户端(Swoole\Client)等都能够。各有优劣,抉择适宜本人和业务的就好,这次咱们抉择Swoole\Client。 2.如何应用?废话不多说,间接上我封装好的代码,看不懂多看正文了解,能够珍藏不便当前能够间接应用。 class SwooleClient{ //链接对象 private $client; /** * SwooleClient constructor. */ public function __construct() { $this->client = new Swoole\Client(SWOOLE_SOCK_TCP); $this->client->set(array( 'open_eof_check' => true, //关上EOF检测 'package_eof' => "\r\n", //设置EOF,包之间的分隔符 'package_max_length' => 1024 * 1024 * 2,//设置一个包的最大数据包尺寸,单位为字节。默认2m )); } /** * Notes: 链接对应的ip * User: 闻铃 * DateTime: 2021/11/17 下午10:46 * @param string $ip 链接的ip,本地或外网ip * @param int $port 端口号 * @throws \Exception */ public function connect($ip = '127.0.0.1', $port = 9501) { if (!$this->client->connect($ip, $port, -1)) { throw new \Exception(sprintf('Swoole Error: %s', $this->client->errCode)); } } /** * Notes: 发送数据 * User: 闻铃 * DateTime: 2021/11/17 下午10:44 * @param $data 发送的数据 * @return mixed * @throws \Exception */ public function send($data) { if ($this->client->isConnected()) { if (!is_string($data)) { $data = json_encode($data); } return $this->client->send($data); } else { throw new \Exception('Swoole Server does not connected.'); } } /** * Notes: 敞开链接 * User: 闻铃 * DateTime: 2021/11/17 下午10:45 */ public function close() { $this->client->close(); } /** * Notes: 接收数据 * User: 闻铃 * DateTime: 2021/11/17 下午10:45 * @return array */ public function recv() { //承受数据 return $this->client->recv(); }}如果想要作为webscoket客户端的话,也是能够的,能够应用官网的websocket包(saber)后续更新 ...

November 17, 2021 · 1 min · jiezi

关于swoole:swoole毫秒定时器讲解以及实战详细分析

本文分享一个本人应用swoole毫秒定时器遇过的坑,以及解决的办法。心愿能帮忙遇到大家。学习毫秒定时器前,须要具备应用的根底语法,请参考官网文档(https://wiki.swoole.com/#/timer),我就不照搬了。 其中应用毫秒定时器的注意事项要多留神一下,以防前面犯错都不晓得啥起因 1. 定时器仅在以后过程空间内无效2. 定时器是纯异步实现的,不能与同步 IO 的函数一起应用,否则定时器的执行工夫会产生错乱3. 定时器在执行的过程中可能存在肯定误差置信大家在看swoole的毫秒定时器时,跟着官网根底的例子都会写。但用到理论我的项目中,要怎么联合呢?上面就开始分享一下我本人在理论我的项目中应用swoole定时器的形式。 咱们以think5.1框架为例子,比方在某个控制器中,应用毫秒定时器依据官网文档,写出这么一个例子。 public function test(){ $param1 = 'aaa'; $path = __DIR__.'/demo.txt'; //每隔两秒往demo.txt文件写一次数据,用此例子代替业务逻辑 swoole_timer_tick(2000,function () use ($param1, $path) { file_put_contents($path,$param1.'|'.'1'.PHP_EOL,FILE_APPEND); });}你们感觉有问题吗? 马上运行看看 解说一下运行形式:咱们都晓得swoole运行在cli形式下,间接通过浏览器拜访这个办法,必定会报错。报swoole只能运行在cli形式下。那咱们要在cli下如何运行呢?在thinkphp中,咱们就能够在入口文件处,以上面的形式运行swoole。php index.php api/task/testapi/task/test tp中拜访形式:模块名/控制器名/办法名 发现报错.这个谬误我当初百度谷歌了半天,没搞懂。通过询问官网作者后,得以解决.须要在程序开端加这么一段代码 `public function test() { $param1 = 'aaa'; $path = __DIR__.'/demo.txt'; //每隔两秒往demo.txt文件写一次数据,用此例子代替业务逻辑 swoole_timer_tick(2000,function () use ($param1, $path) { file_put_contents($path,$param1.'|'.'1'.PHP_EOL,FILE_APPEND); }); \Swoole\Event::wait();}` 因为定时器是异步的,执行完定时器后,会持续往下执行,而上面没有代码程序就完结了。所以在加一个wait操作,使程序阻塞在那里。这样定时器在两秒后能力继续执行,不会报错。 联想:而在swoole\server的onWorkerStart办法中应用定时器,为啥不必加wait操作,是因为在server->start操作中,其实就加了wait操作。swoole底层解决了,让程序始终阻塞在start办法中。 加上后,再次运行 发现不报错了,查看文件也是有数据的,此时胜利了一大半了。 此时发现程序始终占用着终端。如果咱们ctrl + c,退出定时器就退出了,咱们的业务逻辑就不执行了,这时又来了一个问题,如果定时器能在变成守护过程,始终在后盾运行就好了。 如何变成后盾运行的形式呢?分享两个我的做法。 1.应用nohup命令(linux自带命令)。不挂断的执行某个命令 nohup /usr/local/php/bin/php index.php api/task/test >/dev/null 2>&1 &解释:在后盾不间断的运行命令(/usr/local/php/bin/php index.php api/task/test),并把运行中的输入重定向到linxu的/dev/null(黑洞文件),2>&1是标准化输入。 & 是nohup的命令要求。这样子,就能够把脚本变成守护过程运行了。如下图,就不会霸占光标当前台的形式始终运行了 ...

November 15, 2021 · 1 min · jiezi

关于swoole:swoole-是一个高性能网络通信框架

swoole 是一个高性能网络通信框架, 根本不必 Nginx了, 能够思考用Nginx 做代理服务器。 [ Hyperf、Swoft、EasySwoole、MixPHP ] Master 主过程负责创立多个线程来承受和返回用户的申请, 同时生成一个 manager 过程. manager 过程负责生成和治理 N 多个 worker 和 tasker 过程.worker 和 task 是负责干活的. Manager 项目经理 worker 组长 taskWker 组员 一、Swoole 解说 TCP 服务器UDP 服务器 HTTP 服务器 WebSocket 服务器 Task 工作 协程、管道等 二、异步IO 场景 swoole_timer_tick 毫秒级别定时器 异步文件IOSwoole\Coroutine\System::writeFile()Swoole\Coroutine\System::readFile($filename); 异步MySQL//此行代码后,文件操作,sleep,Mysqli,PDO,streams等都变成异步IO,见文档"一键协程化"章节Swoole\Runtime::enableCoroutine(); 异步Redis

September 30, 2021 · 1 min · jiezi

关于swoole:并行加速脚本任务编写总结协程

1、环境筹备php7/8+ swoole4.6+2、示例代码Coroutine\run(function () use ($db_work, $db_process) { $connPool = new PDOPool((new PDOConfig) ->withHost($db_work['host']) ->withPort(3306) // ->withUnixSocket('/tmp/mysql.sock') ->withDbName($db_work['database']) ->withCharset('utf8mb4') ->withUsername($db_work['user']) ->withPassword($db_work['password']) ); $processPool = new PDOPool((new PDOConfig) ->withHost($db_process['host']) ->withPort(3306) // ->withUnixSocket('/tmp/mysql.sock') ->withDbName($db_process['database']) ->withCharset('utf8mb4') ->withUsername($db_process['user']) ->withPassword($db_process['password']) ); $channel = new Channel(60); $conn = $connPool->get(); $data1 = $conn->query('SELECT count(*) FROM `goods_images` WHERE image_url!=\'\'')->fetchAll(PDO::FETCH_NUM); $data2 = $conn->query('SELECT count(*) FROM `goods_sku` WHERE original_img!=\'\' AND is_delete=0')->fetchAll(PDO::FETCH_NUM); $data3 = $conn->query('SELECT count(*) FROM `goods_spu` WHERE original_img!=\'\' AND is_delete=0')->fetchAll(PDO::FETCH_NUM); $connPool->put($conn); $goods_images_total = $data1[0][0]; $goods_sku_total = $data2[0][0]; $goods_spu_total = $data3[0][0]; $todo = [ 'goods_images' => $goods_images_total, 'goods_sku' => $goods_sku_total, 'goods_spu' => $goods_spu_total, ]; print_r($todo); Coroutine::create(function () use ($channel, $todo) { foreach ($todo as $table => $total) { $page = 0; //第一页开始 while ($page++ <= $total) { $channel->push(['page' => $page, 'table' => $table]); } } }); Coroutine::create(function () use ($channel, $connPool, $processPool) { while (1) { $data = $channel->pop(10); if ($data) { $page = $data['page']; $table = $data['table']; // todo echo($page . ' <== task执行结束 ok' . PHP_EOL); } else { if ($channel->isEmpty()) { echo 'images $channel:empty' . PHP_EOL; break; } } } });});a.剖析建设能够独立运行的最小单位cell两头异步瓶颈mysql也应用连接池,连接池容量应该大于协程工作池容量;应用mysql连接池留神“有借有还”,$conn = $connPool->get();$connPool->put($conn); ...

September 18, 2021 · 1 min · jiezi

关于swoole:TP6Swoole4-反向代理配置

Nginx反向代理配置首先配置反向代理前曾经要把之前配置过的伪动态删除掉,不然保留的时候报错以下是反向代理的残缺配置,请留神其中的端口号[8000]以后这个端口号就是config\swoole.php文件中的server.port配置中的端口号location ~* .(php|jsp|cgi|asp|aspx)${ proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header REMOTE-HOST $remote_addr;}location /{ proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header REMOTE-HOST $remote_addr;add_header X-Cache $upstream_cache_status;#Set Nginx Cache add_header Cache-Control no-cache;expires 12h;}复制代码须要兼容wss和ws协定的可依照以下形式配置,只须要更换location /{}内的配置接口一兼容 应用宝塔部署我的项目的间接能够复制上面的代码来替换反向代理配置中location /{}地位的代码,改反向代理中的配置,别改错了;location /{ proxy_pass http://127.0.0.1:8000; proxy_http_version 1.1; proxy_read_timeout 360s; proxy_redirect off; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header REMOTE-HOST $remote_addr; add_header X-Cache $upstream_cache_status; #Set Nginx Cache add_header Cache-Control no-cache; expires 12h;}复制代码长连贯拜访地址 ...

September 10, 2021 · 1 min · jiezi

关于swoole:swoole协程复用提高性能

实现一个HTTP服务器时,每个申请开一个协程解决,解决完申请后销毁,让咱们优化这段逻辑,咱们很容易联想到FASTCGI比照CGI的改良。无非就是事后生成几个worker过程,解决实现后不销毁复用嘛。 那协程也是同理,为了不便演示,只开一个协程,便于了解。 一个申请一个协程: <?phpCo::set(['hook_flags'=> SWOOLE_HOOK_ALL]);use function Co\run;run(function () { $socket = stream_socket_server( 'tcp://0.0.0.0:9998', $errno, $errstr ); if (!$socket) { echo "$errstr ($errno)" . PHP_EOL; exit(1); } while (true) { $conn = stream_socket_accept($socket); if ($conn) { go(function() use ($conn) { fwrite($conn, "HTTP/1.1 200 OK\r\nContent-Length:11\r\n\r\nHello!world"); }); } }});协程复用: <?phpCo::set(['hook_flags'=> SWOOLE_HOOK_ALL]);use Co\Channel;use function Co\run;run(function () { $channel = new Channel(1); go(function() use ($channel) { while (($conn = $channel->pop()) !== false) { fwrite($conn, "HTTP/1.1 200 OK\r\nContent-Length:11\r\n\r\nHello!world"); } }); $socket = stream_socket_server( 'tcp://0.0.0.0:9999', $errno, $errstr ); if (!$socket) { echo "$errstr ($errno)" . PHP_EOL; exit(1); } while (true) { $conn = stream_socket_accept($socket); if ($conn) { $channel->push($conn); } }});事实中,咱们不会只开一个协程,下次有工夫再来撸一个协程池,实现getWorker和putWorker,反对协程池动静扩容。 ...

August 20, 2021 · 1 min · jiezi

关于swoole:-EasySwoole-发布-v346-部分组件更新-企业级分布式-PHP-协程框架

EasySwoole 公布 v3.4.6 局部组件更新更新内容此次更新咱们更新了局部组件的性能,并且修复局部组件的 bug,持续晋升了 EasySwoole 的稳定性。 公布组件 easyswoole/component v2.3.1 版本;公布组件 easyswoole/rpc v5.0.5 版本;公布组件 easyswoole/pay v1.3.0 版本;对于以上组件的具体应用,请查看 EasySwoole 官网文档。 修复修复 easyswoole/pay 组件,修复不兼容最新支付宝单笔转账接口的 bug。优化优化 easyswoole/component 组件,防止过程对象被反复注册的问题。优化 easyswoole/rpc 组件,让用户能够自定义配置 rpc 服务端应用的最大内存限度。对于 EasySwooleEasySwoole 是一款反对企业级分布式部署的协程 PHP 框架,它是一款常驻内存型的分布式 Swoole 框架,专为 API 而生,解脱传统 PHP 运行模式在过程唤起和文件加载上带来的性能损失,反对高并发、高可用,相比于其余的 Swoole 框架(例如 Hyperf、Swoft 等),EasySwoole 的并发能力更强。EasySwoole 高度封装了 Swoole Server 而仍旧维持 Swoole Server 原有个性,反对同时混合监听 HTTP、WebSocket、自定义 TCP、UDP 协定,并且领有丰盛的组件。例如 协程通用连接池、TP 格调的协程 ORM、协程微信 SDK、协程支付宝 SDK、协程 Kafka 客户端、协程 ElasticSearch 客户端、协程 Consul 客户端、协程 Redis 客户端、协程 Apollo 客户端、协程 NSQ 客户端、协程自定义队列、 协程 Memcached 客户端、协程视图引擎、JWT、协程 RPC、协程 SMTP 客户端、协程 HTTP/WebSocket 客户端、协程 Actor、Crontab 定时器、协程 Redis 连接池、协程 MySQL 连接池、协程上下文治理、IOC、雪花算法 Snowflake Id 生成器、协程 HTTP、TCP、UDP、WebSocket 服务端 、验证器、验证码、自定义过程、Tracker 链路追踪、Atomic限流器、Fast-Cache 缓存、注解及 API 文档主动生成、Policy 权限、Casbin 验证权限、主动生成代码、OAuth、协程 OSS/COS 客户端、Printer 易联云打印机 SDK、数据库迁徙、协程 Etcd 客户端 等诸多组件。让开发者以最低的学习老本和精力编写出多过程、可异步、高可用的应用服务。 ...

August 10, 2021 · 1 min · jiezi

关于swoole:MixPHP-V3-开发流程体验-Swoole-Workerman-FPM-CLIServer-多种运行模式介绍

MixPHP V3 公布后,因为自身反对超多的执行模式,用户可能无从下手,这里先大体介绍一下: CLI-Server: 适宜本机开发,零扩大依赖,Windows/MacOS 等全平台反对PHP-FPM: 适宜共享开发环境部署,同时适宜 admin 等治理后盾我的项目Swoole, Workerman: 适宜线上部署,依据须要抉择其一即可Swoole 的多种模式: Swoole 多进程同步: 适宜须要应用那些协程不反对的第三方库的我的项目,和 Workerman 统一Swoole 多过程协程: 适宜专一 mysql + redis 须要超高 io 性能的我的项目Swoole 单过程协程: 单过程协程就是 V2.2 版本那种 golang 格调协程,适宜开发 websocket简直反对 PHP 风行的全副执行模式,并且以上执行模式代码是无缝切换的,真正做到效率与性能并存。 请帮忙 Star 一下: https://github.com/mix-php/mixhttps://gitee.com/mix-php/mix首先创立一个骨架咱们以开发一个 API 我的项目为例,关上 MixPHP 的 开发文档 外面有 cli api web websocket grpc 我的项目的开发教程,V3 开始仓库底下的 README 就是开发文档,如果有不明确的能够加咱们的 官网QQ群 参加探讨。 首先创立一个骨架如果提醒短少 redis 等扩大反对,能够应用 --ignore-platform-reqs 临时疏忽依赖查看 composer create-project --prefer-dist --ignore-platform-reqs mix/api-skeleton api装置后目录构造如下: bin 目录是全副入口文件,不同文件对应的不同驱动模式routes 是路由配置文件public/index.php 是 FPM, CLI-Server 两种模式的入口文件shell/server.sh 是部署是治理过程 start|stop|restart├── README.md├── bin│   ├── cli.php│   ├── swoole.php│   ├── swooleco.php│   └── workerman.php├── composer.json├── composer.lock├── conf│   └── config.json├── public│   └── index.php├── routes│   └── index.php├── runtime├── shell│   └── server.sh├── src│   ├── Command│   ├── Container│   ├── Controller│   ├── Error.php│   ├── Middleware│   ├── Vega.php│   └── functions.php└── vendor应用 CLI-Server 零扩大依赖模式本机开发首先咱们查看一下 composer.json,与其余框架不同的是咱们举荐在本机开发阶段应用 composer run-script 启动程序,能够和 PhpStorm 的调试性能完满配合。 ...

August 10, 2021 · 2 min · jiezi

关于swoole:laravelswoole的扩展不兼容消息队列该怎么办

这段时间用laravel8+laravel-swoole做我的项目,可发现laravel-swoole的扩大不兼容音讯队列; 思来想去这咋办呢,这咋办呢.咋办那就本人写咯!还好thinkphp-swoole扩大曾经兼容了,那不就嘿嘿嘿! 间接上批改的思路和代码!开干! 一种是减少另外启动的命令或者在swoole启动的时候一起启动音讯队列进行生产,我这么懒的人一个命令能解决的,绝不写两命令. 首先重写swoole启动命令 <?phpnamespace crmeb\swoole\command;use Illuminate\Support\Arr;use Swoole\Process;use SwooleTW\Http\Server\Facades\Server;use SwooleTW\Http\Server\Manager;use crmeb\swoole\server\InteractsWithQueue;use crmeb\swoole\server\FileWatcher;use Swoole\Runtime;class HttpServerCommand extends \SwooleTW\Http\Commands\HttpServerCommand{ use InteractsWithQueue; /** * The name and signature of the console command. * * @var string */ protected $signature = 'crmeb:http {action : start|stop|restart|reload|infos}'; /** * Run swoole_http_server. */ protected function start() { if ($this->isRunning()) { $this->error('Failed! swoole_http_server process is already running.'); return; } $host = Arr::get($this->config, 'server.host'); $port = Arr::get($this->config, 'server.port'); $hotReloadEnabled = Arr::get($this->config, 'hot_reload.enabled'); $queueEnabled = Arr::get($this->config, 'queue.enabled'); $accessLogEnabled = Arr::get($this->config, 'server.access_log'); $coroutineEnable = Arr::get($this->config, 'coroutine.enable'); $this->info('Starting swoole http server...'); $this->info("Swoole http server started: <http://{$host}:{$port}>"); if ($this->isDaemon()) { $this->info( '> (You can run this command to ensure the ' . 'swoole_http_server process is running: ps aux|grep "swoole")' ); } $manager = $this->laravel->make(Manager::class); $server = $this->laravel->make(Server::class); if ($accessLogEnabled) { $this->registerAccessLog(); } //热更新重写 if ($hotReloadEnabled) { $manager->addProcess($this->getHotReloadProcessNow($server)); } //启动音讯队列进行生产 if ($queueEnabled) { $this->prepareQueue($manager); } if ($coroutineEnable) { Runtime::enableCoroutine(true, Arr::get($this->config, 'coroutine.flags', SWOOLE_HOOK_ALL)); } $manager->run(); } /** * @param Server $server * @return Process|void */ protected function getHotReloadProcessNow($server) { return new Process(function () use ($server) { $watcher = new FileWatcher( Arr::get($this->config, 'hot_reload.include', []), Arr::get($this->config, 'hot_reload.exclude', []), Arr::get($this->config, 'hot_reload.name', []) ); $watcher->watch(function () use ($server) { $server->reload(); }); }, false, 0, true); }}InteractsWithQueue 类 ...

July 30, 2021 · 4 min · jiezi

关于swoole:MixPHP-V3-增加了-PHPFPMCLIServer-的支持

MixPHP V3 主推 Swoole、WorkerMan 驱动,因为这两个平台性能强劲。然而大部分的 PHP 开发者都是开发 API, Web 的程序员,热更新可能是最大的刚性需要,因而我对 Vega 做了扩大,让 MixPHP 减少了 PHP-FPM、CLI-Server 的反对,开发环境能够应用这两种模式,线上部署时切换为性能更强劲的 Swoole、WorkerMan 驱动。 开箱难即用PHP 生态须要很多外设,当咱们开发时: Swoole:须要装置 swoole 扩大WorkerMan:win 不须要装置,macOS 须要装置 pcntl,线上部署须要装置 eventPHP-FPM: 须要装置 php-fpm, nginxCLI-Server:惟一不须要装置任何货色,间接在命令行启动,自带热更新PHP-FPM和 Laravel、ThinkPHP 部署办法完全一致,将 public/index.php 在 nginx 配置 rewrite 重写即可 实时热更新适宜在多人共享的开发环境中应用这种模式同时如果有些治理后盾开发须要 session 的也能够应用该模式如何在 MixPHP 中应用CLI-ServerCLI-Server 是 PHP 内置的Web服务器,因为是单进程同步,因而性能个别不能在线上应用,然而和 phpfpm 一样自带热更新。 实时热更新适宜在本机开发无需任何额定的扩大,所有平台兼容反对动态文件解决,无需装置 nginx, phpfpm 这些如何在 MixPHP 中应用CLI-Server 模式在 thinkphp 中也反对,然而 thinkphp 不反对动态文件解决,MixPHP 对 动态文件 也做了反对,不须要额定采纳 nginx 解决文件。

July 28, 2021 · 1 min · jiezi

关于swoole:试用-mixvega-mixdb-进行现代化的原生-PHP-开发

最近几年在 javascript、golang 生态中游走,发现很多 npm、go mod 的长处。最近回过头开发 MixPHP V3 ,发现 composer 其实始终都是一个十分优良的工具,然而 phper 们对 composer 的用法很多都不是很深刻,明天我就采纳 composer 手撸一个原生我的项目,帮忙大家了解现代化的原生 PHP 开发流程。 PHP 的开发者可能是所有语言里被惯坏的最厉害的,因为简直每个框架都提供了脚手架,像这样: composer create-project这个在 npm、go mod 是没有这个性能的,须要本人创立程序骨架,当然 npm 和 go 生态产生了本人的解决方案,就是 vue-cli 和 mixcli 这样的脚手架工具来负责创立。 创立一个我的项目和 npm init 、go mod init 一样,咱们应用 composer init 创立一个我的项目 mkdir hellocd hellocomposer init 交互式填写一些内容后,生成了 composer.json 文件 { "name": "liujian/hello", "type": "project", "autoload": { "psr-4": { "Liujian\\Hello\\": "src/" } }, "require": {}}这个文件是以 composer 库的规范创立的,必须要两级名称,这让我很蛋疼,所以我批改一下 { "name": "project/app", "type": "project", "autoload": { "psr-4": { "App\\": "src/" } }, "require": {}}抉择我须要应用的库和 node.js、go 生态一样,第二步就是寻找咱们须要的库,通常咱们的需要是写一个 API 服务,就须要一个 http server 库,一个 db 库就能够开始工作了。 ...

July 7, 2021 · 3 min · jiezi

关于swoole:Mix-Vega-发布-支持-SwooleWorkerMan-的-CLI-HTTP-网络框架

Mix VegaVega 是一个用 PHP 编写的 CLI HTTP 网络框架,反对 Swoole、WorkerMan OverviewVega 是 MixPHP V3+ 内置的最外围的组件 (可独立应用),参考golang gin mux 开发,它蕴含 Web 利用解决的大量性能 (数据库解决除外),包含:路由、渲染、参数获取、中间件、文件上传解决等;具备 CLI 模式下弱小的兼容性,同时反对 Swoole、WorkerMan, 并且反对 Swoole 的多种过程模型。 源码地址Star 一下不迷路,下次用的时候还能找到 https://github.com/mix-php/vegahttps://gitee.com/mix-php/vegaInstallation须要先装置 Swoole 或者 WorkerMancomposer require mix/vegaQuick startSwoole 多过程 (异步) 中应用 <?phprequire __DIR__ . '/vendor/autoload.php';$vega = new Mix\Vega\Engine();$vega->handleF('/hello', function (Mix\Vega\Context $ctx) { $ctx->string(200, 'hello, world!');})->methods('GET');$http = new Swoole\Http\Server('0.0.0.0', 9501);$http->on('Request', $vega->handler());$http->start();Swoole 单过程 (协程) 中应用 <?phprequire __DIR__ . '/vendor/autoload.php';Swoole\Coroutine\run(function () { $vega = new Mix\Vega\Engine(); $vega->handleF('/hello', function (Mix\Vega\Context $ctx) { $ctx->string(200, 'hello, world!'); })->methods('GET'); $server = new Swoole\Coroutine\Http\Server('127.0.0.1', 9502, false); $server->handle('/', $vega->handler()); $server->start();});WorkerMan 中应用 ...

June 29, 2021 · 3 min · jiezi

关于swoole:Mac-Silicon-M1-编译安装-PHP8-Swoole46-ARM64-全过程记录

最近搞了一台 M1 的 Mac mini 筹备用这个开发 mixphp v3 版本,之前尝试了几次没有编译胜利,明天再次尝试装置胜利了,网络上很多人的文章存在问题(可能是零碎环境不同),特此分享让后续的人闭坑。 面临的问题因为最新版本的 macOS Big Sur 即使敞开平安模式 /usr/lib 也无奈写入文件,因而导致 make install 无奈装置任何 php 扩大,因而想装 Swoole 只能自行编译装置 php 到 /usr/local 目录 % csrutil statusSystem Integrity Protection status: disabled.% mkdir /usr/lib/php/extensions/testmkdir: /usr/lib/php/extensions/test: Read-only file systemPHP Build因为 brew arm64 版本无奈应用,只能采纳 x64 版本装置了一些依赖,前面导致了很多问题,本想编译一个 x64 PHP+Swoole 在编译 x64 Swoole 的时候异样就没有持续了,转而钻研 arm64 PHP+Swoole arch -x86_64 brew install openssl zlib curl libjpeg libpng libxml2 gettext freetype pcre libiconv libzip参数中的门路都须要依据本人电脑所装置的理论门路替换 ...

June 24, 2021 · 2 min · jiezi

关于swoole:Swoole-v47-版本新特性预览之-onDisconnect-事件回调

在之前的版本中可能有这样一种状况,在 WebSocket 服务器中无奈在 close 事件回调中辨别该 fd 是否为 WebSocket 连贯,例如以下代码: //创立WebSocket Server对象,监听0.0.0.0:9501端口$ws = new Swoole\WebSocket\Server('0.0.0.0', 9501);//监听WebSocket连贯关上事件$ws->on('Open', function ($ws, $request) { $ws->push($request->fd, "hello, welcome\n");});//监听WebSocket音讯事件$ws->on('Message', function ($ws, $frame) { echo "Message: {$frame->data}\n"; $ws->push($frame->fd, "server: {$frame->data}");});//监听WebSocket连贯敞开事件$ws->on('Close', function ($ws, $fd) { echo "client-{$fd} is closed\n";});$ws->start();启动服务后,应用浏览器对127.0.0.1:9501发动申请,终端会失去输入: client-1 is closed[2021-05-24 16:58:08 *37715.1] NOTICE end (ERRNO 1005): session[1] is closed这样的输入并不能晓得这个$fd为1的连贯是否为 WebSocket 连贯。如果业务代码中存在间接应用该$fd去做一些逻辑解决是无用的,也有可能会产生有人歹意申请导致占用资源。 那么相熟 Swoole 开发的人就会想到能够减少判断:应用 getClientInfo 办法的websocket_status值来获取 WebSocket 连贯状态 当服务器是 WebSocket\Server 时, getClientInfo 会额定减少websocket_status信息,它有对应的 4 种状态,别离为 常量对应值阐明WEBSOCKET_STATUS_CONNECTION1连贯进入期待握手WEBSOCKET_STATUS_HANDSHAKE2正在握手WEBSOCKET_STATUS_ACTIVE3已握手胜利期待浏览器发送数据帧WEBSOCKET_STATUS_CLOSING4连贯正在进行敞开握手,行将敞开能够批改上述代码中的 onClose 回调: ...

May 31, 2021 · 1 min · jiezi

关于swoole:macOS-Big-Sur下pecl安装swoole报需要openssl库解决方法

macOS中pecl形式装置swoole报Enable openssl support, require openssl library谬误 首先确保装置了openssl库,能够通过brew list --formula | grep openssl查看是否曾经装置 而后装置的时候在提醒openssl是否启用,手动指定openssl目录即可 enable openssl support? [no] yes --with-openssl-dir=/usr/local/Cellar/openssl@1.1/1.1.1h/留神openssl@1.1前面的子目录,确保外面存在include目录存在,否则会报openssl/ssl.h file not found谬误

March 17, 2021 · 1 min · jiezi

关于swoole:利用-Swoole-的-Channel-测试-Websocket-异步服务器性能

环境应用 Docker: 在 docker 中搭建 swoole 运行环境 服务器端应用后面文章中的代码: 在 Swoole 中应用 WebSocket 服务端和客户端 测试程序代码: <?phpuse Swoole\Coroutine\Channel;use Swoole\Coroutine\Http\Client;use function Swoole\Coroutine\run;class Test{ protected $request; //总申请量 protected $requested = 0; protected $start_time; protected $channel; function __construct() { $this->request = 5000; $this->channel = new Channel($this->request); } public function run() { $this->start_time = microtime(true); $this->webSocket(); } protected function webSocket() { // 创立一个协程容器 run(function () { for ($i = 0; $i < $this->request; $i++) { // 开启一个协程 go(function() use ($i) { $cli = new Client('127.0.0.1', 8081); $cli->set(['websocket_mask' => false]); $ret = $cli->upgrade('/'); if ($ret) { $ret = $cli->push('1 Hello World'); $this->channel->push([$i => $cli->recv(1)]); } }); } $this->finish(); }); } protected function finish() { for ($i = 1; $i <= $this->request; $i++) { $this->channel->pop(); $this->requested++; } $cost_time = round(microtime(true) - $this->start_time, 4); echo "Request num:" . $this->request.PHP_EOL; echo "Success num:" . $this->requested.PHP_EOL; echo "Total time:" . $cost_time.PHP_EOL; echo "Request per second:" . intval($this->request / $cost_time).PHP_EOL; }}$test = new Test();$test->run();测试后果: ...

December 19, 2020 · 1 min · jiezi

关于swoole:Swoole安装问题

Swoole装置问题官网文档 -- https://wiki.swoole.com/wiki/page/1.html 官网谬误问题 -- https://wiki.swoole.com/wiki/page/438.html 一.装置问题: 编译问题 (1) 明天部署PHP多版本共存,遇到编译失败error,没有找到php-config,在编译装置swoole时,./configure须要一些参数,残缺如下: ./configure --with-php-config=/usr/local/php7/bin/php-config 后边门路请替换成本人的理论门路。 (2) make: * [php_swoole_cxx.lo] 谬误 1 在执行这一步时 谬误:./configure --with-php-config=/www/server/php/72/bin/php 指定的是php-config文件,而不是php文件 正确:./configure --with-php-config=/www/server/php/72/bin/php-config

December 18, 2020 · 1 min · jiezi

关于swoole:thinkswoole实战案例演示

官网文档thinkphp6文档https://www.kancloud.cn/manua...swoole文档https://wiki.swoole.com/#/ think-swoole文档https://www.kancloud.cn/manua... 装置composer require topthink/think-swoole命令行php think swoole [start|stop|reload|restart]服务启动当你在命令行php think swoole下执行实现之后就会启动一个HTTP Server,能够间接拜访以后的利用 'server' => [ 'host' => env('SWOOLE_HOST', '0.0.0.0'), // 监听地址 'port' => env('SWOOLE_PORT', 9501), // 监听端口 'mode' => SWOOLE_PROCESS, // 运行模式 默认为SWOOLE_PROCESS 'sock_type' => SWOOLE_SOCK_TCP, // sock type 默认为SWOOLE_SOCK_TCP 'options' => [ // 服务启动后,过程ID寄存文件 'pid_file' => runtime_path() . 'swoole.pid', // swoole 的日志文件 'log_file' => runtime_path() . 'swoole.log', // 守护过程模式设置 true 后盾运行 'daemonize' => false, // 设置启动的reactor线程数 'reactor_num' => swoole_cpu_num(), // 设置启动的worker过程数 'worker_num' => swoole_cpu_num(), //配置Task过程的数量 'task_worker_num' => swoole_cpu_num(), //开启动态文件申请解决,需配合document_root 'enable_static_handler' => true, //动态文件根目录 'document_root' => root_path('public'), // 设置最大数据包尺寸,单位字节 'package_max_length' => 20 * 1024 * 1024, //配置发送输入缓冲区内存尺寸 'buffer_output_size' => 10 * 1024 * 1024, //设置客户端连贯最大容许占用的内存数量 'socket_buffer_size' => 128 * 1024 * 1024, ],],热更新swoole服务器运行过程中php文件是常驻内存运行,这样就能够防止反复的读取磁盘,反复的解释编译php,以便达到最高的性能,所以批改代码须要重启服务 ...

October 23, 2020 · 5 min · jiezi

关于swoole:Swoole-456-支持零拷贝-JSON-或-PHP-反序列化

在最新的4.5.6开发分支中,底层减少了2个非凡的函数: swoole_substr_json_decodeswoole_substr_unserialize这里为什么要减少这两个函数呢?有这样一种场景。应用Swoole\Server实现RPC服务,在EOF协定或长度协定通信形式下,一个包可能有3局部组成。 $packet = $header + $body + $footer通常$header和$footer比拟小,而$body比拟大,$body可能会应用JSON或PHP序列化格局。如果要解析$body数据,那么就须要先进行substr失去$body的字符串格局数据,再进行json_decode和unserialize操作。 这会引起一次内存拷贝,$body_str = substr($packet, $header_length)的过程会创立一个长期字符串变量,再反序列化操作$body = json_decode($body_str)之后,这个变量就会被开释。 // 先进行 substr,这时会产生内存拷贝,从 $packet 复制数据到 $body_str$body_str = substr($packet, 4, strlen($packet) - 4 - 2);// 反序列化之后 $body_str 这块内存不再应用,会在函数退出时开释$body = json_decode($body_str, true);应用新增的两个函数就能够将substr和反序列化操作合二为一。缩小一次内存拷贝,从而进步性能。 $body = swoole_substr_json_decode($packet, $header_length);$body = swoole_substr_unserialize($packet, $header_length);压测<?phperror_reporting(E_ALL);$a['hello'] = base64_encode(random_bytes(1000));$a['world'] = 'hello';$a['int'] = rand(1, 999999);$a['list'] = ['a,', 'b', 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'];$val = serialize($a);$str = pack('N', strlen($val)).$val."rn";$n = 100000;$s = microtime(true);while($n--) { $l = strlen($str) - 6; // var_dump(unserialize(substr($str, 4, $l))); var_dump(swoole_substr_unserialize($str, 4, $l));}echo "cost: ".(microtime(true)-$s)."n";应用swoole_substr_unserialize与substr + unserialize相比,性能晋升了12% ...

October 22, 2020 · 1 min · jiezi

关于swoole:thinkswoole扩展实现在传统phpfpm环境调用rpc服务

https://github.com/top-think/think-swoole tp官网的think-swoole提供了一个rpc服务和客户端,然而他的rpc客户端只能在swoole的环境下运行 然而swoole官网是提供了同步客户端/swoole/client的,如果把框架的client更换成官网的同步client,应该就能实现在传统的fpm环境中调用swoole环境下的rp~~~~c。 <?phpnamespace appservice;use Exception;use Generator;use SwooleClient;use SwooleCoroutine;use thinkhelperArr;use thinkService;use thinkswooleexceptionRpcClientException;use thinkswoolePool;use thinkswoolerpcclientConnector;use thinkswoolerpcclientGateway;use thinkswoolerpcclientProxy;use thinkswoolerpcJsonParser;use thinkswoolerpcPacker;use Throwable;class SwooleRpcServiceLoad extends Service{ public $rpcServices = []; /** * 注册服务 * * @return mixed */ public function register() { if (php_sapi_name() == 'fpm-fcgi') { if (file_exists($rpc = $this->app->getBasePath() . 'rpc.php')) { $this->rpcServices = (array)include $rpc; } } } /** * 执行服务 * * @return mixed */ public function boot() { if (!empty($clients = config('swoole.rpc.client')) && $this->rpcServices) { try { foreach ($this->rpcServices as $name => $abstracts) { $parserClass = config("swoole.rpc.client.{$name}.parser", JsonParser::class); $parser = $this->app->make($parserClass); $gateway = new Gateway($this->createRpcConnector($name), $parser); foreach ($abstracts as $abstract) { $this->app->bind($abstract, function () use ($gateway, $name, $abstract) { return $this->app->invokeClass(Proxy::getClassName($name, $abstract), [$gateway]); }); } } } catch (Exception | Throwable $e) { } } } protected function createRpcConnector($name) { return new class($name) implements Connector { public $name; public function __construct($name) { $this->name = $name; } public function sendAndRecv($data) { if (!$data instanceof Generator) { $data = [$data]; } $config = config('swoole.rpc.client.' . $this->name); $client = new Client(SWOOLE_SOCK_TCP); $host = Arr::pull($config, 'host'); $port = Arr::pull($config, 'port'); $timeout = Arr::pull($config, 'timeout', 5); $client->set([ 'open_length_check' => true, 'package_length_type' => Packer::HEADER_PACK, 'package_length_offset' => 0, 'package_body_offset' => 8, ]); $client->connect($host, $port, $timeout); try { foreach ($data as $string) { if (!$client->send($string)) { $this->onError($client); } } $response = $client->recv(); if ($response === false || empty($response)) { $this->onError($client); } return $response; } finally { $client->close(); } } protected function onError(Client $client) { $client->close(); throw new RpcClientException(swoole_strerror($client->errCode), $client->errCode); } }; }}把下面这个服务在thinkphp框架中注册,就能够实现在fpm环境下调用think-swoole的rpc ...

October 17, 2020 · 2 min · jiezi

关于swoole:Swoole-v455-版本发布增加配置项检测

此版本减少了配置项检测性能,如果设置了不是 Swoole 提供的选项,会产生一个 Warning。 PHP Warning: unsupported option [foo] in @swoole-src/library/core/Server/Helper.php$http = new Swoole\Http\Server('0.0.0.0', 9501);$http->set(['foo' => 'bar']);$http->on('request', function ($request, $response) { $response->header("Content-Type", "text/html; charset=utf-8"); $response->end("<h1>Hello Swoole. #".rand(1000, 9999)."</h1>");});$http->start();新增 API减少 Process\Manager,批改 Process\ProcessManager 为别名 (swoole/library#eac1ac5) (@matyhtf)反对 HTTP2 服务器 GOAWAY (#3710) (@doubaokun)减少 Co\map()函数 (swoole/library#57) (@leocavalcante)加强反对 http2 unix socket 客户端 (#3668) (@sy-records)当 worker 过程退出之后设置 worker 过程状态为 SW_WORKER_EXIT (#3724) (@matyhtf)在 Server::getClientInfo()的返回值中减少 send_queued_bytes 和 recv_queued_bytes (#3721) (#3731) (@matyhtf) (@Yurunsoft)Server 反对 stats_file 配置选项 (#3725) (@matyhtf) (@Yurunsoft)修复修复 PHP8 下的编译问题 (zend_compile_string change) (#3670) (@twose)修复 PHP8 下的编译问题 (ext/sockets compatibility) (#3684) (@twose)修复 PHP8 下的编译问题 (php_url_encode_hash_ex change) (#3713) (@remicollet)修复从'const char' to 'char'的谬误类型转化 (#3686) (@remicollet)修复 HTTP2 client 在 HTTP proxy 下无奈工作的问题 (#3677) (@matyhtf) (@twose)修复 PDO 断线重连时数据凌乱的问题 (swoole/library#54) (@sy-records)修复 UDP Server 应用 ipv6 时端口解析谬误修复 Lock::lockwait 超时有效的问题 ...

October 15, 2020 · 1 min · jiezi

关于swoole:Swoole-455-对-Server-数据收发时间的优化

Commit: https://github.com/swoole/swoole-src/pull/3708/files 在之前的版本中,底层提供了connect_time和last_time两项工夫信息,单位为秒,示意: 连贯到服务器的工夫最初一次接收数据的工夫在非常复杂理论的我的项目中,这两项信息是远远不能满足需要的,在最新的版本中咱们进行了优化。 工夫精度调整底层的工夫全副改为应用 double 类型,准确到了微秒,包含:建设连贯与数据接管、投递、数据。 减少发送和投递工夫在 Server::getClientInfo() 办法的返回值中减少了3个新的工夫字段,精度为微秒: last_recv_time:最近一次接收数据的工夫last_dispatch_time:最近一次投递数据的工夫,当触发onReceive回调时,读取此信息能够失去,以后的$data在master过程是什么工夫dispatch的,通过判断投递工夫和以后工夫的差值,能够失去工作在管道中期待的耗时last_send_time:最近一次发送到内核Socket缓存区的工夫,通过此工夫能够判断出客户端是否能够失常接管数据包,是否存在接管延时通过更准确的工夫数据,能够实现更细粒度的通信治理。

September 29, 2020 · 1 min · jiezi

关于swoole:Swoole-v453-版本发布

新增 API减少 Swoole\Process\ProcessManager (swoole/library#88f147b) (@huanghantao)减少 ArrayObject::append, StringObject::equals (swoole/library#f28556f) (@matyhtf)减少 Coroutine::parallel (swoole/library#6aa89a9) (@matyhtf)减少 CoroutineBarrier (swoole/library#2988b2a) (@matyhtf)加强减少 usePipelineRead 来反对 http2 client streaming (#3354) (@twose)http 客户端下载文件时,在承受数据前不创立文件 (#3381) (@twose)http client 反对bind_address和bind_port配置 (#3390) (@huanghantao)http client 反对lowercase_header配置 (#3399) (@matyhtf)Swoole\Server反对tcp_user_timeout配置 (#3404) (@huanghantao)Coroutine\Socket减少 event barrier 来缩小协程切换 (#3409) (@matyhtf)为特定的 swString 减少memory allocator (#3418) (@matyhtf)cURL 反对__toString (swoole/library#38) (@twose)反对间接在 WaitGroup 构造函数中设置wait count (swoole/library#2fb228b8) (@matyhtf)减少CURLOPT_REDIR_PROTOCOLS (swoole/library#46) (@sy-records)http1.1 server 反对 trailer (#3485) (@huanghantao)协程 sleep 工夫小于 1ms 将会 yield 以后协程 (#3487) (@Yurunsoft)http static handler 反对软连贯的文件 (#3569) (@LeiZhang-Hunter)在 Server 调用完 close 办法之后立即敞开 WebSocket 连贯 (#3570) (@matyhtf)反对 hook stream_set_blocking (#3585) (@Yurunsoft)异步 HTTP2 server 反对流控 (#3486) (@huanghantao) (@matyhtf)开释 socket buffer 在 onPackage 回调函数执行完 (#3551) (@huanghantao) (@matyhtf)修复修复 WebSocket coredump, 解决协定谬误的状态 (#3359) (@twose)修复 swSignalfd_setup 函数以及 wait_signal 函数里的空指针谬误 (#3360) (@twose)修复在设置了 dispatch_func 时候,调用Swoole\Server::close会报错的问题 (#3365) (@twose)修复Swoole\Redis\Server::format函数中 format_buffer 初始化问题 (#3369) (@matyhtf) (@twose)修复 MacOS 上无奈获取 mac 地址的问题 (#3372) (@twose)修复 MySQL 测试用例 (#3374) (@qiqizjl)修复多处 PHP8 兼容性问题 (#3384) (#3458) (#3578) (#3598) (@twose)修复 hook 的 socket write 中失落了 php_error_docref, timeout_event 和返回值问题 (#3383) (@twose)修复异步 Server 无奈在WorkerStart回调函数中敞开 Server 的问题 (#3382) (@huanghantao)修复心跳线程在操作 conn->socket 的时候,可能会产生 coredump 的问题 (#3396) (@huanghantao)修复 send_yield 的逻辑问题 (#3397) (@twose) (@matyhtf)修复 Cygwin64 上的编译问题 (#3400) (@twose)修复 WebSocket finish 属性有效的问题 (#3410) (@matyhtf)修复脱漏的 MySQL transaction 谬误状态 (#3429) (@twose)修复 hook 后的stream_select与 hook 之前返回值行为不统一的问题 (#3440) (@Yurunsoft)修复应用Coroutine\System来创立子过程时失落SIGCHLD信号的问题 (#3446) (@huanghantao)修复sendwait不反对 SSL 的问题 (#3459) (@huanghantao)修复ArrayObject和StringObject的若干问题 (swoole/library#44) (@matyhtf)修复 mysqli 打印谬误音讯时错别字的问题 (swoole/library#45) (@sy-records)修复当设置open_eof_check后,Swoole\Client无奈获取正确的errCode的问题 (#3478) (@huanghantao)修复 MacOS 上 atomic->wait()/wakeup()的若干问题 (#3476) (@Yurunsoft)修复Client::connect连贯回绝的时候,返回胜利状态的问题 (#3484) (@matyhtf)修复 alpine 环境下 nullptr_t 没有被申明的问题 (#3488) (@limingxinleo)修复 HTTP Client 下载文件的时候,double-free 的问题 (#3489) (@Yurunsoft)修复Server被销毁时候,Server\Port没开释导致的内存透露问题 (#3507) (@twose)修复 MQTT 协定解析问题 (318e33a) (84d8214) (80327b3) (efe6c63) (@GXhua) (@sy-records)修复Coroutine\Http\Client->getHeaderOut办法导致的 coredump 问题 (#3534) (@matyhtf)修复 SSL 验证失败后,失落了错误信息的问题 (#3535) (@twose)修复 README 中,Swoole benchmark链接谬误的问题 (#3536) (@sy-records) (@santalex)修复在HTTP header/cookie中应用CRLF后导致的header注入问题 (#3539) (#3541) (#3545) (chromium1337) (@huanghantao)修复 issue #3463 中提到的变量谬误的问题 (#3547) (chromium1337) (@huanghantao)修复 pr #3463 中提到的错别字问题 (#3547) (@deminy)修复协程 WebSocket 服务器 frame->fd 为空的问题 (#3549) (@huanghantao)修复心跳线程错误判断连贯状态导致的连贯透露问题 (#3534) (@matyhtf)修复Process\Pool中阻塞了信号的问题 (#3582) (@huanghantao) (@matyhtf)修复SAPI中应用 send headers 的问题 (#3571) (@twose) (@sshymko)修复CURL执行失败的时候,设置了谬误的code和message的问题 (swoole/library#1b6c65e) (@sy-records)修复当调用了setProtocol办法后,swoole_socket_coroaccept coredump 的问题 (#3591) (@matyhtf)内核应用 C++格调 (#3349) (#3351) (#3454) (#3479) (#3490) (@huanghantao) (@matyhtf)减少Swoole known strings来进步PHP对象读属性的性能 (#3363) (@huanghantao)多处代码优化 (#3350) (#3356) (#3357) (#3423) (#3426) (#3461) (#3463) (#3472) (#3557) (#3583) (@huanghantao) (@twose) (@matyhtf)多处测试代码的优化 (#3416) (#3481) (#3558) (@matyhtf)简化Swoole\Table的int类型 (#3407) (@matyhtf)减少sw_memset_zero,并且替换bzero函数 (#3419) (@CismonX)优化日志模块 (#3432) (@matyhtf)多处 libswoole 重构 (#3448) (#3473) (#3475) (#3492) (#3494) (#3497) (#3498) (#3526) (@matyhtf)多处头文件引入重构 (#3457) (@matyhtf) (@huanghantao)减少Channel::count()和Channel::get_bytes() (f001581) (@matyhtf)减少scope guard (#3504) (@huanghantao)减少 libswoole 覆盖率测试 (#3431) (@huanghantao)减少 lib-swoole/ext-swoole MacOS 环境的测试 (#3521) (@huanghantao)减少 lib-swoole/ext-swoole Alpine 环境的测试 (#3537) (@limingxinleo) ...

September 2, 2020 · 2 min · jiezi

关于swoole:基于PHP-swoole扩展的秒杀思路

基于PHP swoole扩大的秒杀思路通过ab压力测试,脚本QPS平均值在4500ab -n1000 -c10 http://127.0.0.1:9501/skill脚本代码,思路基本上也在代码正文中说明确了application-Index.php/** * 通过swoole的chan和协程解决秒杀 * 思路 * 设置一个通道channel数量为1,一个协程向外面写入用户的数据比方是用户的ID * 另一个协程来解决通道的数据写入beanstalkd或者Redis中的队列 * 此例子我选用写入Redis的list,因为我本机没装置beanstalkd */ public function skill() { $chan = new channel(1); $redis = new \Redis(); $redis->connect('127.0.0.1', 6379); $res = []; //生成者协程 co::create(function () use ($chan, $redis, &$res) { //查看chan中的数量 $num = $chan->length(); //当数量等于0并且库存还有的状况下的时候表名,通道为空能够写入数据 // stock库存须要提前设置到Redis if ($num == 0 && $redis->get('stock')) { $chan->push(['id' => rand(100, 999)]); } $res = [0, "抢购失败"]; }); //消费者协程 co::create(function () use ($chan, $redis, &$res) { $data = $chan->pop(); if ($data) { //此处默认每个人抢购1件,如果须要抢购多件能够在data中携带购买数量 //并写入到通道外面 $redis->set('stock', $redis->get('stock') - 1); //写入Redis list 其余脚本进行订单解决之类的IO业务。此处不实现了 $redis->lPush("skill_swoole", json_encode($data)); $res = [1, "抢购胜利"]; } }); return Tool::print_json($res[0], $res[1]); }代码仓库https://github.com/SmallForest/Thomas.kj这是自己本人写的框架,CLI模式,常驻内存性能高,感兴趣的能够提提倡议,帮忙一起优化。感激star ...

August 6, 2020 · 1 min · jiezi

关于swoole:PHP-框架-QueryPHP-10-正式版四年打磨生产可用

【开源新闻】https://www.oschina.net/news/... 明天是一个十分非凡的日期,在这里咱们将向大家发表一个重要的音讯,齐全重写的 QueryPHP 1.0 正式版公布了! 对于 QueryPHP QueryPHP 是一款现代化的高性能 PHP 渐进式协程框架, 咱们还是次要面向传统 PHP-FPM 场景,以工程师用户体验为历史使命,让每一个 PHP 利用都有一个好框架。 百分之百单元测试笼罩直面 Bug,致力于发明高品质产品 Level Level Leevel,依靠 Swoole 开启将来更多可能,此刻将来逐渐渐进。 咱们的愿景是USE LEEVEL WITH SWOOLE DO BETTER, 让您的业务撑起更多的用户服务。 https://github.com/hunzhiwange/queryphphttps://gitee.com/dyhb/queryphphttps://www.queryphp.com个性 Production-Ready (生产可用)框架理念 (值得托付的使命感,让每一个 PHP 利用都有一个好框架。)组件零碎 (框架底层由独立的高内聚低耦合组件形成,能够轻松无侵入接入现有零碎。)路由零碎 (框架提供 MVC 主动路由并可能智能解析 Restful 申请和基于 OpenApi 3.0 标准的 swagger-php 注解路由,文档路由一步搞定。)整体解决方案 (框架提供了从缓存、Session、IOC 容器、模板引擎、Ddd ORM 等大量开箱即用的性能,提供了基于 Symfony Console 命令行工具集。)高品质 (百分之百单元测试笼罩直面 Bug,致力于发明高品质产品 Level Level Leevel。)业务协程化 (基于 Swoole 4 开发,咱们的愿景是大量代码或者无批改,让你的业务撑起更多的用户服务。)百分之百单元测试笼罩(超过 3500 例测试用例保障系统可靠性和可继续保护。)PHP 7 严格模式 (每一个 PHP 脚本都是 strict_types=1,严格模式能够防止很多弱类型带来潜在 BUG。)PHP 7 类型提醒 (尽可能为每一个办法提供确定的参数类型和返回值类型,以及类属性的类型反对。)依赖注入(残缺实现,要害 MVC、命令行脚本、事件监听器全副接入 IOC 容器。)畛域驱动设计(反对 UnitOfWork 事务工作单元、Repository 仓储、Specification 查问规约,Entity Getter Setter 畛域实体等。)防止闭门造车 (QueryPHP 始终从 Laravel、Symfony 等框架排汇一些优良的设计,同时咱们本身也进行了大量的翻新设计。)更多的个性期待你的发现...缘起 ...

July 24, 2020 · 2 min · jiezi

关于swoole:PHP73Swoole44-Go113-MixPHP22-性能对比

好几年没有做过性能比照了,因为越来越感觉性能并没有那么的重要(绝对于生态),明天有工夫简略测试一下,因为 Mix v2.1 开始就全副切换为单过程协程模式,因而本次次要测试的是 CoHttpServer 。 环境CPU: Intel(R) Core(TM) i7-8700 CPU @ 3.20GHzCPU(s): 12Mem: 15GLinux version 3.10.0-957.10.1.el7.x86_64PHP 7.3.12 + Swoole 4.4.14代码中应用的单过程 CoHttpServer ,因而须要利用端口复用 (须要 Linux >= 3.10),开启 12 个过程 代码<?php \Swoole\Process::daemon();$scheduler = new \Swoole\Coroutine\Scheduler;$scheduler->set([ 'hook_flags' => SWOOLE_HOOK_ALL,]);$scheduler->add(function () { $server = new \Swoole\Coroutine\Http\Server('0.0.0.0', 8888, false, true); $server->handle('/', function($request, $response){ $response->end('hello, world!'); }); $server->start();});$scheduler->start();开启的过程[nobody@tmp]$ ps -ef | grep test.phpnobody 1917 1 0 20:22 ? 00:00:02 /usr/local/php-7.3.12/bin/php test.phpnobody 1923 1 0 20:22 ? 00:00:02 /usr/local/php-7.3.12/bin/php test.phpnobody 1929 1 0 20:22 ? 00:00:02 /usr/local/php-7.3.12/bin/php test.phpnobody 1934 1 0 20:22 ? 00:00:02 /usr/local/php-7.3.12/bin/php test.phpnobody 2154 1 0 20:22 ? 00:00:02 /usr/local/php-7.3.12/bin/php test.phpnobody 2166 1 0 20:22 ? 00:00:02 /usr/local/php-7.3.12/bin/php test.phpnobody 2173 1 0 20:22 ? 00:00:02 /usr/local/php-7.3.12/bin/php test.phpnobody 2181 1 0 20:22 ? 00:00:02 /usr/local/php-7.3.12/bin/php test.phpnobody 2187 1 0 20:22 ? 00:00:02 /usr/local/php-7.3.12/bin/php test.phpnobody 2194 1 0 20:22 ? 00:00:02 /usr/local/php-7.3.12/bin/php test.phpnobody 2200 1 0 20:22 ? 00:00:02 /usr/local/php-7.3.12/bin/php test.phpnobody 2205 1 0 20:22 ? 00:00:02 /usr/local/php-7.3.12/bin/php test.php测试:多跑几次,根本稳固在 127441.95 左右。[nobody@~]$ ab -n 100000 -c 1000 -k http://127.0.0.1:8888/This is ApacheBench, Version 2.3 <$Revision: 1430300 $>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 10000 requestsCompleted 20000 requestsCompleted 30000 requestsCompleted 40000 requestsCompleted 50000 requestsCompleted 60000 requestsCompleted 70000 requestsCompleted 80000 requestsCompleted 90000 requestsCompleted 100000 requestsFinished 100000 requestsServer Software: swoole-http-serverServer Hostname: 127.0.0.1Server Port: 8888Document Path: /Document Length: 13 bytesConcurrency Level: 1000Time taken for tests: 0.785 secondsComplete requests: 100000Failed requests: 0Write errors: 0Keep-Alive requests: 100000Total transferred: 16600000 bytesHTML transferred: 1300000 bytesRequests per second: 127441.95 [#/sec] (mean)Time per request: 7.847 [ms] (mean)Time per request: 0.008 [ms] (mean, across all concurrent requests)Transfer rate: 20659.53 [Kbytes/sec] receivedConnection Times (ms) min mean[+/-sd] median maxConnect: 0 0 2.4 0 47Processing: 2 7 0.5 7 47Waiting: 0 7 0.4 7 14Total: 2 8 2.6 7 58Percentage of the requests served within a certain time (ms) 50% 7 66% 7 75% 7 80% 7 90% 8 95% 8 98% 8 99% 18 100% 58 (longest request)Go 1.13.4Golang 默认应用全副 CPU 核,因而只需开启一个过程即可。 ...

July 22, 2020 · 6 min · jiezi

Swoole高并发聚合请求实例

一、前言本文旨在阐明在高并发场景下如何通过聚合申请,充分利用数据库的批量解决更高效地实现业务性能。当然,此示例仅用作抛砖引玉,心愿能激发读者更深刻的思考。二、注释本示例选取的背景是并发下单业务。惯例状况下,后端创立订单是逐条 insert 的操作。在并发较低的时候,数据库的 insert 操作确实能放弃不错的效率,然而当遇到申请数量增多,数据库 频繁地单次 insert 就会让下单业务整体效率变低 (本文简略地假如1次下单=1个 insert)。通过下面的形容,其实曾经很容易想到须要优化的中央了。类比现实生活中乘坐电梯的场景:一架电梯 装满后再上行,能够最快地缓解人流压力。上面咱们就来用代码简略实现一下咱们思路:<?phpSwoole\Runtime::enableCoroutine($flags = SWOOLE_HOOK_ALL);// 最大期待次数const MAX_TIMES = 10;// 按批处理时, 每一批的最大申请暂留数量const MAX_REQUEST = 3;// 服务端最大超时工夫, 防止客户端始终期待const MAX_TIMEOUT = 5;Co\run(function () { // 申请传输的channel, 起因是不要在swoole的协程环境中, 应用多个协程批改同一个全局变量 // 如果是golang, 当然是能够不定义这里的$rqChannel // 只须要简略的将上面的$rqQueue和$times定义为全局变量即可达到一样的成果 // 然而最好的形式任然是是通过channel共享内存 $rqChannel = new Swoole\Coroutine\Channel(MAX_REQUEST); // 模仿创立订单 $createOrder = function () use ($rqChannel) { // 应用数组模仿申请暂留队列 $rqQueue = []; // 应用期待次数模仿tick成果 $times = MAX_TIMES; while (true) { $times--; // 必须带上timeout参数, 否则channel是阻塞的 $rq = $rqChannel->pop(1); // 保留1个失常的申请数据 if (!empty($rq)) { $rqQueue[] = $rq; } // 申请数量未达下限或者还有期待次数时, 提前进入下一次循环 if ($times > 0 && count($rqQueue) < MAX_REQUEST) { continue; } // 重置期待次数 $times = MAX_TIMES; // 初始化SQL $sql = "INSERT INTO orders VALUES "; $inserts = []; // 模仿数据验证 $validator = function ($input): bool { // 为了缩减代码, 没有真的做数据验证的解决 array_filter($input); return true; }; // $rqQueue在协程上下文是并发平安的, 所以遍历时不必放心 foreach ($rqQueue as $index => $rq) { list($data, $chan) = $rq; // 这里能够思考后置执行, 起因是前面能够有一些补救逻辑 unset($rqQueue[$index]); // 判断$chan是否敞开å if ($chan->errCode === SWOOLE_CHANNEL_CLOSED) { $data = null; continue; } $bool = $validator($data); if ($bool) { $inserts[] = "({$data['user_name']}, {$data['amount']}, {$data['mobile']})"; $chan->push(['state' => 1]); } else { $chan->push(['state' => 0]); } // unset($rqQueue[$index]); } $sql .= (implode(',', $inserts) . ';'); // 模仿创立订单落库的逻辑 echo $sql; } }; // 老手要留神这一句代码的地位, 起因是 $server->start() 之后的代码不会执行 go($createOrder); // 路由处理器 $orderHandler = function ($rq, $res) use ($rqChannel) { $chan = new Swoole\Coroutine\Channel(1); // 应用timeout参数模仿超时 $bool = $rqChannel->push([$rq->post, $chan], MAX_TIMEOUT); if (!$bool) { // 敞开$chan $chan->close(); $res->end('timeout'); } if (!empty($data = $chan->pop())) { // 敞开$chan $chan->close(); // 辨别胜利或失败状态再输入响应 if ($data['state'] === 1) { $res->end(microtime()); } else { $res->end('error'); } } }; $server = new Co\Http\Server("0.0.0.0", 9502, false); $server->handle('/order/create', $orderHandler); // 以后协程容器的起点 $server->start();});代码整体上还是很容易了解的,变量 $rqQueue 就是类比电梯,暂留申请期待肯定工夫的次数 $times 就是类比电梯须要期待人流顺次进入。当然最在心愿读者留神的一点是:在协程环境下,不要应用共享内存而通信,应该应用通信来共享内存。三、结语本教程面向老手,更多教程会在日后给出。欢送分割在下,探讨倡议都能够,之后会公布其它的教程。

July 13, 2020 · 2 min · jiezi

Swoole-内核开发备忘内存管理优化swString

最新的优化,减少了从recv_buffer到php zval的内存copy,可以从recv_buffer变为PHP层的string类型变量,相当于直接从Socket接收缓存区中读取到了PHP层。 GitHub PR:https://github.com/swoole/swoole-src/pull/3423swString 结构体typedef struct _swString{ size_t length; size_t size; off_t offset; char *str; const swAllocator *allocator;} swString;在设计上,这几个字段的作用分别是: size:内存容量长度,进行append写入操作时,如果有空余的空间,无需扩容。当实际数据长度等于size时,需要进行内存扩容,通过调用swString_extend()完成length:实际数据长度,必须小于或等于size否则会内存越界,底层的swString_*系列函数会检查边界,避免越界读写offset:操作游标,记录应用层实际处理数据的位置,offset必须小于或等于length 新增了 allocator 字段,可以设置内存分配器,目前有3种。 SwooleG.std_allocator:标准的glibc mallocSWOOLE_G(php_allocator):PHP 的 emallocSWOOLE_G(zend_string_allocator):zend_string_alloc数据处理coroutine::Socket::recv_packet() 分为两个阶段从Socket中读取数据。 读取PacketHeader数据,可能是一个比较小的值,如 recv(sizeof(PacketHeader)),在头部中包含PacketLength字段,获取整个包的总长度读取Payload数据,recv(PacketLength - sizeof(PacketHeader)底层会预先将offset值设置为PacketLength,然后分段从网络收取数据,调用recv操作,将数据追加到缓存区,并更新length值。当length==offset时表示,接收完毕。coroutine::Socket::recv_packet()返回到应用层。这时应用层可以有两个操作。 使用memcpy将数据从recv_buffer中读取出来,下一次调用recv_packet时,底层会自动调用swString_reduce()重置recv_buffer缓存区,这会存在一次内存拷贝,但是复用一块内存的使用swString_pop()将recv_buffer->str整块内存弹出,在应用层使用,底层会自动分配新的内存,用于接收下一个包本次的 zerocopy 就是使用第二种方式,recv_buffer 使用了 SWOOLE_G(zend_string_allocator) 内存分配器,弹出来的 recv_buffer->str 内存,正好是一个 zend_string 的val,再使用 sw_get_zend_string(recv_buffer->str) 就可以得到 zend_string 对象的内存地址,最后使用 ZVAL_STR() 或者 RETURN_STR() 可直接将 zend_string 对象作为 PHP 层函数调用的返回值。

June 26, 2020 · 1 min · jiezi

Mix-PHP-V21-发布基于-Swoole-44-单线程协程-PHP-框架

MixPHP 是什么一个基于 Swoole 开发的高性能 PHP 框架,经过两年发展收获了很多中小型团队的支持,框架版本经历了: V1.*: 基于 Swoole 的常驻内存型 PHP 高性能框架V2.0: 基于 Swoole 的 FastCGI、常驻内存、协程三模 PHP 高性能框架V2.1: 基于 Swoole 4.4+ 单线程协程 PHP 框架 ????更新本次 v2.1 重构版本主要修改了框架核心封装部分,而功能库如:Database/Redis/Auth 等库使用方式只是微调,大致重构内容如下: mix-php/mix 库修改为子模块的方式包含框架所有库,require mix/mix 时该库会替换 composer 上的同名库,用户提交 PR 时只需修改该库提交,贡献将出现在 mix-php/mix。从之前的多进程模型全部修改为基于 SwooleCoroutineServer 开发的单进程模型,完全协程化。移除了之前的协程组件自动隔离容器、 App 容器的封装概念,修改为类 Golang 风格的直接使用协程 Server 的开发方式,将之前的库封装代码移动到骨架代码中,让用户能更加细粒度的修改每一处细节。所有 composer 依赖库从 25 个缩减到 20 个,全部独立化,无强依赖,支持 8 种 PSR 规范。强化了 Bean 依赖注入功能,移除了之前的全局组件的概念,用 Bean 的 SINGLETON 取代。增加 mix/event 库引入事件机制。与其他基于 Swoole 的框架比较服务器全部基于 SwooleCoroutineServer 开发,线程模型与 Node.js 一样为单进程单线程模型 (现有其他 Swoole 框架基本都是多进程模型),组件封装风格参考 Golang,这样既拥有 Golang 的 CSP 并发模型,又无需像 Golang 一样处理数据的并发安全。 ...

October 17, 2019 · 1 min · jiezi

QueryPHP-V1beta6-新增-400-单元测试全量覆盖

QueryPHP v1.0.0-beta.6,这个版本主要进行单元测试收尾工作,新编写 400 例单元测试用例,除了我们选择主动忽略的、无法测试的代码和一部分 Swoole 的代码,整个产品实现百分之百覆盖。核心库 framework 单元测试用例 3410, 断言 13556,10659 行源代码被覆盖。 QueryPHP 坚持自己的路线,less is more, 不追求炫酷庞大的功能,产品的稳定性、可持续维护性是我们整个项目最为看重的,这也是我们进行大量地编写单元测试进行自动化测试最为直接的动力。 travis-ci coveralls 关于 QueryPHP QueryPHP 是一款现代化的高性能 PHP 渐进式协程框架, 我们还是主要面向传统 PHP-FPM 场景,以工程师用户体验为历史使命,让每一个 PHP 应用都有一个好框架。 百分之百单元测试覆盖直面 Bug,基于 Zephir 实现框架核心常驻,依托 Swoole 协程提升业务性能,此刻未来逐步渐进。 我们的愿景是 USE LEEVEL WITH SWOOLE DO BETTER, 让您的业务撑起更多的用户服务。 github.com/hunzhiwange/queryphpgitee.com/dyhb/queryphpqueryphp.com更新日志【framework】新增 400 单元测试全量覆盖,进一步缩减了 BUG 生存空间。【framework】由于不完整,删除 Swoole RPC 实现,保留了 Http,Websocket,未来 1.0 正式版本重新设计【framework】通过测试 修复 HTTP 组件,请求,响应等 BUG【framework】修复数据库重连错误极端异常情况兼容【framework】数据库查询和执行去掉了重复代码,抽象了一些公共代码,精简了代码【framework】修复了 ORM 关联查询源数据为空的判断,重构了关联模型作用域实现,并精简了代码【framework】修复数据库工作单元 UnitOfWork的 bug 和精简代码【framework】关联模型,改进 ORM 关联查询源值为空的特殊处理 ,不再执行后续查询,减少数据库查询,修复嵌套关联预载入查询未执行到的 BUG。【framework】ORM 实体 Entity 的 toArray() 支持关联属性读取,并支持 SHOW_PROP_NULL 返回自定义默认返回数据【framework】修复更新实体一些错误,抽离公共代码。【framework】其它若干修复项目,主要写测试用例时发现的问题。【application】修复 debugbar 错误,兼容 Swoole HttpServer 和 PHP 内置 WebServerRoadMap【framework】Beta 6 是 QueryPHP 的最后一个 beta 版本,整个框架功能冻结。【framework】RC 版本只修复 BUG、单元测试 和文档完善,不排除可能有一些必要的功能微调。【framework】v1.0.0 正式版本将随 PHP 7.4 版本后发布 php74,计划是在 2019.12 发布。联系方式www.queryphp.com ...

October 17, 2019 · 1 min · jiezi

kclozeswoolejobs源码分析

所需php扩展https://github.com/alanxz/rab...amqp 需要先安装rabbitmq-cswooleredis 源码难度:易于理解,便于修改 主要使用知识点:swooleprocessswoole定时器信号通信等 使用难度:简单, 易于嵌入框架 发现问题:查看github仓库, 该代码已经半年没有更新,新的swoole版本中,process是无法在swooletimer中使用,因为swooletimer中会自动创建coroutine,而process无法在协程中创建 解决方法:在入口文件增加 (已测试)swoole_async_set([ 'enable_coroutine' => false]); 修改process为协程 (未测试)稳定性:源码中有一段注释://黑科技:实践中发现有可能进不到业务代码,造成消息丢失,job执行太快或者太慢(业务出现异常),worker进程都安全退出自己测试中, 开启了3个redis队列https://www.showdoc.cc/server... 测试结果: 推送3000条数据最小进程数符合预期最大进程数符合预期结果无丢失 执行效率受限于redis队列以及job任务

October 8, 2019 · 1 min · jiezi

tech-开源之路-github-PR-走起

date: 2019-09-10 22:25:56title: tech| 开源之路: github PR 走起 想快速提高编程能力, 还不快来 「全球最大同性交友社区」~PR 只需几步github 开源之路, 从 PR 开始, 只需要如下简单的几步: 找到心仪的项目, fork 它 自己的仓库里就有了, clone 它 最新的 master 拉一个分支, 修改代码# 更新 mastergit merge upstream/master# 基于最新的 master 拉出新的分支进行开发git checkout -b feat-xxx# coding# 提交git addgit commitgit push# PR 神器PR 神器参上 github desktop 完成 PR 只需要一步: cmd+r 快捷键 参与 PR参与 PR 使用 cmd+b 快捷键, 切换不同分支, 包括 PR 在 hyperf-cloud/hyperf 主项目上, 使用 github desktop 就可以切换到 PR, 参与到 PR 中 ...

September 11, 2019 · 2 min · jiezi

QueryPHP-V1beta5-改进-ORM-设计体验

QueryPHP v1.0.0-beta.5,这个版本主要改进 ORM 和大量细节的优化。 关于 QueryPHPQueryPHP 是一款现代化的高性能 PHP 渐进式协程框架, 我们还是主要面向传统 PHP-FPM 场景,以工程师用户体验为历史使命,让每一个 PHP 应用都有一个好框架。 百分之百单元测试覆盖直面 Bug,基于 Zephir 实现框架核心常驻,依托 Swoole 协程提升业务性能,此刻未来逐步渐进。 我们的愿景是 USE LEEVEL WITH SWOOLE DO BETTER, 让您的业务撑起更多的用户服务。 https://github.com/hunzhiwange/queryphphttps://gitee.com/dyhb/queryphphttps://www.queryphp.com更新日志[ framework ] 将底层 redis 服务拆分出来注册到 IOC 容器,可以方便使用[ framework ] 删除自己的 dd,dump 调试函数,Symfony 自带不需要再弄了。[ framework ] 移除全局函数 app,hl 助手函数,由静态代理实现 App (别名 Leevel )来,App::path(),App 可以访问 IOC 容器中的方法 App::make('request')。[ framework ] 删除代理中所有接口设计 LeevelKernelProxyIApp,删除组件中的 Proxy 改为用 @method 来实现 IDE helper,并内置一个用于生成这样的命令工具自动生成。[ framework ] 优化 swoole 热重载代码,利于测试。[ framework ] 改进系统异常处理组件,例外将系统内置异常改为 abstract 方便业务层继承更好地处理异常,添加一个业务处理异常 LeevelKernelExceptionBusinessException,异常响应也会经过中间件处理。[ framework ] 核心 kernel 和路由支持对 CORS options 请求的处理,利用自定义中间件轻松处理跨域访问问题,分拆路由绑定解析方法,代码更清。[ framework ] 关联模型,改进 ORM 关联查询源值为空的特殊处理 ,不再执行后续查询,减少数据库查询,修复嵌套关联预载入查询未执行到的 BUG。[ framework ] 为各个组件助手函数添加一个静态访问,分拆各个组手函数到单独的文件方便 f 调用。[ framework ] 实体添加更多 const 如 CONSTRUCT_PROP_WHITE,MIDDLE_SOURCE_KEY 避免写死实体一些约定的名字。[ framework ] 数据库 PDO 查询改进,该是数字就返回数字,不再全部返回字符串。[ framework ] 查询新增 where('foo', '=', null) 的支持,处理非常特殊场景,以前直接报错。[ framework ] 改进实体 LeevelDatabaseDddEntity toArray 设计,现在 null 会被自动忽略。[ framework ] 改进实体 LeevelDatabaseDddEntity 属性相关设计,withProps,withProp,hasProp,prop 来访问。[ framework ] 改进实体 LeevelDatabaseDddEntity 软删相关设计,实体一旦定义了 const DELETE_AT,系统查询和删除自动走软删除,也可以通过 withSoftDeleted 查询包含软删除的数据,forceDelete 强制删除。[ framework ] 改进实体 LeevelDatabaseDddEntity 中查询实体 find($id) 改为 findEntity($id),更容易理解,例外可以避免和 find() 查询使用理解冲突。[ framework ] 修复 redis 连接池连接 PHPRedis 驱动支持重连。[ framework ] 验证器会自动转换验证参数 not_between:1,5 为 int 和 float,以便于框架实现统一强类型的整体方向,例外数据库唯一验证 LeevelValidateUniqueRule 自动识别 int 和 float,避免数据库查询出现转换类型而不再走索引影响性能。[ framework ] 改进实体 LeevelDatabaseDddEntity 查询设计,去掉查询魔术方法,__call 和 ___callStatic 被屏蔽,所有查询均需要通过静态入口 select(别名 find,保留致敬 QeePHP 习惯),meta(保留致敬 QeePHP 习惯) 发起,再加上 ide helper 支持,完美支持 IDE。[ framework ] 改进实体连接 LeevelDatabaseDddEntity 切库设计,由每个实体自己实现 withConnect 和 connect,更加更新可控,提供切库安全沙盒 connectSandbox。[ framework ] 采用 PDOStatement->debugDumpParams() 来获取最后的 SQL,同时在 debug 组件将 SQL 写入日志方便开发调试。[ framework ] 事务工作单元 LeevelDatabaseDddUnitOfWork 增加对软删除」强删除的支持,也包含仓储对此的调整 LeevelDatabaseDddRepository。[ framework ] 修复并删除实体和仓储中的 flushed 已经刷新过数据的特性,无必要。[ tests ] 新增 10 多例 swoole 相关的测试用例和其它,单元测试 3080 多例,断言 10000+。[ application ] QueryPHP 自身是一个基于 IViewUI 的标准后台,自带基于资源的权限系统,由于框架大量调整后台做了相应的调整以便于运行。[ application ] QueryPHP 自身是主要引入 Workflow 来改善 curd 操作,将查询并入到服务中来。RoadMap[ framework ] Beta 4-6 主要是对 Swoole 4 最新版的协程完善支持。[ framework ] RC 版本会冻结计划功能,只修复 BUG、单元测试 和文档完善。[ framework ] v1.0.0 正式版本将随 PHP 7.4 版本后发布 https://wiki.php.net/todo/php74,计划是在 2019.12 发布。联系方式https://www.queryphp.com ...

September 10, 2019 · 2 min · jiezi

swoolekafka-高级消费者案例

Runtime::enableCoroutine(false);// mgo(function () {// Create the logger// $logger = new Logger('my_logger');// // Now add some handlers// $logger->pushHandler(new StdoutHandler());// //$logger->pushHandler(new \Monolog\Handler\NullHandler());// \Amp\Loop::set(new \Swoole\Driver\Amp());$config = ConsumerConfig::getInstance();$config->setMetadataRefreshIntervalMs(500);$config->setMetadataBrokerList('192.168.3.243:9092');$config->setGroupId('swoole');$config->setBrokerVersion('1.0.0');$config->setTopics(['save_user_travel_data_one']);$config->setOffsetReset('earliest');$consumer = new Consumer();// $consumer->setLogger($logger);$consumer->start(function ($topic, $part, $message): void { mgo(function () use ($message) { $data = json_decode(json_decode($message['message']['value'], true), true); $clearing_start_time = $data['clearing_start_time'];//清算时间 $user_id = $data['user_id'];//清算用户id $total = $data['total'];//这次清算总用户数量 Console::Debug('开始一个任务.' . $user_id); $this->saveUserTravel($user_id, $clearing_start_time); Redis::incr('save_user_travel_log:' . $clearing_start_time); Console::Debug('完成了第.' . Redis::get('save_user_travel_log:' . $clearing_start_time)); if (Redis::get('save_user_travel_log:' . $clearing_start_time) >= $total) { //do Console::Debug('完成了所有任务'); } }, false);});\Swoole\Event::wait();

September 7, 2019 · 1 min · jiezi

Mac安装swoole出现Enable-openssl-support-require-openssl-library

Mac 执行 pecl install swoole出现 error "Enable openssl support, require openssl library. 在安装的时候 直接指定brew安装的openssl目录即可解决此问题

September 6, 2019 · 1 min · jiezi

Swoft-205-更新新增高效秒级定时任务异常管理组件

什么是 Swoft ?Swoft 是一款基于 Swoole 扩展实现的 PHP 微服务协程框架。Swoft 能像 Go 一样,内置协程网络服务器及常用的协程客户端且常驻内存,不依赖传统的 PHP-FPM。有类似 Go 语言的协程操作方式,有类似 Spring Cloud 框架灵活的注解、强大的全局依赖注入容器、完善的服务治理、灵活强大的 AOP、标准的 PSR 规范实现等等。 Swoft 通过长达三年的积累和方向的探索,把 Swoft 打造成 PHP 界的 Spring Cloud, 它是 PHP 高性能框架和微服务治理的最佳选择。 高效秒级定时任务如下简单几行代码,就定义了一个每秒执行的定时任务,完全可以取代系统定时任务。 <?php declare(strict_types=1);namespace App\Crontab;use Swoft\Crontab\Annotaion\Mapping\Cron;use Swoft\Crontab\Annotaion\Mapping\Scheduled;/** * Class CronTask * * @since 2.0 * * @Scheduled() */class CronTask{ /** * @Cron("* * * * * *") */ public function secondTask() { printf("second task run: %s ", date('Y-m-d H:i:s', time())); }}定时任务跟随服务一起启动,将看到如下显示: ...

August 8, 2019 · 1 min · jiezi

SMProxy-129-发布-新增状态命令监控面板功能

Swoole MySQL Proxy一个基于 MySQL 协议,Swoole 开发的MySQL数据库连接池。 原理将数据库连接作为对象存储在内存中,当用户需要访问数据库时,首次会建立连接,后面并非建立一个新的连接,而是从连接池中取出一个已建立的空闲连接对象。使用完毕后,用户也并非将连接关闭,而是将连接放回连接池中,以供下一个请求访问使用。而连接的建立、断开都由连接池自身来管理。 同时,还可以通过设置连接池的参数来控制连接池中的初始连接数、连接的上下限数以及每个连接的最大使用次数、最大空闲时间等等。也可以通过其自身的管理机制来监视数据库连接的数量、使用情况等。超出最大连接数会采用协程挂起,等到有连接关闭再恢复协程继续操作。 特性支持读写分离支持数据库连接池,能够有效解决 PHP 带来的数据库连接瓶颈支持 SQL92 标准采用协程调度支持多个数据库连接,多个数据库,多个用户,灵活搭配遵守 MySQL 原生协议,跨语言,跨平台的通用中间件代理支持 MySQL 事务支持 HandshakeV10 协议版本完美兼容 MySQL5.5 - 8.0兼容各大框架,无缝提升性能设计初衷PHP 没有连接池,所以高并发时数据库会出现连接打满的情况,Mycat 等数据库中间件会出现部分 SQL 无法使用,例如不支持批量添加等,而且过于臃肿。所以就自己编写了这个仅支持连接池和读写分离的轻量级中间件,使用 Swoole 协程调度 HandshakeV10 协议转发使程序更加稳定,不用像 Mycat 一样解析所有 SQL 包体,增加复杂度。 版本更新新增status命令监控面板功能 修复日志目录创建失败的问题修复PHP字符串索引取值版本兼容问题开发与讨论文档:https://smproxy.louislivi.com 若被墙请访问:https://smproxy.gitee.louisli...QQ群:722124111欢迎各类 Issue 和 Pull Request。

July 26, 2019 · 1 min · jiezi

imi-v101Swoole-协程应用开发框架

imi 介绍 imi 是基于 PHP 协程应用开发框架,它支持 HttpApi、WebSocket、TCP、UDP 应用开发。 由 Swoole 提供强力驱动,Swoole 拥有常驻内存、协程非阻塞 IO 等特性。 框架遵守 PSR 标准规范,提供 AOP、注解、连接池、请求上下文管理、ORM模型等常用组件。 imi 的模型支持关联关系的定义,增删改查一把梭! 与其他专注微服务领域的 Swoole 框架不同,imi 专注单体应用开发。原因很简单:大部分公司都不需要上微服务,单体应用足矣。 imi 框架第一个版本 v0.0.1 首发于 2018年6月21日更新内容新增增加单元测试(PHP7.1-7.4、nightly 全部通过),从此不再担心修复一个 bug,产生更多 bug。这是一个长期任务,不断充实测试用例,也欢迎大家来贡献测试代码!新增 ImiUtilCoroutine::create(),与 imigo() 行为一致新增 ImiUtilHttpMessageUtil 类新增框架优先级定义,常量类:ImiUtilImiPriority修复修复热重启问题:worker exit timeout, forced to terminate修复 PDO Statement 无法命中缓存问题修复日志重复写入问题修复 MySQL->query() 方法不记录最后执行 SQL 语句问题修复当设置task_enable_coroutine但不设置enable_coroutine时,task事件监听参数错误问题修复类型声明修复 RedisManager::getNewInstance() 无法被 RedisManager::release() 释放问题修正 Redis 相关类型注释修复 withAddedHeader() 问题修复使用 TAutoInject 时,有父类导致无法正常注入的问题修复Callback、CallableValue注解无法注入属性问题修复配置值注入、常量注入注解,无法注入到属性问题优化优化 BeanProxy::getConfigInjects()优化 ImiDbQueryResult非 worker 进程投递的 task,不再自动 finish同步池子 getResource() 不再有可能返回 null,一律抛出异常ITaskHandler->handle() 支持返回值自动 finish规范内部监听 IMI.INITED 事件的优先级优化 BeanProxy,现在不用等到回收周期,立即会被释放注入属性值的注解类改为 ImiAopAnnotationBaseInjectValue进程、进程池进程,强制开启协程化测试用例计划及进度[x] Aop[x] Config[ ] Cache[x] Db[x] Enum[x] Event[x] Inject[x] Redis[x] HttpServer[x] HttpValidate[ ] WebSocketServer[ ] TCPServer[ ] UDPServer[x] Task[ ] Lock[ ] Log[ ] Model[ ] Process[ ] Utils ...

July 16, 2019 · 1 min · jiezi

借助URLOS快速安装swoole环境

环境需求最低硬件配置:1核CPU,1G内存(1+1)提示:如果你的应用较多,而主机节点的硬件配置较低,建议在部署节点时开通虚拟虚拟内存;生产环境建议使用2G或以上内存;推荐安装系统:Ubuntu-16.04、Ubuntu-18.04、CentOS7.X、Debian9X的64位的纯净的操作系统;URLOS安装curl -LO www.urlos.com/iu && sh iuswoole环境安装流程登录URLOS系统后台,在应用市场中搜索“swoole”,找到之后,选择合适的版本点击安装按钮 填写服务名称、选择运行节点、服务端口、选择智能部署 填写ssh密码(这里的密码是root密码) 然后点击“提交”按钮,等待部署完成;

July 15, 2019 · 1 min · jiezi

Swoole-44-正式版已发布

向下不兼容改动和PHP官方保持一致, 不再支持PHP7.0 (@matyhtf)移除Serialize模块, 在单独的 ext-serialize 扩展中维护. 废弃原因: 由于PHP内核频繁变更, 导致无法实现稳定可用的模块, 与php serialize相比没有太大差异化定位移除PostgreSQL模块,在单独的 ext-postgresql 扩展中维护. 废弃原因: PostgreSQL使用了异步回调方式实现协程调度, 不符合目前内核协程化的统一规划。另外PostgreSQL目前用户量非常低, 并且缺少必要的单元测试, 无法保证质量Runtime::enableCoroutine不再会自动兼容协程内外环境, 一旦开启, 则一切阻塞操作必须在协程内调用 (@matyhtf)由于引入了全新的协程MySQL客户端驱动, 底层设计更加规范, 但有一些小的向下不兼容的变化 fetch/nextResult优化为按需读取, 会产生IO调度启动defer特性时, statement发出的的请求, 需要使用statement->recv接收启动defer/fetch_mode特性时, 如有未接收完的数据, 将无法发起新的请求与异步不同, connected属性不再会实时基于事件更新, 而是在IO操作失败后更新废弃警告将废弃Buffer模块,废弃原因:可替代性强,使用率低,可用PHP字符串、fopen("memory")代替。将废弃Lock模块,废弃原因:在协程模式下加锁可能存在问题,可使用chan实现协程版本的锁由于引入了stream_socket_pair协程化, 建议开启hook时, 如有单独配置需求, 请使用SWOOLE_HOOK_STREAM_FUNCTION常量而不是SWOOLE_HOOK_STREAM_SELECT新特性新增Library, 使用纯PHP编写内核功能而非C/C++, 提供了以下功能 新增高质量PHP模块Coroutine\WaitGroup (@twose)使用PHP代码实现CURL的hook, 一键使CURL协程化, 目前为实验特性, 需特别调用Runtime::enableCoroutine(SWOOLE_HOOK_CURL)来开启 (@matyhtf) (@Yurunsoft)使用PHP代码实现exec/shell_exec的协程化 (#2657) (@Yurunsoft)开启RuntimeHook时, 将替换函数array_walk, array_walk_recursive为swoole实现的版本, 解决原生函数不可重入的问题, 但会造成无法遍历object (@matyhtf) (@twose)新增协程抢占式调度器, 可防止协程占用CPU时间过长导致其它协程饿死, 通过php.ini配置swoole.enable_preemptive_scheduler = On 开启, 相关例子详见preemptive_scheduler (@shiguangqi)新增Timer::list()返回Timer\Iterator, 可遍历所有定时器, Timer\clearAll清除所有定时器, Timer\info(int $id)获取定时器信息, Timer::stats()获取全局定时器状态 (#2498) (@twose)新增 Co\Socket的两个方法getOption 和 setOption (9d13c29) (@matyhtf)新增 Process\Pool的$master_pid 属性和 shutdown方法 (a1d6eaa) (@matyhtf)新增Process\Pool的构造方法的第四个参数, 为true时底层将自动在onWorkerStart回调开启协程 (8ceb32cd) (@matyhtf)新增stream_socket_pair协程化支持 (#2546) (@matyhtf)新增Http\Server的static_handler_locations设置, 可以设定静态文件路径 (@matyhtf)新增Co\Http\Client->setBasciAuth方法, 用于自动发送Authorization头 (#2542) (@hongbshi)新增 Co\Http2\Client->ping方法 (40041f6) (@shiguangqi)新增hook_flags配置项,用于取代Runtime::enableCoroutine()函数调用增强全新的协程MySQL客户端驱动, 底层全面协程化 (#2538) (@twose) ...

July 9, 2019 · 1 min · jiezi

Swoole-5-将移除自动添加-Eventwait-特性

在之前的版本中,编写Cli命令行脚本中使用异步或协程功能时,Swoole会自动在脚本末尾检测是否有Event::wait()调用,如果没有,底层会自动调用register_shutdown_function在脚本结束时调用Event::wait(),进入事件循环。 这个特性为底层带来了非常多的麻烦,我们不得不做了大量编码工作,以解决带来的问题。即便如此,仍然存在大量潜在的风险,如某些版本下内存泄露,某些特殊用法导致core dump等。在最新的Swoole 5版本中,我们准备抛弃这个历史包袱。不再自动添加 Event::wait() 到PHP的shutdown function列表。 Server、Process、Process\Pool相关程序请忽略此更新错误实例go(function () { Co::sleep(1); echo "done\n";});此代码中没有在脚本末尾添加Swoole\Event::wait(),底层会自动注册到shutdown function列表中。相当于: register_shutdown_function(function () { Swoole\Event::wait();});go(function () { Co::sleep(1); echo "done\n";});在shutdown阶段处理程序逻辑会带来很多额外的维护性难题。 正确实例在Swoole 4.4或Swoole 5版本中,建议: 使用Coroutine\Scheduler作为程序的入口或者在程序末尾手工添加Event::wait()$scheduler = new Coroutine\Scheduler;$scheduler->add(function () { Co::sleep(1); echo "Done.\n";});$scheduler->start();推荐使用或者: go(function () { Co::sleep(1); echo "done\n";});Swoole\Event::wait();不推荐使用

July 9, 2019 · 1 min · jiezi

????-Hyperf-多个组件-v104-更新-企业级的-PHP-微服务协程框架

v1.0.4 更新内容本次更新涉及以下组件,主要增加了 Swoole 4.4 的支持及部分组件的功能强化,以及修复了一些 Bug hyperf/async-queue hyperf/command hyperf/config hyperf/constants hyperf/consul hyperf/contract hyperf/database hyperf/db-connection hyperf/di hyperf/dispatcher hyperf/framework hyperf/http-server hyperf/pool hyperf/redis hyperf/rpc-client hyperf/service-governance hyperf/utils hyperf/websocket-server 新增#140 支持 Swoole v4.4.0.#152 数据库连接在低使用率时连接池会自动释放连接#163 constants 组件的AbstractConstants::__callStatic 支持自定义参数变更#124 DriverInterface::push 增加 $delay 参数用于设置延迟时间, 同时 DriverInterface::delay 将标记为弃用的,将于 1.1 版本移除#125 更改 config() 函数的 $default 参数的默认值为 null.修复#110 #111 修复 Redis::select 无法正常切换数据库的问题#131 修复 middlewares 配置在 Router::addGroup 下无法正常设置的问题#132 修复 request->hasFile 判断条件错误的问题#135 修复 response->redirect 在调整外链时无法正确生成链接的问题#139 修复 ConsulAgent 的 URI 无法自定义设置的问题#148 修复当 migrates 文件夹不存在时无法生成迁移模板的问题#169 修复处理请求时没法正确处理数组类型的参数#170 修复当路由不存在时 WebSocket Server 无法正确捕获异常的问题移除#131 移除 Router options 里的 server 参数关于 HyperfHyperf 是基于 Swoole 4.3+ 实现的高性能、高灵活性的 PHP 协程框架,内置协程服务器及大量常用的组件,性能较传统基于 PHP-FPM 的框架有质的提升,提供超高性能的同时,也保持着极其灵活的可扩展性,标准组件均均基于 PSR 标准 实现,基于强大的依赖注入设计,保证了绝大部分组件或类都是 可替换 与 可复用 的。   框架组件库除了常见的协程版的 MySQL 客户端、Redis 客户端,还为您准备了协程版的 Eloquent ORM、JSON RPC 服务的及客户端、GRPC 服务端及客户端、Zipkin (OpenTracing) 客户端、Guzzle HTTP 客户端、Elasticsearch 客户端、Consul 客户端、ETCD 客户端、AMQP 组件、Apollo 配置中心、阿里云 ACM 应用配置管理、基于令牌桶算法的限流器、通用连接池、熔断器、Swagger 文档生成 等组件,省去了自己实现对应协程版本的麻烦,Hyperf 还提供了 基于 PSR-11 的依赖注入容器、注解、AOP 面向切面编程、基于 PSR-15 的中间件、自定义进程、基于 PSR-14 的事件管理器、Redis/RabbitMQ 消息队列、自动模型缓存、基于 PSR-16 的缓存 等非常便捷的功能,满足丰富的技术场景和业务场景,开箱即用。 ...

July 9, 2019 · 1 min · jiezi

Laravel-中使用-swoole-项目实战开发案例二-后端主动分场景给界面推送消息

最近使用 swoole 做了项目,里面设计推送信息给界面前端,和无登陆用户的状态监控,以下是本人从中获取的一点心得,有改进的地方请留言评论。 需求分析我们假设有一个需求,我在后端点击按钮1,首页弹出“后端触发了按钮1”。后端点了按钮2,列表页弹出“后端触发了按钮2”。做到根据不同场景推送到不同页面。 代码思路Swoole fd客户端浏览器打开或者刷新界面,在swoole服务会生成一个进程句柄 fd ,每次浏览器页面有打开链接websocket的js代码,便会生成,每次刷新的时候,会关闭之前打开的 fd,重新生成一个新的,关闭界面的时候会生成一个新的。swoole的 fd生成规则是从1开始递增。 Redis Hash存储 fd我们建立一个key为swoole:fds redis哈希类型数据,fd 为hash的字段,每个字段的值我们存储前端websocket请求的url参数信息(根据业务复杂度自己灵活变通,我在项目中会在url带上sessionId)。每次链接打开swoole服务的时候我们存储其信息,每次关闭页面时候我们清除其字段。在redis存储如下 触发分场景推送在界面上当进行了触发操作的时候,通过后台curl请求swoole http服务,swoole http服务根据你向我传递的参数分发给对应的逻辑处理。如curl请求127.0.0.1:9502page=back&func=pushHomeLogic&token=123456 我们可以根据传入的func参数,在后台分发给对应逻辑处理。如分发给pushHomeLogic方法。在其里面实现自己的逻辑。为防止过多的if else 以及 foreach 操作,我们采用的是闭包,call_user_func等方法实现如下 public function onRequest($request,$response) { if ($this->checkAccess("", $request)) { $param = $request->get; // 分发处理请求逻辑 if (isset($param['func'])) { if (method_exists($this,$param['func'])) { call_user_func([$this,$param['func']],$request); } } } }// 往首页推送逻辑处理 public function pushHomeLogic($request) { $callback = function (array $aContent,int $fd,SwooleDemo $oSwoole)use($request) { if ($aContent && $aContent['page'] == "home") { $aRes['message'] = "后端按了按钮1"; $aRes['code'] = "200"; $oSwoole::$server->push($fd,xss_json($aRes)); } }; $this->eachFdLogic($callback); }完整代码swool脚本代码逻辑<?phpnamespace App\Console\Commands;use Closure;use Illuminate\Console\Command;use Illuminate\Support\Facades\Redis;class SwooleDemo extends Command{ // 命令名称 protected $signature = 'swoole:demo'; // 命令说明 protected $description = '这是关于swoole websocket的一个测试demo'; // swoole websocket服务 private static $server = null; public function __construct() { parent::__construct(); } // 入口 public function handle() { $this->redis = Redis::connection('websocket'); $server = self::getWebSocketServer(); $server->on('open',[$this,'onOpen']); $server->on('message', [$this, 'onMessage']); $server->on('close', [$this, 'onClose']); $server->on('request', [$this, 'onRequest']); $this->line("swoole服务启动成功 ..."); $server->start(); } // 获取服务 public static function getWebSocketServer() { if (!(self::$server instanceof \swoole_websocket_server)) { self::setWebSocketServer(); } return self::$server; } // 服务处始设置 protected static function setWebSocketServer():void { self::$server = new \swoole_websocket_server("0.0.0.0", 9502); self::$server->set([ 'worker_num' => 1, 'heartbeat_check_interval' => 60, // 60秒检测一次 'heartbeat_idle_time' => 121, // 121秒没活动的 ]); } // 打开swoole websocket服务回调代码 public function onOpen($server, $request) { if ($this->checkAccess($server, $request)) { self::$server->push($request->fd,xss_json(["code"=>200,"message"=>"打开swoole服务成功"])); } } // 给swoole websocket 发送消息回调代码 public function onMessage($server, $frame) { } // http请求swoole websocket 回调代码 public function onRequest($request,$response) { if ($this->checkAccess("", $request)) { $param = $request->get; // 分发处理请求逻辑 if (isset($param['func'])) { if (method_exists($this,$param['func'])) { call_user_func([$this,$param['func']],$request); } } } } // websocket 关闭回调代码 public function onClose($serv,$fd) { $this->redis->hdel('swoole:fds', $fd); $this->line("客户端 {$fd} 关闭"); } // 校验客户端连接的合法性,无效的连接不允许连接 public function checkAccess($server, $request):bool { $bRes = true; if (!isset($request->get) || !isset($request->get['token'])) { self::$server->close($request->fd); $this->line("接口验证字段不全"); $bRes = false; } else if ($request->get['token'] != 123456) { $this->line("接口验证错误"); $bRes = false; } $this->storeUrlParamToRedis($request); return $bRes; } // 将每个界面打开websocket的url 存储起来 public function storeUrlParamToRedis($request):void { // 存储请求url带的信息 $sContent = json_encode( [ 'page' => $request->get['page'], 'fd' => $request->fd, ], true); $this->redis->hset("swoole:fds", $request->fd, $sContent); } /** * @param $request * @see 循环逻辑处理 */ public function eachFdLogic(Closure $callback = null) { foreach (self::$server->connections as $fd) { if (self::$server->isEstablished($fd)) { $aContent = json_decode($this->redis->hget("swoole:fds",$fd),true); $callback($aContent,$fd,$this); } else { $this->redis->hdel("swoole:fds",$fd); } } } // 往首页推送逻辑处理 public function pushHomeLogic($request) { $callback = function (array $aContent,int $fd,SwooleDemo $oSwoole)use($request) { if ($aContent && $aContent['page'] == "home") { $aRes['message'] = "后端按了按钮1"; $aRes['code'] = "200"; $oSwoole::$server->push($fd,xss_json($aRes)); } }; $this->eachFdLogic($callback); } // 往列表页推送逻辑处理 public function pushListLogic($request) { $callback = function (array $aContent,int $fd,SwooleDemo $oSwoole)use($request) { if ($aContent && $aContent['page'] == "list") { $aRes['message'] = "后端按了按钮2"; $aRes['code'] = "200"; $oSwoole::$server->push($fd,xss_json($aRes)); } }; $this->eachFdLogic($callback); } // 启动websocket服务 public function start() { self::$server->start(); }}控制器代码<?phpnamespace App\Http\Controllers;use Illuminate\Http\Request;use Illuminate\Support\Facades\Redis;class TestController extends Controller{ // 首页 public function home() { return view("home"); } // 列表 public function list() { return view("list"); } // 后端控制 public function back() { if (request()->method() == 'POST') { $this->curl_get($this->getUrl()); return json_encode(['code'=>200,"message"=>"成功"]); } else { return view("back"); } } // 获取要请求swoole websocet服务地址 public function getUrl():string { // 域名 端口 请求swoole服务的方法 $sBase = request()->server('HTTP_HOST'); $iPort = 9502; $sFunc = request()->post('func'); $sPage = "back"; return $sBase.":".$iPort."?func=".$sFunc."&token=123456&page=".$sPage; } // curl 推送 public function curl_get(string $url):string { $ch_curl = curl_init(); curl_setopt ($ch_curl, CURLOPT_TIMEOUT_MS, 3000); curl_setopt($ch_curl, CURLOPT_SSL_VERIFYPEER, 0); curl_setopt ($ch_curl, CURLOPT_HEADER,false); curl_setopt($ch_curl, CURLOPT_HTTPGET, 1); curl_setopt($ch_curl, CURLOPT_RETURNTRANSFER,true); curl_setopt ($ch_curl, CURLOPT_URL,$url); $str = curl_exec($ch_curl); curl_close($ch_curl); return $str; }}页面js代码后端控制页<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>后端界面</title> <meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"></head><body><button class="push" data-func="pushHomeLogic">按钮1</button><button class="push" data-func="pushListLogic">按钮2</button></body><script src="{{ asset("/vendor/tw/global/jQuery/jquery-2.2.3.min.js")}} "></script><script> $(function () { $(".push").on('click',function(){ var func = $(this).attr('data-func').trim(); ajaxGet(func) }) function ajaxGet(func) { url = "{{route('back')}}"; token = "{{csrf_token()}}"; $.ajax({ url: url, type: 'post', dataType: "json", data:{func:func,_token:token}, error: function (data) { alert("服务器繁忙, 请联系管理员!"); return; }, success: function (result) { }, }) } })</script></html>首页<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>swoole首页</title> <meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"></head><body><h1>这是首页</h1></body><script> var ws;//websocket实例 var lockReconnect = false;//避免重复连接 var wsUrl = 'ws://{{$_SERVER["HTTP_HOST"]}}:9502?page=home&token=123456'; function initEventHandle() { ws.onclose = function () { reconnect(wsUrl); }; ws.onerror = function () { reconnect(wsUrl); }; ws.onopen = function () { //心跳检测重置 heartCheck.reset().start(); }; ws.onmessage = function (event) { //如果获取到消息,心跳检测重置 //拿到任何消息都说明当前连接是正常的 var data = JSON.parse(event.data); if (data.code == 200) { console.log(data.message) } heartCheck.reset().start(); } } createWebSocket(wsUrl); /** * 创建链接 * @param url */ function createWebSocket(url) { try { ws = new WebSocket(url); initEventHandle(); } catch (e) { reconnect(url); } } function reconnect(url) { if(lockReconnect) return; lockReconnect = true; //没连接上会一直重连,设置延迟避免请求过多 setTimeout(function () { createWebSocket(url); lockReconnect = false; }, 2000); } //心跳检测 var heartCheck = { timeout: 60000,//60秒 timeoutObj: null, serverTimeoutObj: null, reset: function(){ clearTimeout(this.timeoutObj); clearTimeout(this.serverTimeoutObj); return this; }, start: function(){ var self = this; this.timeoutObj = setTimeout(function(){ //这里发送一个心跳,后端收到后,返回一个心跳消息, //onmessage拿到返回的心跳就说明连接正常 ws.send("heartbeat"); self.serverTimeoutObj = setTimeout(function(){//如果超过一定时间还没重置,说明后端主动断开了 ws.close();//如果onclose会执行reconnect,我们执行ws.close()就行了.如果直接执行reconnect 会触发onclose导致重连两次 }, self.timeout); }, this.timeout); }, header:function(url) { window.location.href=url } }</script></html>列表页面<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>swoole列表页</title> <meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"></head><body><h1>swoole列表页</h1></body><script> var ws;//websocket实例 var lockReconnect = false;//避免重复连接 var wsUrl = 'ws://{{$_SERVER["HTTP_HOST"]}}:9502?page=list&token=123456'; function initEventHandle() { ws.onclose = function () { reconnect(wsUrl); }; ws.onerror = function () { reconnect(wsUrl); }; ws.onopen = function () { //心跳检测重置 heartCheck.reset().start(); }; ws.onmessage = function (event) { //如果获取到消息,心跳检测重置 //拿到任何消息都说明当前连接是正常的 var data = JSON.parse(event.data); if (data.code == 200) { console.log(data.message) } heartCheck.reset().start(); } } createWebSocket(wsUrl); /** * 创建链接 * @param url */ function createWebSocket(url) { try { ws = new WebSocket(url); initEventHandle(); } catch (e) { reconnect(url); } } function reconnect(url) { if(lockReconnect) return; lockReconnect = true; //没连接上会一直重连,设置延迟避免请求过多 setTimeout(function () { createWebSocket(url); lockReconnect = false; }, 2000); } //心跳检测 var heartCheck = { timeout: 60000,//60秒 timeoutObj: null, serverTimeoutObj: null, reset: function(){ clearTimeout(this.timeoutObj); clearTimeout(this.serverTimeoutObj); return this; }, start: function(){ var self = this; this.timeoutObj = setTimeout(function(){ //这里发送一个心跳,后端收到后,返回一个心跳消息, //onmessage拿到返回的心跳就说明连接正常 ws.send("heartbeat"); self.serverTimeoutObj = setTimeout(function(){//如果超过一定时间还没重置,说明后端主动断开了 ws.close();//如果onclose会执行reconnect,我们执行ws.close()就行了.如果直接执行reconnect 会触发onclose导致重连两次 }, self.timeout); }, this.timeout); }, header:function(url) { window.location.href=url } }</script></html>界面效果后台控制点击按钮1 ...

July 2, 2019 · 5 min · jiezi

????-Hyperf-发布-WebSocket-组件-及-多个组件-v103-更新-企业级的-PHP-微服务协程框架

v1.0.3 更新内容本次更新涉及以下组件,主要新增了 WebSocket 服务端 及 WebSocket 协程客户端 组件,以及修复了一些 Bug hyperf/constantshyperf/databasehyperf/dihyperf/frameworkhyperf/http-serverhyperf/json-rpchyperf/model-cachehyperf/websocket-clienthyperf/websocket-server Added#48 增加 WebSocket 协程客户端及服务端#51 增加了 enableCache 参数去控制 DefinitionSource 是否启用注解扫描缓存#61 通过 db:model 命令创建模型时增加属性类型#65 模型缓存增加 JSON 格式支持Changed#46 移除了 hyperf/di, hyperf/command and hyperf/dispatcher 组件对 hyperf/framework 组件的依赖Fixed#45 修复当引用了 hyperf/websocket-server 组件时有可能会导致 HTTP Server 启动失败的问题#55 修复方法级别的 @Middleware 注解可能会被覆盖的问题#73 修复 db:model 命令对短属性处理不正确的问题#88 修复当控制器存在多层文件夹时生成的路由可能不正确的问题#101 修复常量不存在 @Message 注解时会报错的问题WebSocket 服务Hyperf 提供了对 WebSocket Server 的封装,可基于 hyperf/websocket-server 组件快速搭建一个 WebSocket 应用。 安装composer require hyperf/websocket-server配置 Server修改 config/autoload/server.php,增加以下配置。 <?php'servers' => [ [ 'name' => 'ws', 'type' => Server::SERVER_WEBSOCKET, 'host' => '0.0.0.0', 'port' => 9502, 'sock_type' => SWOOLE_SOCK_TCP, 'callbacks' => [ SwooleEvent::ON_HAND_SHAKE => [Hyperf\WebSocketServer\Server::class, 'onHandShake'], SwooleEvent::ON_MESSAGE => [Hyperf\WebSocketServer\Server::class, 'onMessage'], SwooleEvent::ON_CLOSE => [Hyperf\WebSocketServer\Server::class, 'onClose'], ], ],],配置路由目前暂时只支持配置文件的模式配置路由,后续会提供注解模式。在 config/routes.php 文件内增加对应 ws 的 Server 的路由配置,这里的 ws 值取决于您在 config/autoload/server.php 内配置的 WebSocket Server 的 name 值。 ...

July 2, 2019 · 2 min · jiezi

thinkphp-60-swoole扩展websocket使用教程thinkswoole

前言ThinkPHP即将迎来最新版本6.0,针对目前越来越流行Swoole,thinkphp也推出了最新的扩展think-swoole 3.0。介绍即将推出的tp6.0,已经适配swoole.并推出think-swoole 3.0,并且默认适配了socketio。和2.0版本在使用方法上面有些许不同。 Websocket 继承与Http,进行websocket连接之前需要一次HTTP请求,如果当期地址支持websocket则返回101,然后进行连接。也就是说并不是我的服务支持websocket后,请求每个连接地址都可以进行websocket连接,而是需要预先适配才可以连接。 参数配置如果要使用websocket需要在配置中启用,将websocket下的enable设置为true 'server' => [ 'host' => '0.0.0.0', // 监听地址 'port' => 808, // 监听端口 'mode' => SWOOLE_PROCESS, // 运行模式 默认为SWOOLE_PROCESS 'sock_type' => SWOOLE_SOCK_TCP, // sock type 默认为SWOOLE_SOCK_TCP 'options' => [ 'pid_file' => runtime_path() . 'swoole.pid', 'log_file' => runtime_path() . 'swoole.log', 'daemonize' => false, // Normally this value should be 1~4 times larger according to your cpu cores. 'reactor_num' => swoole_cpu_num(), 'worker_num' => swoole_cpu_num(), 'task_worker_num' => 4,//swoole_cpu_num(), 'enable_static_handler' => true, 'document_root' => root_path('public'), 'package_max_length' => 20 * 1024 * 1024, 'buffer_output_size' => 10 * 1024 * 1024, 'socket_buffer_size' => 128 * 1024 * 1024, 'max_request' => 3000, 'send_yield' => true, ], ], 'websocket' => [ 'enabled' => true,// 开启websocket 'handler' => Handler::class, //自定义wbesocket绑定类 'parser' => Parser::class, //自定义解析类 'route_file' => base_path() . 'websocket.php', 'ping_interval' => 25000, 'ping_timeout' => 60000, 'room' => [ 'type' => TableRoom::class, 'room_rows' => 4096, 'room_size' => 2048, 'client_rows' => 8192, 'client_size' => 2048, ], ], 'auto_reload' => true, 'enable_coroutine' => true, 'resetters' => [], 'tables' => [],handler和parser大大方便了自定义websocket服务,默认系统集成socketio。 ...

June 29, 2019 · 3 min · jiezi

全栈学习实践三创建php添加扩展其他Dockerfile编写

一、测试PHP1、在官方的基础上简化: # php7.3.5; Feb 7, 2019 link: https://github.com/docker-library/php/blob/master/7.3/alpine3.9/fpm/Dockerfile# Base images 基础镜像+阿里源FROM alpine:3.9#MAINTAINER 维护者信息MAINTAINER cffycls@foxmail.com# dependencies required for running "phpize"ENV PHP_VERSION 7.3.6ENV PHP_URL https://secure.php.net/get/php-$PHP_VERSION.tar.xz/from/this/mirrorENV PHPIZE_DEPS \ autoconf \ dpkg-dev dpkg \ file \ g++ \ gcc \ libc-dev \ make \ pkgconf \ re2cENV PHPIZE_DEVS \ argon2-dev \ coreutils \ curl-dev \ libedit-dev \ libsodium-dev \ libxml2-dev \ openssl-dev \ sqlite-dev \ libjpeg-turbo-dev \ libpng-dev \ gd-dev \ gettext-dev \ freetype-dev \ libxpm-dev \ libevent-devRUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories \ && apk update \ && addgroup -g 82 -S www-data \ && adduser -u 82 -D -S -G www-data www-data \ && mkdir -p "/usr/local/etc/php/conf.d" && mkdir -p "/var/www/html" \ && chown www-data:www-data /var/www/html && chmod 777 /var/www/html \ && apk add --no-cache\ curl \ tar \ xz \ openssl \ wget COPY php.tar.xz php.tar.xzRUN set -eux; \ apk add $PHPIZE_DEPS $PHPIZE_DEVS \ # && wget -O php.tar.xz "$PHP_URL" \ && tar -Jxf php.tar.xz && cd php-$PHP_VERSION && ./configure \ --prefix="/usr/local/php" \ --with-config-file-path="/usr/local/php/etc" \ --with-config-file-scan-dir="/usr/local/php/etc/conf.d" \ \ --enable-option-checking=fatal \ --with-mhash \ \ --enable-ftp \ --enable-exif \ --enable-mbregex \ --enable-mbstring \ --enable-mysqlnd \ --enable-sysvmsg \ --enable-opcache \ --enable-pcntl \ --enable-sockets \ --enable-sysvsem \ --enable-xml \ --with-curl \ --with-libedit \ --with-openssl \ --with-zlib \ --with-pcre-regex \ --with-pear \ --with-libxml-dir=/usr \ --with-jpeg-dir \ --with-freetype-dir \ --with-xpm-dir \ --with-png-dir \ --with-gettext \ --with-mhash \ --with-iconv \ --disable-fileinfo \ \ --enable-fpm --with-fpm-user=www-data --with-fpm-group=www-data --disable-cgi \ && make -j "$(nproc)" \ && find -type f -name '*.a' -delete \ && make install \# && make clean \ && rm -rf /tmp/pear ~/.pearrc \ && cd ../ && rm -rf php-$PHP_VERSION.tar.xz php-$PHP_VERSION2、添加扩展:这里目标 swoole-inotify-redis-uuid-memcached,这里是在运行上面镜像的容器测试得到结果,部分需要自行下载的插件或交互式安装的,使用单独下载源码编译的方式。/usr/local/php/etc/文件夹需要准备一个共享,这里取上面官方的配置稍加修改,并共享出来供后续安装使用: ...

June 27, 2019 · 3 min · jiezi

????-Hyperf-多个组件-v102-更新-企业级的-PHP-微服务协程框架

v1.0.2 更新内容本次更新涉及以下组件 hyperf/databasehyperf/dihyperf/eventhyperf/http-serverhyperf/loggerhyperf/redishyperf/contract Added接入 Travis CI,目前 Hyperf 共存在 426 个单测,1124 个断言; #25完善了对 Redis::connect 方法的参数支持; #29Fixed修复了 HTTP Server 会被 WebSocket Server 影响的问题(WebSocket Server 尚未发布);修复了代理类部分注解没有生成的问题;修复了数据库连接池在单测环境下会无法获取连接的问题;修复了 co-phpunit 在某些情况下不能按预期运行的问题;修复了模型事件 creating, updating ... 运行与预期不一致的问题;修复了 flushContext 方法在单测环境下不能按预期运行的问题;关于 HyperfHyperf 是基于 Swoole 4.3+ 实现的高性能、高灵活性的 PHP 协程框架,内置协程服务器及大量常用的组件,性能较传统基于 PHP-FPM 的框架有质的提升,提供超高性能的同时,也保持着极其灵活的可扩展性,标准组件均均基于 PSR 标准 实现,基于强大的依赖注入设计,保证了绝大部分组件或类都是 可替换 与 可复用 的。   框架组件库除了常见的协程版的 MySQL 客户端、Redis 客户端,还为您准备了协程版的 Eloquent ORM、JSON RPC 服务的及客户端、GRPC 服务端及客户端、Zipkin (OpenTracing) 客户端、Guzzle HTTP 客户端、Elasticsearch 客户端、Consul 客户端、ETCD 客户端、AMQP 组件、Apollo 配置中心、阿里云 ACM 应用配置管理、基于令牌桶算法的限流器、通用连接池、熔断器、Swagger 文档生成 等组件,省去了自己实现对应协程版本的麻烦,Hyperf 还提供了 基于 PSR-11 的依赖注入容器、注解、AOP 面向切面编程、基于 PSR-15 的中间件、自定义进程、基于 PSR-14 的事件管理器、Redis/RabbitMQ 消息队列、自动模型缓存、基于 PSR-16 的缓存 等非常便捷的功能,满足丰富的技术场景和业务场景,开箱即用。 ...

June 26, 2019 · 1 min · jiezi

YurunHttp-v310新增-Cookie-管理机制单元测试支持

YurunHttp 是开源的 PHP HTTP 类库,支持链式操作,简单易用。 支持所有常见的 GET、POST、PUT、DELETE、UPDATE 等请求方式,支持上传下载、设置和读取 header、Cookie、请求参数、失败重试、限速、代理、证书等。 3.x 版完美支持 Curl、Swoole 协程。 我们有完善的在线技术文档:http://doc.yurunsoft.com/Yuru... API 文档:https://apidoc.gitee.com/yuru... Gitee:https://gitee.com/yurunsoft/Y... Github:https://github.com/Yurunsoft/... git仓库中test目录里是示例代码! 更新日志(v3.1.0):新增: 新增 CookieManager,用于管理一个实例下的会话。现在同一个HttpRequest类实例,会自动管理 Cookie。 增加测试用例(PHP5.4 - PHP7.3)、示例代码 优化: 现在只有状态码为301、302、303,才更改重定向请求方法为 GET 优化 StatusCode 类 Swoole 重定向地址的 host、port、scheme 与上一个地址保持一致的话,复用 $client 同一个 HttpRequest 对象重复请求时,Curl 资源不再重新 init Uri->getPort() 不再根据协议自动返回缺省端口新增 Uri::getServerPort() 方法获取端口,支持获取缺省 Uri::getDomain() 方法改为静态方法 优化 close()、__destruct() Curl CURLOPT_COOKIEJAR 默认值改为 php://memory,不再写入文件 优化 Curl header 处理性能 修复: 修复 Swoole Handler Host 不带端口号问题Composer本项目可以使用composer安装,遵循psr-4自动加载规则,在你的 composer.json 中加入下面的内容 { "require": { "yurunsoft/yurun-http": "~3.1" }}然后执行 composer update 安装。 ...

June 25, 2019 · 1 min · jiezi

Swoole4x之协程变量访问安全与协程连接池实现

访问安全问题为什么说有访问安全问题呢?传统地,在php的的环境中,很少有Phper遇到所谓变量安全访问问题。举个例子,代码大约如下: class db{ protected static $instance; protected $dbCon; function __construct() { /* * 我们这里用stdclass来模拟一个数据库连接 */ $this->dbCon = new \stdClass(); } public static function getInstance() { if(!isset(self::$instance)){ self::$instance = new db(); } return self::$instance; } function dbCon() { return $this->dbCon; }}$con = db::getInstance()->dbCon();$con->key = 'new';var_dump($con->key);这个是在fpm模式下,很常见的数据库连接单例模式的使用。乍一看没有问题,但实际上,在协程环境下,会出现连接跨协程使用问题,举例如下 go(function (){ go(function (){ db::getInstance()->dbCon()->key = 'one'; //假设这sql执行了1s \co::sleep(1); var_dump(db::getInstance()->dbCon()->key); }); go(function (){ db::getInstance()->dbCon()->key = 'two'; //假设这sql执行了0.1s \co::sleep(0.1); var_dump(db::getInstance()->dbCon()->key); });});我们会发现,以上代码当中,协程2的数据污染到了协程1的数据,那么因此这样肯定是不行的。 上下文管理器为了解决这个问题,我们引入协程上下文管理这样的概念,由此来实现每个协程环境内的数据隔离。 class dbContext{ private $container = []; private static $instance; public static function getInstance() { if(!isset(self::$instance)){ self::$instance = new dbContext(); } return self::$instance; } function dbCon() { $cid = \co::getCid(); if(!isset($this->container[$cid])){ $this->container[$cid] = new stdClass(); defer(function (){ $this->destroy(); }); } return $this->container[$cid]; } function destroy() { $cid = \co::getCid(); if(!isset($this->container[$cid])){ unset($this->container[$cid]); } }}go(function (){ go(function (){ dbContext::getInstance()->dbCon()->key = 'one'; //假设这sql执行了1s \co::sleep(1); var_dump(dbContext::getInstance()->dbCon()->key); }); go(function (){ dbContext::getInstance()->dbCon()->key = 'two'; //假设这sql执行了0.1s \co::sleep(0.1); var_dump(dbContext::getInstance()->dbCon()->key); });});以上代码中,我们用每个协程的id,来作为每个协程栈的数据token,用了defer方法,实现了每个协程退出的时候的数据自动清理,从而避免了内存泄露。 ...

June 24, 2019 · 1 min · jiezi

????-Hyperf-骨架-v103-及多个组件-v101-更新-企业级的-PHP-微服务协程框架

更新内容hyperf-skeleton v1.0.3Added安装器为 RPC 部分增加 JSON RPC with Service Governance 选项, 选择该选项会自动安装 hyperfservice-governance 组件;骨架内提供的 App\Exception\Handler\AppExcpetionHandler 默认输出异常信息Changed将 App\Model\Model 修改为一个抽象类Fixed修复 open(runtime/hyperf.pid) failed, Error: No such file or directory 的错误;修复 format_throwable() 函数未被定义的错误;database v1.0.1Fixed修复 DB::raw() 无法支持强制索引的问题;http-server v1.0.1Fixed定义路由时的路由方法不再区分大小写testing v1.0.1Fixed修复 flushContext 方法没有按预期运行的问题关于 HyperfHyperf 是基于 Swoole 4.3+ 实现的高性能、高灵活性的 PHP 协程框架,内置协程服务器及大量常用的组件,性能较传统基于 PHP-FPM 的框架有质的提升,提供超高性能的同时,也保持着极其灵活的可扩展性,标准组件均均基于 PSR 标准 实现,基于强大的依赖注入设计,保证了绝大部分组件或类都是 可替换 与 可复用 的。   框架组件库除了常见的协程版的 MySQL 客户端、Redis 客户端,还为您准备了协程版的 Eloquent ORM、JSON RPC 服务的及客户端、GRPC 服务端及客户端、Zipkin (OpenTracing) 客户端、Guzzle HTTP 客户端、Elasticsearch 客户端、Consul 客户端、ETCD 客户端、AMQP 组件、Apollo 配置中心、阿里云 ACM 应用配置管理、基于令牌桶算法的限流器、通用连接池、熔断器、Swagger 文档生成 等组件,省去了自己实现对应协程版本的麻烦,Hyperf 还提供了 基于 PSR-11 的依赖注入容器、注解、AOP 面向切面编程、基于 PSR-15 的中间件、自定义进程、基于 PSR-14 的事件管理器、Redis/RabbitMQ 消息队列、自动模型缓存、基于 PSR-16 的缓存 等非常便捷的功能,满足丰富的技术场景和业务场景,开箱即用。 ...

June 24, 2019 · 1 min · jiezi

Swoft-202-发布PHP-微服务协程框架

新增(Enhancement): 新增 $request->parsedQuery() 方法 (ab45089) 新增 Bean 属性注入基础数据类型(string/int/bool/float/array),自动根据注释类型转换。(e3d4085) 新增 db(), 使 Model/Query/DB 支持切库(f3b12c9) 新增 DbSelectorInterface 用于根据业务自动切库(b36ca03) 新增 Http server 增加Cookies响应支持 (1a024bf) 新增 devtool 组件的实体生成(14b4d39c) 新增 允许注册 swoole 的 pipeMessage, packet 事件(afec0e3f) 新增 添加更多启动时的相关事件(8aaa38c4) 修复(Fixed): 修复 paginate 无法指定查询字段(308d330) 修复 实体 join 操作,显示不出关联表数据 (446a3a2) 修复 实体属性名 attributes等与系统定义属性冲突问题 (1a9d25b) 修复 AOP 切面重复执行问题 (54e00ac) 修复 Http 服务全局异常 contentType 失效问题(e704116) 修复 Http server在启用https 时,type 检查无法通过 (3e9b431) 修复 Http server通过data响应html时可能出现错误(e5513df) 修复 控制器 table 数据显示 (4d27718) ...

June 24, 2019 · 1 min · jiezi

QueryPHP-V1beta3-完成-100-计划功能冻结

QueryPHP v1.0.0-beta. 版本完成 1.0 正式版前的除 Swoole 之外计划功能,启动冻结。下一版本专注 Swoole 协程改进。 关于 QueryPHPQueryPHP 是一款现代化的渐进式高性能 PHP 7 不仅仅是常驻框架,以工程师用户体验为历史使命,让每一个 PHP 应用都有一个好框架。 百分之百单元测试覆盖直面 Bug 一剑封喉,基于 Zephir 实现框架常驻,依托 Swoole 生态实现业务常驻,此刻未来逐步渐进。 我们的愿景是 USE LEEVEL WITH SWOOLE DO BETTER, 让您的业务撑起更多的用户服务。 https://github.com/hunzhiwange/queryphp https://gitee.com/dyhb/queryphp 更新日志【framework】除 Swoole 之外组件单元测试覆盖率保持在 97 % 高覆盖率【framework】Validate 验证器组件调整验证策略,默认为必须验证 must,支持可选 optional,断言 Assert 也进行了优化【framework】调整部分组件命名 API,如果 FlowControl 中的 ifs,elsesIf endIfs 为像 shell 一样的 if elif fi,Nulls 重名为 test.【framework】参数可选 PHPDoc 改为 null|xx,可选参数严格加入 ?string $name = null,PHPDoc 严格加入 throws Exception【framework】数据库测试用例在 Mysql 严格模式下面修复【framework】优化助手函数在 ide 的表现,系统所有 facade 均加入代理模式 proxy 提高 ide 下面的用户体验【framework】链式风格代码全部重构,代码更整洁【framework】更好的单元测试文档自动化工具【framework】清理大量技术债务,1.0.0 正式版计划功能差不多完结,即将冻结【application】主要跟随框架层的变化做的调整RoadMap【framework】Beta 下一版本和下下版本主要计划是对 Swoole 最新版的协程完善支持【framework】RC 版本会冻结计划功能,只修复 BUG 和文档完善  ...

June 24, 2019 · 1 min · jiezi

Swoole-启动一个服务开启了哪些进程和线程

概述Swoole 启动一个服务,开启了哪些进程和线程? 为了解决这个问题,咱们启动一个最简单的服务,一起看看究竟启动了哪些进程和线程? 然后结合官网运行流程图,对每个进程和线程进行归类。 服务启动后打印出当前 Swoole 版本 和 当前 CPU 核数。 打印 Swoole 版本,是让大家可以下载这个版本 去运行代码。 打印 CPU 核数,是因为这个参数下面会用到。 废话不多说,直接看代码吧。 代码serv.php <?phpclass Server{ private $serv; public function __construct() { $this->serv = new swoole_server("0.0.0.0", 9502); $this->serv->set([ 'worker_num' => 3, 'task_worker_num' => 3, ]); $this->serv->on('Start', function ($serv) { echo "SWOOLE:".SWOOLE_VERSION . " 服务已启动".PHP_EOL; echo "SWOOLE_CPU_NUM:".swoole_cpu_num().PHP_EOL; }); $this->serv->on('Receive', function ($serv, $fd, $from_id, $data) { }); $this->serv->on('Task', function ($serv, $task) { }); $this->serv->on('Finish', function ($serv, $task_id, $data) {}); $this->serv->start(); }}$server = new Server();上面的代码简单说下,创建了一个 TCP 服务器,启动了 3 个 worker 进程, 3 个 task 进程,因为启用了 task 功能,所以必须注册 onTask、onFinish 2 个事件的回调函数。 ...

June 24, 2019 · 1 min · jiezi

imi-v10-正式版专注单体应用的-PHP-协程应用开发框架

imi 介绍 imi 是基于 PHP 协程应用开发框架,它支持 HttpApi、WebSocket、TCP、UDP 应用开发。 由 Swoole 提供强力驱动,Swoole 拥有常驻内存、协程非阻塞 IO 等特性。 框架遵守 PSR 标准规范,提供 AOP、注解、连接池、请求上下文管理、ORM模型等常用组件。 imi 的模型支持关联关系的定义,增删改查一把梭! 作者介绍宇润,江苏无锡人,94年出生。初中自己接触易语言,从此踏入代码的世界。 2013 年开发并发布 YurunPHP 框架(现已停止维护)。 2017 年开始正式玩 git 和发布开源项目,开源的东西都是自己能用到才会去做,不会为了造轮子而造,一切为了实用。 2018 年接触 Swoole,打开了新世界的大门,原来 PHP 还能这么玩! 2018年4月16日,我为 imi 写下了第一个字节的代码。 2018年6月21日,发布了第一个公开版本 v0.0.1。 经过一年实战,2019年6月21日,一周年之际正式发布 v1.0.0 版本。 宇润部分开源项目:imi、YurunHttp、YurunOAuthLogin、PaySDK、ChineseUtil 我已通过码云平台,向 Swoole 项目力所能及地捐款 1111,聊表心意。 核心组件[x] Server (Http/Websocket/Tcp/Udp)[x] 容器 (PSR-11)[x] Aop 注入[x] Http 中间件 (PSR-15)[x] MySQL 连接池 (协程&同步,主从,负载均衡)[x] Redis 连接池 (协程&同步,负载均衡)[x] Db 连贯操作[x] 关系型数据库 模型[x] 跨进程共享内存表 模型[x] Redis 模型[x] 日志 (PSR-3 / File + Console)[x] 缓存 (PSR-16 / File + Redis)[x] 验证器 (Valitation)[x] Task 异步任务[x] 进程/进程池[x] 命令行开发辅助工具[x] 业务代码热更新扩展组件imi-rpc ...

June 21, 2019 · 1 min · jiezi

仿照asyncawait风格对Swoole4协程的简单包装

Swoole官方文档中对协程的示例大多按照一次请求一个协程(或脚本并发大量协程)的方式来举例说明,这种使用方式下提升的是整体的性能,而非单次请求的响应时间.要提升单次请求的响应效率(或提升非网络服务下php脚本代码的运行效率),需要在业务代码中主动使用协程来处理那些可并发的,耗时的代码.这时便涉及到协程数据交互的情况,官方文档中使用chan举了一个生产者消费者的例子,但是如果业务代码都按照这个风格的话较为复杂.js及c#的async/await风格使用相对简单,所以结合Swoole协程的csp模型及php语法情况,仿照async/await的风格做了如下简单包装.包装方法代码class CoTask { protected $chan = null; public function __construct(\Chan $chan){ $this->chan = $chan; } public function wait(){ if($this->chan instanceof \Chan){ $result = $this->chan->pop(); $this->chan = null; if(!empty($result['exception']) && $result['exception'] instanceof \Throwable){ throw $result['exception']; }else{ return $result['result']; } }else{ throw new \Exception('exception'); } }}function co_run($func){ $chan = new \Chan(1); $task = new \CoTask($chan); go(function() use ($chan,$func){ $result = ['result' => null,'exception' => null]; try{ $result['result'] = $func(); }catch (\Throwable $e){ $result['exception'] = $e; } $chan->push($result); }); return $task;}function co_wait(&$task){ if($task instanceof \CoTask){ $task = $task->wait(); } return $task;}调用举例$test = co_run(function(){ //执行代码并返回结果});//执行其他代码co_wait($test);//由于使用了chan的pop方法,所以需要当前在协程上下文var_dump($test);PHP7.4后箭头函数调用举例$test = co_run(fn() => "单行的执行代码,如多行仍需按照原有方式");//执行其他代码co_wait($test);//由于使用了chan的pop方法,所以需要当前在协程上下文var_dump($test);总结经过这样简单的包装,可以在业务代码中存在可并发的多次调用或循环调用场景下使用,压缩单次处理时间.

June 20, 2019 · 1 min · jiezi

现有PHP项目引入Swoole4协程支持的流程及难点

Swoole4之后,协程化支持已经完善,并且支持大量的PHP扩展自动协程化.一些基于Swoole4的框架也蓬勃发展,光看着文档就让人跃跃欲试.但是对于现有旧项目如何引入并启用Swoole协程成了实际场景中的客观问题,由于协程性质及生命周期等原因,这并非想象的那么容易.本文整理了在现有项目中引入Swoole4并开启协程的一些步骤及需要注意的问题,期望可以为有需要的人提供帮助.前置要求请阅读Swoole文档中环境依赖的章节,本文只针对代码部分的调整.下文默认已成功编译安装了Swoole4扩展.框架代码部分首先请查阅所用框架是否有结合Swoole的开源方案,如laravel-s等.如有可按需选用,如没有也参照业务代码部分对框架代码进行改造.(这会导致升级框架版本变困难)业务代码部分首先阅读Swoole文档中协程编程须知的章节.对单例对象按协程ID做隔离,防止单例对象跨协程使用.对Mysql,Redis等连接资源需要defer进行回收复用或关闭,防止连接数持续增加.对全局变量及常量做评估,所有可能引起问题的地方全部按协程ID做隔离.对项目内直接echo,print之类输出的位置做修改,或使用ob_start方法进行获取输出内容进行处理.对项目内使用不支持自动协程化的库做修改,采用协程客户端进行替换.(如:curl).对项目内使用exit,die的地方做修改.对static静态类,属性或变量及引用传递进协程的变量都要小心操作,尽量避免这种情况,只使用局部变量.对每次修改做好单元测试,做好备份及回滚措施.可从某些单一场景下入手逐步进行修改.(如:某个单一业务模块,某个简单PHP脚本等).总结上述修改看似内容不多,但是在一个现有的项目中进行修改并保证服务正常运行却并非易事,希望大家小心操作,早日成功.

June 20, 2019 · 1 min · jiezi

????-Hyperf-v10-发布全新企业级的-PHP-协程框架

HyperfHyperf 是基于 Swoole 4.3+ 实现的高性能、高灵活性的 PHP 协程框架,内置协程服务器及大量常用的组件,性能较传统基于 PHP-FPM 的框架有质的提升,提供超高性能的同时,也保持着极其灵活的可扩展性,标准组件均均基于 PSR 标准 实现,基于强大的依赖注入设计,保证了绝大部分组件或类都是 可替换 与 可复用 的。 框架组件库除了常见的协程版的 MySQL 客户端、Redis 客户端,还为您准备了协程版的 Eloquent ORM、JSON RPC 服务的及客户端、GRPC 服务端及客户端、Zipkin (OpenTracing) 客户端、Guzzle HTTP 客户端、Elasticsearch 客户端、Consul 客户端、ETCD 客户端、AMQP 组件、Apollo 配置中心、阿里云 ACM 应用配置管理、基于令牌桶算法的限流器、通用连接池、熔断器、Swagger 文档生成 等组件,省去了自己实现对应协程版本的麻烦,Hyperf 还提供了 基于 PSR-11 的依赖注入容器、注解、AOP 面向切面编程、基于 PSR-15 的中间件、自定义进程、基于 PSR-14 的事件管理器、Redis/RabbitMQ 消息队列、自动模型缓存、基于 PSR-16 的缓存 等非常便捷的功能,满足丰富的技术场景和业务场景,开箱即用。 框架初衷尽管现在基于 PHP 语言开发的框架处于一个百花争鸣的时代,但仍旧未能看到一个优雅的设计与超高性能的共存的完美框架,亦没有看到一个真正为 PHP 微服务铺路的框架,此为 Hyperf 及其团队成员的初衷,我们将持续投入并为此付出努力,也欢迎你加入我们参与开源建设。 设计理念Hyperspeed + Flexibility = Hyperf,从名字上我们就将 超高速 和 灵活性 作为 Hyperf 的基因。 ...

June 20, 2019 · 1 min · jiezi

Swoole-5-将移除-PSR0-下划线风格类名

Swoole 在 1.x - 4.x版本中同时提供了PSR-0规范的下划线风格类名和PSR-4的命名空间风格。目前PSR-0规范已于2014年10月21日被标记为弃用,目前最新的替代规范为 PSR-4。 在最新的5版本中我们计划彻底移除下划线类名。请各位Swoole用户逐步将类名改为PSR-4规范的纯命名空间风格。 现在Swoole正在逐渐重构移除陈旧落后的设计,未来会变得越来越精致。 正确$server = new Swoole\Http\Server;$server = new Swoole\Server;$client = new Swoole\Coroutine\Http\Client;$client = new Co\Http\Client;错误$server = new Swoole_Http_Server;$server = new Swoole_Server;$client = new Swoole_Coroutine_Http_Client;$client = new Co_Http_Client;在5.0版本中将不再支持下划线类名,使用了上述风格代码的PHP会报类不存在的致命错误

June 20, 2019 · 1 min · jiezi

服务器接入swoole-关联的防火墙问题

接上面的操作,在腾讯云服务器上,安装 easyswoole 成功之后,想简单的写个demo,先在服务器上跑起来。 后面的研究交给第二天1. 对外公网 IP首先混淆了一个对外IP的概念 1.1 错误的ifconfig swoole脚本程序对外暴露了 9502端口。 通过URL进行HTTP访问,发现服务器怎么也响应不了 PS :中间的过程不再多说,怀疑了IP问题和防火墙问题1.2 获取真正的对外IPcurl ifconfig.mewget -qO- ifconfig.me/ip2. 端口和防火墙2.1 排错过程安装demo 实例,写了一个简单的脚本 对外暴露。 通过HTTP访问,无响应。 排错1 : 查看端口是否启动 排错2 : 内网访问 9502服务正常开启,内部访问正常,外部访问出现异常,就很可能是外部访问受限。 因为是买的腾讯云,所以可能是默认的 防火墙 deny 2.2 关闭防火墙先通过关闭防火墙测试一下。 sudo ufw disable发现通过浏览器已经可以正常的访问了。 但是如果因为一个服务,把所有的端口都暴露出去,也太危险了 所以选择对外暴露 固定端口 sudo ufw enable // 开启防火墙sudo ufw status // 查看防火墙的配置信息sudo ufw allow 9502 // 对外暴露 9502 端口测试 可以进程正常的访问 9502 端口,但是如果swoole服务启动 9503端口,虽然服务正常启动,内网访问正常,但是外网不可以访问。 2.3 防火墙的命令在 Ubuntu 中用 UFW 配置防火墙 ...

June 20, 2019 · 1 min · jiezi

压测-swoolewebsocketserver-性能

概述这是关于 Swoole 入门学习的第十篇文章:压测 swoole_websocket_server 性能。 第九篇:Swoole Redis 连接池的实现第八篇:Swoole MySQL 的实现第七篇:Swoole RPC 的实现第六篇:Swoole 整合成一个小框架第五篇:Swoole 多协议 多端口 的应用第四篇:Swoole HTTP 的应用第三篇:Swoole WebSocket 的应用第二篇:Swoole Task 的应用第一篇:Swoole Timer 的应用收到读者提问 “使用 Swoole 开发的群聊功能,想知道并发情况,也就是想压测下 QPS,一直未找到方法 ...” 对 swoole_http_server 压测,咱们可以使用 Apache 的 ab 命令。 对 swoole_websocket_server 压测,使用 ab 命令是不能压测的,我从网上一直也没找到合适的方法,看官方提供的代码 benchmark/async.php 中,使用的异步模块 swoole\http\client 方法进行压测的,但在 Swoole 4.3 版本就移除了异步模块,让使用 Coroutine 协程模块。 在本地我用 Coroutine 协程实现了一下, 测的差不多的时候,一直不确定是否正确,就在 segmentfault 发了个提问,没想到韩老师回答了,'如果的如果'老师也回答了,非常感谢两位老师的答案,然后整理出文章分享给大家。 测试机Mac 上安装的 Parallels Desktop 虚拟机 系统:Ubuntu 16.04.3 LTS 内存: ...

June 10, 2019 · 2 min · jiezi

PHP物联网开发利器之Actor并发模型

PHP不适合做物联网服务端吗?在传统的思维中,经常会有人告诉你,php不适合用来做物联网服务端,让你换java,node,go等其他语言,是的,没错传统意义上的php,确实很难做物联网服务器,因为它实在太蹩脚了,当然,这也不是意味着彻底就不能做。举个例子,当你想实现一个TCP服务器的时候,你可能需要写出原理大约如下的代码: for ($i = 0;$i <= 1;$i++){ $pid = pcntl_fork(); if($pid){ if($i == 0){ $server = stream_socket_server("tcp://127.0.0.1:9501", $errno, $errstr, STREAM_SERVER_BIND); }else if($i == 1){ $tickTime = time()+3600; while (1){ usleep(1); if($tickTime == time()){ //do my tick func } } } }}以上代码的意义等于在一个进程中创建一个TCP 服务端,另外一个进程中死循环来做时间检测,从而实现定时器逻辑。这样看起来,确实很蹩脚,而且对于编程基础普遍比较薄弱的PHPer来说,这真的很难维护。当然这个时候,就会有人说,这不是还有Workerman吗,是的,确实还有Workerman,Workerman就是高度封装了上述代码原理,帮助你专心于实现代码逻辑的一个PHP多进程框架,因此说PHP不时候做物联网,其实这是谬论。当然这个时候可能又会有人说,go语言有协程,你用Workerman当出现阻塞数据库调用的时候,那效率就非常的差,很难出现高并发,这么说没错,但是实际上,我们可以尽可能的用多进程去弥补这个不足,也就是堆机器。当然,如果你真的想锱铢必较,没关系,这个时候我们就可以拿出我们的杀器,那就是Swoole4.x的协程。 Swoole做TCP服务器举个例子,如下代码: $server = new swoole_server("127.0.0.1", 9501);$server->on('workerstart',function ($ser,$workerId){ if($workerId == 0){ swoole_timer_tick(1000,function (){ }); }});$server->on('connect', function ($server, $fd){ echo "connection open: {$fd}\n";});$server->on('receive', function ($server, $fd, $reactor_id, $data) { $server->send($fd, "Swoole: {$data}"); $server->close($fd);});$server->on('close', function ($server, $fd) { echo "connection close: {$fd}\n";});$server->start();我们就可以很快的创建出一个多进程的协程TCP服务器,而且在各个回调函数内,均自动创建协程环境,我们可以在协程回调内,去调用协程的数据库API,这样就避免了因为阻塞数据库调用而导致无法处理其他客户端请求的问题。然而尽管如此,很多人可能都没有思考过,如何优雅的写出自己的物联网服务器。举个例子,我们常见的互联网设备管理服务中,大约可能出现如下代码: ...

June 9, 2019 · 2 min · jiezi

Swoole4x探究之多进程TCP协程服务实现

有研究过Workman框架的同学就会发现,其实workman最核心的,就是用了php socket拓展加上pcntl拓展来实现其底层的网络服务和多进程调度。那我们今天就来探讨如何使用Swoole的CoroutineSocket模块来实现自己的tcp服务。我们先编写一段小的测试代码,test.php 代码如下 $socket = new Co\Socket(AF_INET, SOCK_STREAM, 0);$socket->bind('127.0.0.1', 9601);$socket->listen(128);go(function () use ($socket) { while(true) { $client = $socket->accept(-1); $data = $client->recv(64,10); var_dump('Recv:'.$data); $client->sendAll('reply at '.time()); $client->close(); }});我们执行 php test.php并新建一个cmd控制台,用telnet模拟tcp客户端,可以看到如下结果: telnet 127.0.0.1 9601Trying 127.0.0.1...Connected to localhost.Escape character is '^]'.asdreply at 1559713416Connection closed by foreign host以上说明我们已经成功建立了一个简单的TCP服务器。而有细心的同学就会发现,以上代码如果我在recv后,有一些数据库行为发生,那我该TCP服务器在同一时间就仅仅只能accept链接并处理一个链接的事情,并发能力接近于1。因此我们可以做一点小小的改进,如下: $socket = new Co\Socket(AF_INET, SOCK_STREAM, 0);$socket->bind('127.0.0.1', 9601);$socket->listen(128);go(function () use ($socket) { while(true) { $client = $socket->accept(-1); go(function () use ($client){ $data = $client->recv(64,10); var_dump('Recv:'.$data); //模拟数据库耗时,假定我们的数据库也是用协程api \co::sleep(1); $client->sendAll('reply at '.time()); $client->close(); }); }});我们利用协程,把连接accept后的逻辑,全部放到另外一个子协程当中处理,让我们的TCP服务器可以继续accept连接,也就提高了我们的并发能力。然而,在实际的编程中,我们不可能做到完全的百分百协程API,而且我的机器也是多核心的处理器,那么此刻我如何尽可能的利用我的CPU呢?因此我们可以利用端口复用的特性和Swoole的Process来构建一个多进程TCP协程服务器。 ...

June 5, 2019 · 1 min · jiezi

Swoole-44支持-CURL-协程化

在4.4之前的版本中,Swoole一直不支持CURL协程化,在代码中无法使用curl。由于curl使用了libcurl库实现,无法直接hook它的socket,4.4版本使用Swoole\Coroutine\Http\Client模拟实现了curl的API,并在底层替换了curl_init等函数的C Handler。 提示CURL Hook的特性尚处于试验阶段,请勿在生产环境中直接使用暂不支持文件上传、CURL Multi仍然需要依赖curl,请务必安装curl扩展支持的特性列表GET/POSTHeaderCookieHttps经过验证Guzzle CURL完全可以使用开启使用Runtime::enableCoroutine来开启CURL Hook。 默认不开启CURL HookSwoole\Runtime::enableCoroutine(SWOOLE_HOOK_ALL);Swoole\Runtime::enableCoroutine(SWOOLE_HOOK_CURL);使用$n = 10;while($n--) { go(function () { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, "http://www.xinhuanet.com/"); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_HEADER, 0); $output = curl_exec($ch); if ($output === FALSE) { echo "CURL Error:" . curl_error($ch); } curl_close($ch); echo strlen($output) . " bytes\n"; });}要将上面两段代码合并到一个文件中执行运行结果htf@LAPTOP-0K15EFQI:~/swoole-src/examples$ time php curl.php177173 bytes177173 bytes177173 bytes177173 bytes177173 bytes177173 bytes177173 bytes177173 bytes177173 bytes177173 bytesreal 0m0.534suser 0m0.031ssys 0m0.297s可以看到整个程序是并行的,进程没有任何阻塞。 strace 跟踪使用strace跟踪发现,所有系统调用均变成epoll+socket的异步非阻塞调用了。 ...

June 5, 2019 · 10 min · jiezi

swoole结合swoole-和-nsq-的实际应用todo

集合 swoole 的框架设计为了减少理解度,我尽量的从源头开始引入 1. nsq2. php 订阅3. 引入 swoole就是构造方法引入 swoole 的实例化同时,重写 workerStart 的方法。

June 4, 2019 · 1 min · jiezi

Swoole-Redis-连接池的实现

概述这是关于 Swoole 入门学习的第九篇文章:Swoole Redis 连接池的实现。 第八篇:Swoole MySQL 的实现第七篇:Swoole RPC 的实现第六篇:Swoole 整合成一个小框架第五篇:Swoole 多协议 多端口 的应用第四篇:Swoole HTTP 的应用第三篇:Swoole WebSocket 的应用第二篇:Swoole Task 的应用第一篇:Swoole Timer 的应用收到读者反馈,“亮哥,文章能多点图片吗?就是将运行结果以图片的形式展示...” 我个人觉得这是比较懒、动手能力差的表现,恩... 要勤快些。 但谁让文章是写给你们看的那,我以后尽量文章写的图文并茂一点。 上篇文章 分享了 MySQL 连接池,这篇文章 咱们来分享下 Redis 连接池。 在上篇文章的基础上进行简单调整即可,将实例化 MySQL 的地方,修改成实例化 Redis 即可,还要注意一些方法的调整。 这篇文章仅仅只实现一个 Redis 连接池,篇幅就太少了,顺便将前几篇整合一下。 大概 Demo 中包含这些点: 实现 MySQL 连接池实现 MySQL CURD 方法的定义实现 Redis 连接池实现 Redis 方法的定义满足 HTTP、TCP、WebSocket 调用提供 Demo 供测试调整 目录结构HTTP 调用: 实现 读取 MySQL 中数据的 Demo实现 读取 Redis 中数据的 Demo ...

June 3, 2019 · 5 min · jiezi

swoole-学习笔记swoole-真的优秀一个框架

》 todo 前期学完了操作系统,特别是知道了进程,线程 调度的问题0.学习参考链接100. 致敬

June 3, 2019 · 1 min · jiezi

Swoole-MySQL-连接池的实现

概述这是关于 Swoole 入门学习的第八篇文章:Swoole MySQL 连接池的实现。 第七篇:Swoole RPC 的实现第六篇:Swoole 整合成一个小框架第五篇:Swoole 多协议 多端口 的应用第四篇:Swoole HTTP 的应用第三篇:Swoole WebSocket 的应用第二篇:Swoole Task 的应用第一篇:Swoole Timer 的应用收到读者的咨询,这情况大家可能也会有,所以就在这说说: “亮哥,我今年30岁了,有点中年危机,最近有点焦虑,发现工作虽然很忙,但是没感觉能力有所提升,整天都有写不完的业务代码,时间紧有时代码质量也不怎么样,知道还有很多改进空间,但一直没时间改,主要是后面项目压着,马上又要进入开发了,这种情况怎么办?” 首先,我是菜鸡,观点不喜勿喷,那我就说下自己的看法: 上面的描述比较主观,人呀有时候发现不了自己的能力很正常,有时候有能力了并不是马上就能显现的,而是到了某个阶段后突然发现,哇塞,原来自己这么厉害。 当然能力也分很多种,比如专业能力,快速学习能力,进度把控能力,还有自信也是一种能力,不要脸是一种能力,坚持不要脸更是一种能力。 其实能力提升最快的还是靠工作实践,悄悄问问自己加入了很多大牛的微信群,能力提升了吗?看书自学不实践是不是吸收的也不多。 如果非要给一个具体的方案,那就是在团队内多分享吧,因为在分享前你会做充分的准备来避免分享时出丑,即使有时候自己知道,当讲出来的时候就不是那么回事了。 前期分享可以是看稿,后期练习无稿分享。 然后,再多说一点,30了给自己一个目标,不要盲目每天就是学学学,比如目标是技术专家,目标是业务专家,都很好呀,当然目标与自己性格有关也不是一成不变的。 围绕着目标设置一些计划,不要以为每天的学学学,就觉得其他的一切就自然而来,其中还有很多机遇和人脉的因素。 最后,如果实在感觉压得喘不过气,就换个环境吧,别和自己过不去。 开始今天的文章,这篇文章实现了 Swoole MySQL 连接池,代码是在《Swoole RPC 的实现》文章的基础上进行开发的。 先回顾上篇文章的内容: 实现了 HTTP / TCP 请求实现了 同步 / 异步 请求分享了 OnRequest.php、OnReceive.php 源码业务逻辑 Order.php 中返回的是假数据本篇文章主要的功能点: 业务逻辑 Order.php 中返回 MySQL 数据库中的数据。Task 启用了协程支持 主/从 数据库配置实现数据库连接池实现数据库 CURD代码Order.php<?phpif (!defined('SERVER_PATH')) exit("No Access");class Order{ public function get_list($uid = 0, $type = 0) { //TODO 业务代码 $rs[0]['order_code'] = '1'; $rs[0]['order_name'] = '订单1'; $rs[1]['order_code'] = '2'; $rs[1]['order_name'] = '订单2'; $rs[2]['order_code'] = '3'; $rs[2]['order_name'] = '订单3'; return $rs; }}修改成:class Order{ private $mysql; private $table; public function __construct() { $pool = MysqlPool::getInstance(); $this->mysql = $pool->get(); $this->table = 'order'; } public function add($code = '', $name = '') { //TODO 验证 return $this->mysql->insert($this->table, ['code' => $code, 'name' => $name]); } public function edit($id = 0, $name='') { //TODO 验证 return $this->mysql->update($this->table, ['name' => $name], ['id' => $id]); } public function del($id = 0) { //TODO 验证 return $this->mysql->delete($this->table, ['id' => $id]); } public function info($code = '') { //TODO 验证 return $this->mysql->select($this->table, ['code' => $code]); }}Task 启用协程一、需要新增两项配置: ...

May 27, 2019 · 5 min · jiezi

PHP-微服务之分布式事务阅读提示

前几天写一篇 , 一种新思路实现分布式事务的文章。https://segmentfault.com/a/11... 部分死脑筋就开始,各种不解。看反馈 确实有点搞笑。 不要一听到 session 就觉得是 $_SEESION不要别人换个名字 token 或者 jwt 就不认识。抽象出来 统一都是会话id 。 session token jwt 没有任何区别。我给你标识符 ,你下次传给我,我就能认识你。至于我根据这个标识符怎么拿认识的 ,你不用管。各自根据各自的情况,怎么方便怎么来。 写个分布式事务 就有人开始喷了 事务提交了, 怎么回滚doTransaction 都知道怎么回滚。 就增加一步 就不知道怎么回滚了?这种智商基本 只能走出家一步 走两步就找不到回家的路了。 可能觉得commit了啊,php的pdo没有提供commit了还能回滚啊 这个片文章只是一个全新可简单实现的思想。恰巧用了 commit() , 我要自定义个名字 step3() 估计有人更晕了。 前执行的每一步,难道不知道吗? 不能做个对应的回滚方案吗?就2步,即使10步都回滚 array_unshift($this->callback,function(){ // rollback do});array_unshift($this->callback,function(){ // rollback commit});funciton rollback(){ foreach ($this->callback as $c){ $c->call($this); }}或许把事务换个名字 “分布式一致性解决方案” 死脑筋是不是好转弯点。还是希望各位稍微能变通一点,站在更高的一个角度来看问题的共性。

May 24, 2019 · 1 min · jiezi

Mix-PHP-V2-实例AliCloud-短信协程池异步发送守护程序

前些时间我们发布了 Mix PHP V2 实例:协程池异步邮件发送守护程序 范例,这一次我们提供一个使用大厂 SDK 通过 Swoole Hook 协程化来并行执行短信发生任务,本文是一个代码简单、IO性能极其强大的范例。 请先升级到 mix-framework >= v2.0.5。本范例依然使用消息队列的方式接收短信发送任务,消息中间件使用: redis生产者通常框架中使用 Redis 会安装一个类库来使用,本例使用原生代码,便于理解。// 连接$redis = new \Redis();if (!$redis->connect('127.0.0.1', 6379)) { throw new \Exception('Redis connect failed.');}$redis->auth('');$redis->select(0);// 投递任务for($i = 0; $i < 3; $i++){ $data = [ 'phone' => '***', 'templateCode' => 'SMS_***', 'templateParam' => ['code' => 123456], ]; $redis->lpush('queue:sms', serialize($data));}消费者使用的是 ali 云的短信服务,查看官方 PHP SDK 文档 ,使用的库为: composer require alibabacloud/client通过查看该库的 composer 依赖文件,我们得知该库基于 guzzlehttp 开发,因为 Mix PHP 提供了无需修改代码就可 Hook Guzzle 库可在协程中使用的工具 Mix PHP V2 生态:让 Guzzle 支持 Swoole 的 Hook 协程,所以能基本确定该库可在 Swoole 协程中使用。 ...

May 24, 2019 · 3 min · jiezi

使用swoole写的PHP-API框架快速应用生产

废话不多说 github请点击https://github.com/SmallFores... 基本内容需要swoole扩展php run.php运行错误信息在php_error.log中查看MedooJWT路由数据库配置 application/Base.php中JWT token有效期以及key在tool/Tool.php中查看

May 23, 2019 · 1 min · jiezi

Swoole协程抢占式调度

前言Swoole内核团队开设的专栏,会逐渐投入精力写文章介绍Swoole的开发历程,实现原理,应用实践等,大家可以更好的交流,共同学习,建设PHP生态。 协程调度去年Swoole推出了4.0版本后,完整的支持PHP协程,我们可以基于协程实现CSP编程,身边的开发者惊呼,原来PHP代码还可以这样写。Swoole的协程默认是基于IO调度,程序中有阻塞会自动让出当前协程,协程的各种优势我们不在这里展开讨论。如果是IO密集型的场景,可以表现得很不错。但是对于CPU密集型的场景,会导致一些协程因为得不到CPU时间片被饿死。 抢占式调度我们在今年年初就计划实现Swoole的抢占式调度,以满足实现有些场景下的不均衡调度带来的问题。我们中间经历了几个版本,在这里和大家分享一下开发过程中的动机和解决办法。 起初,我们的想法是可以从PHP的循环中自动检测执行实践,若达到限制,可以自动让出当前协程。因为毕竟很少有人一马平川的写出占用很多CPU的代码,大都通过循环条件来控制。我们hook循环指令,每次执行循环指令的时候,都来检查协程的执行时间,我们很欣喜的得到了最初的版本。但是这样做比较hack,而且opcode经过opcache优化后,情况会变得有些复杂。 后来我们使用PHP的ticks机制,也就是在PHP代码编译期间,注入ticks指令,可以执行相应的函数,我们可以在这些函数中检测处理协程的时间,达到抢占式的效果,但是这里有一个问题,PHP的declare(ticks=N)语法,只对当前脚本范围有效,也就是说项目稍微大点,require或者include进来的脚本,并不会自动注入ticks指令,这样Swoole开发者几乎是无法接受的。我们也试图给PHP官方提一个PR,可以在扩展层设置一个全局默认的ticks,但是官方不愿意采纳我们的提交,因为官方觉得这个功能对性能损耗比较大,而且有可能在PHP8移除这个功能。其实经过实测这个性能损耗并不大,而且我们已经在生产环境验证,并取得了显著的效果,即可以让出某些CPU密集的逻辑部分,使得服务整个相应时间更加均衡。 想要做抢占式调度,对于PHP来说,有两个途径 单线程的PHP的执行流,通过执行指令做文章,可以在PHP执行流程中注入逻辑,以检查执行时间,再急上Swoole的协程能力,可以在不同的协程中切换,以达到抢占CPU的目的。考虑开线程,负责检查当前执行协程执行时间。经过以上办法的尝试,注入指令的路数基本是无法得到官方的支持,我们只能另谋出路,多开一个线程,只负责检查当前协程。具体的做法是,利用PHP-7.1.0引入的VM interrupt机制,默认每隔5ms检查一下当前协程是否达到最大执行时间,默认为10ms,如果超过,则让出当前协程,达到被其他协程抢占的目的。 来一段代码 <?phpCo::set(['enable_preemptive_scheduler' => 1]);$start = microtime(1);echo "start\n";$flag = 1;go(function () use (&$flag) { echo "coro 1 start to loop\n"; $i = 0; for (;;) { if (!$flag) { break; } $i++; } echo "coro 1 can exit\n";}); $end = microtime(1);$msec = ($end - $start) * 1000;echo "use time $msec\n";go(function () use (&$flag) { echo "coro 2 set flag = false\n"; $flag = false;});echo "end\n";//输出结果startcoro 1 start to loopuse time 11.121988296509coro 2 set flag = falseendcoro 1 can exit可以发现,代码逻辑可以从第一个协程的死循环中自动yield出来,执行第二个协程,如果没有这个特性,第二个协程永远不会被执行,导致被饿死。而这样做,第二个协程可以顺利被执行,最后执行结束后,第一个协程也会接着继续往下执行。达到我们的第二个协程主动抢占第一个协程CPU的效果。 ...

May 21, 2019 · 1 min · jiezi

swoole-compiler加密drupal产生的一些问题

问题描述 上个星期碰到个客户使用swoole compiler加密drupal导致drupal项目无法运行的问题,逐步排查后总结问题是drupal中有部分代码直接通过file_get_contents获取php源码导致的,因为项目代码是加密过后的,所以直接获取php源码解析是获取不到想要的内容的。 加密框架drupal Drupal是使用PHP语言编写的开源内容管理框架,下载完后需要进行相关的配置和安装才能进行使用。 在加密后的影响drupal代码运行的主要问题:代码路径:drupal/vendor/doctrine/common/lib/Doctrine/Common/Reflection/StaticReflectionParser.php:126 protected function parse() { if ($this->parsed || !$fileName = $this->finder->findFile($this->className)) { return; } $this->parsed = true; $contents = file_get_contents($fileName); if ($this->classAnnotationOptimize) { if (preg_match("/\A.*^\s*((abstract|final)\s+)?class\s+{$this->shortClassName}\s+/sm", $contents, $matches)) { $contents = $matches[0]; } } $tokenParser = new TokenParser($contents); $docComment = ''; while ($token = $tokenParser->next(false)) { if (is_array($token)) { switch ($token[0]) { case T_USE: $this->useStatements = array_merge($this->useStatements, $tokenParser->parseUseStatement()); break; case T_DOC_COMMENT: $docComment = $token[1]; break; case T_CLASS: $this->docComment['class'] = $docComment; $docComment = ''; break; case T_VAR: case T_PRIVATE: case T_PROTECTED: case T_PUBLIC: $token = $tokenParser->next(); if ($token[0] === T_VARIABLE) { $propertyName = substr($token[1], 1); $this->docComment['property'][$propertyName] = $docComment; continue 2; } if ($token[0] !== T_FUNCTION) { // For example, it can be T_FINAL. continue 2; } // No break. case T_FUNCTION: // The next string after function is the name, but // there can be & before the function name so find the // string. while (($token = $tokenParser->next()) && $token[0] !== T_STRING); $methodName = $token[1]; $this->docComment['method'][$methodName] = $docComment; $docComment = ''; break; case T_EXTENDS: $this->parentClassName = $tokenParser->parseClass(); $nsPos = strpos($this->parentClassName, '\\'); $fullySpecified = false; if ($nsPos === 0) { $fullySpecified = true; } else { if ($nsPos) { $prefix = strtolower(substr($this->parentClassName, 0, $nsPos)); $postfix = substr($this->parentClassName, $nsPos); } else { $prefix = strtolower($this->parentClassName); $postfix = ''; } foreach ($this->useStatements as $alias => $use) { if ($alias == $prefix) { $this->parentClassName = '\\' . $use . $postfix; $fullySpecified = true; } } } if (!$fullySpecified) { $this->parentClassName = '\\' . $this->namespace . '\\' . $this->parentClassName; } break; } } } }其中部分代码如上,通过class名获取文件路径,然后通过file_get_contents获取php文件的内容,TokenParser类中构造函数如下 ...

May 21, 2019 · 4 min · jiezi

Swoole-RPC-的实现

概述这是关于 Swoole 学习的第七篇文章:Swoole RPC 的实现。 第六篇:Swoole 整合成一个小框架第五篇:Swoole 多协议 多端口 的应用第四篇:Swoole HTTP 的应用第三篇:Swoole WebSocket 的应用第二篇:Swoole Task 的应用第一篇:Swoole Timer 的应用有位读者说 “上篇文章,下载代码后直接运行成功,代码简洁明了,简直是 Swoole 入门最好的 Demo ”。 “哈哈哈...” 还有读者说 “有一起学习的组织群吗,可以在里面进行疑难答疑?” 这个还真没有,总觉得维护一个微信群不容易,因为自己本身就不爱在群里说话,另外,自己也在很多微信群中,开始氛围挺好的,大家都聊聊技术,后来技术聊的少了改成聊八卦啦,再后来慢慢就安静了,还有在群里起冲突的... 当然我也知道维护一个微信群的好处是非常大的,如果有这方面经验的同学,咱们一起交流交流 ~ 还有出版社找我写书的. 他们也真是放心,我自己肚子里几滴墨水还是知道的,目前肯定是不行,以后嘛,再说。 还有一些大佬加了微信,可能是出于对晚辈的提携吧,偷偷告诉你,从大佬的朋友圈能学到很多东西。 我真诚的建议,做技术的应该自己多总结总结,将自己会的东西写出来分享给大家,先不说给别人带来太多的价值,反正对自己的帮助是非常非常大的,这方面想交流的同学,可以加我,我可以给你无私分享。 可能都会说时间少,时间只要挤,总会有的,每个人都 24 小时,时间对每个人是最公平的。说到这推荐大家读一下《暗时间》这本书,这是我这本书的 读书笔记,大家可以瞅瞅。 开始今天的文章吧,这篇文章实现了一个简单的 RPC 远程调用,在实现之前需要先了解什么是 RPC,不清楚的可以看下之前发的这篇文章 《我眼中的 RPC》。 下面的演示代码主要使用了 Swoole 的 Task 任务池,通过 OnRequest/OnReceive 获得信息交给 Task 去处理。 举个工作中的例子吧,在电商系统中的两个模块,个人中心模块和订单管理模块,这两个模块是独立部署的,可能不在一个机房,可能不是一个域名,现在个人中心需要通过 用户ID 和 订单类型 获取订单数据。 实现效果客户端HTTP 请求//代码片段<?php$demo = [ 'type' => 'SW', 'token' => 'Bb1R3YLipbkTp5p0', 'param' => [ 'class' => 'Order', 'method' => 'get_list', 'param' => [ 'uid' => 1, 'type' => 2, ], ],];$ch = curl_init();$options = [ CURLOPT_URL => 'http://10.211.55.4:9509/', CURLOPT_POST => 1, CURLOPT_POSTFIELDS => json_encode($demo),];curl_setopt_array($ch, $options);curl_exec($ch);curl_close($ch);TCP 请求//代码片段$demo = [ 'type' => 'SW', 'token' => 'Bb1R3YLipbkTp5p0', 'param' => [ 'class' => 'Order', 'method' => 'get_list', 'param' => [ 'uid' => 1, 'type' => 2, ], ],];$this->client->send(json_encode($demo));请求方式SW 单个请求,等待结果发出请求后,分配给 Task ,并等待 Task 执行完成后,再返回。 ...

May 21, 2019 · 3 min · jiezi

php微服务之分布式事物

分布式事物一直是微服务的一个难点。相关的解决方案和框架大部分是java的,那么php该如何解决呢?下面一步一步讲解如何用php解决分布式事物。 单机单数据源事物首先从单机事物开始。 大概逻辑如下 : try { // 开始事物 $db->beginTransaction(); // 执行你的操作 // ... // 提交事物 $db->commit(); } catch (Exception $e) { // 执行失败 回滚 $db->rollBack(); }单机多个数据源事物如果你业务涉及到多个数据库,事物大概逻辑是这个样子: try { // 开始事物 $db1->beginTransaction(); $db2->beginTransaction(); // 执行你的操作 // ... // 提交事物 $db1->commit(); $db2->commit(); } catch (Exception $e) { // 执行失败 回滚 $db1->rollBack(); $db2->rollBack(); }多机多数据源事物(分布式事物)如果你的数据源和业务代码都是分开的(微服务)这就是我们今天的核心。由前面两种情况来看,大概逻辑是差不多的,主要也分为4个步骤。 开始事物执行逻辑代码提交事物回滚事物有些文章也称为tcc也就是 234 步骤。 我们用一个常用的例子:下单。 主要3个步骤: 创建订单修改库存修改用户积分假设订单,库存,用户都是独立的服务。 按照前面的经验大概分为4个步骤,我们以用户为例 代码如下: class User{ // 开始事物 public function beginTransaction() { $db->beginTransaction(); return $this; } // 执行代码 public function doTransaction() { // 执行你的操作 // ... return $this; } public function commit() { $db->commit(); } public funtion rollBack() { $db->rollBack(); }}库存(stock),订单(order)和上面类似,也需要这4个方法,我就不写了。难点在于我们没法直接操作数据源,只能通过rpc调用相应的服务来操作。依次执行上面的方法就好了。代码如下: ...

May 18, 2019 · 1 min · jiezi

Swoole-HTTP-的应用

概述这是关于 Swoole 学习的第四篇文章:Swoole HTTP 的应用。 第三篇:Swoole WebSocket 的应用第二篇:Swoole Task 的应用第一篇:Swoole Timer 的应用我们都知道 HTTP 是一种协议,允许 WEB 服务器和浏览器通过互联网进行发送和接受数据。 想对 HTTP 进行详细的了解,可以找下其他文章。 我们在网上能看到的界面,图片,动画,音频,视频 等,都有依赖这个协议的。 在做 WEB 系统的时候,都使用过 IIS、Apache、Nginx 吧,我们利用 Swoole 也可以 简单的实现一个 WEB 服务器。 主要使用了 HTTP 的两大对象:Request 请求对象、Response 响应对象。 Request,包括 GET、POST、COOKIE、Header等。 Response,包括 状态、响应体、跳转、发送文件等。 不多说,分享两个程序: 一、实现一个基础的 Demo:“你好,Swoole.”二、实现一个简单的 路由控制本地版本: PHP 7.2.6Swoole 4.3.1代码一、Demo:“你好,Swoole.” 示例效果: 备注:IP 地址是我的虚拟机。 示例代码: <?phpclass Server{ private $serv; public function __construct() { $this->serv = new swoole_http_server("0.0.0.0", 9502); $this->serv->set([ 'worker_num' => 2, //开启2个worker进程 'max_request' => 4, //每个worker进程 max_request设置为4次 'daemonize' => false, //守护进程(true/false) ]); $this->serv->on('Start', [$this, 'onStart']); $this->serv->on('WorkerStart', [$this, 'onWorkStart']); $this->serv->on('ManagerStart', [$this, 'onManagerStart']); $this->serv->on("Request", [$this, 'onRequest']); $this->serv->start(); } public function onStart($serv) { echo "#### onStart ####".PHP_EOL; echo "SWOOLE ".SWOOLE_VERSION . " 服务已启动".PHP_EOL; echo "master_pid: {$serv->master_pid}".PHP_EOL; echo "manager_pid: {$serv->manager_pid}".PHP_EOL; echo "########".PHP_EOL.PHP_EOL; } public function onManagerStart($serv) { echo "#### onManagerStart ####".PHP_EOL.PHP_EOL; } public function onWorkStart($serv, $worker_id) { echo "#### onWorkStart ####".PHP_EOL.PHP_EOL; } public function onRequest($request, $response) { $response->header("Content-Type", "text/html; charset=utf-8"); $html = "<h1>你好 Swoole.</h1>"; $response->end($html); }}$server = new Server();二、路由控制 ...

May 15, 2019 · 3 min · jiezi

Swoole-整合成一个小框架

概述这是关于 Swoole 学习的第六篇文章:Swoole 整合成一个小框架。 第五篇:Swoole 多协议 多端口 的应用第四篇:Swoole HTTP 的应用第三篇:Swoole WebSocket 的应用第二篇:Swoole Task 的应用第一篇:Swoole Timer 的应用写了关于 Swoole 入门的 5 篇文章后,增加了不少的关注者,也得到了一些大佬的鼓励,也有很多关注者都加了微信好友,交流之后发现一些朋友比我优秀还比我努力,也得到了一些大佬的建议。 发现持续写文章真的不是件容易的事,担心别人认为没价值,担心想法太幼稚或有漏洞被别人笑话,担心脑子里墨水太少,写不出来... 知道自己思路还不够清晰,逻辑还不够严谨,告诉自己没关系,一些都会好起来的,逆境才能成长嘛,敢写就是好的开始,以此来激励自己持续的学习和思考。 跑题了,说回正题。 这篇文章其实是读者的小小要求,事情是这样的: 读者:“亮哥,看了你的文章很有收获,将文章 Demo 放在本地直接就能运行了,太感谢了” 本人:“哈哈。。。有收获就好,感谢支持 ~ ” 读者:“我有一个小小的要求,现在每个文件都是独立的,我想部署到生产环境,想操作上更便捷,有日志...” 本人:“你说的不是框架吗?现在有很多现成的,看 Swoole 官网推荐的 Swoft、EasySwoole、MixPHP 等。详细的参考这个地址:https://wiki.swoole.com/wiki/...” 读者:“看了,发现文件太多了,看不懂,你能帮忙讲解下吗?” 本人:“What?我也是入门呀,要不我搞个简单的轮子吧” ...... 于是就有了这篇文章,正好也是对前面 5 篇文章的复习吧。 效果 命令如下: php index.php 可以查看到上图php index.php start 开启服务(Debug模式)php index.php start -d 开启服务(Daemon模式)php index.php status 查看服务状态php index.php reload 服务热加载php index.php stop 关闭服务index.php 这是文件名称,大家叫什么都可以。 目录结构如下: ├─ controller│ ├── ...├─ client│ ├─ websocket│ ├── ...│ ├─ tcp│ ├── ...├─ server│ ├─ config│ ├── config.php│ ├─ core│ ├── Common.php│ ├── Core.php│ ├── HandlerException.php│ ├─ log -- 需要 读/写 权限│ ├── ...├─ index.php目前就这几个文件,后期研究新的知识点会直接集成到这里面。 ...

May 15, 2019 · 6 min · jiezi

Swoole-多协议-多端口-的应用

概述这是关于 Swoole 学习的第五篇文章:Swoole 多协议 多端口 的应用。 第四篇:Swoole HTTP 的应用第三篇:Swoole WebSocket 的应用第二篇:Swoole Task 的应用第一篇:Swoole Timer 的应用主要参考官方的这两篇文章,进行实现的 Demo。 网络通信协议设计:https://wiki.swoole.com/wiki/...多端口监听的使用:https://wiki.swoole.com/wiki/...希望通过我提供的 Demo,能够对文档有更加深刻的理解。 网络通信协议设计为什么需要通信协议? 官方:TCP协议在底层机制上解决了UDP协议的顺序和丢包重传问题。但相比UDP又带来了新的问题,TCP协议是流式的,数据包没有边界。应用程序使用TCP通信就会面临这些难题。因为TCP通信是流式的,在接收1个大数据包时,可能会被拆分成多个数据包发送。多次Send底层也可能会合并成一次进行发送。这里就需要2个操作来解决:分包 和 合包,所以TCP网络通信时需要设定通信协议。Swoole 支持了2种类型的自定义网络通信协议 :EOF结束符协议、固定包头+包体协议。 EOF结束符协议 先看下,未设置协议的效果: 发送的每条数据长度都是 23,但在 onReceive 接收数据的时候每次接收的长度不一样,并没有按照想象的方式进行分包。 再看下,设置了EOF结束符协议的效果: 发送的每条数据长度都是 23,在 onReceive 接收数据的时候每次接收的也是 23 ,完美。 主要设置项如下: 'package_max_length' => '8192','open_eof_split' => true,'package_eof' => "\r\n"不做解释,官方文档已经写的很清楚。 示例代码如下: server.php <?phpclass Server{ private $serv; public function __construct() { $this->serv = new swoole_server('0.0.0.0', 9501); $this->serv->set([ 'worker_num' => 2, //开启2个worker进程 'max_request' => 4, //每个worker进程 max_request设置为4次 'dispatch_mode' => 2, //数据包分发策略 - 固定模式 //EOF结束符协议 'package_max_length' => '8192', 'open_eof_split' => true, 'package_eof' => "\r\n" ]); $this->serv->on('Start', [$this, 'onStart']); $this->serv->on('Connect', [$this, 'onConnect']); $this->serv->on("Receive", [$this, 'onReceive']); $this->serv->on("Close", [$this, 'onClose']); $this->serv->start(); } public function onStart($serv) { echo "#### onStart ####".PHP_EOL; echo "SWOOLE ".SWOOLE_VERSION . " 服务已启动".PHP_EOL; echo "swoole_cpu_num:".swoole_cpu_num().PHP_EOL; echo "master_pid: {$serv->master_pid}".PHP_EOL; echo "manager_pid: {$serv->manager_pid}".PHP_EOL; echo "########".PHP_EOL.PHP_EOL; } public function onConnect($serv, $fd) { echo "#### onConnect ####".PHP_EOL; echo "客户端:".$fd." 已连接".PHP_EOL; echo "########".PHP_EOL.PHP_EOL; } public function onReceive($serv, $fd, $from_id, $data) { echo "#### onReceive ####".PHP_EOL; var_dump($data); } public function onClose($serv, $fd) { echo "Client Close.".PHP_EOL; }}$server = new Server();client.php ...

May 15, 2019 · 5 min · jiezi

Swoole-Timer-的应用

你好,SwoolePHP 的协程高性能网络通信引擎,使用 C/C++ 语言编写,提供了多种通信协议的网络服务器和客户端模块。 Swoole 可应用于互联网、移动通信、企业软件、网络游戏、物联网、车联网、智能家庭等领域。 学习 Swoole 之前,最好先了解下底层知识,比如,线程/进程、IO、TCP/IP协议 等。 推荐大家读一下《Linux 高性能服务器编程》这本书。我有这本书的PDF版,需要的可以关注公众号,回复 “Linux 高性能服务器编程” 即可获取。 这篇文章主要分享 Timer 毫秒精度的定时器。 本地版本:PHP 7.2.6、Swoole 4.3.1。 Timer主要有三个方法: swoole_timer_tick 间隔的时钟控制器 swoole_timer_after 指定的时间后执行 swoole_timer_clear 删除定时器 示例代码: //每隔3000ms触发一次$timer_id = swoole_timer_tick(3000, function () { echo "tick 3000ms - ".date('Y-m-d H:i:s')."\n";});//9000ms后删除定时器swoole_timer_after(9000, function () use ($timer_id) { echo "after 9000ms - ".date('Y-m-d H:i:s')."\n"; swoole_timer_clear($timer_id);});运行结果: tick 3000ms - 2019-04-07 21:36:56tick 3000ms - 2019-04-07 21:36:59tick 3000ms - 2019-04-07 21:37:02after 9000ms - 2019-04-07 21:37:02应用场景一、比如,每天凌晨跑业务脚本,脚本中包括了请求其他业务方或第三方的接口,如果接口超时无响应或没有数据返回,需要进行重试。 ...

May 15, 2019 · 2 min · jiezi

Swoole-Task-的应用

概述这是关于 Swoole 学习的第二篇文章:Swoole Task 的应用。 第一篇:Swoole Timer 的应用Swoole 异步Task,主要实现调用异步任务的执行。 常用的场景:异步支付处理、异步订单处理、异步日志处理、异步发送邮件/短信等。 Swoole 的实现方式是 worker 进程处理数据请求,分配给 task 进程执行。 官方介绍: task 底层使用Unix Socket管道通信,是全内存的,没有IO消耗。单进程读写性能可达100万/s,不同的进程使用不同的管道通信,可以最大化利用多核。本地版本:PHP 7.2.6、Swoole 4.3.1。 不多说,先看效果图: 代码server.php<?phpclass Server{ private $serv; public function __construct() { $this->serv = new swoole_server('0.0.0.0', 9501); $this->serv->set([ 'worker_num' => 2, //开启2个worker进程 'max_request' => 4, //每个worker进程 max_request设置为4次 'task_worker_num' => 4, //开启4个task进程 'dispatch_mode' => 2, //数据包分发策略 - 固定模式 ]); $this->serv->on('Start', [$this, 'onStart']); $this->serv->on('Connect', [$this, 'onConnect']); $this->serv->on("Receive", [$this, 'onReceive']); $this->serv->on("Close", [$this, 'onClose']); $this->serv->on("Task", [$this, 'onTask']); $this->serv->on("Finish", [$this, 'onFinish']); $this->serv->start(); } public function onStart($serv) { echo "#### onStart ####".PHP_EOL; echo "SWOOLE ".SWOOLE_VERSION . " 服务已启动".PHP_EOL; echo "master_pid: {$serv->master_pid}".PHP_EOL; echo "manager_pid: {$serv->manager_pid}".PHP_EOL; echo "########".PHP_EOL.PHP_EOL; } public function onConnect($serv, $fd) { echo "#### onConnect ####".PHP_EOL; echo "客户端:".$fd." 已连接".PHP_EOL; echo "########".PHP_EOL.PHP_EOL; } public function onReceive($serv, $fd, $from_id, $data) { echo "#### onReceive ####".PHP_EOL; echo "worker_pid: {$serv->worker_pid}".PHP_EOL; echo "客户端:{$fd} 发来的Email:{$data}".PHP_EOL; $param = [ 'fd' => $fd, 'email' => $data ]; $rs = $serv->task(json_encode($param)); if ($rs === false) { echo "任务分配失败 Task ".$rs.PHP_EOL; } else { echo "任务分配成功 Task ".$rs.PHP_EOL; } echo "########".PHP_EOL.PHP_EOL; } public function onTask($serv, $task_id, $from_id, $data) { echo "#### onTask ####".PHP_EOL; echo "#{$serv->worker_id} onTask: [PID={$serv->worker_pid}]: task_id={$task_id}".PHP_EOL; //业务代码 for($i = 1 ; $i <= 5 ; $i ++ ) { sleep(2); echo "Task {$task_id} 已完成了 {$i}/5 的任务".PHP_EOL; } $data_arr = json_decode($data, true); $serv->send($data_arr['fd'] , 'Email:'.$data_arr['email'].',发送成功'); $serv->finish($data); echo "########".PHP_EOL.PHP_EOL; } public function onFinish($serv,$task_id, $data) { echo "#### onFinish ####".PHP_EOL; echo "Task {$task_id} 已完成".PHP_EOL; echo "########".PHP_EOL.PHP_EOL; } public function onClose($serv, $fd) { echo "Client Close.".PHP_EOL; }}$server = new Server();client.php<?phpclass Client{ private $client; public function __construct() { $this->client = new swoole_client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_ASYNC); $this->client->on('Connect', [$this, 'onConnect']); $this->client->on('Receive', [$this, 'onReceive']); $this->client->on('Close', [$this, 'onClose']); $this->client->on('Error', [$this, 'onError']); } public function connect() { if(!$fp = $this->client->connect("127.0.0.1", 9501 , 1)) { echo "Error: {$fp->errMsg}[{$fp->errCode}]".PHP_EOL; return; } } public function onConnect($cli) { fwrite(STDOUT, "输入Email:"); swoole_event_add(STDIN, function() { fwrite(STDOUT, "输入Email:"); $msg = trim(fgets(STDIN)); $this->send($msg); }); } public function onReceive($cli, $data) { echo PHP_EOL."Received: ".$data.PHP_EOL; } public function send($data) { $this->client->send($data); } public function onClose($cli) { echo "Client close connection".PHP_EOL; } public function onError() { }}$client = new Client();$client->connect();小结一、上面的配置总共开启了几个进程? ...

May 15, 2019 · 2 min · jiezi

Swoole-WebSocket-的应用

概述这是关于 Swoole 学习的第三篇文章:Swoole WebSocket 的应用。 第二篇:Swoole Task 的应用第一篇:Swoole Timer 的应用什么是 WebSocket ? WebSocket 是一种在单个TCP连接上进行全双工通信的协议。WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。 在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。 我们利用 WebSocket 进行及时通讯,今天实现一个 视频弹幕效果。 实现弹幕其实就和群聊类似,将消息推送给所有的客户端,只不过前端的展示所有不同。 本地版本: 后端 PHP 7.2.6、Swoole 4.3.1。前端 HTML5 WebSocket、Canvas。废话不多说,先看效果。 批量版: 手动版: 代码server.php <?phpclass Server{ private $serv; public function __construct() { $this->serv = new swoole_websocket_server("0.0.0.0", 9501); $this->serv->set([ 'worker_num' => 2, //开启2个worker进程 'max_request' => 4, //每个worker进程 max_request设置为4次 'task_worker_num' => 4, //开启4个task进程 'dispatch_mode' => 4, //数据包分发策略 - IP分配 'daemonize' => false, //守护进程(true/false) ]); $this->serv->on('Start', [$this, 'onStart']); $this->serv->on('Open', [$this, 'onOpen']); $this->serv->on("Message", [$this, 'onMessage']); $this->serv->on("Close", [$this, 'onClose']); $this->serv->on("Task", [$this, 'onTask']); $this->serv->on("Finish", [$this, 'onFinish']); $this->serv->start(); } public function onStart($serv) { echo "#### onStart ####".PHP_EOL; echo "SWOOLE ".SWOOLE_VERSION . " 服务已启动".PHP_EOL; echo "master_pid: {$serv->master_pid}".PHP_EOL; echo "manager_pid: {$serv->manager_pid}".PHP_EOL; echo "########".PHP_EOL.PHP_EOL; } public function onOpen($serv, $request) { echo "#### onOpen ####".PHP_EOL; echo "server: handshake success with fd{$request->fd}".PHP_EOL; $serv->task([ 'type' => 'login' ]); echo "########".PHP_EOL.PHP_EOL; } public function onTask($serv, $task_id, $from_id, $data) { echo "#### onTask ####".PHP_EOL; echo "#{$serv->worker_id} onTask: [PID={$serv->worker_pid}]: task_id={$task_id}".PHP_EOL; $msg = ''; switch ($data['type']) { case 'login': $msg = '我来了...'; break; case 'speak': $msg = $data['msg']; break; } foreach ($serv->connections as $fd) { $connectionInfo = $serv->connection_info($fd); if ($connectionInfo['websocket_status'] == 3) { $serv->push($fd, $msg); //长度最大不得超过2M } } $serv->finish($data); echo "########".PHP_EOL.PHP_EOL; } public function onMessage($serv, $frame) { echo "#### onMessage ####".PHP_EOL; echo "receive from fd{$frame->fd}:{$frame->data},opcode:{$frame->opcode},fin:{$frame->finish}".PHP_EOL; $serv->task(['type' => 'speak', 'msg' => $frame->data]); echo "########".PHP_EOL.PHP_EOL; } public function onFinish($serv,$task_id, $data) { echo "#### onFinish ####".PHP_EOL; echo "Task {$task_id} 已完成".PHP_EOL; echo "########".PHP_EOL.PHP_EOL; } public function onClose($serv, $fd) { echo "#### onClose ####".PHP_EOL; echo "client {$fd} closed".PHP_EOL; echo "########".PHP_EOL.PHP_EOL; }}$server = new Server();index.php ...

May 15, 2019 · 3 min · jiezi

用Swoole来写个联机对战游戏呀八创建游戏房间

联机逻辑开发进度:■■■■■■■□□□□□ 本章结束开发进度:■■■■■■■■■□□□ 上一章的答案:Logic类: <?php...class Logic{ public function matchPlayer($playerId) { ... //发起一个Task尝试匹配 DataCenter::$server->task(['code' => TaskManager::TASK_CODE_FIND_PLAYER]); }}Server类: <?php...class Server{ ... public function onTask($server, $taskId, $srcWorkerId, $data) { DataCenter::log("onTask", $data); $result = []; switch ($data['code']) { case TaskManager::TASK_CODE_FIND_PLAYER: $ret = TaskManager::findPlayer(); if (!empty($ret)) { $result['data'] = $ret; } break; } if (!empty($result)) { $result['code'] = $data['code']; return $result; } } ...}...童鞋们的作业完成情况如何呢? 我们来再次梳理一下目前的匹配功能进度: 前端连接时发送player_id服务端连接时保存玩家信息前端发送code为600的指令服务端将player_id放入匹配队列服务端发起一个task进行玩家匹配,当寻找到两个玩家时返回两个player_id到worker进程那下一步就很明显了,就是创建游戏房间。 创建房间分析在Server类的onFinish()方法中,根据传入的code,执行Logic的createRoom()方法。Server类: <?php...class Server{ ... public function onFinish($server, $taskId, $data) { DataCenter::log("onFinish", $data); switch ($data['code']) { case TaskManager::TASK_CODE_FIND_PLAYER: $this->logic->createRoom($data['data']['red_player'], $data['data']['blue_player']); break; } }}...显然,下一步就是完成这个createRoom()方法匹配机制就大功告成了。但是真的这么简单吗?下面我们要思考一件事情。 ...

May 12, 2019 · 2 min · jiezi

用Swoole来写个联机对战游戏呀七异步匹配玩家

联机逻辑开发进度:■■■■■□□□□□□□ 本章结束开发进度:■■■■■■■□□□□□ 上一章的答案:DataCenter类: <?php...class DataCenter{ const PREFIX_KEY = "game"; ... public static function getPlayerWaitListLen() { $key = self::PREFIX_KEY . ":player_wait_list"; return self::redis()->lLen($key); } public static function pushPlayerToWaitList($playerId) { $key = self::PREFIX_KEY . ":player_wait_list"; self::redis()->lPush($key, $playerId); } public static function popPlayerFromWaitList() { $key = self::PREFIX_KEY . ":player_wait_list"; return self::redis()->rPop($key); } public static function getPlayerFd($playerId) { $key = self::PREFIX_KEY . ":player_fd:" . $playerId; return self::redis()->get($key); } public static function setPlayerFd($playerId, $playerFd) { $key = self::PREFIX_KEY . ":player_fd:" . $playerId; self::redis()->set($key, $playerFd); } public static function delPlayerFd($playerId) { $key = self::PREFIX_KEY . ":player_fd:" . $playerId; self::redis()->del($key); } public static function getPlayerId($playerFd) { $key = self::PREFIX_KEY . ":player_id:" . $playerFd; return self::redis()->get($key); } public static function setPlayerId($playerFd, $playerId) { $key = self::PREFIX_KEY . ":player_id:" . $playerFd; self::redis()->set($key, $playerId); } public static function delPlayerId($playerFd) { $key = self::PREFIX_KEY . ":player_id:" . $playerFd; self::redis()->del($key); } public static function setPlayerInfo($playerId, $playerFd) { self::setPlayerId($playerFd, $playerId); self::setPlayerFd($playerId, $playerFd); }}我们先来测试一下,前面所写的代码有没有问题,重新运行Server.php,并在浏览器打开游戏前端页面。 ...

May 9, 2019 · 2 min · jiezi

基于-Swoole-构建的-CTF-AWD-比赛环境搭建与实践

Author: RytiaDate: 20190427Blog: www.zzfly.net本人才学疏浅,望君不吝赐教背景受学校老师邀请,为学弟学妹举办分享会介绍 AWD 相关经验,本人一时头脑风暴采用 PHP 的 Swoole 扩展搭建了比赛的环境,将分享会变成了友谊赛。 出题思路本次题目来自于我的一个外包项目实践。项目里面大致有这么一个需求:客户登录系统后,由外部设备触发一个 websocket 发送操作(例如嵌入式中常遇见的“打开门禁”、“滴卡”、“按下开关”等),该请求接收方为某个已经登陆的某个用户(通常靠 user id 或用户名绑定)。本人在初次开发这类应用时,将用户唯一身份标识的 user id 作为了这个 websocket 通道的名字,如此一来带来的后果便是无论这个用户在哪台电脑登录,无论用户登陆多少次(小项目无重复登陆+挤下线判断),只要成功登录并打开相应网页便会收到这个 websocket 请求,带来了某些非预期的信息泄露。在实际的项目中解决方法有很多,例如重复登陆的判断以及 websocket “匿名通道”建立。本次出题,便以此为基础展开 AWD 运作原理CTF-AWD 是线下赛中常见的比赛类型,通常因为攻守兼备而广受选手喜爱。这里我主要实现了 AWD 两大关键功能:回合制 、 存活检测 与 动态 flag,至于 flag 提交等前端部分内容,则交由我校 GOCTF 平台处理。 回合制:比赛以 10 分钟为一回合。在中心服务器方面,事先使用脚本,根据已开启的靶机数量以及 ip 生成 flag 并保存为文件,并于比赛开始时记录开始时间。每次中心服务器接收到选手靶机的请求时,根据靶机的 ip,以及距离比赛开始的时间,计算出当前处于第几个回合,并返回 flag 数组中相应的值。而在 pusher (一个第三方 websocket 工具,可以为 cli 运行的脚本语言提供发送 websocket 的能力,本次出题依赖 pusher 进行),同样以 10 分钟为一回合,主动向选手端推送含有比赛关键内容的消息存活检测:本次平台存货监控设置的比较简单,主要为 web 服务(靶机 80 端口)的监控。每隔一段时间向靶机发送 http 请求,下载靶机上某个保存着 pusher 密钥的 js 文件,若该文件大小符合预期,则判定靶机存活。选手在比赛过程中需要盗取到对手的密钥,以窃听他方 websocket 内容,并修改己方 pusher 密钥以放泄露动态 flag:该部分同样由中心服务器与 pusher 完成。中心服务器在被请求时根据时间不同(回合不同)向选手返回不同 flag。pusher 根据时间不同(回合不同)主动推送不同的 flag 到选手的页面上。AWD 缺陷总结本次比赛完成之后,发现这套平台想要真正用于日常选手的训练还有几个问题需要克服 ...

May 9, 2019 · 2 min · jiezi

用Swoole来写个联机对战游戏呀六游戏匹配机制

联机逻辑开发进度:■■□□□□□□□□□□ 本章结束开发进度:■■■■■□□□□□□□ 上一章的答案:index.html: var app = new Vue({ el: '#app', data: { message: 'Hello Vue!', websock: null, }, created() { this.initWebSocket(); }, destroyed() { this.websock.close() //离开路由之后断开websocket连接 }, methods: { initWebSocket() { //初始化websocket const wsuri = "ws://192.168.3.41:8811"; this.websock = new WebSocket(wsuri); this.websock.onmessage = this.websocketonmessage; this.websock.onopen = this.websocketonopen; this.websock.onerror = this.websocketonerror; this.websock.onclose = this.websocketclose; }, websocketonopen() { //连接建立之后执行send方法发送数据 let actions = {"test": "12345"}; this.websocketsend(actions); }, websocketonerror() {//连接建立失败重连 this.initWebSocket(); }, websocketonmessage(e) { //数据接收 let message = JSON.parse(e.data); }, websocketsend(Data) {//数据发送 this.websock.send(JSON.stringify(Data)); }, websocketclose(e) { //关闭 console.log('断开连接', e); }, }})记得将192.168.3.41改为你的IP地址哦。Server类: ...

May 7, 2019 · 2 min · jiezi

用Swoole来写个联机对战游戏呀五联机初始化

联机逻辑开发进度:□□□□□□□□□□□□ 本章结束开发进度:■■□□□□□□□□□□ Swoole开发环境教程使用Swoole 4.3.0版本开发,但并没有使用协程等功能,只是使用了WebSocket Server,理论上安装旧版也是没问题的。环境需要大家自行安装,这个也是学习的一个过程。 以下是一些有可能有帮助的资料: CentOS中一键安装PHP开发环境:https://lnmp.org/install.htmlSwoole安装教程:https://wiki.swoole.com/wiki/...Redis安装教程:https://redis.io/downloadphp-redis扩展包:http://pecl.php.net/package/r...童鞋们也可以像我一样在Windows使用PHPStorm进行开发,再通过一些方法如共享文件夹、WinSCP等工具将项目在CentOS中运行起来。 安装swoole-ide-helper扩展使用PHPStorm来开发Swoole项目的童鞋,如果不想面向运气编程的话,最好安装一个swoole-ide-Helper扩展来协作开发。 在项目根目录运行以下命令: composer require --dev "eaglewu/swoole-ide-helper:dev-master"安装完毕我们就可以愉快地编写Swoole代码了。 服务端基本架构赵童鞋设想的基本架构需要三个层,他们分别是: 网络层:管理Swoole WebSocket对象,主要负责接收前端消息,传递到逻辑层进行处理。逻辑层:接收网络层传递的消息,主要负责游戏房间创建、玩家移动、结束检测等游戏逻辑。数据层:用于管理玩家ID、房间ID、匹配队列等数据。管理这三个层分别需要三个类:Server、Logic、DataCenter。 其中最重要的就是Server类,它的作用就是作为服务端和客户端的消息交换中心,我们先来写一个初始化的Server类,在项目app目录新建Server.php。 使用面向对象的方式实现一个WebSocket Server类。分别绑定start、workerStart、open、message、close回调方法在类中引入composer自动加载机制。为WebSocket对象设置4个worker进程。Swoole Websocket:https://wiki.swoole.com/wiki/...Server类: <?phprequire_once __DIR__ . '/../vendor/autoload.php';class Server{ const HOST = '0.0.0.0'; const PORT = 8811; const CONFIG = [ 'worker_num' => 4, ]; private $ws; public function __construct() { $this->ws = new \Swoole\WebSocket\Server(self::HOST, self::PORT); $this->ws->set(self::CONFIG); $this->ws->on('start', [$this, 'onStart']); $this->ws->on('workerStart', [$this, 'onWorkerStart']); $this->ws->on('open', [$this, 'onOpen']); $this->ws->on('message', [$this, 'onMessage']); $this->ws->on('close', [$this, 'onClose']); $this->ws->start(); } public function onStart($server) { swoole_set_process_name('hide-and-seek'); echo sprintf("master start (listening on %s:%d)\n", self::HOST, self::PORT); } public function onWorkerStart($server, $workerId) { echo "server: onWorkStart,worker_id:{$server->worker_id}\n"; } public function onOpen($server, $request) { } public function onClose($server, $fd) { } public function onMessage($server, $request) { }}new Server();我们来运行一下Server.php文件,顺便检验一下Swoole扩展是否运行正常,在项目app目录下,运行php Server。 ...

May 7, 2019 · 2 min · jiezi

Mix-PHP-V2-生态让-Guzzle-支持-Swoole-的-Hook-协程

Guzzle 是一个非常流行的 PHP 的 HTTP 客户端,现在各大厂的 SDK 也都开始基于 Guzzle 开发,因为 Swoole 只支持 PHP Stream 的协程 Hook ,而 Guzzle 默认是使用 cURL 扩展的,所以 Mix PHP 开发了 Guzzle Hook,能在不修改源码的情况下让 Guzzle 协程化。 Githubhttps://github.com/mix-php/guzzle-hook安装使用 Composer 安装: composer require mix/guzzle-hook在项目的 composer.json 文件中增加 extra 配置项,如下: "extra": { "include_files": [ "vendor/mix/guzzle-hook/src/functions_include.php" ]}使用直接使用 Guzzle 开发无需做任何特殊的代码处理,直接根据 Guzzle 文档使用: // Mix PHP 中是 xgo ,原生 swoole 是 gogo(function () { $client = new GuzzleHttp\Client(); $res = $client->request('GET', 'https://api.github.com/user', [ 'auth' => ['user', 'pass'], ]); echo $res->getStatusCode();});第三方 SDK 依赖 Guzzle比如: ...

May 6, 2019 · 1 min · jiezi

用Swoole来写个联机对战游戏呀四游戏结束判断

游戏逻辑开发进度:■■■■■■■■□□□□ 本章结束开发进度:■■■■■■■■■■■■ 上一章的答案:在我们的$mapData数组中,0就是墙,1就是路,canMoveToDirection()方法主要就是获取方向,计算得出目标坐标,检测一下目标坐标能不能走,所以当数组中是0的时候就返回false,否则返回true。 Game类: private function canMoveToDirection($player, $direction){ $x = $player->getX(); $y = $player->getY(); $moveCoor = $this->getMoveCoor($x, $y, $direction); $mapData = $this->gameMap->getMapData(); if (!$mapData[$moveCoor[0]][$moveCoor[1]]) { return false; } return true;}private function getMoveCoor($x, $y, $direction){ switch ($direction) { case Player::UP: return [--$x, $y]; case Player::DOWN: return [++$x, $y]; case Player::LEFT: return [$x, --$y]; case Player::RIGHT: return [$x, ++$y]; } return [$x, $y];}增加canMoveToDirection()方法后再次运行test.php文件输出地图数据: 墙,墙,墙,墙,墙,墙,墙,墙,墙,墙,墙,墙,墙, 墙,墙, 墙, 墙,墙,墙,墙,墙,墙, 墙, 墙, 墙,墙,墙,追, 墙, 墙,墙, 墙,墙,墙,墙,墙, 墙, 躲,墙,墙, 墙,墙, 墙, 墙,墙, 墙, 墙, 墙,墙,墙, 墙,墙, 墙, 墙, 墙,墙, 墙,墙,墙,墙,墙,墙,墙,墙,墙,墙,墙,墙,墙,可以看到,即使经过了三次的up操作,追捕者都没有再跑到墙上面去。 ...

May 5, 2019 · 2 min · jiezi

用Swoole来写个联机对战游戏呀三完善游戏功能

游戏逻辑开发进度:■■■■□□□□□□□□ 本章结束开发进度:■■■■■■■■□□□□ 上一章的答案createPlayer方法其实就是创建一个Player对象,然后指定坐标,放入$players数组中,但是怎么区分追捕者和躲藏者呢?我们可以用最简单粗暴的方法,先来后到。 Game类: public function createPlayer($playerId, $x, $y){ $player = new Player($playerId, $x, $y); if (!empty($this->players)) { $player->setType(Player::PLAYER_TYPE_HIDE); } $this->players[$playerId] = $player;}第一个添加的将会使用Player类默认的追捕者,第二个添加的将$player对象设置为躲藏者。 playerMove()方法也很简单,通过传入的$direction变量,增减对应$player的x或y坐标,应该直接调用$player的移动方法,所以需要新增两部分代码: Game类: public function playerMove($playerId, $direction){ $this->players[$playerId]->{$direction}();}Player类: public function up(){ $this->x--;}public function down(){ $this->x++;}public function left(){ $this->y--;}public function right(){ $this->y++;}尝试打印地图目前我们三个实体类的基础游戏逻辑就写得差不多了,但是我们的游戏到现在都还没运行过,我们需要一个能直观看到地图、玩家的画面。 请童鞋们自己尝试在Game类中新增printGameMap()方法,打印游戏地图。 在Game类中有一个变量$gameMap就是我们的游戏地图对象。Map类中也有了getMapData()方法能够获取地图数组数据,Game类: public function printGameMap(){ $mapData = $this->gameMap->getMapData(); foreach ($mapData as $line) { foreach ($line as $value) { if (empty($value)) { echo "墙,"; } else { echo " "; } } echo PHP_EOL; }}打印地图的代码很简单,就是遍历我们的地图数据,当数组里的元素值为0的时候就是墙,否则就是路,路就不用输出文字啦~ ...

May 4, 2019 · 2 min · jiezi

用Swoole来写个联机对战游戏呀二单机游戏架构

游戏逻辑开发进度:□□□□□□□□□□□□ 本章结束开发进度:■■■■□□□□□□□□ 开发环境教程需要PHP-7.x版本支持,由于不是面向基础小白,PHP环境需要童鞋们自行安装配置,以下是一些有可能有帮助的资料: Windows:https://www.apachefriends.org...Linux:https://lnmp.org/install.htmlMac:不存在的。。赵童鞋没玩过引入Composer在合适的位置新建一个文件夹HideAndSeek作为我们的项目根目录。 为了后续开发的方便,我们需要为我们的项目引入composer的自动加载机制,并在项目根目录运行以下命令: composer init小提示:没有安装composer的童鞋需要自行安装喔:https://pkg.phpcomposer.com/#...启用国内镜像地址,下载速度更快。国内镜像一:composer config -g repo.packagist composer https://packagist.phpcomposer.com国内镜像二:composer config -g repo.packagist composer https://packagist.laravel-china.org经过几次回车之后,composer就会为我们生成一个composer.json文件,需要在这个文件中增加以下代码: "autoload": { "psr-4": { "App\\": "app" }},再运行一次composer命令: composer installcomposer就会为我们创建vendor文件夹,里面会有一个autoload.php文件,这个文件就是用来实现类自动加载机制。 建立项目结构下一步就是编写游戏的逻辑,实现一个单机版的捉迷藏。 在上面创建的文件夹HideAndSeek中新建一个test.php文件 HideAndSeek└─test.php为什么要新建这个test.php呢?因为我们现在对整个项目的架构毫无头绪,所以需要将这个游戏逻辑拆分成几个步骤,并且将逻辑预先写在test.php文件中。 赵童鞋设想的游戏逻辑是这样的: 每个玩家要有一个ID,用来区别玩家。要有一个游戏控制器,用来创建玩家、执行移动逻辑、判断游戏是否结束。使用游戏控制器创建玩家,游戏开始。使用游戏控制器控制某个玩家进行移动。test.php文件: $redId = "red_player";$blueId = "blue_player";//创建游戏控制器$game = new Game();//添加玩家$game->createPlayer($redId, 6, 1);//添加玩家$game->createPlayer($blueId, 6, 10);//移动坐标$game->playerMove($redId, 'up');现在就很明显了,我们首要任务就是先实现这样一个游戏控制器。 在HideAndSeek文件夹中创建app文件夹,app文件夹用来存放我们项目的各种类文件。并在app文件夹中创建Manager文件夹用来存放所有管理者类的类文件,在Manager文件夹中创建Game游戏控制器类。 为了composer能够自动加载类文件,需要在Game类编写命名空间代码:namespace App\Manager,细心的童鞋已经发现了,我们上面在composer.json文件中新增代码时,就有提到App这个词,不了解自动加载机制的童鞋可以自行搜索一下composer psr-4。 <?phpnamespace App\Manager;class Game{}我们现在要思考一下游戏还需要哪些实体类,我们捉迷藏游戏有玩家,有地图,所以游戏还需要两个实体类:一个是玩家类,另一个是地图类,没错,这就是面向对象编程,而不是面向运气编程。 在app文件夹下新建Model文件夹,用来存放各种游戏实体类,并在其中新建Player类和Map类: <?phpnamespace App\Model;class Player{}<?phpnamespace App\Model;class Map{}完善各类代码现在我们需要思考一下,这三个类各自的属性和方法需要有哪些。 Map类我们先从最简单的Map类开始: Map对象应该在创建的时候可以指定长和宽,并生成一个地图。Map对象中应该有一个数组,用来保存完整的地图数据,我们假设这个数组中,0是墙,1是路。Map类中需要有一个方法来获取地图数据。由于生成一个地图的算法有点复杂,我们现在的重点只在于编写游戏逻辑,所以地图数据可以先写死。 <?phpnamespace App\Model;class Map{ private $width; private $height; private $map = [ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0], [0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0], [0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0], [0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0], [0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0], [0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0], [0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0], [0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0], [0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0], [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], ]; public function __construct($width, $height) { $this->width = $width; $this->height = $height; } public function getMapData() { return $this->map; }}Player类第二个轮到我们的Player类: ...

April 30, 2019 · 2 min · jiezi