默认
打赏 发表评论 57
想开发IM:买成品怕坑?租第3方怕贵?找开源自已撸?尽量别走弯路了... 找站长给点建议
为什么说基于TCP的移动端IM仍然需要心跳保活?
微信扫一扫关注!

本文原作者:项望烽,毕业于浙江大学,目前是网易云信 iOS 端研发负责人。文章内容有修改,感谢原作者的分享。


1、前言


很多人认为,TCP协议自身先天就有KeepAlive机制,为何基于它的通讯链接,仍然需要在应用层实现额外的心跳保活?本文将从移动端IM实践的角度告诉你,即使使用的是TCP协议,应用层的心跳保活仍旧必不可少。

有关TCP协议的权威理论介绍,请参见《TCP/IP详解》这本书(尤其 第23章·TCP的保活定时器)。

相关文章:


2、参考资料


TCP/IP详解 - 第11章·UDP:用户数据报协议
TCP/IP详解 - 第17章·TCP:传输控制协议
TCP/IP详解 - 第18章·TCP连接的建立与终止
TCP/IP详解 - 第21章·TCP的超时与重传
通俗易懂-深入理解TCP协议(上):理论基础
通俗易懂-深入理解TCP协议(下):RTT、滑动窗口、拥塞处理
理论经典:TCP协议的3次握手与4次挥手过程详解
计算机网络通讯协议关系图(中文珍藏版)
NAT详解:基本原理、穿越技术(P2P打洞)、端口老化等

3、本文源起


做移动端IM多年以来,经常会与相关人员进行讨论和交流。也经常会碰到些较真的技术人员询问技术细节,如主流的移动端IM如何做心跳、如何保证消息必达、如何加快文件上传等。因为平时工作太忙,没有时间深入整理和总结,往往只能简略介绍,并不能具体展开,于是决定写成文字,也有了有关移动 IM 问题处理的系列文章。

4、什么是心跳保活?


a.jpg

在使用 TCP 长连接的 IM 服务设计中,往往都会涉及到心跳。心跳一般是指某端(绝大多数情况下是客户端)每隔一定时间向对端发送自定义指令,以判断双方是否存活,因其按照一定间隔发送,类似于心跳,故被称为心跳指令。

有兴趣了解IM/推送的心跳保活技术的文章,请参见:

Android进程保活详解:一篇文章解决你的所有疑问
Android端消息推送总结:实现原理、心跳保活、遇到的问题等
微信团队原创分享:Android版微信后台保活实战分享(进程保活篇)
微信团队原创分享:Android版微信后台保活实战分享(网络保活篇)
移动端IM实践:实现Android版微信的智能心跳机制
移动端IM实践:WhatsApp、Line、微信的心跳策略分析
>> 更多同类文章 ……

5、TCP协议不是自带KeepAlive的吗?


那么问题就随之而来了:为什么需要在应用层做心跳,难道 TCP 不是个可靠连接吗?我们不能够依赖 TCP 做断线检测吗?比如使用 TCP 的 KeepAlive 机制来实现。应用层心跳是目前的最佳实践吗?怎么样的心跳才是最佳实践。

很多做移动端IM的同行,以前确实没有仔细考虑过这些问题,潜意识里想当然的认为这仅仅只是个简单的心跳而已啊。好吧,事实并非这么简单,请继续往下看。

6、IM中保持有效长连接的重要性


对于客户端而言,使用 TCP 长连接来实现业务的最大驱动力在于:在当前连接可用的情况下,每一次请求都只是简单的数据发送和接受,免去了 DNS 解析,连接建立等时间,大大加快了请求的速度,同时也有利于接受服务器的实时消息。但前提是连接可用。

如果连接无法很好地保持,每次请求就会变成撞大运:运气好,通过长连接发送请求并收到反馈。运气差,当前连接已失效,请求迟迟没有收到反馈直到超时,又需要一次连接建立的过程,其效率甚至还不如 HTTP。而连接保持的前提必然是检测连接的可用性,并在连接不可用时主动放弃当前连接并建立新的连接。

基于这个前提,必须要有一种机制用于检测连接可用性。同时移动网络的特殊性也要求客户端需要在空余时间发送一定的信令,避免连接被回收。详见微信和运营商的撕B(另一篇针对微信的信令风暴技术研究文章请见:微信对网络影响的技术试验及分析

而对于服务器而言,能够及时获悉连接可用性也非常重要:一方面服务器需要及时清理无效连接以减轻负载,另一方面也是业务的需求,如游戏副本中服务器需要及时处理玩家掉线带来的问题。

7、TCP的KeepAlive无法替代应用层心跳保活机制的原因


上面说了保持连接的重要性,那么现在回到具体实现上。为什么我们需要使用应用层心跳来做检测,而不是直接使用 TCP 的特性呢?

我们知道 TCP 是一个基于连接的协议,其连接状态是由一个状态机进行维护,连接完毕后,双方都会处于 established 状态,这之后的状态并不会主动进行变化。这意味着如果上层不进行任何调用,一直使 TCP 连接空闲,那么这个连接虽然没有任何数据,但仍是保持连接状态,一天、一星期、甚至一个月,即使在这期间中间路由崩溃重启无数次。举个现实中经常遇到的栗子:当我们 ssh 到自己的 VPS 上,然后不小心踢掉网线,此时的网络变化并不会被 TCP 检测出,当我们重新插回网线,仍旧可以正常使用 ssh,同时此时并没有发生任何 TCP 的重连。

有人会说 TCP 不是有 KeepAlive 机制么,通过这个机制来实现不就可以了吗?但是事实上,TCP KeepAlive 的机制其实并不适用于此。Keep Alive 机制开启后,TCP 层将在定时时间到后发送相应的 KeepAlive 探针以确定连接可用性。一般时间为 7200 s(详情请参见《TCP/IP详解》中第23章),失败后重试 10 次,每次超时时间 75 s。显然默认值无法满足我们的需求,而修改过设置后就可以满足了吗?答案仍旧是否定的。

因为 TCP KeepAlive 是用于检测连接的死活,而心跳机制则附带一个额外的功能:检测通讯双方的存活状态。两者听起来似乎是一个意思,但实际上却大相径庭。

考虑一种情况,某台服务器因为某些原因导致负载超高,CPU 100%,无法响应任何业务请求,但是使用 TCP 探针则仍旧能够确定连接状态,这就是典型的连接活着但业务提供方已死的状态,对客户端而言,这时的最好选择就是断线后重新连接其他服务器,而不是一直认为当前服务器是可用状态,一直向当前服务器发送些必然会失败的请求。

从上面我们可以知道,KeepAlive 并不适用于检测双方存活的场景,这种场景还得依赖于应用层的心跳。应用层心跳有着更大的灵活性,可以控制检测时机,间隔和处理流程,甚至可以在心跳包上附带额外信息。从这个角度而言,应用层的心跳的确是最佳实践。

PS:如果你对tcp的KeepAlive扔有疑问,可以详读《不为人知的网络编程(十二):彻底搞懂TCP协议层的KeepAlive保活机制》。

8、心跳保活机制的实现方案参考


从上面我们可以得出结论,目前而言,应用层心跳的确是检测连接有效性,双方是否存活的最佳实践,那么剩下的问题就是怎么实现。

最简单粗暴做法当然是定时心跳,如每隔 30 秒心跳一次,15 秒内没有收到心跳回包则认为当前连接已失效,断开连接并进行重连。这种做法最直接,实现也简单。唯一的问题是比较耗电和耗流量。以一个协议包 5 个字节计算,一天收发 2880 个心跳包,一个月就是 5 * 2 * 2880 * 30 = 0.8 M 的流量,如果手机上多装几个 IM 软件,每个月光心跳就好几兆流量没了,更不用说频繁的心跳带来的电量损耗。

既然频繁心跳会带来耗电和耗流量的弊端,改进的方向自然是减少心跳频率,但也不能过于影响连接检测的实时性。基于这个需求,一般可以将心跳间隔根据程序状态进行调整,当程序在后台时(这里主要考虑安卓),尽量拉长心跳间隔,5 分钟、甚至 10 分钟都可以。

而当 App 在前台时则按照原来规则操作。连接可靠性的判断也可以放宽,避免一次心跳超时就认为连接无效的情况,使用错误积累,只在心跳超时 n 次后才判定当前连接不可用。当然还有一些小 trick 比如从收到的最后一个指令包进行心跳包周期计时而不是固定时间,这样也能够一定程度减少心跳次数。

9、更多文章


微信对网络影响的技术试验及分析
移动端IM开发需要面对的技术问题
iOS端移动网络调优的8条建议

(更多有关IM心跳保活方面的文章,请参见:http://www.52im.net/forum.php?mod=collection&action=view&ctid=17

附录:更多相关资料


[1] 网络编程基础资料:
TCP/IP详解 - 第11章·UDP:用户数据报协议
TCP/IP详解 - 第17章·TCP:传输控制协议
TCP/IP详解 - 第18章·TCP连接的建立与终止
TCP/IP详解 - 第21章·TCP的超时与重传
技术往事:改变世界的TCP/IP协议(珍贵多图、手机慎点)
通俗易懂-深入理解TCP协议(上):理论基础
通俗易懂-深入理解TCP协议(下):RTT、滑动窗口、拥塞处理
理论经典:TCP协议的3次握手与4次挥手过程详解
理论联系实际:Wireshark抓包分析TCP 3次握手、4次挥手过程
计算机网络通讯协议关系图(中文珍藏版)
UDP中一个包的大小最大能多大?
P2P技术详解(一):NAT详解——详细原理、P2P简介
P2P技术详解(二):P2P中的NAT穿越(打洞)方案详解(基本原理篇)
P2P技术详解(三):P2P中的NAT穿越(打洞)方案详解(进阶分析篇)
P2P技术详解(四):P2P技术之STUN、TURN、ICE详解
通俗易懂:快速理解P2P技术中的NAT穿透原理
高性能网络编程(一):单台服务器并发TCP连接数到底可以有多少
高性能网络编程(二):上一个10年,著名的C10K并发连接问题
高性能网络编程(三):下一个10年,是时候考虑C10M并发问题了
高性能网络编程(四):从C10K到C10M高性能网络应用的理论探索
高性能网络编程(五):一文读懂高性能网络编程中的I/O模型
高性能网络编程(六):一文读懂高性能网络编程中的线程模型
Java的BIO和NIO很难懂?用代码实践给你看,再不懂我转行!
不为人知的网络编程(一):浅析TCP协议中的疑难杂症(上篇)
不为人知的网络编程(二):浅析TCP协议中的疑难杂症(下篇)
不为人知的网络编程(三):关闭TCP连接时为什么会TIME_WAIT、CLOSE_WAIT
不为人知的网络编程(四):深入研究分析TCP的异常关闭
不为人知的网络编程(五):UDP的连接性和负载均衡
不为人知的网络编程(六):深入地理解UDP协议并用好它
不为人知的网络编程(七):如何让不可靠的UDP变的可靠?
不为人知的网络编程(八):从数据传输层深度解密HTTP
不为人知的网络编程(九):理论联系实际,全方位深入理解DNS
网络编程懒人入门(一):快速理解网络通信协议(上篇)
网络编程懒人入门(二):快速理解网络通信协议(下篇)
网络编程懒人入门(三):快速理解TCP协议一篇就够
网络编程懒人入门(四):快速理解TCP和UDP的差异
网络编程懒人入门(五):快速理解为什么说UDP有时比TCP更有优势
网络编程懒人入门(六):史上最通俗的集线器、交换机、路由器功能原理入门
网络编程懒人入门(七):深入浅出,全面理解HTTP协议
网络编程懒人入门(八):手把手教你写基于TCP的Socket长连接
网络编程懒人入门(九):通俗讲解,有了IP地址,为何还要用MAC地址?
网络编程懒人入门(十):一泡尿的时间,快速读懂QUIC协议
技术扫盲:新一代基于UDP的低延时网络传输层协议——QUIC详解
让互联网更快:新一代QUIC协议在腾讯的技术实践分享
现代移动端网络短连接的优化手段总结:请求速度、弱网适应、安全保障
聊聊iOS中网络编程长连接的那些事
移动端IM开发者必读(一):通俗易懂,理解移动网络的“弱”和“慢”
移动端IM开发者必读(二):史上最全移动弱网络优化方法总结
IPv6技术详解:基本概念、应用现状、技术实践(上篇)
IPv6技术详解:基本概念、应用现状、技术实践(下篇)
从HTTP/0.9到HTTP/2:一文读懂HTTP协议的历史演变和设计思路
脑残式网络编程入门(一):跟着动画来学TCP三次握手和四次挥手
脑残式网络编程入门(二):我们在读写Socket时,究竟在读写什么?
脑残式网络编程入门(三):HTTP协议必知必会的一些知识
脑残式网络编程入门(四):快速理解HTTP/2的服务器推送(Server Push)
脑残式网络编程入门(五):每天都在用的Ping命令,它到底是什么?
脑残式网络编程入门(六):什么是公网IP和内网IP?NAT转换又是什么鬼?
脑残式网络编程入门(七):面视必备,史上最通俗计算机网络分层详解
以网游服务端的网络接入层设计为例,理解实时通信的技术挑战
迈向高阶:优秀Android程序员必知必会的网络基础
全面了解移动端DNS域名劫持等杂症:技术原理、问题根源、解决方案等
美图App的移动端DNS优化实践:HTTPS请求耗时减小近半
Android程序员必知必会的网络通信传输层协议——UDP和TCP
IM开发者的零基础通信技术入门(一):通信交换技术的百年发展史(上)
IM开发者的零基础通信技术入门(二):通信交换技术的百年发展史(下)
IM开发者的零基础通信技术入门(三):国人通信方式的百年变迁
IM开发者的零基础通信技术入门(四):手机的演进,史上最全移动终端发展史
IM开发者的零基础通信技术入门(五):1G到5G,30年移动通信技术演进史
IM开发者的零基础通信技术入门(六):移动终端的接头人——“基站”技术
IM开发者的零基础通信技术入门(七):移动终端的千里马——“电磁波”
IM开发者的零基础通信技术入门(八):零基础,史上最强“天线”原理扫盲
IM开发者的零基础通信技术入门(九):无线通信网络的中枢——“核心网”
IM开发者的零基础通信技术入门(十):零基础,史上最强5G技术扫盲
IM开发者的零基础通信技术入门(十一):为什么WiFi信号差?一文即懂!
IM开发者的零基础通信技术入门(十二):上网卡顿?网络掉线?一文即懂!
IM开发者的零基础通信技术入门(十三):为什么手机信号差?一文即懂!
IM开发者的零基础通信技术入门(十四):高铁上无线上网有多难?一文即懂!
IM开发者的零基础通信技术入门(十五):理解定位技术,一篇就够
百度APP移动端网络深度优化实践分享(一):DNS优化篇
百度APP移动端网络深度优化实践分享(二):网络连接优化篇
百度APP移动端网络深度优化实践分享(三):移动端弱网优化篇
技术大牛陈硕的分享:由浅入深,网络编程学习经验干货总结
可能会搞砸你的面试:你知道一个TCP连接上能发起多少个HTTP请求吗?
知乎技术分享:知乎千万级并发的高性能长连接网关技术实践
>> 更多同类文章 ……

[2] 有关IM/推送的进程保活/网络保活方面的文章汇总:
应用保活终极总结(一):Android6.0以下的双进程守护保活实践
应用保活终极总结(二):Android6.0及以上的保活实践(进程防杀篇)
应用保活终极总结(三):Android6.0及以上的保活实践(被杀复活篇)
Android进程保活详解:一篇文章解决你的所有疑问
Android端消息推送总结:实现原理、心跳保活、遇到的问题等
深入的聊聊Android消息推送这件小事
为何基于TCP协议的移动端IM仍然需要心跳保活机制?
微信团队原创分享:Android版微信后台保活实战分享(进程保活篇)
微信团队原创分享:Android版微信后台保活实战分享(网络保活篇)
移动端IM实践:实现Android版微信的智能心跳机制
移动端IM实践:WhatsApp、Line、微信的心跳策略分析
Android P正式版即将到来:后台应用保活、消息推送的真正噩梦
全面盘点当前Android后台保活方案的真实运行效果(截止2019年前)
一文读懂即时通讯应用中的网络心跳包机制:作用、原理、实现思路等
融云技术分享:融云安卓端IM产品的网络链路保活技术实践
正确理解IM长连接的心跳及重连机制,并动手实现(有完整IM源码)
2020年了,Android后台保活还有戏吗?看我如何优雅的实现!
史上最强Android保活思路:深入剖析腾讯TIM的进程永生技术
Android进程永生技术终极揭密:进程被杀底层原理、APP对抗被杀技巧
>> 更多同类文章 ……

(原文链接:点此进入

即时通讯网 - 即时通讯开发者社区! 来源: - 即时通讯开发者社区!

上一篇:IM应用中的文件传输如何实现断点上传?下一篇:谈谈移动端 IM 开发中登录请求的优化

本帖已收录至以下技术专辑

推荐方案
评论 57
文章里的这一段“TCP的KeepAlive无法替代应用层心跳保活机制的原因”写的不错,第1看到关于TCP keepalive不能替换心跳保活的靠谱文字了。
签名: 星期六,那又怎样,还是得上班
写的很好,学习了
签名: 国庆长假还没有缓过来,请让我静一静,产品狗死远点...
论坛的人多吗,文章都是不错的,感觉回复的人不多啊。
签名: 该会员没有填写今日想说内容.
引用:fanfu 发表于 2017-03-23 12:19
论坛的人多吗,文章都是不错的,感觉回复的人不多啊。

光看不吭声,你也没办法强迫别人发声啊
引用:JackJiang 发表于 2017-03-23 14:28
光看不吭声,你也没办法强迫别人发声啊

感觉网站都是干货,太给力了。。觉得应该很多人啊,无论技术,质量都很好。
签名: 该会员没有填写今日想说内容.
mark
签名: 该会员没有填写今日想说内容.
楼主非常棒,学习了,膜拜一下
签名: 该会员没有填写今日想说内容.
Mark. Thx.
写得挺好,谢谢分享
签名: 该会员没有填写今日想说内容.
都是干货呀,怒赞!
TCP KeepAlive默认机制不适用的原因还在于默认心跳间隔太长了,2个小时远远高于NAT的超时时间,而如果修改系统默认设置又会影响所有应用
签名: 该会员没有填写今日想说内容.
引用:ipray 发表于 2017-09-06 11:53
TCP KeepAlive默认机制不适用的原因还在于默认心跳间隔太长了,2个小时远远高于NAT的超时时间,而如果修改 ...

是的,TCP设计的年代远没想到它能应用的这么广,而且它是基础设施,深入到操作系统和网络设备的各种协议栈里,很难再改变了。
新手膜拜

RE: 为什么说基于TCP的移动端IM仍然需要心跳保活?

我们做的im项目就遇到这个问题了,偶尔的就会出现服务端误判断客户端掉线,然后不给客户端push消息的问题。我们也并没有在应用层实现自己 的心跳逻辑,只是使用的session默认的idle机制来接收空闲状态消息。如果加上心跳机制或许就解决这个问题了。
有做过IM安卓项目,用的是环信,一直不太懂原理,作者写得很好
签名: 该会员没有填写今日想说内容.
都是干货,感谢分享
学习中
签名:
看看
签名:
学习了,全是干货
签名: 烦心事一大堆
打赏楼主 ×
使用微信打赏! 使用支付宝打赏!

返回顶部