PHP 怎么PHP 多路复用

wen PHP项目 3

本文目录导读:

PHP 怎么PHP 多路复用

  1. 基础:使用 stream_select() 函数
  2. 进阶:使用 PHP 扩展(推荐)
  3. 生产首选:使用 Swoole / Workerman
  4. 总结:如何选择?
  5. 附:为什么 PHP 要这么麻烦?

在 PHP 中实现多路复用(I/O Multiplexing),主要目的是让一个进程/线程同时处理多个连接(如 WebSocket、TCP 连接),提高并发性能,减少资源消耗。

核心思想:不等待单个连接的数据,而是同时监控多个连接,哪个连接有数据到达就处理哪个

以下是几种常见的 PHP 多路复用实现方式:


基础:使用 stream_select() 函数

这是 PHP 自带的、最底层的多路复用实现,它直接对应 Unix 系统调用的 select

原理:传入一组 socket 流(资源),调用 stream_select 阻塞等待,当有任意一个 socket 可读/可写时,函数返回,然后你遍历返回的数组处理。

适用场景:简单的聊天服务器、自定义 TCP 服务器。

案例:一个简单的 TCP Echo 服务器

<?php
// 创建监听 socket
$server = stream_socket_server("tcp://0.0.0.0:8080", $errno, $errstr);
if (!$server) {
    die("服务器创建失败: $errstr ($errno)");
}
// 保存所有客户端连接的数组
$clients = [$server];
echo "服务器启动,监听 0.0.0.0:8080...\n";
while (true) {
    // 1. 复制一份数组,select 会修改它们
    $read = $clients;
    $write = [];
    $except = [];
    // 2. 阻塞等待,直到有 socket 可读
    // 参数:监控数组,超时时间(null 表示无限等待)
    if (stream_select($read, $write, $except, 0) === false) {
        // 被信号中断或出错,继续
        continue;
    }
    // 3. 处理有事件的 socket
    foreach ($read as $socket) {
        if ($socket === $server) {
            // 有新连接接入
            $newClient = stream_socket_accept($server);
            $clients[] = $newClient;
            echo "新客户端连接\n";
        } else {
            // 已有客户端发来数据
            $data = fread($socket, 1024);
            if ($data === false || $data === '') {
                // 客户端断开连接
                fclose($socket);
                $key = array_search($socket, $clients);
                unset($clients[$key]);
                echo "客户端断开\n";
            } else {
                // 处理数据(这里原样返回)
                fwrite($socket, "收到: " . $data);
            }
        }
    }
}

运行上述代码后,你可以用 telnet 127.0.0.1 8080nc 127.0.0.1 8080 多开几个终端测试。

缺点

  • 性能受限于 select 的最大文件描述符数(1024)。
  • 需要自行管理连接数组和事件循环。

进阶:使用 PHP 扩展(推荐)

PHP 官方提供了两个强大的扩展来实现高效多路复用:

  • pcntl:用于进程控制(多进程/多 worker)。
  • event:基于 libevent 库(高性能事件通知机制)。
  • swoole / workerman:第三方的生产级网络框架,内部封装了多路复用。

A. 使用 event 扩展

基于 libevent,效率比 stream_select 高很多,支持 epoll(Linux)、kqueue(macOS)、IOCP(Windows)。

安装pecl install event (需要 libevent-dev

原理:创建事件循环(EventBase),把 socket 和回调函数绑定,当 socket 有数据时,libevent 自动触发回调。

示例:

<?php
// 创建监听 socket
$server = stream_socket_server('tcp://0.0.0.0:8080');
stream_set_blocking($server, false); // 非阻塞模式
$base = new EventBase();
$event = new Event($base, $server, Event::READ | Event::PERSIST, function ($server) use ($base) {
    $conn = @stream_socket_accept($server);
    if ($conn) {
        stream_set_blocking($conn, false);
        // 新连接事件
        $event = new Event($base, $conn, Event::READ | Event::PERSIST, function ($conn) {
            $data = fread($conn, 1024);
            if ($data === '' || $data === false) {
                fclose($conn);
            } else {
                fwrite($conn, "收到: $data");
            }
        });
        $event->add();
    }
});
$event->add();
$base->loop(); // 启动事件循环

优点:高效跨平台,适合生产环境。 缺点:需要安装扩展。


生产首选:使用 Swoole / Workerman

除非你有非常底层的需求,不然直接用这些框架是最佳实践,它们把多路复用、进程管理、协程等细节全部封装好了。

Swoole(推荐,协程支持好)

Swoole 是一个 C 扩展,集成了多路复用(epoll)、协程、TCP/UDP/WebSocket 服务器。

示例:高性能 TCP 服务器

<?php
// 创建 Server 对象,监听 0.0.0.0:8080
$server = new Swoole\Server('0.0.0.0', 8080);
// 监听连接进入事件
$server->on('Connect', function ($server, $fd) {
    echo "客户端连接: {$fd}\n";
});
// 监听数据接收事件
$server->on('Receive', function ($server, $fd, $reactor_id, $data) {
    $server->send($fd, "收到: {$data}");
});
// 监听连接关闭事件
$server->on('Close', function ($server, $fd) {
    echo "客户端断开: {$fd}\n";
});
// 启动服务器(内部会使用 epoll 多路复用)
$server->start();

启动php server.php (配合 swoole-cli 或 FPM 模式)

Workerman(纯 PHP 实现)

Workerman 是纯 PHP 写的网络框架,通过 stream_selectevent 扩展实现多路复用,推荐同时安装 event 扩展提升性能。

示例:

<?php
use Workerman\Worker;
require_once __DIR__ . '/vendor/autoload.php';
// 创建一个 TCP 服务器
$worker = new Worker('tcp://0.0.0.0:8080');
// 4 个进程
$worker->count = 4;
$worker->onConnect = function ($connection) {
    echo "新连接\n";
};
$worker->onMessage = function ($connection, $data) {
    $connection->send("收到: {$data}");
};
$worker->onClose = function ($connection) {
    echo "断开\n";
};
Worker::runAll();

运行php start.php start


如何选择?

方案 场景 性能 开发成本 生产可靠性
stream_select 学习原理、简单原型
event 扩展 高性能自定义服务
Swoole 高并发 API、WebSocket、微服务 中(需学习)
Workerman 中等并发、纯 PHP 环境 低(PHP 原生)

如果你是在做生产项目:

  • 首选 Swoole(协程模式,性能天花板最高,社区活跃,文档好)。
  • 如果无法安装 C 扩展(例如虚拟主机环境),选择 Workerman

如果你只是想理解多路复用的原理:

  • stream_select 写一个简单 demo,理解事件循环模型,再过渡到 Swoole/Workerman。

附:为什么 PHP 要这么麻烦?

对比 Java/Go/Node.js

  • Java 的 NIO/Netty、Go 的 goroutine(GMP 模型)、Node.js 的 libuv 都是原生自带高效多路复用。
  • PHP 传统上是 “共享无状态” 的 CGI 模型(每个请求一个进程),处理长连接/高并发需要额外工作。

PHP 的多路复用能力完全够用(Swoole 在同类框架中性能居前),但需要你主动选择相应的工具和框架,如果你是从共享主机的古老 PHP 编程过来,先习惯用 Swoole/Workerman,才算真正进入现代 PHP 网络编程。

抱歉,评论功能暂时关闭!