客户端进程被杀、WiFi 切 4G、NAT 超时,这些场景下 TCP 连接不会立刻断开,服务器要靠心跳判定连接死活。心跳间隔太短浪费流量,太长则死连接占用资源,玩家换网络后还可能收到幽灵状态,网关层的心跳保活是长连接游戏的标配。
local HEARTBEAT_INTERVAL = 15
local HEARTBEAT_TIMEOUT = 45
function Gateway.onHeartbeat(conn, clientTime)
conn.lastAlive = os.time()
conn.rtt = os.time() - clientTime
QF_Send(conn, "heartbeat_ack", { t = os.time() })
end
客户端每 15 秒发一次心跳,服务器回 ack 并顺带做时间对齐。超时线设在 45 秒,容忍连续两次丢失,移动网络的单次抖动不至于误杀。
function Gateway.sweep()
local now = os.time()
for _, conn in pairs(connections) do
if now - conn.lastAlive > HEARTBEAT_TIMEOUT then
Gateway.close(conn, "heartbeat_timeout")
end
end
end
清理扫描每 15 秒跑一轮,死连接关闭时触发玩家离线流程,清理挂机占用的地图资源与组队状态。心跳还顺带承载往返延迟采样,rtt 数据进入分地区质量报表,运营商网络问题据此定位。NAT 环境下 15 秒间隔能维持大多数运营商的映射表项,实测 30 秒就会出现单向假活,这也是间隔不能放大的实证。
心跳超时导致的断线占比按网络类型统计,正常应低于总断线的两成。
最早的心跳由业务层处理,某个业务 bug 吞掉心跳包后全服玩家被批量误踢,心跳下沉到网关层与业务严格隔离后才稳定。重连风暴场景也优化过:大版本更新后十万连接同时重连,心跳扫描的全表遍历造成主循环尖峰,改成时间轮分桶扫描,单轮耗时从 40 毫秒压到 2 毫秒。
心跳包的载荷设计留了扩展位,早期只有时间戳,后来顺带携带客户端版本号与网络类型,网关层的质量报表因此能按版本与网络细分。心跳超时的处置做了分级:第一次超时发探测包主动询问,第二次超时才断开连接,两次探测的设计把弱网玩家的误踢率压到千分之一以下。重连后的心跳节奏自动降频,后台挂机场景的心跳间隔放宽到六十秒,流量敏感的玩家感知友好。
本文由 996 技术组基于 996 引擎官方知识库与浮生梦老师课程体系整理。团队长期从事传奇类引擎 Lua 后端逻辑、客户端界面与商业版本交付,内容以官方知识库与真实项目为出处,按版本持续修订。
全站技术干货持续更新:996 引擎 / Lua 实战帖,语法、参数与示例一篇讲透。进入文章地图 · 查看全部 →
实战应用:用在哪里 行会内部的物资交换长期靠喊话:谁有强效太阳水、谁收祝福油,喊话靠缘分看见。行会商会日把内部交易固定成市集…
实战应用:用在哪里 角色存档直接输出到展示接口时,内部字段(GM 标记、风控计数、内部编号)会被一并带出去,轻则泄露信息,重…
实战应用:用在哪里 排查一个玩家的异常行为,要在十几种日志里来回 grep,一次排查半小时起步。日志检索工具按角色名聚合所有…
实战应用:用在哪里 开服首日的问题七成是低级失误:礼包配置没发、传送点没开、公告没挂。开服检查单把首日开放前的三十项检查固化…
实战应用:用在哪里 祖玛阁深处有一间密室,墙上有四块浮雕,按正确顺序踩亮才能打开暗格,顺序每天随机。序列触发设计让解谜每日一…
实战应用:用在哪里 点击传送后站在黑屏里等五秒,是切图体验最差的来源:地图资源在传送确认后才开始加载,加载完才渲染。切图预热…