不少人認爲,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】的做者,可前往下載交流。