路由器NAT超时设置引起的APP的TCP长连接丢失

本文记录了一名开发者解决游戏中长连接在WIFI路由器NAT超时情况下丢失的问题,通过调整服务器心跳包发送间隔为45秒以适应不同路由器的超时设置,确保了连接稳定性。

之前碰到一个奇怪的现象,玩家在打战场的时候,由于一场打下来时间比较长,结果战斗结束之后,丢失了与Portal Server的连接,但奇怪的是没有收到任何连接丢失的事件。能成功发送消息,但服务器收不到,也无法收到从服务器来的消息。

先以为是切后台之后连接被系统回收了,但反馈是根本没有任何切后台的操作。于是怀疑是不是客户端的网络的线程模型有问题,检查之后,发现线程模型没有大毛病,而且线程也是在很正常的运行。只是没有收到任何连接丢失的事件。

随着收集的样本越来越多,发现一个共同点,就是这些APP都是在WIFI的条件下出现问题,一旦切换到手机网络就没有这个问题。这让我意识到可能与路由器的设置有关。

于是查了一下关于路由器的资料,发现如果使用NAT,那么在端口映射的表内有一个超时时间,而这个超时时间各个路由器的出厂设置不一样,有一天,有1分钟,有两小时等等。而我们长连接在没有消息的时候,服务器会定时的发送心跳包来检测,但设定的时间是5分钟。

而这个问题反馈得比较多,看来5分钟已经无法满足现在得路由器的检测要求。于是将心跳包的发送间隔改为45秒。这个问题得以解决。

### 什么是心跳机制?为什么长连接需要心跳? **心跳机制**(Heartbeat Mechanism)是一种用于检测长连接是否仍然存活的技术。它通过客户端或服务器定期发送一个微小的“探测包”(称为心跳包),来确认对方是否在线、网络是否通畅。 如果一段时间内没有收到对方的心跳回应,就认为连接已经断开,可以进行重连或资源清理。 --- #### 🧠 类比理解:两个人打电话 想象你和朋友打语音电话: - 你说:“喂,你在吗?” —— 这就是一次**心跳请求**(ping) - 对方回:“在的!” —— 这是**心跳响应**(pong) 但如果对方很久不说话,你也听不到任何声音,你就怀疑: > “是不是掉线了?还是他手机没电了?” 于是你再喊一声:“还在吗?” 如果还是没人回应 → 你决定挂电话 → **关闭连接** 这就是心跳机制的核心逻辑:**保活 + 探测 + 断线识别** --- ### ✅ 为什么长连接需要心跳机制? 长连接虽然“一直开着”,但在真实网络环境中,可能因为以下原因导致连接看似存在,实则已失效: | 问题 | 说明 | |------|------| | 网络中断 | WiFi 断开、切换4G、路由器重启等 | | 客户端崩溃 | App 被杀掉、手机休眠、浏览器关闭 | | 中间设备超时 | NAT 超时、防火墙/代理自动断开空闲连接(通常60~300秒) | | TCP 半开连接 | 一端已断开,另一端还不知道(TCP 没有主动通知) | > ❗ 在这些情况下,操作系统或中间设备不会主动通知对方“我断了”,所以必须靠**心跳**来发现异常。 --- ### 🔁 心跳机制的工作方式 通常有两种实现模式: #### 1. **定时 ping-pong 模式** - 双方约定每隔一段时间(如 30 秒)发送一次心跳。 - 发送方发 `ping`,接收方回复 `pong`。 - 如果连续几次未收到 `pong`,判定连接失败。 ```text Client Server | --- ping -----------> | | <- pong --------------| | | | (等待30秒) | | --- ping -----------> | | <- pong --------------| ``` #### 2. **仅发送心跳数据(单向探测)** - 一方持续发送心跳消息(如 WebSocket 的文本帧 `"heartbeat"`) - 不强制要求回应,但接收方可通过应用层逻辑判断是否超时 --- ### 💡 实际代码示例:WebSocket + 心跳机制(Node.js) ```javascript const WebSocket = require('ws'); const wss = new WebSocket.Server({ port: 8080 }); wss.on('connection', function connection(ws) { console.log('客户端已连接'); // 设置定时器:每 15 秒发送一次 ping const heartbeatInterval = setInterval(() => { if (ws.readyState === WebSocket.OPEN) { ws.ping(); // 发送一个 ping 帧(底层协议级) console.log('发送 ping'); } }, 15_000); // 监听 pong 回应(用于检测连接是否健康) ws.on('pong', () => { console.log('收到 pong,连接正常'); }); // 当连接关闭时清除定时器 ws.on('close', () => { clearInterval(heartbeatInterval); console.log('客户端断开'); }); }); console.log('WebSocket 服务启动在 ws://localhost:8080'); ``` > ⚠️ 注意:`ws.ping()` 是 WebSocket 协议内置的心跳帧(OpCode=9),属于控制帧,比发普通消息更轻量。 --- ### 📱 客户端也应设置心跳监听 ```javascript const socket = new WebSocket('ws://localhost:8080'); let isAlive = true; socket.onopen = () => { console.log('连接建立'); // 启动心跳检测 const heartbeat = () => { if (!isAlive) { console.warn('未收到 pong,连接可能已断开'); socket.close(); return; } isAlive = false; socket.send('ping'); // 应用层心跳 }; setInterval(heartbeat, 15_000); }; socket.onmessage = (event) => { if (event.data === 'pong') { isAlive = true; // 收到回应,标记为存活 } else { console.log('收到消息:', event.data); } }; ``` --- ### ⚙️ 心跳参数设计建议 | 参数 | 推荐值 | 说明 | |------|--------|------| | 心跳间隔 | 15~30 秒 | 太短浪费流量,太长无法及时感知断线 | | 超时时间 | ≤ 3 倍心跳间隔 | 如 30s 间隔,则最多等 90s 无响应才断开 | | 是否使用协议级 ping/pong | 推荐 | 更高效,不影响业务数据流 | --- ### ✅ 心跳机制的应用场景 | 场景 | 使用技术 | 是否需要心跳 | |------|----------|---------------| | 实时聊天 | WebSocket / TCP 长连接 | ✅ 必须 | | 推送系统 | SSE / WebSocket | ✅ 必须 | | 视频直播 | RTMP / WebRTC | ✅ 需要保活 | | HTTP 短轮询 | HTTP/1.1 Keep-Alive | ❌ 不需要(每次都是新请求) | | gRPC 流通信 | gRPC Stream | ✅ 支持 keepalive 配置 | --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值