IM心跳策略
心跳的字段定義
- minHeart 最小心跳,本地默認(rèn)120秒悯仙,服務(wù)器定120秒
- maxHeart 最大心跳龄毡,本地默認(rèn)580秒,服務(wù)器定580秒
- startHeart 起步心跳锡垄,本地默認(rèn)240秒沦零,服務(wù)器定240秒
心跳信息字段
- networkTag 當(dāng)前網(wǎng)絡(luò)類型,如CMCC-4G
- stabled 穩(wěn)定心跳的標(biāo)志位货岭,true表示穩(wěn)定心跳
- stabledSuccessCount 穩(wěn)定心跳連續(xù)成功次數(shù)路操,這里是在心跳穩(wěn)定一段時(shí)間后,再嘗試上調(diào)的時(shí)候用千贯,例如stabledSuccessCount > 50的時(shí)候屯仗,穩(wěn)定心跳嘗試上調(diào)
- failedCount 心跳連續(xù)失敗次數(shù),當(dāng)failedCount >= 3的時(shí)候搔谴,才會(huì)認(rèn)為當(dāng)前心跳是不可用的魁袜,會(huì)嘗試下調(diào),如果心跳一直失敗敦第,那么failedCount是不斷累計(jì)遞增
- successCount 心跳連續(xù)成功次數(shù)慌核,心跳成功后就遞增1,同時(shí)successCount > 2的時(shí)候會(huì)把failedCount清零
- curMinHeart 當(dāng)前心跳探測(cè)區(qū)間極小值
- curMaxHeart 當(dāng)前心跳探測(cè)區(qū)間極大值
- curHeart 當(dāng)前心跳
- successHeartList 成功心跳列表申尼,每次心跳成功后垮卓,會(huì)把當(dāng)前的成功心跳記錄進(jìn)來(lái)
重置心跳
- 當(dāng)TCP連接有除了心跳包以外的消息包在進(jìn)行傳輸(read)時(shí)候,就認(rèn)為該TCP連接在這個(gè)時(shí)刻仍然有效师幕,在程序中read到消息包數(shù)據(jù)后會(huì)對(duì)數(shù)據(jù)進(jìn)行短時(shí)間處理(ms級(jí)別)粟按,然后再write數(shù)據(jù)诬滩,只有收到同步通知,或者單推的時(shí)候本地發(fā)現(xiàn)消息已經(jīng)同步灭将,那此時(shí)就不會(huì)write疼鸟,不過(guò)這種情況發(fā)生的概率比較小,所以心跳是在write數(shù)據(jù)出去的時(shí)候進(jìn)行重置庙曙,這里不在read數(shù)據(jù)的時(shí)候重置心跳是為了避免在弱網(wǎng)環(huán)境下空镜,數(shù)據(jù)包要在網(wǎng)絡(luò)中傳輸幾分鐘,導(dǎo)致服務(wù)器連接超時(shí)捌朴,然后把TCP連接誤斷的這種情況
- 如果心跳包在write的時(shí)候進(jìn)行重置吴攒,當(dāng)遇到此TCP已經(jīng)是無(wú)效連接,但是服務(wù)器和客戶端都沒(méi)有感知到這中情況砂蔽,那么客戶端對(duì)于write出去的消息會(huì)有一個(gè)超時(shí)檢測(cè)(20s洼怔,但是消息ack沒(méi)有超時(shí)檢測(cè)),write數(shù)據(jù)出去后收不到響應(yīng)的回饋左驾,20s超時(shí)到期镣隶,此時(shí)會(huì)通過(guò)心跳來(lái)驗(yàn)證TCP連接的有效性,心跳超時(shí)就進(jìn)行斷線重連诡右,所以這里會(huì)有60秒以上的消息延遲
- TCP無(wú)效連接安岂,如果是客戶端的消息ack數(shù)據(jù)發(fā)送出去但是服務(wù)端沒(méi)有收到,那么將遇到兩種情況帆吻,第一是服務(wù)器連接超時(shí)端開(kāi)嗜闻,第二是客戶端下一個(gè)心跳檢測(cè)發(fā)現(xiàn)TCP連接是無(wú)效的,然后斷線重連桅锄,這里會(huì)有最多一個(gè)心跳周期的延遲
心跳策略圖
這里寫(xiě)圖片描述
觸發(fā)心跳上調(diào)
- 探測(cè)期間的心跳發(fā)送成功并及時(shí)收到服務(wù)器的響應(yīng),這時(shí)候會(huì)執(zhí)行心跳上調(diào)
- 穩(wěn)定一定的時(shí)間后嘗試上調(diào)(有待優(yōu)化)
心跳上調(diào)策略
- 記錄成功心跳的信息
- successHeartList.add(curHeart);successCount++;
- if (successCount >= 2) failedCount = 0;心跳連續(xù)成功兩次样眠,才認(rèn)為當(dāng)前心跳在該網(wǎng)絡(luò)環(huán)境下運(yùn)行穩(wěn)定
- 把當(dāng)前的心跳信息更新到文件中友瘤。
- if (stabled == true) stabledSuccessCount++;
- 如果當(dāng)前心跳不是穩(wěn)定心跳,那么執(zhí)行以下操作:
- 從成功心跳列表中篩選比當(dāng)前心跳大一級(jí)的心跳周期作為當(dāng)前心跳:curHeart = successHeart;
- 如果1沒(méi)有篩選出結(jié)果檐束,則用二分法進(jìn)行上調(diào):
(1)curMinHeart = curHeart;
(2)if (curMaxHeart < curMinHeart) curMaxHeart = curMinHeart;
(3)curHeart = (curMinHeart + curMaxHeart) / 2;
- 判斷curHeart > maxHeart辫秧,如果是則curHeart = maxHeart;stabled = true被丧;這是用來(lái)異常過(guò)濾
- 檢測(cè)心跳探測(cè)區(qū)間是否達(dá)到機(jī)值條件:
- if (curMaxHeart - curMinHeart <= 10 && stabled == false) curHeart = curMinHeart;
- 如果已經(jīng)達(dá)到極值條件(curMaxHeart - curMinHeart <= 10)盟戏,那么stabled = true;
- 使用curHeart進(jìn)行下一個(gè)心跳的發(fā)送
觸發(fā)心跳下調(diào)
- 心跳下調(diào)無(wú)非是TCP連接斷線導(dǎo)致心跳下調(diào),但并不是所有的TCP斷線都要下調(diào)心跳甥桂,當(dāng)前遇到會(huì)導(dǎo)致TCP斷線的情況有以下幾種:
- 心跳超時(shí)主動(dòng)斷開(kāi)TCP連接(socket closed)柿究,此時(shí)應(yīng)該下調(diào)心跳
- IM SDK初始化會(huì)主動(dòng)斷開(kāi)TCP并重新連接(socket closed),不應(yīng)該下調(diào)心跳
- 本地網(wǎng)絡(luò)斷開(kāi)造成 TCP連接被動(dòng)斷開(kāi)(Software caused connection abort黄选,socket closed)蝇摸,這里分為兩種情況婶肩,第一個(gè)是網(wǎng)絡(luò)切換,那么這時(shí)候是網(wǎng)絡(luò)斷開(kāi)貌夕,然后再重新連上的一個(gè)過(guò)程律歼,應(yīng)用能明顯的感知到這個(gè)過(guò)程(網(wǎng)絡(luò)切換廣播),TCP連接在網(wǎng)絡(luò)切換的時(shí)候會(huì)被動(dòng)斷開(kāi)啡专,這時(shí)候在下調(diào)心跳之前要先檢測(cè)下本地網(wǎng)絡(luò)是否可用险毁,如果不可用則不進(jìn)行心跳下調(diào),其實(shí)因?yàn)楸镜鼐W(wǎng)絡(luò)斷開(kāi)導(dǎo)致的TCP斷線是不應(yīng)該下調(diào)心跳的们童,這里多了個(gè)檢測(cè)就是為了在一定程度上過(guò)濾掉一部分因?yàn)楸镜鼐W(wǎng)絡(luò)斷開(kāi)導(dǎo)致的心跳誤下調(diào)畔况;還有一種是modem其實(shí)已經(jīng)斷網(wǎng)了,此時(shí)modem可能在進(jìn)行重連病附,但是并沒(méi)有網(wǎng)絡(luò)切換廣播问窃,此時(shí)應(yīng)用層是無(wú)感知的,但是TCP連接可以立馬感知到完沪,并被動(dòng)斷開(kāi)域庇,這時(shí)候檢測(cè)本地網(wǎng)絡(luò)也是可用的(不準(zhǔn)),所以這時(shí)候會(huì)導(dǎo)致心跳誤下調(diào)覆积,Android sdk接口判斷本地網(wǎng)絡(luò)是否可用其實(shí)是不準(zhǔn)確的听皿,如果接口返回不可用,那么本地網(wǎng)絡(luò)一定是不可用的宽档,如果接口返回可用尉姨,那網(wǎng)絡(luò)還不一定真的可用,因?yàn)榻涌跈z測(cè)的只是設(shè)備本地網(wǎng)絡(luò)而已吗冤,如果連接上一個(gè)假wifi(需要驗(yàn)證密碼)又厉,那么設(shè)備到wifi路由器這段網(wǎng)絡(luò)是通的,但是wifi路由器到外網(wǎng)是不通的椎瘟,這時(shí)候設(shè)備是感知不到的覆致,通過(guò)ping才能準(zhǔn)確的知道網(wǎng)絡(luò)是否真的可用,當(dāng)手機(jī)卡欠費(fèi)的時(shí)候肺蔚,本地接口也是返回網(wǎng)絡(luò)可用煌妈,道理類似
- 服務(wù)器close造成TCP連接被動(dòng)斷開(kāi)(read返回-1),此時(shí)會(huì)下調(diào)心跳
- 其他網(wǎng)絡(luò)原因造成的TCP連接被動(dòng)斷開(kāi)(connection reset等)宣羊,此時(shí)會(huì)下調(diào)心跳
- TLV數(shù)據(jù)解析錯(cuò)誤主動(dòng)斷開(kāi)TCP連接璧诵,不應(yīng)該下調(diào)心跳
- 除了以上6中原因會(huì)造成TCP斷開(kāi),如果還有其他原因在成TCP斷開(kāi)仇冯,需要檢測(cè)三個(gè)條件才滿足心跳下調(diào)的條件:第一是當(dāng)前心跳是否已經(jīng)啟動(dòng)之宿,第二是當(dāng)前設(shè)備本地網(wǎng)絡(luò)是否可用,第三是TCP斷開(kāi)前苛坚,已經(jīng)持續(xù)連接超過(guò)一個(gè)最小心跳周期的時(shí)間澈缺,滿足以上三個(gè)條件才進(jìn)行下調(diào)心跳坪创,否則不下調(diào)
心跳下調(diào)策略
- 記錄心跳失敗信息:
- 從successHeartList移除當(dāng)前心跳鎖對(duì)應(yīng)的心跳周期;stabledSuccessCount;successCount;failedCount++;
- 把當(dāng)前的心跳信息更新到文件中。
- if (stabled == true && failedCount >= 3)那么執(zhí)行以下操作:
- stabled = false;
- 從successHeartList篩選比當(dāng)前心跳小一級(jí)的心跳heart
- if ((minHeart + curHeart) / 2 < heart) selectedHeart = heart;curHeart = selectedHeart;
- 如果沒(méi)有篩選到適合條件的selectedHeart,那么就進(jìn)行二分法下調(diào):
(1)currentMaxHeart = currentHeart;
(2)currentMinHeart = minHeart;
(3)currentHeart = (currentMinHeart + currentMaxHeart) / 2;
- if (stabled == false && failedCount >= 3)那么執(zhí)行以下操作:
- 從successHeartList篩選比當(dāng)前心跳小一級(jí)的心跳heart;
- if ((minHeart + curHeart) / 2 < heart) selectedHeart = heart;curHeart = selectedHeart;
- 如果沒(méi)有篩選到適合條件的selectedHeart,那么就進(jìn)行二分法下調(diào):
(1)currentMaxHeart = currentHeart;
(2)if (currentMaxHeart < curentMinHeart) currentMaxHeart = currentMinHeart;
(3)currentHeart = (currentMinHeart + currentMaxHeart) / 2;
- 檢測(cè)心跳探測(cè)區(qū)間是否達(dá)到極值條件:
- if (curMaxHeart - curMinHeart <= 10 && stabled == false) currentMinHeart = minHeart;
- 之所以加入這個(gè)判斷是為了當(dāng)心跳一直失敗下調(diào)姐赡,但是curMinHeart和curMaxHeart又很接近導(dǎo)致二分法無(wú)法下調(diào)的時(shí)候莱预,就直接把curHeart設(shè)置成minHeart
穩(wěn)定心跳
- 有效的穩(wěn)定心跳是NAT臨界值
- 探測(cè)心跳達(dá)到最大心跳值的時(shí)候認(rèn)為是穩(wěn)定心跳
- 探測(cè)心跳滿足二分法的極值條件(curMaxHeart - curMinHeart < 10)的時(shí)候認(rèn)為是穩(wěn)定心跳
- 探測(cè)心跳達(dá)到最小心跳值的時(shí)候認(rèn)為是穩(wěn)定心跳
- 當(dāng)探測(cè)到穩(wěn)定心跳之后,正式使用的心跳值會(huì)在探測(cè)到的穩(wěn)定心跳的基礎(chǔ)上扣除20秒项滑,但是扣除后的心跳值一定要在最大值和最小值之間依沮,避開(kāi)臨界值
Android機(jī)子上存在的問(wèn)題
- 對(duì)于系統(tǒng)APP發(fā)起的alarm,在android原生系統(tǒng)不會(huì)存在alarm被對(duì)齊的問(wèn)題枪狂,因?yàn)閍ndroid系統(tǒng)對(duì)于系統(tǒng)app發(fā)起的alarm會(huì)設(shè)置alarm的flag為FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED危喉,在Android6.0以上系統(tǒng)AlarmManagerService會(huì)在doze模式下忽略有該flag的alrm,因此不會(huì)被延遲喚醒州疾,至于AlarmClokc辜限,flag是FLAG_WAKE_FROM_IDLE,doze模式下也不會(huì)對(duì)該flag的alarm做延遲喚醒
- 在Android6.0系統(tǒng)以上严蓖,休眠的時(shí)候alarm是會(huì)被延遲執(zhí)行的薄嫡,可通過(guò)加入系統(tǒng)白名單的方式來(lái)避免,Google的GCM就是默認(rèn)系統(tǒng)白名單颗胡,但是在手機(jī)上毫深,系統(tǒng)白名單嘗試過(guò),并沒(méi)有用毒姨;手表是原生的Android系統(tǒng)哑蔫,可以嘗試加入白名單更加可靠
- alarm的對(duì)齊喚醒:國(guó)內(nèi)的手機(jī)廠商例如華為,魅族弧呐,小米都是自定制的android系統(tǒng)闸迷,對(duì)于AlarmManager都有對(duì)齊喚醒策略,因此會(huì)導(dǎo)致心跳alarm的時(shí)間不準(zhǔn)確俘枫,例如設(shè)置了270秒alarm一次腥沽,但是在這些手機(jī)上可能要推遲到300秒才能喚醒,那么問(wèn)題來(lái)了崩哩,如果NAT超時(shí)時(shí)間是2分鐘,而這些手機(jī)的alarm最小間隔是5分鐘言沐,那就坑了邓嘹,永遠(yuǎn)無(wú)法探測(cè)到最佳心跳,你設(shè)置120秒的alarm险胰,手機(jī)系統(tǒng)也給你延遲到5分鐘才執(zhí)行alarm汹押,不過(guò)這種情況只有在手機(jī)休眠的時(shí)候才會(huì)對(duì)齊喚醒,在手機(jī)不休眠的時(shí)候起便,我側(cè)過(guò)棚贾,alarm計(jì)時(shí)還是準(zhǔn)確的