当前位置: 首页
编程语言
ThinkPHP6.0基于Swoole实现TCP/UDP服务端长连接

ThinkPHP6.0基于Swoole实现TCP/UDP服务端长连接

热心网友 时间:2026-07-09
转载

ThinkPHP 6.0 本质上是一个 HTTP 框架,专注于处理短生命周期的 Web 请求,因此原生不支持纯 TCP/UDP 长连接服务端并不意外。然而,如果需要在 TP6 中实现实时通信(如即时通讯、设备指令下发、长连接通道),则必须借助底层网络扩展来提供辅助。

需要明确的是:think-swoole 和 think-worker 这两种方式都无法直接实现需求。think-swoole 专为 HTTP/WebSocket 场景设计,而 think-worker 本质上是 Workerman 的封装,与 Swoole 的原生协程模型不兼容,无法直接构建标准的 TCP/UDP Server。

真正可行的方向仅有一条:绕过 TP6 的生命周期,使用原生 Swoole 启动独立服务,然后通过 Redis、Swoole Table 等中间件与 TP6 应用进行数据联动。听起来有些复杂,但拆分后其实并不难理解。

ThinkPHP 6.0 使用 Swoole 实现 TCP/UDP 服务端【长连接】


一、为什么不能直接在 TP6 里写 Swoole TCP Server?

TP6 的 think-swoole 扩展仅封装了 SwooleHttpServerSwooleWebSocketServer,其启动逻辑完全绑定在 HTTP 请求的生命周期内。具体而言:

  • 启动时注册的是 onRequest 事件,而非 onConnectonReceive
  • 所有回调均运行在“HTTP 上下文”中,无法直接访问 $fd,不支持自定义协议解析,也缺少连接元信息管理的能力;
  • 若强行在 onReceive 中调用 TP6 容器或 ORM,极易引发协程上下文丢失、数据库连接复用混乱、内存泄漏等问题。

简单来说:不要在框架的河道里强行航行一艘海船。

二、正确的做法:分离架构 + 协同通信

将长连接服务与 Web 业务解耦,各司其职:

  • TCP/UDP 服务端:使用原生 SwooleServer 实现,负责连接管理、心跳保活、协议解析及广播路由;
  • TP6 Web 层:专注于用户认证、消息持久化、权限校验和 API 接口提供;
  • 协同枢纽:借助 SwooleTable(跨 Worker 共享连接状态)、Redis(发布/订阅以通知业务变更)、MySQL(存储消息和关系数据)来打通两端。

这就是关键——各尽其责,不要期望一个进程完成所有任务。

三、TCP 长连接服务端(Swoole 原生)代码实战

来看一个生产级的 TCP 长连接最小可行实现,已经适配了 SLB 和高并发场景:

// server/tcp_server.php
use SwooleServer;
use SwooleTable;

// 1. 创建跨 Worker 连接表
$table = new Table(2048);
$table->column('ip', SwooleTable::TYPE_STRING, 46);
$table->column('port', SwooleTable::TYPE_INT, 2);
$table->column('connect_time', SwooleTable::TYPE_INT, 8);
$table->column('last_active', SwooleTable::TYPE_INT, 8);
$table->create();

$serv = new Server('0.0.0.0', 9501, SWOOLE_PROCESS, SWOOLE_SOCK_TCP);
$serv->set([
    'worker_num'         => swoole_cpu_num() * 2,
    'heartbeat_idle_time'   => 300,     // 必须 > SLB 空闲超时(如阿里云默认 300s)
    'heartbeat_check_interval' => 60,  // 内核级心跳检测间隔
    'open_tcp_nodelay'      => true,
    'package_max_length'    => 10 * 1024 * 1024,
]);

$serv->on('Start', function ($serv) {
    echo "TCP server started at port {$serv->port}\n";
});

$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(),
    ]);
});

$serv->on('Receive', function ($serv, $fd, $from_id, $data) use ($table) {
    // 无条件刷新活跃时间(哪怕收到空包或非法帧)
    $table->set($fd, ['last_active' => time()]);

    // 示例:识别 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();

这段代码有几个值得注意的技术细节:心跳检测采用内核级机制,避免了 PHP 层的轮询开销;Task 进程专门处理耗时的业务逻辑,不会阻塞主 Reactor 线程;连接表直接存储在内存中,具备极快的读取速度。

四、UDP 服务端(轻量设备通信场景)

UDP 无连接、低开销,适合物联网终端上报、日志采集这类场景。不过要牢记:UDP 不保证送达,应用层需要自行实现重传和确认机制。

// server/udp_server.php
$serv = new SwooleServer('0.0.0.0', 9601, SWOOLE_PROCESS, SWOOLE_SOCK_UDP);

$serv->on('Packet', function ($serv, $data, $client_info) {
    // $client_info 包含 address/port,可作为唯一标识(类似 fd)
    $addr = $client_info['address'] . ':' . $client_info['port'];

    // 记录活跃时间(可用 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();

UDP 服务端的优势在于连接开销几乎为零,但相应的代价是需要自行处理丢包、乱序、重复等问题。

五、与 TP6 项目如何联动

核心原则是:不要让 TP6 直接启动 Swoole Server,而是让两者并行独立运行。

  • 启动方式:分别执行 php server/tcp_server.phpphp think serve(或者部署 Nginx + PHP-FPM);
  • 共享数据
    • 连接状态 → SwooleTable(内存级,毫秒级响应);
    • 用户会话/消息 → Redis(支持发布订阅,TP6 可监听事件进行异步处理);
    • 持久记录 → MySQL(TP6 负责写入,TCP Server 读取群组关系等配置);
  • 安全协同:TCP Server 收到设备登录请求后,调用 TP6 提供的 REST API(例如 POST /api/auth/bind)完成身份核验,再将授权状态写入 Redis,后续通信直接查询缓存即可。

该方案本身并不复杂,但容易忽略各组件之间的数据一致性保障。例如连接状态和认证令牌存放于不同位置,如何确保它们同步?建议将 TP6 的鉴权逻辑封装为独立服务,通过 Redis 或 HTTP 接口双向共享,避免出现“连接仍在、权限已过期”的尴尬情况。

来源:https://www.php.cn/faq/2787355.html

游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。

同类文章
更多
Debian下Golang跨平台开发方法指南

Debian下Golang跨平台开发方法指南

在Debian系统上,通过Go原生交叉编译、标准库跨平台抽象及合理代码设计,实现“一次编写,多平台运行”。方法包括环境配置、平台差异处理、交叉编译、依赖管理与多平台测试,最终生成稳定静态可执行文件。

时间:2026-07-09 06:54
Express服务器JSON请求体正确解析完整实践指南

Express服务器JSON请求体正确解析完整实践指南

Express应用中发现`req body`显示为`[Object]`,并非JSON解析失败,而是`console log()`默认对象缩略行为所致。使用`JSON stringify()`或`util inspect()`可完整查看数据结构。正确配置`express json()`中间件并设置请求头,即可确保解析成功。生产环境应避免直接输出敏感数据,建议限

时间:2026-07-09 06:54
Java泛型构造惯用模式:工厂模式替代反射与冗余参数

Java泛型构造惯用模式:工厂模式替代反射与冗余参数

Java接口无法声明构造方法,初始化泛型子类型时应使用工厂接口或Supplier函数式接口,避免反射与自引用泛型。工厂模式实现编译期安全、零反射开销、IDE友好,按需选用Supplier或专用工厂接口。

时间:2026-07-09 06:54
Debian系统Golang并发编程入门教程

Debian系统Golang并发编程入门教程

在Debian系统通过包管理器安装Golang,介绍并发编程:Goroutines是轻量级线程,用go关键字启动;Channels用于同步通信,两者结合实现高并发服务。

时间:2026-07-09 06:54
Debian下Golang机器学习库推荐与使用指南

Debian下Golang机器学习库推荐与使用指南

在Debian系统配置Golang环境后,可选用Gorgonia、Gonum和GoLearn等机器学习库。以Gorgonia为例,通过计算图定义线性回归模型,利用梯度下降优化均方误差,训练后即可预测新数据。

时间:2026-07-09 06:54
热门专题
更多
刀塔传奇破解版无限钻石下载大全 刀塔传奇破解版无限钻石下载大全
洛克王国正式正版手游下载安装大全 洛克王国正式正版手游下载安装大全
思美人手游下载专区 思美人手游下载专区
好玩的阿拉德之怒游戏下载合集 好玩的阿拉德之怒游戏下载合集
不思议迷宫手游下载合集 不思议迷宫手游下载合集
百宝袋汉化组游戏最新合集 百宝袋汉化组游戏最新合集
jsk游戏合集30款游戏大全 jsk游戏合集30款游戏大全
宾果消消消原版下载大全 宾果消消消原版下载大全
  • 日榜
  • 周榜
  • 月榜