使用 Swoole 扩展
PHP Swoole 扩展于 2013 年 12 月首次在 PHP 扩展 C 库网站( https://pecl.php.net/ )上发布。从那时起,它就获得了相当大的关注。随着 PHP 8 引入 JIT 编译器,人们对 Swoole 扩展再次产生了浓厚的兴趣,因为它快速、稳定,能够让 PHP 应用程序运行得更快。总下载量接近 600 万次,平均每月约为 5 万次。
在本节中,你将了解到该扩展的相关信息、安装方式和使用方法。让我们先来了解一下该扩展的概况。
检查 Swoole 扩展
由于该扩展是用 C 语言编写的,因此一旦编译、安装并启用,就会在当前的 PHP 安装中添加一组函数和类。不过,该扩展利用了某些仅适用于 UNIX 衍生操作系统的底层功能。这意味着,如果运行的是 Windows 服务器,要运行使用 Swoole 扩展的 PHP 异步应用程序,唯一的办法就是安装 Windows Services for Linux (WSL),或者将应用程序设置为在 Windows 服务器上的 Docker 容器中运行。
如果想在 Windows 服务器上尝试使用 PHP 异步,可以考虑使用 ReactPHP(在 使用 ReactPHP 部分中讨论),它没有 Swoole 扩展所需的操作系统依赖性。 |
PHP 异步的一大优势是,初始代码块会立即加载并保留在内存中,直到异步服务器实例停止。使用 Swoole 扩展时就是这种情况。在你的代码中,你创建了一个异步服务器实例,它有效地将 PHP 变成了一个在指定端口监听的持续运行的守护进程。但这也有一个缺点,那就是如果你修改了程序代码,异步服务器实例无法识别这些修改,直到你重新加载它。
Swoole 扩展的一大特色是它对 协程的支持。在现实生活中,这意味着我们不必对使用同步编程模型编写的应用程序进行大手术。Swoole 会自动挑选出阻塞操作,如文件系统访问和数据库查询,并允许在应用程序的其他部分继续进行时暂停这些操作。有了这种支持,您通常只需使用 Swoole 运行同步应用程序,就能立即提高性能。
Swoole 扩展的另一个非常棒的功能是 Swoole/Table
。该功能可让您完全在内存中创建一个相当于数据库表的数据表,该数据表可在多个进程之间共享。这种结构有许多可能的用途,其潜在的性能增益确实惊人。
Swoole 扩展程序能够监听用户数据报协议(UDP)而非传输控制协议(TDP)的传输。这是一个非常有趣的可能性,因为 UDP 比 TCP 快得多。Swoole 还包括精确到毫秒的计时器实现,以及 MySQL、PostgreSQL、Redis 和 cURL 的同步客户端。Swoole 扩展还能让你使用 Golang 风格的通道设置进程间通信(IPC)。现在让我们来看看如何安装 Swoole。
安装 Swoole 扩展
安装 Swoole 扩展的方法与安装任何用 C 语言编写的 PHP 扩展的方法相同。一种方法是使用操作系统软件包管理器。例如 Debian 或 Ubuntu Linux 的 apt(或其不太友好的表亲 apt-get),以及 Red Hat、CentOS 或 Fedora 的 yum 或 dnf。使用操作系统软件包管理器时,Swoole 扩展会以预编译二进制文件的形式提供。
不过,建议使用 pecl 命令。如果您安装的操作系统上没有该命令,则可以在 Ubuntu 或 Debian 操作系统上安装 pecl 命令(以 root 用户身份登录),方法如下:apt install php-pear。如果安装的是 Red Hat、CentOS 或 Fedora 操作系统,则可使用以下方法:yum install php-pear。
使用 pecl 安装 Swoole 扩展时,可以指定一些选项。这里总结了这些选项:

有关这些选项的更多信息以及安装过程的概述,请点击此处: https://www.swoole.co.uk/docs/get-started/installation 现在让我们来看看包含 Swoole 套接字、JavaScript Object Notation (JSON) 和 cURL 支持的安装示例,如下所示:
-
我们首先要做的是更新
pecl
频道。这是 PHP 扩展源代码库和函数的列表,很像apt
或yum
软件包管理器使用的源列表。下面是执行此操作的代码:pecl channel-update pecl.php.net
-
接下来,我们指定安装命令,并使用
-D
标志添加选项,如下所示:pecl install -D \ 'enable-sockets="yes" \ enable-openssl="no" \ enable-http2="no" \ enable-mysqlnd="no" \ enable-swoole-json="yes" \ enable-swoole-curl="yes"' \ swoole
-
这将启动扩展的安装过程。现在,你将看到各种 C 语言代码文件和头文件被下载,然后本地 C 编译器将用于编译扩展。下面是编译过程的部分视图:
root@php8_tips_php8 [ / ]# pecl install swoole downloading swoole-4.6.7.tgz ... Starting to download swoole-4.6.7.tgz (1,649,407 bytes) ........................................................ ......................................................... ........................................................ ......................................................... ......................................................... ..........................................done: 1,649,407 bytes 364 source files, building running: phpize Configuring for: PHP Api Version: 20200930 Zend Module Api No: 20200930 Zend Extension Api No: 420200930 building in /tmp/pear/temp/pear-build-defaultuserQakGt8/ swoole-4.6.7 running: /tmp/pear/temp/swoole/configure --with-phpconfig=/usr/bin/php-config --enable-sockets=no --enableopenssl=no --enable-http2=no --enable-mysqlnd=yes --enable-swoole-json=yes --enable-swoole-curl=yes ... Build process completed successfully Installing '/usr/include/php/ext/swoole/config.h' Installing '/usr/lib/php/extensions/no-debug-nonzts-20200930/swoole.so' install ok: channel://pecl.php.net/swoole-4.6.7 configuration option "php_ini" is not set to php.ini location You should add "extension=swoole.so" to php.ini
-
如果找不到 C 编译器,系统会发出警告。此外,可能还需要为操作系统安装 PHP 开发库。如果出现这种情况,警告信息会提供进一步的指导。
-
完成后,您需要启用扩展。这可以通过在
php.ini
文件中添加extension=swoole
来实现。如果不确定其位置,请使用php -i
命令查找php.ini
文件的位置。以下是您可以在命令行中发出的添加该指令的命令:echo "extension=swoole" >>/etc/php.ini
-
然后,您可以使用以下命令确认 Swoole 扩展是否可用:
php --ri swoole
至此,Swoole 扩展的安装完成。如果是自定义编译 PHP,也可以在编译前运行 configure
时添加 --enable-swoole
选项。这将使 Swoole 扩展与核心 PHP 安装一起编译和启用(并允许你绕过刚才概述的安装步骤)。现在我们来看一个从文档中摘录的 Hello World 示例,以测试安装情况。
测试安装
Swoole 文档提供了一个简单的示例,您可以用它来快速测试安装是否成功。示例代码位于 Swoole 文档主页 ( https://www.swoole.co.uk/docs/ )。出于版权原因,我们不在此复制。以下是运行 Hello World 测试的步骤:
-
首先,我们将 Hello World 示例从 https://www.swoole.co.uk/docs/ 复制到 /path/to/repo/ch12/php8_swoole_hello_world.php 文件。
接下来,我们修改了演示程序,将
$server = new Swoole\ HTTP\Server("127.0.0.1", 9501);
改为$server = new Swoole\ HTTP\Server("0.0.0.0", 9501);
。这一修改允许 Swoole 服务器监听任意互联网协议(IP)地址的 9501 端口。
-
然后,我们修改了
/repo/ch12/docker-compose.yml
文件,使端口 9501 在 Docker 容器外可用,如下所示:version: "3" services: ... php8-tips-php8: ... ports: - 8888:80 - 9501:9501 ...
-
为了使这一更改生效,我们必须先关闭服务,然后再重新启动。在本地计算机的命令提示符/终端窗口中,使用这两条命令:
/path/to/repo/init.sh down /path/to/repo/init.sh up
-
请注意,如果您运行的是 Windows,请删除
.sh
。 -
然后,我们在 PHP 8 Docker 容器中打开一个 shell,运行 Hello World 程序,如下所示:
$ docker exec -it php8_tips_php8 /bin/bash # cd /repo/ch12 # php php8_swoole_hello_world.php
-
最后,我们从 Docker 容器外部打开浏览器,IP 地址和端口是:http://172.16.0.88:9501。
下面的截图显示了 Swoole Hello World 程序的结果:

在详细了解如何使用 Swoole 扩展来提高应用程序性能之前,我们需要检查一个示例应用程序,它是 PHP 异步模型的主要候选程序。
检查示例 I/O 密集型应用程序
为了便于说明,我们创建了一个示例应用程序,该示例应用程序是以呈现状态传输(REST)API 的形式编写的,设计在 PHP 8 中运行。该示例应用程序提供了一个聊天或即时消息 API,具有以下简单功能:
-
使用超文本传输协议(HTTP)POST 方法向特定用户或所有用户发布信息。发布成功后,API 会返回刚刚发布的信息。
-
带有
from=username
参数的 HTTPGET
方法会返回与该用户名往来的所有信息,以及发给所有用户的信息。如果设置了all=1
参数,则会返回所有用户名的列表。 -
HTTP DELETE 方法会删除 messages 表中的所有消息。
本节仅显示部分程序代码。如果您对整个聊天应用程序感兴趣,源代码位于 /path/to/repo/src/Chat 下。这里提供了主要的 API 端点:http://172.16.0.81/ch12/php8_ chat_ajax.php。
以下示例在 PHP 8.1 Docker 容器中执行。请确保在 Windows 计算机上通过本地计算机上的命令提示符,按如下步骤关闭现有容器: C:\path\to\repo\init down
。对于 Linux 或 Mac,从终端窗口:/path/to/repo/init.sh down
。从 Windows 计算机启动 PHP 8.1 容器:C:path\to/repo\ch12\init up
。从 Linux 或 Mac 终端窗口 /path/to/repo/ch12/init.sh up
。
下面的示例在 PHP 8.1 Docker 容器中执行。请确保在 Windows 计算机上通过本地计算机上的命令提示符,按如下方式调用现有容器: C:path\to\repo\init down
对于 Linux 或 Mac,可从终端窗口进行操作:
/path/to/repo/init.sh down
要从 Windows 计算机启动 PHP 8.1 容器:
C:\path\to\repo\ch12\init up
从 Linux 或 Mac 终端窗口:
/path/to/repo/ch12/init.sh up
我们现在看一下核心 API 程序本身的源代码,如下:
-
首先,我们定义一个
Chat\Message\Pipe
类,确定我们需要使用的所有外部类,就像这样:// /repo/src/Chat/Messsage/Api.php; namespace Chat\Message; use Chat\Handler\ {GetHandler, PostHandler, NextHandler,GetAllNamesHandler,DeleteHandler}; use Chat\Middleware\ {Access,Validate,ValidatePost}; use Chat\Message\Render; use Psr\Http\Message\ServerRequestInterface; class Pipe {
-
然后,我们定义了一个
exec()
静态方法,用于调用一组符合 PHP 标准建议 15(PSR-15)的处理程序。我们还通过调用Chat\Middleware\Access
中间件类的process
方法来调用管道的第一阶段。NextHandler
的返回值将被忽略:public static function exec( ServerRequestInterface $request) { $params = $request->getQueryParams(); $method = strtolower($request->getMethod()); $dontcare = (new Access()) ->process($request, new NextHandler());
-
还是在同一个方法中,我们使用
match()
结构来检查 HTTPGET
、POST
和DELETE
方法调用。如果方法是POST
,我们就使用Chat\Middleware\ValidatePost
验证中间件类来验证POST
参数。如果验证成功,经过处理的数据就会传递给Chat\ Handler\PostHandler
。如果 HTTP 方法是DELETE
,我们就直接调用Chat\Handler\DeleteHandler
:$response = match ($method) { 'post' => (new ValidatePost()) ->process($request, new PostHandler()), 'delete' => (new DeleteHandler()) ->handle($request),
-
如果 HTTP 方法是
GET
,我们首先检查是否设置了all
参数。如果是,我们就调用Chat\Handler\GetAllNamesHandler
。否则,默认子句将通过Chat\MiddleWare\Validate
传递数据。如果验证成功,经过消毒的数据将传递给Chat\Handler\GetHandler
:'get' => (!empty($params['all']) ? (new GetAllNamesHandler())->handle($request) : (new Validate())->process($request, new GetHandler())), default => (new Validate()) ->process($request, new GetHandler())}; return Render::output($request, $response); } }
-
然后就可以使用一个简短的常规程序来调用核心 API 类,如图所示。在这个调用程序中,我们使用
Laminas\Diactoros\ ServerRequestFactory
建立了一个符合 PSR-7 标准的Psr\Http\Message\ServerRequestInterface
实例。然后通过管道类传递请求并产生响应:// /repo/ch12/php8_chat_ajax.php include __DIR__ . '/vendor/autoload.php'; use Laminas\Diactoros\ServerRequestFactory; use Chat\Message\Pipe; $request = ServerRequestFactory::fromGlobals(); $response = Pipe::exec($request); echo $response;
我们还创建了一个测试程序(/repo/ch12/php8_chat_test.php-未显示),它调用 API 端点的次数是设定好的(默认为 100)。每次迭代时,测试程序都会发布一条随机消息,包括一个随机的收件人用户名、一个随机的日期,以及 /repo/sample_data/geonames.db
数据库中的一个连续条目。测试程序需要两个参数。第一个参数是表示 API 的 URL。第二个参数(可选)表示迭代次数。
下面是从命令 shell 到 PHP 8.1 Docker 容器运行 /ch12/php8_chat_test.php
的示例结果:
root@php8_tips_php8_1 [ /repo/ch12 ]# php php8_chat_test.php \
http://localhost/ch12/php8_chat_ajax.php 10000 bboyer :
Dubai:AE:2956587 : 2021-01-01 00:00:00
1 fcompton : Sharjah:AE:1324473 : 2022-02-02 01:01:01
...
998 hrivas : Caloocan City:PH:1500000 : 2023-03-19 09:54:54
999 lpena : Budta:PH:1273715 : 2021-04-20 10:55:55
From User: dwallace
Elapsed Time: 3.3177478313446
从输出结果中,请注意所花费的时间。在下一节中,通过使用 Swoole,我们可以将时间缩短一半!不过,在使用 Swoole 之前,我们必须加入 JIT 编译器。我们使用以下命令启用 JIT:
# php /repo/ch10/php8_jit_reset.php on
在 PHP 8.0.0 中,您可能会遇到一些错误,甚至可能出现分段错误。但在 PHP 8.1 中,启用 JIT 编译器后,API 应能按预期运行。不过,JIT 编译器能否提高性能还很值得怀疑,因为频繁的 API 调用会导致应用程序等待。任何频繁阻塞 I/O 操作的应用程序都是异步编程模型的最佳候选。不过,在继续之前,我们需要关闭 JIT,使用与之前相同的实用程序,如下所示:
# php /repo/ch10/php8_jit_reset.php off
现在让我们来看看如何使用 Swoole 扩展来提高这个 I/O 密集型应用程序的性能。
使用 Swoole 扩展提高应用程序性能
鉴于 Swoole 提供了例程支持,为了提高聊天应用程序的性能,我们真正需要做的就是重写 /repo/ch12/php8_chat_ajax.php
调用程序,将其转化为一个 API,作为 Swoole 服务器实例监听 9501 端口。以下是重写主 API 调用程序的步骤:
-
首先,我们启用自动加载并确定所需的外部类:
// /repo/ch12/php8_chat_swoole.php include __DIR__ . '/vendor/autoload.php'; use Chat\Message\Pipe; use Chat\Http\SwooleToPsr7; use Swoole\Http\Server; use Swoole\Http\Request; use Swoole\Http\Response;
-
接下来,我们启动一个 PHP 会话,并创建一个
Swoole\HTTP\Server
实例,该实例监听 9501 端口上的任何 IP 地址:session_start(); $server = new Swoole\HTTP\Server('0.0.0.0', 9501);
-
然后,我们调用
on()
方法,并将其与启动事件相关联。在这种情况下,我们会记录一条日志,以确定 Swoole 服务器何时启动。其他服务器事件记录在这里: https://www.swoole.co.uk/docs/modules/swoole-http-server-doc :$server->on("start", function (Server $server) { error_log('Swoole http server is started at ' . 'http://0.0.0.0:9501'); });
-
最后,我们定义了一个主服务器事件
$server->on('request',function () {})
,用于处理接收到的请求。下面是实现这一功能的代码:$server->on("request", function ( Request $swoole_request, Response $swoole_response){ $request = SwooleToPsr7:: swooleRequestToServerRequest($swoole_request); $swoole_response->header( "Content-Type", "text/plain"); $response = Pipe::exec($request); $swoole_response->end($response); }); $server->start();
不幸的是,传递给与 on()
方法相关的回调的 Swoole\Http\Request
实例不符合 PSR-7 标准!因此,我们需要定义一个 Chat\Http\SwooleToPsr7
类和一个使用静态调用执行转换的 swooleRequestToServerRequest()
方法。然后,我们在 Swoole\Http|Response
实例上设置标头,并从管道返回一个值,以完成整个电路。
需要注意的是,标准的 PHP 超级全局变量(如 $_GET
和 $_POST
)并不能像运行中的 Swoole 服务器实例所期望的那样工作。主要的入口点是您用来从命令行启动 Swoole 服务器的初始程序。唯一的输入请求参数是实际的初始程序文件名。任何后续输入都必须通过传递给 on()
函数的 Swoole\Http\Request
实例来捕获。
在 https://php.net/swoole 上找到的文档并没有显示 Swoole\HTTP\Request
和 Swoole\HTTP\Response
类的所有可用方法。不过,您可以在 Swoole 网站上找到相关文档,这些文档也列在此处:
还值得注意的是,Swoole\HTTP\Request
对象的属性与 PHP 的 superglobals 大致对应,如图所示:

另一个需要考虑的问题是,在 Swoole 例程中使用 Xdebug 可能会导致分段故障和其他问题,甚至包括 内核转储。最佳做法是在首次使用 pecl 安装 Swoole 时使用 --enable-debug
标志启用 Swoole 调试。测试应用程序的步骤如下:
-
通过命令 shell 进入 PHP 8.1 Docker 容器,我们运行了 Swoole 版本的聊天 API,如下所示。立即显示的消息是
$server->on("start", function() {})
的结果:# cd /repo/ch12 # php php8_chat_swoole.php Swoole http server is started at http://0.0.0.0:9501
-
然后,我们在主机上打开另一个终端窗口,并在 PHP 8.1 Docker 容器中打开另一个 shell。在这里,我们可以运行 /repo/ ch12/php8_chat_test.php 测试程序,如下所示:
# cd /repo/ch12 # php php8_chat_test.php http://localhost:9501 1000
-
请注意两个附加参数。第一个参数告诉测试程序使用 Swoole 版本的 API,而不是使用 Apache 网络服务器的旧版本。最后一个参数告诉测试程序运行 1000 次迭代。
下面让我们来看看输出结果:
root@php8_tips_php8_1 [ /repo/ch12 ]# php php8_chat_test.php \
http://localhost:9501 1000
0 coconnel : Dubai:AE:2956587 : 2021-01-01 00:00:00
1 htyler : Sharjah:AE:1324473 : 2022-02-02 01:01:01
...
998 cvalenci : Caloocan City:PH:1500 : 2023-03-19 09:54:54
999 smccormi : Budta:PH:1273715 : 2021-04-20 10:55:55
From User: ajenkins
Elapsed Time: 1.8595671653748
输出中最显著的特征是耗时。如果您回顾上一节,就会发现使用 Apache 作为传统 PHP 应用程序运行的应用程序接口完成 1,000 次迭代需要大约 3.35 秒,而在 Swoole 下运行的相同应用程序接口只需大约 1.86 秒即可完成:几乎是原来时间的一半!
请注意,这是在没有任何额外优化的情况下。Swoole 还有许多其他功能可供我们使用,包括定义内存表、从额外的工作线程中生成任务、使用事件循环促进缓存等。正如您所看到的,Swoole 可以立即提升性能,作为从现有应用程序中获得更高性能的一种可能方法,它非常值得研究。
现在,您已经了解了如何使用 Swoole 来提高应用程序性能,让我们来看看其他潜在的 PHP 异步解决方案。