关于workerman:高性能-PHP-应用容器之-Workerman

介绍Workerman 是一款纯 PHP 开发的开源高性能的 PHP 利用容器。能够用它开发 tcp 代理、梯子代理、做游戏服务器、邮件服务器、ftp 服务器、甚至开发一个 php 版本的 redis、php 版本的数据库、php 版本的 nginx、php 版本的 php-fpm 等等。Workerman 能够说是 PHP 畛域的一次翻新,让开发者彻底解脱了 PHP 只能做 WEB 的解放。 实际上 Workerman 相似一个 PHP 版本的 nginx,外围也是多过程 + Epoll + 非阻塞 IO。 Workerman 每个过程能维持上万并发连贯。因为自身常驻内存,不依赖 Apache、nginx、php-fpm 这些容器,领有超高的性能。同时反对 TCP、UDP、UNIXSOCKET,反对长连贯,反对 Websocket、HTTP、WSS、HTTPS 等通信协定以及各种自定义协定。领有定时器、异步 socket 客户端、异步 Redis、异步 Http、异步音讯队列等泛滥高性能组件。 装置与操作设置阿里云镜像 composer config -g repo.packagist composer https://mirrors.aliyun.com/co... 装置 workerman composer require workerman/workerman 启动 调试模式php start.php start 守护过程模式php start.php start -d 进行 php start.php stop ...

May 28, 2022 · 2 min · jiezi

关于workerman:webman-框架简单体验

webman 是一款基于 workerman 开发的 http 服务框架,用于开发 web 站点或者 http 接口。反对路由、中间件、主动注入、多利用、自定义过程、无需更改间接兼容现有 composer 我的项目组件等诸多个性。具备学习成本低、简略易用、超高性能、超高稳定性等特点。https://www.workerman.net/doc...简略来说,webman 是基于 workerman 的一款常驻内存的 利用 服务框架,运行模式为多过程阻塞模式,IO模型必定是多路复用,至于是select/poll 还是 epoll 应该同 workerman 的场景统一,看是否装置了 event 扩大了(倡议装置,高并发下 epoll 模型更具劣势)。 尽管不像以后许多基于 swoole 的协程 或 相似 node/reactPHP 等 eventLoop 的异步非阻塞模式的框架,但基于 epoll 模型时,开 cpu 个 worker 单机 C10K 也没什太大鸭梨。 小课堂单过程模式一个服务过程,来一个申请就阻塞,解决期间回绝响应其余申请。1、开始期待以后申请网络IO实现。2、紧接着解决代码业务(期间可能也会随同着各种网络IO,你的业务网代码总不能只是 "hello world" 吧,数据库IO、文件IO、调用其余微服务的网络IO,都会产生阻塞)。3、发送响应结束。能够持续接管解决下一个申请。毛病:无奈承载高并发,你将会收到各种 502 响应。 多线程/协程模式一个主服务过程,来一个申请就创立一个线程去专用解决,线程专一解决负责的申请。相比单过程模式,能够承载较高的申请并发量,但创立和切换线程的开销也是很大的,还有死锁的问题(当初又有了协程,用户态线程,更加轻量级,还能够)。 IO多路复用模式IO 多路复用模式下,worker 过程在接管一个申请后,如果该申请还未就绪(内核还未实现 socket 数据的读取及未 copy 至用户态),那么 worker 是能够持续去接管其余申请的,当某申请的 socket 数据读取实现后,worker 便开始执行业务解决(留神:此阶段 worker 是被业务解决独占的,期间无奈解决其余申请)。业务解决实现,worker 被开释,复原最后的状态流程。select/poll 和 epoll 都是 IO多路复用,不同之处在于 epoll 采纳更敌对的告诉机制,select/poll 要被动的忙轮训来监测是否有已就绪的申请socket,epoll 则是期待内核的被动告诉。 ...

March 9, 2021 · 4 min · jiezi

Laravel整合PHPSocketIo实现web消息推送

PHPSocket.IO,PHP跨平台实时通讯框架PHPSocket.IO是PHP版本的Socket.IO服务端实现,基于workerman开发,用于替换node.js版本Socket.IO服务端。PHPSocket.IO底层采用websocket协议通讯,如果客户端不支持websocket协议, 则会自动采用http长轮询的方式通讯。环境Ubuntu 18Laravel 5.8PHPSocket.IO 1.1安装依赖composer require workerman/phpsocket.iocomposer require guzzlehttp/guzzle启动程序整合到artisan命令中创建文件命令php artisan make:command MsgPush app/Console/Commands/MsgPush.php <?phpnamespace App\Console\Commands;use Illuminate\Console\Command;use Workerman\Worker;use Workerman\Lib\Timer;use PHPSocketIO\SocketIO;class MsgPush extends Command{ protected $signature = 'msg-push {action=start : start | restart | reload(平滑重启) | stop | status | connetions} {--d : deamon or debug}'; protected $description = 'web消息推送服务'; // 全局数组保存uid在线数据 private static $uidConnectionCounter = []; // 广播的在线用户数,一个uid代表一个用户 private static $onlineCount = 0; // 广播的在线页面数,同一个uid可能开启多个页面 private static $onlinePageCount = 0; //PHPSocketIO服务 private static $senderIo = null; public function __construct() { parent::__construct(); } /** * 根据脚本参数开启PHPSocketIO服务 * PHPSocketIO服务的端口是`2120` * 传递数据的端口是`2121` */ public function handle() { global $argv; //启动php脚本所需的命令行参数 $argv[0] = 'MsgPush'; $argv[1] = $this->argument('action'); // start | restart | reload(平滑重启) | stop | status | connetions $argv[2] = $this->option('d') ? '-d' : ''; // 守护进程模式或调试模式启动 // PHPSocketIO服务 self::$senderIo = new SocketIO(2120); // 客户端发起连接事件时,设置连接socket的各种事件回调 self::$senderIo->on('connection', function ($socket) { // 当客户端发来登录事件时触发,$uid目前由页面传值决定,当然也可以根据业务需要由服务端来决定 $socket->on('login', function ($uid) use ($socket) { // 已经登录过了 if (isset($socket->uid)) return; // 更新对应uid的在线数据 $uid = (string)$uid; // 这个uid有self::$uidConnectionCounter[$uid]个socket连接 self::$uidConnectionCounter[$uid] = isset(self::$uidConnectionCounter[$uid]) ? self::$uidConnectionCounter[$uid] + 1 : 1; // 将这个连接加入到uid分组,方便针对uid推送数据 $socket->join($uid); $socket->uid = $uid; // 更新这个socket对应页面的在线数据 self::emitOnlineCount(); }); // 当客户端断开连接是触发(一般是关闭网页或者跳转刷新导致) $socket->on('disconnect', function () use ($socket) { if (!isset($socket->uid)) { return; } // 将uid的在线socket数减一 if (--self::$uidConnectionCounter[$socket->uid] <= 0) { unset(self::$uidConnectionCounter[$socket->uid]); } }); }); // 当self::$senderIo启动后监听一个http端口,通过这个端口可以给任意uid或者所有uid推送数据 self::$senderIo->on('workerStart', function () { // 监听一个http端口 $innerHttpWorker = new Worker('http://0.0.0.0:2121'); // 当http客户端发来数据时触发 $innerHttpWorker->onMessage = function ($httpConnection, $data) { $type = $_REQUEST['type'] ?? ''; $content = htmlspecialchars($_REQUEST['content'] ?? ''); $to = (string)($_REQUEST['to'] ?? ''); // 推送数据的url格式 type=publish&to=uid&content=xxxx switch ($type) { case 'publish': // 有指定uid则向uid所在socket组发送数据 if ($to) { self::$senderIo->to($to)->emit('new_msg', $content); } else { // 否则向所有uid推送数据 self::$senderIo->emit('new_msg', $content); } // http接口返回,如果用户离线socket返回fail if ($to && !isset(self::$uidConnectionCounter[$to])) { return $httpConnection->send('offline'); } else { return $httpConnection->send('ok'); } } return $httpConnection->send('fail'); }; // 执行监听 $innerHttpWorker->listen(); // 一个定时器,定时向所有uid推送当前uid在线数及在线页面数 Timer::add(1, [self::class, 'emitOnlineCount']); });// Worker::$daemonize = true; Worker::runAll(); } /** * 将在线数变化推送给所有登录端 * 须是public方法,可供其它类调用 */ public static function emitOnlineCount() { $newOnlineCount = count(self::$uidConnectionCounter); $newOnlinePageCount = array_sum(self::$uidConnectionCounter); // 只有在客户端在线数变化了才广播,减少不必要的客户端通讯 if ($newOnlineCount != self::$onlineCount || $newOnlinePageCount != self::$onlinePageCount) {// var_dump('emitOnlineCount: ', self::$uidConnectionCounter); //将在线数变化推送给所有登录端 self::$senderIo->emit( 'update_online_count', [ 'onlineCount' => $newOnlineCount, 'onlinePageCount' => $newOnlinePageCount ] ); self::$onlineCount = $newOnlineCount; self::$onlinePageCount = $newOnlinePageCount; } }}启动PHPSocket.Io服务#守护进程模式启动php artisan msg-push start -d#调式模式启动php artisan msg-push startweb页面resources/views/socketio.blade.php ...

May 1, 2019 · 3 min · jiezi

php异步编程

前言我对 php 异步的知识还比较混乱,写这篇是为了整理,可能有错。传统的 php-fpm 一个进程执行一个请求,要达到多少并发,就要生成多少个进程。更糟糕的是每次请求都需要重新编译执行,导致并发一直上不来。因此出现了 Swoole 和 WorkerMan 两个国内流行的常驻内存框架[1]。这两个框架原理都是通过事件循环,让程序一直停留在内存,等待外部请求,达到高并发。为什么需要异步先来看一个例子在工作目录下新建文件 slowServer.php<?phpsleep(5); // 5秒后才能返回请求echo ‘done’;开启服务$ php -S localhost:8081 slowServer.php开另一个终端,安装依赖$ pecl install event # 安装 event 扩展$ composer require workerman/workerman$ composer require react/http-client:^0.5.9新建文件 worker.phprequire_once DIR . ‘/vendor/autoload.php’;use Workerman\Worker;use Workerman\Connection\AsyncTcpConnection;use Amp\Artax\Response;$http_worker = new Worker(“http://0.0.0.0:8082”);$http_worker->count = 1; // 只开一个进程$http_worker->onMessage = function($connection, $host) { echo 1; $data = file_get_contents(‘http://localhost:8081’); $connection->send($data);};Worker::runAll();开启服务器php worker.php start在浏览器开启两个标签,都打开网址 http://localhost:8082 。这时可以看到终端输出“1”,过了一会儿又输出“1”,原因是8081服务器在处理第一个请求的时候阻塞在了等待8081返回之中,等第一个请求结束后,才开始处理第二个请求。也就是说请求是一个一个执行的,要达到多少个并发,就要建立多少个进程,跟 php-fpm 一样。现在修改一下代码$http_worker->onMessage = function($connection, $host) { echo 1; $loop = Worker::getEventLoop(); $client = new \React\HttpClient\Client($loop); $request = $client->request(‘GET’, ‘http://localhost:8081’); $request->on(’error’, function(Exception $e) use ($connection) { $connection->send($e); }); $request->on(‘response’, function ($response) use ($connection) { $response->on(‘data’, function ($data) use ($connection) { $connection->send($data); }); }); $request->end();};现在打开服务,再在浏览器发起请求,发现第二个“1”在请求后就马上输出了,而这时第一个请求还没结束。这表明进程不再阻塞,并发量取决于 cpu 和 内存,而不是进程数。为什么需要异步通过上面的例子已经很明白了,reactphp 框架通过把 http 请求变成异步,让 onMessage 函数变成非阻塞,cpu 可以去处理下一个请求。即从 cpu 循环等待 8081 返回,变成了 epoll 等待。异步的意义在于把 cpu 从 io 等待中解放出来,可以处理其他计算任务。 如果你想知道怎么用框架实现异步,看到这里就可以了。WorkerMan 配合 ReactPHP 或者自身的 AsyncTcpConnection 已经可以满足很多 io 请求异步化的需求。下面继续讨论这些框架是怎么做到异步的。哪些地方应该被做成异步通过上面的例子已经知道一旦执行到不需要 cpu,但是要等待 io 的时候,应该把 io 的过程做成异步。实现事件循环上面的例子是通过 reactphp 把 http 请求变成了异步,其实 WorkerMan 框架本身也是异步的,下面来看看 WorkerMan 是怎么使 onMessage 函数可以异步接受请求。先来新建下面这个文件 react.php<?php$context = stream_context_create();$socket = stream_socket_server(’tcp://0.0.0.0:8081’, $errno, $errmsg, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN,$context); // 注册一个 fd(file descriptor)function react($socket){ $new_socket = stream_socket_accept($socket, 0, $remote_address); echo 1;}$eventBase = new EventBase();$event = new Event($eventBase, $socket, Event::READ | Event::PERSIST, ‘react’, $socket); // 注册一个事件,检测 fd 有没有写入内容$event->add();$eventBase->loop(); // 开始循环开始执行$ php react.php在另一个终端执行telnet 127.0.0.1 8081这时就会看到第一个终端输出'1’。我之前写过一篇文章《php使用epoll》,是这篇文章的基础。那篇文章里事件回调是通过定时来实现,即$event->add($seconds);而这里,事件回调是通过检测 fd 是否有写入内容来实现,这个过程不需要 cpu 参与。当 fd 有内容写入时,会调函数 ‘react’,这时开始使用 cpu。如果这时候进程执行另一个异步请求,比如用 reactphp 框架请求一个网页,那么程序会让出 cpu,此时如果有另一个请求进来,就可以回调执行另一个 ‘react’ 函数。由此提高了并发量。协程生成器 Generater这是生成器的 PHP 官方文档 http://php.net/manual/zh/lang...<?phpfunction gen_one_to_three() { for ($i = 1; $i <= 3; $i++) { //注意变量$i的值在不同的yield之间是保持传递的。 yield $i; }}$generator = gen_one_to_three();foreach ($generator as $value) { echo “$value\n”;}生成器就是每次程序执行到 yield 的时候保存状态,然后返回 $i,是否继续执行 gen_one_to_three 里的循环,取决于主程序是否继续调用什么是协程上面的程序另一种写法是<?php$i = 1;function gen_one_to_three() { global $i; if ($i<=3){ return $i++; }}while ($value = gen_one_to_three()) { echo “$value\n”;}由此可见,协程就是一种对函数的封装,使其变成一种可以被中断的函数,行为更像是子进程或子线程,而不是函数。协程的具体写法这里不细写,因为协程的写法十分复杂,可能需要再做一层封装才能好用。协程与异步既然协程可以被中断,那么只要在程序发起请求后发起事件循环,然后用 yield 返回,然后程序继续执行主程序部分,等事件返回后触发函数,执行 Generatot::next() 或 Generator::send() 来继续执行协程部分。封装好后就好像没有异步回调函数一样,和同步函数很像。现在已经有 ampphp 和 swoole 两个框架封装了协程,有兴趣可以了解一下。国外还有 https://amphp.org 和 https://reactphp.org 这两个框架博客地址:http://b.ljj.pub ...

January 22, 2019 · 2 min · jiezi