多端口监听(listen)和原生回调(callbacks)

在 ThinkPHP6 中,通过官方命令生成的默认 config/swoole.php 配置文件,其结构是一个庞大的、多维的一维或嵌套关联数组,并且默认不包含 listen 这一项配置

如果您直接把我上面给出的简版 server 数组覆盖进去,会导致原本的默认配置(如热更新、连接池、RPC等)丢失。

为了不破坏 TP6 原有的配置体系,我们需要在官方默认生成的 config/swoole.php 中寻找对应的位置进行精细化修改。请按照以下步骤,把多端口监听和原生回调插入到您现有的配置文件中:

正确的合并与修改步骤

打开您项目现有的 config/swoole.php,找到 'server' 这一项,然后按照以下格式追加 'listen' 配置:

php

<?php
// config/swoole.php (这是 TP6 默认生成的配置文件结构)

return [
    'server'     => [
        'host'      => env('SWOOLE_HOST', '127.0.0.1'),
        'port'      => env('SWOOLE_PORT', 80),
        'mode'      => SWOOLE_PROCESS,
        'sock_type' => SWOOLE_SOCK_TCP,
        'options'   => [
            'pid_file'              => runtime_path() . 'swoole.pid',
            'log_file'              => runtime_path() . 'swoole.log',
            'daemonize'             => false,
            // ... 这里保留你默认生成的其他 options 项 ...
        ],
        
        // 🌟 核心:不要覆盖上面的,请在这里【追加】listen 数组
        'listen'    => [
            [
                'host'      => '0.0.0.0', // 监听任意IP
                'port'      => 9502,      // RPC端口
                'sock_type' => SWOOLE_SOCK_TCP,
                'options'   => [
                    'open_eof_check' => true,
                    'package_eof'    => "\r\n",
                ],
                // 🚀 在这里绑定原生 Swoole 回调
                'callbacks' => [
                    \Swoole\Constant::EVENT_RECEIVE => function ($server, $fd, $reactorId, $rawData) {
                        /** @var \app\common\rpc\RpcDispatcher $dispatcher */
                        $dispatcher = app(\app\common\rpc\RpcDispatcher::class);
                        
                        // 调度并解析
                        $responsePayload = $dispatcher->dispatch($rawData);
                        
                        // 直接发回客户端
                        $server->send($fd, $responsePayload);
                    }
                ]
            ],
        ],
    ],
    
    // 下面这些是 TP6 默认生成的,请【保持原样】,千万不要删掉:
    'websocket'  => [
        'enable'        => false,
        // ...
    ],
    'rpc'        => [
        'enable'        => false, // 注意:这是think-swoole自带的官方RPC开关,我们用自定义封装的,这里保持false即可
        // ...
    ],
    'hot_update' => [
        'enable'  => env('APP_DEBUG', false),
        // ...
    ],
    // ... 其他默认配置
];

请谨慎使用此类代码。

💡 为什么默认生成的配置里找不到这些?

  1. 隐藏高级特性:多端口监听(listen)和原生回调(callbacks)属于 Swoole 的高级进阶用法,TP6 官方为了保证配置文件的简洁性,默认生成的模板里没有打印出这一项。但 think-swoole 框架底层是完全支持并会去解析这个键名的。

  2. 官方自带的 RPC:你会发现默认配置里有一个 'rpc' => [...] 的区域。那是 think-swoole 官方自带的一套基于 Hprose/Swoole 的 RPC 方案。因为我们现在是完全自主封装一套对前、后端都极度可控的轻量级 JSON-RPC 客户端与服务端,所以官方自带的那个 rpc.enable 保持 false 关闭即可,互不干扰。

您按照这个结构把 listen 部分插到您现有的 server 数组里,再重启 php think swoole 就能顺利运行了。

config/swoole.phpcallbacks 配置中,完全可以使用类名与方法名(例如 [ClassName::class, 'methodName'])来代替匿名闭包函数。

使用类名不仅可以让 config/swoole.php 配置文件变得干净、好维护,还能方便我们进行更复杂的依赖注入对象状态管理

以下是使用 Class 类重构后的完整落地方案:

1. 创建专门的 RPC 处理器类 (Handler)

app\common\rpc 目录下创建 RpcHandler.php。这个类中的 onReceive 方法将作为 Swoole 专属的事件回调函数:

php

<?php
namespace app\common\rpc;

class RpcHandler
{
    private RpcDispatcher $dispatcher;

    /**
     * 依赖注入:TP6 容器会自动实例化 RpcDispatcher 传入
     */
    public function __construct(RpcDispatcher $dispatcher)
    {
        $this->dispatcher = $dispatcher;
    }

    /**
     * 对应 Swoole 的 onReceive 标准回调参数
     */
    public function onReceive($server, int $fd, int $reactorId, string $rawData): void
    {
        try {
            // 调度并解析
            $responsePayload = $this->dispatcher->dispatch($rawData);
            
            // 发回客户端
            $server->send($fd, $responsePayload);
            
        } catch (\Throwable $e) {
            $errorResponse = json_encode([
                'jsonrpc' => '2.0',
                'error'   => ['code' => -32603, 'message' => 'Internal handler error'],
                'id'      => null
            ]) . "\r\n";
            $server->send($fd, $errorResponse);
        }
    }
}

请谨慎使用此类代码。

2. 在 config/swoole.php 中使用 Class 配置

现在我们可以把 config/swoole.php 里那一长串的匿名函数代码全部删掉,直接引用刚才创建的类和方法。由于 think-swoole 框架底层支持使用 TP6 的容器机制,它会自动将这个类实例化并进行依赖注入

修改 config/swoole.php 里的 listen 区域:

php

<?php
// config/swoole.php

return [
    'server' => [
        // ... 其他 host, port 等配置保持不变 ...
        
        'listen' => [
            [
                'host'      => '0.0.0.0',
                'port'      => 9502,
                'sock_type' => SWOOLE_SOCK_TCP,
                'options'   => [
                    'open_eof_check' => true,
                    'package_eof'    => "\r\n",
                ],
                // 🚀 核心:替换为 Class 配置方式
                'callbacks' => [
                    \Swoole\Constant::EVENT_RECEIVE => [\app\common\rpc\RpcHandler::class, 'onReceive']
                ]
            ],
        ],
    ],
    // ... 其他默认配置 ...
];

请谨慎使用此类代码。


💡 生产环境避坑与原理说明

  1. 单例与多例问题
    通过
    [\app\common\rpc\RpcHandler::class, 'onReceive'] 的方式配置后,think-swoole 会在 Worker 进程启动时 通过 app()->make() 实例化该类。

    • 这意味着在同一个 Worker 进程内,RpcHandler 是常驻内存的单例对象。

    • 如果你在 RpcHandler 的构造函数中注入了数据库连接或敏感数据,请注意多请求之间状态的清理。

  2. 热更新限制
    被绑定在
    callbacks 中的类(包括其内部依赖的 RpcDispatcher 以及您配置的 UserService),在修改代码后,普通的 TP6 热更新(Hot Update)可能会失效

    • 原因:Swoole 的端口监听和回调是在 Master/Manager 进程初始化时就已经加载并常驻内存的。

    • 解决办法:每次修改了 RPC 相关的业务代码后,必须在终端执行 php think swoole restart 重启整个 Swoole 服务才能生效。

您将配置修改为 Class 类后,代码的解耦度就非常高了。