萬字長文:手把手教你實(shí)現(xiàn)一套高效的IM長連接自適應(yīng)心跳?;顧C(jī)制

本文作者“Carson”,現(xiàn)就職于騰訊公司,原題“高效?;铋L連接:手把手教你實(shí)現(xiàn)自適應(yīng)的心跳保活機(jī)制”,有較多修訂和改動。
1、引言
當(dāng)要實(shí)現(xiàn)IM即時通訊聊天、消息推送等高實(shí)時性需求時,我們一般會選擇長連接的通信方式。
而真正當(dāng)實(shí)現(xiàn)長連接方式時,會遇到很多技術(shù)問題,比如最常見的長連接保活問題。
今天,我將通過本篇文章,手把手教大家實(shí)現(xiàn)一套可自適應(yīng)的心跳?;顧C(jī)制,從而能高效穩(wěn)定地維持諸如IM聊天這類需求的長連接。

?
學(xué)習(xí)交流:
- 移動端IM開發(fā)入門文章:《新手入門一篇就夠:從零開發(fā)移動端IM》
- 開源IM框架源碼:https://github.com/JackJiang2011/MobileIMSDK?
(本文同步發(fā)布于:http://www.52im.net/thread-3908-1-1.html)
2、相關(guān)文章
《為何基于TCP協(xié)議的移動端IM仍然需要心跳保活機(jī)制?》
《一文讀懂即時通訊應(yīng)用中的網(wǎng)絡(luò)心跳包機(jī)制:作用、原理、實(shí)現(xiàn)思路等》
《一種Android端IM智能心跳算法的設(shè)計與實(shí)現(xiàn)探討(含樣例代碼)》
《自已開發(fā)IM有那么難嗎?手把手教你自擼一個Andriod版簡易IM (有源碼)》
《跟著源碼學(xué)IM(一):手把手教你用Netty實(shí)現(xiàn)心跳機(jī)制、斷線重連機(jī)制》
《跟著源碼學(xué)IM(五):正確理解IM長連接、心跳及重連機(jī)制,并動手實(shí)現(xiàn)》
3、什么是長連接
認(rèn)識長連接:

長連接的主要作是通過長時間保持雙方連接,從而:
1)提高通信速度;
2)確保實(shí)時性;
3)避免短時間內(nèi)重復(fù)連接所造成的信道資源和網(wǎng)絡(luò)資源的浪費(fèi)。
長連接與短連接的區(qū)別:

PS:對于IM這類的開發(fā)者而言,通常大家都把HTTP協(xié)議稱“短連接”、把直接基于TCP、UDP或WebSocket的socket稱為“長連接”。
4、導(dǎo)致長連接斷開的原因
4.1 基本概念
從上節(jié)可知,在使用長連接的情況下,雙方的所有通信都建立在1條長連接上(比如1次TCP連接)。所以,長連接需要持續(xù)保持雙方連接才可使得雙方持續(xù)通信。
然而,實(shí)際情況是,長連接會存在斷開的情況。
這些斷開原因主要是:
1)長連接所在進(jìn)程被殺死(這主要說的是移動端);
2)NAT超時;
3)網(wǎng)絡(luò)狀態(tài)發(fā)生變化;
4)其他不可抗因素(網(wǎng)絡(luò)狀態(tài)差、DHCP的租期等等 )。
下面,我將對每種原因進(jìn)行分析。
4.2 具體分析
1)原因1:進(jìn)程被殺死
當(dāng)進(jìn)程被殺死后,長連接也會隨之?dāng)嚅_。進(jìn)程被殺在Andriod端是最常見的問題,限于篇幅就不在此展開這個話題,有興趣可以閱讀這篇:《Android P正式版即將到來:后臺應(yīng)用?;睢⑾⑼扑偷恼嬲瑝簟?。
2)原因2:NAT 超時(重點(diǎn)關(guān)注)
NAT超時現(xiàn)象如下:

?
各運(yùn)營商和地區(qū)的NAT超時時間如下:

PS:上述數(shù)據(jù)來源于微信團(tuán)隊(duì)的《移動端IM實(shí)踐:實(shí)現(xiàn)Android版微信的智能心跳機(jī)制》一文,隨著4G、5G的普及,這些數(shù)據(jù)有可能已發(fā)生變化,請以實(shí)際測試結(jié)果為準(zhǔn)。
特別注意:排除其他外因(網(wǎng)絡(luò)切換、NAT超時、人為原因),TCP長連接在雙方都不斷開連接的情況上,本質(zhì)上是不會自動中斷的(也就是不需要心跳包來維持,可以驗(yàn)證一下:讓2臺電腦連上同1個Wifi,其中1臺做服務(wù)器, 另1臺做客戶端連接服務(wù)器(無設(shè)置KeepAlive)。只要電腦、路由器不斷網(wǎng)斷電,那么,2臺電腦的長連接是不會自動中斷的)。
Jack Jiang注:上述論述可能不太準(zhǔn)確,有新興趣的讀者可以詳讀《拔掉網(wǎng)線再插上,TCP連接還在嗎?一文即懂!》。
3)原因3:網(wǎng)絡(luò)狀態(tài)發(fā)生變化
當(dāng)移動客戶端網(wǎng)絡(luò)狀態(tài)發(fā)生變化時(如移動網(wǎng)絡(luò) & Wifi切換、斷開、重連),也會使長連接斷開。
4)原因4:其他不可抗因素
如網(wǎng)絡(luò)狀態(tài)差、DHCP的租期到期等等,都會使得長連接發(fā)生 偶然的斷開。DHCP的租期到期:對于 Android系統(tǒng), DHCP到了租期后不會主動續(xù)約(繼續(xù)使用過期IP),從而導(dǎo)致長連接斷開。
5、高效維持長連接的解決方案
5.1 基本介紹
在了解長連接斷開原因后,針對這些原因,此處給出我的高效維持長連接的解決方案(如下圖所示)。

為此,若需有效維持長連接,則需要做到:
說得簡單點(diǎn),高效維持長連接的關(guān)鍵在于:
1)?;睿禾幱谶B接狀態(tài)時要做到盡量不要斷;
2)重連:連接斷了之后要能繼續(xù)重連回來。
5.2 具體措施
1)措施1:進(jìn)程保活
整體概括如下:
PS:關(guān)于Android的進(jìn)程?;睿@個話題就很熱門了,感興趣可以順著下面的文章詳細(xì)讀一讀:
《應(yīng)用?;罱K極總結(jié)(一):Android6.0以下的雙進(jìn)程守護(hù)?;顚?shí)踐》
《應(yīng)用?;罱K極總結(jié)(二):Android6.0及以上的保活實(shí)踐(進(jìn)程防殺篇)》
《應(yīng)用?;罱K極總結(jié)(三):Android6.0及以上的?;顚?shí)踐(被殺復(fù)活篇)》
《Android進(jìn)程?;钤斀猓阂黄恼陆鉀Q你的所有疑問》
《微信團(tuán)隊(duì)原創(chuàng)分享:Android版微信后臺?;顚?shí)戰(zhàn)分享(進(jìn)程?;钇?》
《Android P正式版即將到來:后臺應(yīng)用?;睢⑾⑼扑偷恼嬲瑝簟?/p>
《全面盤點(diǎn)當(dāng)前Android后臺?;罘桨傅恼鎸?shí)運(yùn)行效果(截止2019年前)》
《2020年了,Android后臺?;钸€有戲嗎?看我如何優(yōu)雅的實(shí)現(xiàn)!》
《史上最強(qiáng)Android保活思路:深入剖析騰訊TIM的進(jìn)程永生技術(shù)》
《Android進(jìn)程永生技術(shù)終極揭密:進(jìn)程被殺底層原理、APP應(yīng)對被殺技巧》
《Android?;顝娜腴T到放棄:乖乖引導(dǎo)用戶加白名單吧(附7大機(jī)型加白示例)》
2)措施2:心跳?;顧C(jī)制
這是本文的重點(diǎn),下節(jié)開始會詳細(xì)解析
3)措施3:斷線重連機(jī)制
原理就是:檢測網(wǎng)絡(luò)狀態(tài)變化并及時判斷連接的有效性。
具體實(shí)現(xiàn):這個其實(shí)跟心跳?;顧C(jī)制是一套完整的邏輯,所以下面會在心跳?;顧C(jī)制中一起講解。
6、心跳?;顧C(jī)制簡介
心跳?;顧C(jī)制的整體介紹如下:

不過,很多人容易混淆把心跳機(jī)制和傳統(tǒng)的HTTP輪詢機(jī)制搞混。
下面給出二者區(qū)別:

7、主流IM的心跳機(jī)制分析和對比
對國、內(nèi)外主流的移動IM產(chǎn)品(WhatsAPP、Line、微信)進(jìn)行了心跳機(jī)制的簡單分析和對比。
具體請看下圖:

PS:以上數(shù)據(jù)來自于微信團(tuán)隊(duì)分享的《移動端IM實(shí)踐:WhatsAPP、Line、微信的心跳策略分析》一文。
8、心跳?;顧C(jī)制方案總體設(shè)計
下面,我將根據(jù)市面上主流的心跳機(jī)制,設(shè)計了一套心跳機(jī)制方案。
心跳機(jī)制方案的基本流程:

?
對于心跳機(jī)制方案設(shè)計的主要考慮因素是:
1)要保證消息的實(shí)時性;
2)要考慮耗費(fèi)設(shè)備的資源(網(wǎng)絡(luò)流量、電量、CPU等等)。
從上圖可以看出,對于心跳機(jī)制方案設(shè)計的要點(diǎn)在于:
1)心跳包的規(guī)格(內(nèi)容 & 大?。?;
2)心跳發(fā)送的間隔時間;
3)斷線重連機(jī)制 (核心 = 如何 判斷長連接的有效性)。
在下面的方案設(shè)計中,將針對這3個問題給出詳細(xì)的解決方案。
9、心跳機(jī)制方案的詳細(xì)設(shè)計
9.1 心跳包的規(guī)格
為了減少流量并提高發(fā)送效率,需要精簡心跳包的設(shè)計。
主要從心跳包的內(nèi)容和大小入手,設(shè)計原則具體如下:

設(shè)計方案:
心跳包 = 1個攜帶少量信息 & 大小在10字節(jié)內(nèi)的信息包
9.2 心跳發(fā)送的間隔時間
為了 防止NAT超時并減少設(shè)備資源的消耗(網(wǎng)絡(luò)流量、電量、CPU等等),心跳發(fā)送的間隔時間是整個心跳機(jī)制方案設(shè)計的重點(diǎn)。
心跳發(fā)送間隔時間的設(shè)計原則如下:

?
9.3 最常用的心跳間隔方案
一般,最直接且常用的心跳發(fā)送間隔時間設(shè)置方案多采用:“每隔估計 x 分鐘發(fā)送心跳包1次”。其中,x <5分鐘即可(綜合主流移動IM產(chǎn)品,此處建議 x= 4分鐘)。
但是,這種方案存在一些問題:

PS:關(guān)于固定心跳間隔的方案具體實(shí)現(xiàn),可以詳細(xì)參考:
《跟著源碼學(xué)IM(一):手把手教你用Netty實(shí)現(xiàn)心跳機(jī)制、斷線重連機(jī)制》;
《跟著源碼學(xué)IM(五):正確理解IM長連接、心跳及重連機(jī)制,并動手實(shí)現(xiàn)》;
《自已開發(fā)IM有那么難嗎?手把手教你自擼一個Andriod版簡易IM (有源碼)》。
9.4 自適應(yīng)心跳間隔方案
下面,我將詳細(xì)講解自適應(yīng)心跳間隔時間的設(shè)計方案。
基本邏輯:

?
該方案需要解決的有2個核心問題。
1)如何自適應(yīng)計算心跳間隔 從而使得心跳間隔 接近 當(dāng)前NAT 超時時間?
答:不斷增加心跳間隔時間進(jìn)行心跳應(yīng)答測試,直到心跳失敗5次后,即可找出最接近 當(dāng)前NAT 超時時間的心跳間隔時間。
具體請看下圖:

注:只有當(dāng)心跳間隔 接近 NAT 超時時間 時,才能最大化平衡 長連接不中斷 & 設(shè)備資源消耗最低的問題。
2)如何檢測 當(dāng)前網(wǎng)絡(luò)環(huán)境的NAT 超時時間 發(fā)生了變化 ?
答:當(dāng)前發(fā)送心跳包成功 的最大間隔時間(即最接近NAT超時時間的心跳間隔) 發(fā)送失敗5次后,則判斷當(dāng)前網(wǎng)絡(luò)環(huán)境的NAT 超時時間 發(fā)生了變化。
具體請看下圖:

注:在檢測到 NAT 超時時間 發(fā)生變化后,重新自適應(yīng)計算心跳間隔 從而使得心跳間隔 接近 NAT 超時時間
總結(jié)一下:統(tǒng)籌以上2個核心問題,總結(jié)出自適應(yīng)心跳間隔時間設(shè)計方案為下圖:

PS:關(guān)于自適應(yīng)心跳機(jī)制的設(shè)計和實(shí)現(xiàn),可以詳細(xì)參考:
《移動端IM實(shí)踐:實(shí)現(xiàn)Android版微信的智能心跳機(jī)制》;
《一種Android端IM智能心跳算法的設(shè)計與實(shí)現(xiàn)探討(含樣例代碼)》。
10、斷線重連機(jī)制的實(shí)現(xiàn)
技術(shù)上來說:長連接的心跳?;钜蕾囉谛奶鴻C(jī)制,在心跳機(jī)制起作用的情況下,適時啟動斷線重連機(jī)制,在心跳機(jī)制和斷線重連機(jī)制的共同作用下才能實(shí)現(xiàn)真正的心跳?;睢5珵榱俗屵壿嫺逦?,我把斷線重連機(jī)制跟心跳機(jī)制單獨(dú)各作為一節(jié)來講解。本節(jié)講的是斷片線重連機(jī)制。
該機(jī)制的核心在于:如何判斷長連接的有效性。即:什么情況下視為長連接斷線?
1)設(shè)計原則:
基本邏輯就是:判斷長連接是否有效的準(zhǔn)則 = 服務(wù)器是否返回心跳應(yīng)答。
此處需要分清長連接的“存活 & 有效“狀態(tài)的區(qū)別:

?
2)具體方案:
實(shí)現(xiàn)思路:通過計數(shù)計算,若連續(xù)5次發(fā)送心跳后,服務(wù)器都無心跳應(yīng)答,則視為長連接無效。
判斷流程:
3)網(wǎng)上流傳的方案:
在網(wǎng)上流傳著一些用于判斷長連接是否有效的方案,具體介紹如下:?
至此,關(guān)于心跳?;顧C(jī)制已經(jīng)講解完畢。
11、方案小結(jié)
有必要總結(jié)一下我在上兩節(jié)分享的心跳機(jī)制和斷線重連機(jī)制,這兩個機(jī)制組成了本文的長連接心跳?;钔暾壿?。
設(shè)計方案:?
流程設(shè)計:
注:標(biāo)識 “灰色” 的判斷流程參考上文描述。
12、進(jìn)一步優(yōu)化和完善心跳?;罘桨?/h1>12.1 基本情況
上兩節(jié)中的方案依然會存在技術(shù)缺陷,從而導(dǎo)致長連接斷開(比如:長連接本身不可用(此時重連多少次也沒用))。
下面將優(yōu)化和完善上述方案,從而保證 客戶端與服務(wù)器依然保持著通信狀態(tài)。
優(yōu)化點(diǎn)主要是:
1)確保當(dāng)前網(wǎng)絡(luò)的有效性和穩(wěn)定性再開始長連接;
2)自適應(yīng)計算心跳包間隔時間的時機(jī)。
12.2 確保網(wǎng)絡(luò)的有效性和穩(wěn)定性后再開始長連接
問題描述:
?
解決方案:
加入到原有的心跳保活機(jī)制主流程:
12.3 自適應(yīng)計算心跳包間隔時間的時機(jī)
問題描述:
?
方案設(shè)計:
?
加入到到原有的心跳?;顧C(jī)制主流程:
?
12.4 小結(jié)一下
13、額外思考:TCP協(xié)議自帶的KeepAlive機(jī)制能否替代心跳機(jī)制?
很多人認(rèn)為,TCP 協(xié)議自身就有KeepAlive機(jī)制,為何基于它的通訊鏈接,仍需在應(yīng)用層實(shí)現(xiàn)額外的心跳保活機(jī)制?
結(jié)論是:無法替代;
原因是:TCP KeepAlive機(jī)制的作用是檢測連接的有無(死活),但無法檢測連接是否有效。
注:“連接有效”的定義 = 雙方具備發(fā)送 & 接收消息的能力。
先來看看KeepAlive 機(jī)制是什么:
?
KeepAlive 的機(jī)制不可替代心跳機(jī)制的具體原因如下:
?
特別注意:
1)KeepAlive 機(jī)制只是操作系統(tǒng)底層的一個被動機(jī)制,不應(yīng)該被上層應(yīng)用層使用;
2)當(dāng)系統(tǒng)關(guān)閉一個由KeepAlive 機(jī)制檢查出來的死連接時,是不會主動通知上層應(yīng)用的,只能通過調(diào)用相應(yīng)IO操作的返回值中發(fā)現(xiàn)。
小結(jié)一下就是:KeepAlive機(jī)制無法代替心跳機(jī)制,需要在應(yīng)用層 自己實(shí)現(xiàn)心跳機(jī)制以檢測長連接的有效性,從而高效維持長連接。
Jack Jiang注:關(guān)于TCP本身的KeepAlive機(jī)制,可能詳讀:
《為何基于TCP協(xié)議的移動端IM仍然需要心跳?;顧C(jī)制?》
《徹底搞懂TCP協(xié)議層的KeepAlive?;顧C(jī)制》
14、本文總結(jié)
看完本文后,相信在高效維持長連接的需求下,你可以完美地解決了!
本文方案的主體設(shè)計就是:
方案的優(yōu)化和完善內(nèi)容就是:

15、參考資料
[1]?TCP/IP詳解 卷1:協(xié)議
[2]?為何基于TCP協(xié)議的移動端IM仍然需要心跳?;顧C(jī)制?
[3]?徹底搞懂TCP協(xié)議層的KeepAlive?;顧C(jī)制
[4]?萬字長文,一篇吃透WebSocket:概念、原理、易錯常識、動手實(shí)踐
[5]?移動端IM實(shí)踐:實(shí)現(xiàn)Android版微信的智能心跳機(jī)制
[6]?移動端IM實(shí)踐:WhatsAPP、Line、微信的心跳策略分析
[7]?微信團(tuán)隊(duì)原創(chuàng)分享:Android版微信后臺保活實(shí)戰(zhàn)分享(網(wǎng)絡(luò)?;钇?
[8]?融云技術(shù)分享:融云安卓端IM產(chǎn)品的網(wǎng)絡(luò)鏈路?;罴夹g(shù)實(shí)踐
[9]?阿里IM技術(shù)分享(五):閑魚億級IM消息系統(tǒng)的及時性優(yōu)化實(shí)踐
[10]?2020年了,Android后臺?;钸€有戲嗎?看我如何優(yōu)雅的實(shí)現(xiàn)!
(本文同步發(fā)布于:http://www.52im.net/thread-3908-1-1.html)