在 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 服务的初始化事件。
在
config/swoole.php的listen字典中找到并绑定swoole.init事件:php
'listen' => [ 'swoole.init' => [ \app\listener\SwooleInitListener::class, ], ],请谨慎使用此类代码。
创建并编写
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)只能设置特定几个事件回调(如connect、receive、packet、close)。像workerStart、task、finish等管理类和多进程类事件只能由主服务器(Master)统一设置,子端口设置会直接报错。多端口状态下的 $fd 安全:在
Receive或Message回调中,向客户端发送数据使用的是主服务的$server->send($fd, $data)。Swoole 底层会自动根据$fd的数值识别它属于哪一个子端口并正确投递。但要注意,如果跨进程或者在 Task 异步任务中向客户端发消息,必须提前确认该$fd连接是否依然存活(使用$server->exist($fd)校验)。