think-swoole 高级多端口监听配置

think-swoole 中,高级配置 server.listen(或者是系统底层对应的多端口监听)主要用于在一个 Swoole 服务中同时监听多个端口、绑定不同的协议或业务逻辑

例如:你可以让主服务器运行高性能 HTTP API(8080端口),同时利用 listen 再额外开一个端口专门处理 WebSocket(9501端口)、内部定制的 TCP/UDP 协议(9502端口),或者内部 RPC 协议。它们共享同一个底层 Swoole Master/Manager 进程架构,完美复用框架资源。

以下是 think-swoole 针对高级监听(listen)的多端口/多协议混合配置指南:


1. 核心配置架构 (config/swoole.php)

config/swoole.php 配置文件中,主要通过 server.options 或底层的自定义配置来实现多端口监听。核心思路是利用 Swoole 原生的 listen() 方法(在 think-swoole 内部通常由框架初始化或通过自定义服务提供)。

think-swoole 较新版本中,可以通过 server.listeners 配置项(如无该配置项,可通过下方第3节的自定义自定义服务实现)直接定义:

php

return [
    'server' => [
        'host'      => '0.0.0.0', // 主服务器监听地址
        'port'      => 8080,      // 主服务器监听端口(处理 HTTP)
        'type'      => SWOOLE_SOCK_TCP,
        'mode'      => SWOOLE_PROCESS,
        'options'   => [
            'worker_num' => 4,
            'task_worker_num' => 4,
            // ... 其他原生 Swoole 设置
        ],
        // 高级多端口监听配置 (Listeners)
        'listeners' => [
            [
                'host'     => '0.0.0.0',
                'port'     => 9502,
                'type'     => SWOOLE_SOCK_TCP,
                'settings' => [
                    'open_websocket_protocol' => false, // 关闭WebSocket
                    'open_http_protocol'      => false, // 关闭HTTP
                    // 开启特定的 TCP 协议设置(如特定结束符、包头包尾)
                    'open_eof_check'         => true,
                    'package_eof'            => "\r\n",
                ],
                // 绑定该端口专用的事件回调(必须是类名或闭包)
                'callbacks' => [
                    'receive' => [\app\listener\TcpReceive::class, 'handle'],
                ]
            ],
            [
                'host'     => '0.0.0.0',
                'port'     => 9503,
                'type'     => SWOOLE_SOCK_UDP,
                'callbacks' => [
                    'packet' => [\app\listener\UdpPacket::class, 'handle'],
                ]
            ]
        ],
    ],
    // ... 其他配置
];

请谨慎使用此类代码。


2. 编写多端口专用的事件监听器

当多端口监听(listen)创建成功后,子端口返回的是一个 Swoole\Server\Port 对象。你需要为其单独编写事件回调。

以刚才配置的 9502 端口 TCP 接收事件为例,编写 app\listener\TcpReceive.php

php

namespace app\listener;

use Swoole\Server;

class TcpReceive
{
    /**
     * 处理子端口接收到的 TCP 数据
     * @param Server $server 主服务器实例
     * @param int $fd 客户端连接的唯一标识
     * @param int $reactorId 线程ID
     * @param string $data 接收到的原始数据
     */
    public function handle(Server $server, int $fd, int $reactorId, string $data)
    {
        // 1. 解析业务数据(如硬件传感器报文、内部私有协议)
        $payload = trim($data);

        // 2. 在这里可以完美使用 ThinkPHP 所有的容器、Db、模型和缓存功能
        // \app\model\DeviceLog::create(['fd' => $fd, 'content' => $payload]);

        // 3. 向该子端口对应的客户端回执数据
        $server->send($fd, "SUCCESS\r\n");
    }
}

请谨慎使用此类代码。


3. 终极灵活性:通过「Swoole 启动事件」动态 listen

如果你的 think-swoole 版本配置项中没有直接提供 listeners 数组,或者你需要在运行时动态判断、加载多端口,最标准的高级做法是监听 Swoole 服务的初始化事件

  1. config/swoole.phplisten 字典中找到并绑定 swoole.init 事件:

    php

    'listen' => [
        'swoole.init' => [
            \app\listener\SwooleInitListener::class,
        ],
    ],
    

    请谨慎使用此类代码。

  2. 创建并编写 app\listener\SwooleInitListener.php 监听器:

    php

    namespace app\listener;
    
    class SwooleInitListener
    {
        /**
         * 框架在启动 Swoole Server 之前会触发该事件,并传入原始 Swoole\Server 实例
         */
        public function handle($server)
        {
            // 动态让主服务额外 listen 一个 9505 端口
            $port = $server->listen('0.0.0.0', 9505, SWOOLE_SOCK_TCP);
    
            if ($port === false) {
                throw new \RuntimeException("子端口 9505 监听失败");
            }
    
            // 为这个新增的端口单独设置参数
            $port->set([
                'open_http_protocol' => false,
                'open_websocket_protocol' => false,
            ]);
    
            // 绑定该端口独有的回调事件
            $port->on('connect', function ($server, $fd) {
                echo "子端口 9505 收到新的连接: fd={$fd}\n";
            });
    
            $port->on('receive', function ($server, $fd, $reactorId, $data) {
                // 联动系统的 Event 系统或直接注入逻辑
                $server->send($fd, "来自 9505 端口的回应: " . $data);
            });
    
            $port->on('close', function ($server, $fd) {
                echo "子端口 9505 连接断开: fd={$fd}\n";
            });
        }
    }
    

    请谨慎使用此类代码。


4. 高级配置「避坑与隔离」军规

在使用 listen 混合监听多端口时,必须注意以下底层逻辑,否则极易出现协议冲突或安全隐患:

  • ⚠️ 协议污染隔离(重要):子端口默认会继承主服务器(server)的 options 设置。如果主服务开启了 HTTP 或 WebSocket 协议,子端口默认也会去解析 HTTP 报文。如果你想在子端口跑纯 TCP(如硬件对接),必须在子端口的 settings 中显式地将 'open_http_protocol' => false'open_websocket_protocol' => false 关掉,否则客户端发来非 HTTP 数据时,Swoole 会直接报错并断开连接。

  • 回调事件的权限限制:子端口(Swoole\Server\Port)只能设置特定几个事件回调(如 connectreceivepacketclose)。像 workerStarttaskfinish 等管理类和多进程类事件只能由主服务器(Master)统一设置,子端口设置会直接报错。

  • 多端口状态下的 $fd 安全:在 ReceiveMessage 回调中,向客户端发送数据使用的是主服务的 $server->send($fd, $data)。Swoole 底层会自动根据 $fd 的数值识别它属于哪一个子端口并正确投递。但要注意,如果跨进程或者在 Task 异步任务中向客户端发消息,必须提前确认该 $fd 连接是否依然存活(使用 $server->exist($fd) 校验)。