在 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),
// ...
],
// ... 其他默认配置
];
请谨慎使用此类代码。
💡 为什么默认生成的配置里找不到这些?
隐藏高级特性:多端口监听(
listen)和原生回调(callbacks)属于 Swoole 的高级进阶用法,TP6 官方为了保证配置文件的简洁性,默认生成的模板里没有打印出这一项。但think-swoole框架底层是完全支持并会去解析这个键名的。官方自带的 RPC:你会发现默认配置里有一个
'rpc' => [...]的区域。那是think-swoole官方自带的一套基于 Hprose/Swoole 的 RPC 方案。因为我们现在是完全自主封装一套对前、后端都极度可控的轻量级 JSON-RPC 客户端与服务端,所以官方自带的那个rpc.enable保持false关闭即可,互不干扰。
您按照这个结构把 listen 部分插到您现有的 server 数组里,再重启 php think swoole 就能顺利运行了。
在 config/swoole.php 的 callbacks 配置中,完全可以使用类名与方法名(例如 [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']
]
],
],
],
// ... 其他默认配置 ...
];
请谨慎使用此类代码。
💡 生产环境避坑与原理说明
单例与多例问题:
通过[\app\common\rpc\RpcHandler::class, 'onReceive']的方式配置后,think-swoole会在 Worker 进程启动时 通过app()->make()实例化该类。这意味着在同一个 Worker 进程内,
RpcHandler是常驻内存的单例对象。如果你在
RpcHandler的构造函数中注入了数据库连接或敏感数据,请注意多请求之间状态的清理。
热更新限制:
被绑定在callbacks中的类(包括其内部依赖的RpcDispatcher以及您配置的UserService),在修改代码后,普通的 TP6 热更新(Hot Update)可能会失效。原因:Swoole 的端口监听和回调是在 Master/Manager 进程初始化时就已经加载并常驻内存的。
解决办法:每次修改了 RPC 相关的业务代码后,必须在终端执行
php think swoole restart重启整个 Swoole 服务才能生效。
您将配置修改为 Class 类后,代码的解耦度就非常高了。