不少人认为,TCP协议自身先天就有KeepAlive机制,为什么基于它的通信连接,仍然须要在应用层实现额外的心跳保活?本文将从移动端IM实践的角度告诉你,即便使用的是TCP协议,应用层的心跳保活仍旧必不可少。php
有关TCP协议的权威理论介绍,请参见《TCP/IP详解》这本书。html
说明:本文引用了网易云信项望烽的技术文章,感谢分享。(本文同步发布于:http://www.52im.net/thread-33...)git
即时通信开发交流群:215891622 [推荐]服务器
移动端IM开发推荐文章:《新手入门一篇就够:从零开发移动端IM》微信
《TCP/IP详解-第18章·TCP链接的创建与终止》ssh
《TCP/IP详解-第21章·TCP的超时与重传》tcp
《通俗易懂-深刻理解TCP协议(下):RTT、滑动窗口、拥塞处理》
《NAT详解:基本原理、穿越技术(P2P打洞)、端口老化等》
作移动端IM多年以来,常常会与相关人员进行讨论和交流。也常常会碰到些较真的技术人员询问技术细节,如主流的移动端IM如何作心跳、如何保证消息必达、如何加快文件上传等。由于平时工做太忙,没有时间深刻整理和总结,每每只能简略介绍,并不能具体展开,因而决定写成文字,也有了有关移动 IM 问题处理的系列文章。
在使用 TCP 长链接的 IM 服务设计中,每每都会涉及到心跳。心跳通常是指某端(绝大多数状况下是客户端)每隔必定时间向对端发送自定义指令,以判断双方是否存活,因其按照必定间隔发送,相似于心跳,故被称为心跳指令。
有兴趣了解IM/推送的心跳保活技术的文章,请参见:
《Android端消息推送总结:实现原理、心跳保活、遇到的问题等》
《微信团队原创分享:Android版微信后台保活实战分享(进程保活篇)》
《微信团队原创分享:Android版微信后台保活实战分享(网络保活篇)》
《移动端IM实践:WhatsApp、Line、微信的心跳策略分析》
那么问题就随之而来了:为何须要在应用层作心跳,难道 TCP 不是个可靠链接吗?咱们不可以依赖 TCP 作断线检测吗?好比使用 TCP 的 KeepAlive 机制来实现。应用层心跳是目前的最佳实践吗?怎么样的心跳才是最佳实践。
不少作移动端IM的同行,之前确实没有仔细考虑过这些问题,潜意识里想固然的认为这仅仅只是个简单的心跳而已啊。好吧,事实并不是这么简单,请继续往下看。
对于客户端而言,使用 TCP 长链接来实现业务的最大驱动力在于:在当前链接可用的状况下,每一次请求都只是简单的数据发送和接受,免去了 DNS 解析,链接创建等时间,大大加快了请求的速度,同时也有利于接受服务器的实时消息。但前提是链接可用。
若是链接没法很好地保持,每次请求就会变成撞大运:运气好,经过长链接发送请求并收到反馈。运气差,当前链接已失效,请求迟迟没有收到反馈直到超时,又须要一次链接创建的过程,其效率甚至还不如 HTTP。而链接保持的前提必然是检测链接的可用性,并在链接不可用时主动放弃当前链接并创建新的链接。
基于这个前提,必需要有一种机制用于检测链接可用性。同时移动网络的特殊性也要求客户端须要在空余时间发送必定的信令,避免链接被回收。详见微信和运营商的撕B(另外一篇针对微信的信令风暴技术研究文章请见:《微信对网络影响的技术试验及分析》)。
而对于服务器而言,可以及时获悉链接可用性也很是重要:一方面服务器须要及时清理无效链接以减轻负载,另外一方面也是业务的需求,如游戏副本中服务器须要及时处理玩家掉线带来的问题。
上面说了保持链接的重要性,那么如今回到具体实现上。为何咱们须要使用应用层心跳来作检测,而不是直接使用 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 并不适用于检测双方存活的场景,这种场景还得依赖于应用层的心跳。应用层心跳有着更大的灵活性,能够控制检测时机,间隔和处理流程,甚至能够在心跳包上附带额外信息。从这个角度而言,应用层的心跳的确是最佳实践。
从上面咱们能够得出结论,目前而言,应用层心跳的确是检测链接有效性,双方是否存活的最佳实践,那么剩下的问题就是怎么实现。
最简单粗暴作法固然是定时心跳,如每隔 30 秒心跳一次,15 秒内没有收到心跳回包则认为当前链接已失效,断开链接并进行重连。这种作法最直接,实现也简单。惟一的问题是比较耗电和耗流量。以一个协议包 5 个字节计算,一天收发 2880 个心跳包,一个月就是 5 2 2880 * 30 = 0.8 M 的流量,若是手机上多装几个 IM 软件,每月光心跳就好几兆流量没了,更不用说频繁的心跳带来的电量损耗。
既然频繁心跳会带来耗电和耗流量的弊端,改进的方向天然是减小心跳频率,但也不能过于影响链接检测的实时性。基于这个需求,通常能够将心跳间隔根据程序状态进行调整,当程序在后台时(这里主要考虑安卓),尽可能拉长心跳间隔,5 分钟、甚至 10 分钟均可以。
而当 App 在前台时则按照原来规则操做。链接可靠性的判断也能够放宽,避免一次心跳超时就认为链接无效的状况,使用错误积累,只在心跳超时 n 次后才断定当前链接不可用。固然还有一些小 trick 好比从收到的最后一个指令包进行心跳包周期计时而不是固定时间,这样也可以必定程度减小心跳次数。
(本文同步发布于:http://www.52im.net/thread-33...)
做者:[Jack Jiang]28
出处:http://www.52im.net/space-uid...
交流:欢迎加入即时通信开发交流群 215891622
Jack Jiang同时是【原创Java Swing外观工程BeautyEye】和【轻量级移动端即时通信框架MobileIMSDK】的做者,可前往下载交流。