ThinkPHP 6.0 不原生支持纯 TCP/UDP 长连接服务端,因其为 HTTP 框架,需借助原生 Swoole 启动独立服务,并通过 Redis、Swoole\Table 等与 TP6 协同。

ThinkPHP 6.0 本身不原生支持纯 TCP/UDP 长连接服务端,它是一个 HTTP 框架,设计目标是处理短生命周期的 Web 请求。若要在 TP6 中实现真正的 TCP/UDP 长连接(如 IM、设备通信、实时指令通道),必须借助底层网络扩展——不能靠 think-swoole(专为 HTTP/WebSocket 设计)或 think-worker(本质是 Workerman 封装,与 Swoole 原生协程模型不兼容)直接实现标准 TCP/UDP Server。
真正可行的路径只有一条:绕过 TP6 生命周期,用原生 Swoole 启动独立 TCP/UDP 服务,并通过统一数据层(如 Redis、Table、数据库)与 TP6 应用协同。
一、为什么不能直接在 TP6 中写 Swoole TCP Server?
TP6 的 think-swoole 扩展仅封装了 Swoole\Http\Server 和 Swoole\WebSocket\Server,其启动逻辑绑定在 HTTP 请求生命周期中:
– 启动时注册 onRequest,而非 onConnect/onReceive;
– 所有回调运行在「HTTP 上下文」里,无法访问 $fd、不支持自定义协议解析、不提供连接元信息管理能力;
– 若强行在 onReceive 中调用 TP6 容器或 ORM,会引发协程上下文丢失、DB 连接复用错乱、内存泄漏等问题。
二、正确做法:分离架构 + 协同通信
将长连接服务与 Web 业务解耦,各自专注所长:
-
TCP/UDP 服务端:用原生
Swoole\Server实现,负责连接管理、心跳保活、协议解析、广播路由; - TP6 Web 层:专注用户认证、消息持久化、权限校验、API 提供;
-
协同枢纽:用
Swoole\Table(跨 Worker 共享连接状态)、Redis(发布订阅通知业务变更)、MySQL(存消息/关系)打通两边。
三、TCP 长连接服务端(Swoole 原生)关键代码
以下为生产级 TCP 长连接最小可行实现,已适配 SLB 和高并发场景:
// server/tcp_server.php
use Swoole\Server;
use Swoole\Table;
<p>// 1. 创建跨 Worker 连接表
$table = new Table(2048);
$table->column('ip', \Swoole\Table::TYPE_STRING, 46);
$table->column('port', \Swoole\Table::TYPE_INT, 2);
$table->column('connect_time', \Swoole\Table::TYPE_INT, 8);
$table->column('last_active', \Swoole\Table::TYPE_INT, 8);
$table->create();</p><p>$serv = new Server('0.0.0.0', 9501, SWOOLE_PROCESS, SWOOLE_SOCK_TCP);
$serv->set([
'worker_num' => swoole_cpu_num() <em> 2,
'heartbeat_idle_time' => 300, // 必须 > SLB 空闲超时(如阿里云默认 300s)
'heartbeat_check_interval' => 60, // 内核级心跳检测间隔
'open_tcp_nodelay' => true,
'package_max_length' => 10 </em> 1024 * 1024,
]);</p><p>$serv->on('Start', function ($serv) {
echo "TCP server started at port {$serv->port}\n";
});</p><p>$serv->on('Connect', function ($serv, $fd, $from_id) use ($table) {
$info = $serv->getClientInfo($fd);
$table->set($fd, [
'ip' => $info['remote_ip'],
'port' => $info['remote_port'],
'connect_time' => time(),
'last_active' => time(),
]);
});</p><p>$serv->on('Receive', function ($serv, $fd, $from_id, $data) use ($table) {
// 无条件刷新活跃时间(哪怕收到空包或非法帧)
$table->set($fd, ['last_active' => time()]);</p><pre class="brush:php;toolbar:false;">// 示例:识别 PING 心跳并立即回 PONG
if (trim($data) === 'PING') {
$serv->send($fd, "PONG\n");
return;
}
// 解析业务协议(如 JSON / 自定义二进制),投递到 Task 进程避免阻塞
$serv->task(['fd' => $fd, 'data' => $data]);});
$serv->on('Task', function ($serv, $task_id, $from_id, $data) { // 在此处做 DB 查询、Redis 推送、权限校验等耗时操作 // 注意:不可调用 $serv->send(),需用 finish 或 onFinish 回传 $result = handleBusinessLogic($data['data']); $serv->finish(['fd' => $data['fd'], 'result' => $result]); });
$serv->on('Finish', function ($serv, $task_id, $data) { if (isset($data['fd']) && $serv->exist($data['fd'])) { $serv->send($data['fd'], json_encode($data['result']) . "\n"); } });
$serv->on('Close', function ($serv, $fd) use ($table) { $table->del($fd); // 清理连接元信息 });
$serv->start();
四、UDP 服务端(轻量设备通信适用)
UDP 无连接、低开销,适合物联网终端上报、日志采集等场景。注意:UDP 不保证送达,需应用层补重传/确认机制。
// server/udp_server.php
$serv = new Swoole\Server('0.0.0.0', 9601, SWOOLE_PROCESS, SWOOLE_SOCK_UDP);
<p>$serv->on('Packet', function ($serv, $data, $client_info) {
// $client_info 包含 address/port,可作为唯一标识(类似 fd)
$addr = $client_info['address'] . ':' . $client_info['port'];</p><pre class="brush:php;toolbar:false;">// 记录活跃时间(可用 Redis hash 存)
redis()->hSet('udp_clients', $addr, time());
redis()->expire('udp_clients', 300);
// 解析并响应
$response = processUdpPacket($data);
$serv->sendto($client_info['address'], $client_info['port'], $response);});
$serv->start();
五、与 TP6 项目联动方式
不要让 TP6 启动 Swoole Server,而是让两者并行运行:
-
启动方式:分别运行
php server/tcp_server.php和php think serve(或部署 Nginx + PHP-FPM); -
共享数据:
- 连接状态 →
Swoole\Table(内存级,毫秒响应); - 用户会话/消息 → Redis(支持 pub/sub,TP6 可监听事件);
- 持久记录 → MySQL(TP6 负责写,TCP Server 读取群组关系等);
- 连接状态 →
-
安全协同:TCP Server 收到设备登录请求后,调用 TP6 提供的 REST API(如
POST /api/auth/bind)完成身份核验,再写入授权状态到 Redis,后续通信直接查缓存。
不复杂但容易忽略。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











