在异步模式下使用选定的 PHP 框架
还有许多其他 PHP 框架也实现了异步编程模型。在本节中,我们将介绍最流行的 PHP 异步框架 ReactPHP 以及另一种流行的 PHP 异步框架 Amp。此外,我们还将向您展示如何在异步模式下使用某些 PHP 框架。
需要注意的是,许多能以异步模式运行的 PHP 框架都依赖 Swoole 扩展。不依赖 Swoole 扩展的 PHP 框架是 ReactPHP。
使用 ReactPHP
ReactPHP (https://reactphp.org/) 是 Reactor 软件设计模式的一种实现,其灵感来自非阻塞异步 Node.js 框架 (https://nodejs.org/en/) 等。
虽然 ReactPHP 无法像 Swoole 扩展那样自动提高性能,但它有一个很大的优势,那就是不依赖 UNIX 或 Linux 的功能,因此可以在 Windows 服务器上运行。ReactPHP 的另一个优势是,除了那些已经作为标准扩展的 PHP 扩展之外,它对其他 PHP 扩展没有特定的依赖性。
任何 ReactPHP 应用程序的核心都是 React\EventLoop\Loop
类。顾名思义,Loop
实例启动时实际上是一个 无限循环。大多数 PHP 无限循环都会给应用程序带来灾难!但在本例中,循环是与服务器实例一起使用的,该实例会持续监听指定端口上的请求。
ReactPHP 的另一个关键组件是 React/Socket/Server
。该类会在给定端口上打开一个套接字,使 ReactPHP 应用程序能够直接监听 HTTP 请求,而无需涉及网络服务器。
ReactPHP 的其他功能还包括监听 UDP 请求、非阻塞缓存和异步承诺的实现。ReactPHP 还有一个 Stream 组件,它允许您延迟文件系统的读取和写入,从而大大提高了性能,因为您的应用程序无需再等待此类文件 I/O 请求的完成。
使用 ReactPHP 的最后一个优势是它完全符合 PSR-7(HTTP 消息传递)。现在我们来看看使用 ReactPHP 重写的运行前面描述的聊天 API 的示例程序。以下是重写程序的步骤:
-
从命令提示符进入 Docker PHP 8 容器,使用 Composer 安装必要的 ReactPHP 组件:
cd /repo/ch12 composer require --ignore-platform-reqs react/event-loop composer require --ignore-platform-reqs react/http composer require --ignore-platform-reqs react/socket
-
然后我们重写
/repo/ch12/php8_chat_swoole.php
,并将其重命名为/repo/ch12/php8_chat_react.php
。我们首先需要修改的是使用语句:// /repo/ch12/php8_chat_react.php include __DIR__ . '/vendor/autoload.php'; use Chat\Message\Pipe; use React\EventLoop\Factory; use React\Http\Server; use React\Http\Message\Response as ReactResponse; use Psr\Http\Message\ServerRequestInterface;
-
然后,我们启动一个会话并创建一个
React\EventLoop\Loop
实例,如下所示:session_start(); $loop = Factory::create();
-
我们现在定义一个处理程序,它接受 PSR-7
ServerRequestInterface
实例作为参数,并返回一个React\Http\Message\Response
实例:$server = new Server($loop, function (ServerRequestInterface $request) { return new ReactResponse(200, ['Content-Type' => 'text/plain'], <8 SPACES>Pipe::exec($request) ); });
-
然后,我们建立一个
React\Socker\Server
实例来监听 9501 端口,并执行一个循环,就像这样:$socket = new React\Socket\Server(9501, $loop); $server->listen($socket); echo "Server running at http://locahost:9501\n"; $loop->run();
然后,我们在 PHP 8.1 容器中打开一个单独的命令 shell,启动 ReactPHP 服务器,如下所示:
root@php8_tips_php8_1 [ /repo/ch12 ]# php php8_chat_react.php
然后,我们可以从另一个命令 shell 进入 PHP 8.1 容器,运行测试程序如下:
root@php8_tips_php8_1 [ /repo/ch12 ]# php php8_chat_test.php \
http://localhost:9501
输出结果(未显示)与使用 Swoole 扩展时显示的结果类似。
接下来,我们看看另一个流行的 PHP 异步框架: Amp。
使用 Amp 实现 PHP 异步
Amp 框架( https://amphp.org/ )与 ReactPHP 非常相似,提供定时器、承诺和流的实现。Amp 还提供了例程支持以及异步迭代组件。后者非常吸引人,因为迭代对于大多数 PHP 应用程序来说都是必不可少的。如果能将迭代移入异步处理模式,虽然可能会涉及大量的重构工作,但却能极大地提升应用程序的性能。另一个有趣的变化是,Amp 可以直接使用任何 ReactPHP 组件!
要安装 Amp,请使用 Composer。Amp 的各种组件在不同的软件源中提供,因此您不必安装整个框架,只需安装您需要的组件即可。PHP Amp 服务器的实际实现与 ReactPHP 的示例非常相似。
现在让我们看看另一个可以在 PHP 异步模式下运行的框架:Mezzio,以前叫 Zend Expressive。
将 Mezzio 与 Swoole 结合使用
Mezzio 框架 ( https://docs.mezzio.dev/ ) 是 Matthew Weier O’Phinney (https://mwop.net/) 的心血结晶,是老框架 Zend Framework 和新框架 Zend Expressive 的延续。Mezzio 属于微型框架这一相对较新的类别。微框架不依赖于老旧的模型-视图-控制器(MVC)软件设计模式,主要面向 RESTful API 开发。在实际应用中,微框架支持 PHP 中间件原理,运行时开销更少,速度也相应更快。
要在 Swoole 中使用 Mezzio 应用程序,只需具备以下三点:
-
安装 Swoole 扩展(本章前面已有介绍)。
-
像这样安装
mezzio-swoole
组件:composer require mezzio/mezzio-swoole
-
然后,您需要使用 Swoole 服务器实例运行 Mezzio。这可以通过以下命令实现:
/path/to/project/vendor/bin/laminas mezzio:swoole:start
-
在 Mezzio 应用程序的配置文件中,您需要添加以下键:
return [ 'mezzio-swoole' => [ 'swoole-http-server' => [ 'host' => '0.0.0.0', // all IP addresses 'port' => 9501, ] ], ];
为了进一步提高性能,当然还应该重写代码的相应部分,以利用 PHP 的异步功能。接下来,我们来看看超越 async 的 PHP 扩展。
使用并行扩展
并行扩展 (https://www.php.net/parallel) 在 PHP 7.2 及以上版本中引入。其目的是在 PHP async 之外更进一步,进入全面并行处理的世界。并行扩展提供了五个关键的底层类,它们可以构成并行处理应用程序的基础。使用该扩展,PHP 开发人员可以像 Go 语言一样编写并行代码。让我们从 parallel\Runtime
开始。
parallel\Runtime 类
每个 parallel\Runtime
实例都会产生一个新的 PHP 线程。然后你可以使用 parallel\Runtime::run()
来安排任务。run()
的第一个参数是 Closure
(匿名函数)。第二个可选参数是 $argv
,代表运行时传递给任务的输入参数。 parallel\Runtime::close()
用于优雅地关闭线程。当出现错误时,可以使用 parallel\Runtime::kill()
立即退出线程。
parallel\Future 类
parallel\Future
实例是作为 parallel\ Runtime::run()
的返回值创建的。它的行为很像 PHP 异步承诺(本章前面有描述)。该类有三个方法,执行以下操作:
-
parallel\Future::value() 返回任务已完成的值
-
parallel\Future::cancel() 取消代表承诺失败状态的任务
-
parallel\Future::cancelled()|done() 如果任务仍未完成,则返回任务状态
parallel\Channel 类
parallel\Channel
类允许开发者在任务间共享信息。使用 __construct()
方法或 make()
方法创建通道。如果没有为 __construct()
方法提供参数,或没有为 make()
方法提供第二个参数,通道将被视为未缓冲通道。如果向 __construct()
或作为 make()
的第二个参数提供了一个整数,该值就代表通道的容量。然后,你可以使用 parallel\Channel::send()
和 parallel\Channel::recv()
方法通过通道发送和接收数据。
非缓冲通道会阻塞对 send()
的调用,直到有接收者为止,反之亦然。另一方面,缓冲通道在容量达到之前不会阻塞。