Android 架構之長連線技術
本文首發於小專欄《 ofollow,noindex">Android 架構之長連線技術 》,更多 Android 架構文章歡迎關注《 億級 Android 架構 》
上一篇文章 《Android 架構之網路框架(上)》 中,我們談過了網路框架 OkHttp、網路加速方案如 HttpDNS、資料壓縮與序列化等技術點。本文我們結合 騰訊 Mars 框架 和 美團 Shark 體系 等業內主流長連線方案,談一談長連線技術的各個方面。
本文會包括下面的技術點:
- 長連線與 Http 短連線、Keep-Alive 傻傻分不清
- 你為什麼需要長連線
- 長連線何時會斷開
- 如何建立穩定長連線
- Mars 智慧心跳機制
- 長連線資料協議及加密
- 長連線通道建設及容災
除了大家常用的 Http 短連線,大型 App 幾乎都會搭建一套完整的 TCP 長連線
網路通道。我們先來看下 美團 Shark 長連線
的線上資料:


圖片來源 《 美團點評行動網路優化實踐 》
上面兩張圖片對比了長 / 短連線的成功率和網路延時資料,這兩個是網路模組最重要的衡量指標。可以看出,無論是成功率,還是網路延時,長連線都明顯優於短連線。
另外,大家都知道微信的訊息收發非常即時,這便歸功於背後穩定高可用的長連線系統。實際上,微信除了訊息收發,其他的小資料通訊都是通過長連線來實現的。
下面我們來講解一些長連線的一些核心技術點。
I. 長連線與 Http 短連線、Keep-Alive 傻傻分不清
為防止大家對於長連線和短連線混淆,這裡先簡單說明下幾點區別。
長連線 vs Http 短連線
這兩者分別對應的是 TCP 協議層
的 長連線
和 短連線
。
大家都知道,TCP 會通過三次握手,建立與服務端的連線,然後傳遞資料,只不過 短連線
在資料傳輸完後,會主動關閉連線,而 長連線
會繼續保持這條連線,後續的資料讀寫繼續使用這條連線。
長連線 vs Http 的 Keep-Alive
上一篇文章中提到了 連線複用
,通過 Http1.1 的 Keep Alive
欄位,我們可以讓一條 Http 連線保持不被立即關閉。有些同學這時就疑惑了,是不是長連線就是 Keep Alive
呢?
其實不是的。長連線我們也叫 TCP 長連線
,它是架設在 TCP 協議上的,而上面說的 Keep Alive
是 Http 協議的內容,連協議都不同,兩者自然不是一個東西。
開啟了 Keep Alive
是 Http 連線,我們也稱之為 持久連線
,和長連線並不同。感興趣可參考此文: 《TCP 進階》 。
TCP 的 Keep-Alive
vs Http 的 Keep-Alive
提到 Keep Alive
,有些同學就會問了,TCP 協議裡也有一個 Keep Alive
,它和 Http 協議裡的 Keep Alive
有什麼區別嗎?
二者的用處並不同。Http 協議在完成一個請求後,伺服器會自動關閉連線。這時,可以在請求裡帶上一個 Keep Alive
給伺服器,告訴 伺服器不要立即關閉連線
,我還想繼續複用這條連線;而對 TCP 協議層而言,是不會自動斷開的,但這也帶來了一個問題,萬一由於某些外部原因導致連線斷開,那我如何知道連線已失效呢?TCP 會在 2 個小時間隔後,自動傳送一個 Keep Alive
資料包給服務端,探測一下伺服器是否還在響應。它的功能類似心跳包,只是間隔太長,不適合做真正的心跳包。
II. 你為什麼需要長連線
那麼,相比 Http 短連線,長連線技術能帶來什麼好處呢?
1. 不同域名的請求可以複用同一個長連線通道
以前我們不同域名的請求,需要做對應的 DNS 請求,然後建立對應的 Http 連線。上篇文章裡說的 Http 連線池
在不同域名下不可複用,需要重新建立連線。這些都是一些資源開銷,但是如果通過長連線通道,那域名只是這個請求裡的一個欄位,可以直接複用同一條長連線通道。
2. 不依賴 DNS,無 DNS 耗時和劫持等問題
上文中我們提到了 HttpDNS
,雖然它比系統 DNS 更優,但終歸還是要做 DNS 操作。而長連線都是 IP 直接連線,因此沒有 DNS 相關的開銷和耗時。
3. 如果有大量網路請求,可以明顯減少網路延時,節省頻寬
對於大型 App 而言,存在繁多密集的網路請求,這中間就會存在非常多次的 Http 斷開和重新連線,浪費了很多時間和頻寬。而通過長連線通道的話,則沒有這部分耗時,直接傳輸二進位制資料即可,節省了每次連線裡 Header 之類的頻寬開銷。
4. 服務端主動 Push 資料到客戶端
對於上面提到的微信訊息接收等場景,如果需要客戶端主動去輪詢,則會頻繁發起請求,對於伺服器會產生很大的負載壓力,浪費頻寬流量。而通過長連線,服務端可以主動把訊息下發給客戶端,做到最高實時性,且節省流量。
III. 長連線何時會斷開?
正常而言,長連線是不會斷開的。大家可以自己試一試,兩個 socket 建立連線,只要網路不變、一切正常,那麼這兩個 socket 可以一直互相傳送資料,不會斷開。
但是,在行動網路下,網路狀態複雜多變,比如網路線路被切斷、伺服器宕機等,都會導致長連線中斷。除了這些線路異常外,我們需要關注下面幾個長連線斷開原因:
1. 長連線所在程序被殺
這個很容易理解,如果我們的 App 切換到後臺,那麼系統隨時可能將我們的 App 殺掉,這時長連線自然也就隨之斷開。
2. 使用者切換網路
比如手機網路斷開,或者發生 Wi-Fi 和蜂窩資料切換,這時會導致手機 IP 地址變更。而我們知道,TCP 連線是基於 IP + Port 的,一旦 IP 變更,TCP 連線自然也就失效了,或者說長連線也就相當於斷開了。
3. 系統休眠等導致 NAT 超時
這裡對 NAT 簡單解釋下,方便有的同學不太瞭解。當手機連線上網路時,閘道器會給我們分配一個 IP 地址,這個其實是內網 IP,此時還未真正連線上公網,也連線不上伺服器;如果想要連線公網,需要運營商將我們的內網 IP 對映成一個公網 IP,有了公網 IP,伺服器就能與我們建立連線了。NAT 指的就是這個對映過程。
也就是說,運營商會給每臺裝置分配一個公網 IP,類似一張通訊證。不過,隨著連線網路的裝置不斷增多,閘道器負載也會不斷加大,這時,運營商就會對一些不太活躍的裝置進行公網 IP 回收了,如果下次這個裝置需要連網,那就重新分配一個 IP 即可。
看似沒問題,但實際上,如果我們的 App 在一段時間不活躍,發生了 NAT 超時,便會導致我們的公網 IP 失效,長連線也隨之失效了。
4. DHCP 租期
DHCP 租期過期,如果沒有及時續約,同樣會導致 IP 地址失效。
綜合而言,長連線在正常情況下是不會斷開的,但是,一旦手機的 IP 地址失效,這時就不得不重新建立連線了。
IV. 如何建立穩定長連線?
上面我們提到了多種長連線斷開的原因,那我們應該如何進行優化,儘可能保證長連線不斷開,或者及時斷開了,也要儘快重連呢?
1. Mars 長連線獨立程序
為了減少程序被殺的機率,在 Mars 的 Demo 程式碼 裡我們可以看到,它將長連線邏輯單獨提取到了一個獨立的程序裡。這個程序只做網路互動,消耗的記憶體等資源自然較少,從而減少了被系統回收的概率。

圖片來自 《Android 版微信後臺保活實戰分享(程序保活篇)》
2. 長連線程序復活
程序被殺難以避免,不過可以通過 AlarmReceiver、 ConnectReceiver、BootReceiver,達到程序的及時喚醒。
當然,程序保活是一個比較大的話題,而且不恰當的程序保活也會對系統體驗造成危害。這裡就不深究了。
3. 心跳機制
對於心跳包很多人誤以為只是用來定期告訴服務端我們的狀態,實際並非如此。
上面我們提到了 NAT 超時,即如果 App 一段時間內不活躍,會導致運營商那裡刪除我們的公網 IP 對映關係,這會導致我們的 TCP 長連線斷開。因此,我們需要通過心跳機制來保證 App 的活躍度,防止發生 NAT 超時。
4. 斷開重連
在線上執行時,長連線很有可能會由於網路切換之類的原因斷開。這時,我們需要 儘快發現
長連線斷開,並 立即重連
。一般有下面幾種做法:
- 建立 Receiver,監控網路狀態,如果網路發生切換則立即重連;
- 監控服務端心跳包回包,如果連續 5 次沒有收到回包,則認為長連線已經失效;
- 設定心跳包超時限制,如果超過時間還沒有收到心跳回包,則重連,這種方式比較耗電;
- 等 socket IO 異常丟擲,不過耗時太長,需要 15s 左右才能發現。
V. Mars 智慧心跳機制
1. 固定心跳機制
上面我們說了,心跳機制主要是為了防止 NAT 超時,外網 IP 地址失效。因此,一般的做法就是在 NAT 失效前,保證有心跳包發出。或者說,客戶端應當以略小於 NAT 超時時間的間隔來發送心跳包。
早期的微信的心跳是 4.5 分鐘傳送一次心跳,可以不錯的執行。
2. Mars 智慧心跳策略
在儘量不影響使用者收訊息及時性的前提下,根據網路型別自適應的找出保活信令 TCP 連線的儘可能大的心跳間隔,從而達到減少安卓微信因心跳引起的空中通道資源消耗,減少心跳 Server 的負載,以及減少部分因心跳引起的耗電。
自適應心跳
因此,在固定心跳機制下,微信又研究了一套動態計算心跳的方案,動態的探測最大的 NAT 超時時間,然後選定合適的心跳間隔區間去傳送心跳包。這裡說一下大致思路:
首先,如果心跳間隔越久,產生的負載和消耗也會越小。因此微信採用了 自適應心跳
:當找到一個有效心跳間隔後,我們主動去加大這個間隔,然後測試是否能成功,如果不能,則使用比上一次成功間隔稍短的時間作為間隔;否則繼續加大間隔,直到找到可用的有效間隔。
那麼,如何判斷一個心跳間隔有效呢?微信採用的方案是使用固定短心跳直到滿足三次連續短心跳成功,則認為這個間隔有效。
探測過程大致為:60 秒短心跳,連續發 3 次後開始探測,90,120,150,180,210,240,270
前後臺策略
另外,考慮到 App 在前後臺對於長連線的需求是不同的。因此當微信在前臺活躍態時,採用了 固定心跳
機制;在前臺熄屏態或者後臺活躍態(進入後臺 10 分鐘內)時,先用幾次最小心跳維持長連線,然後進入 自適應心跳
機制;在後臺穩定態(超過 10 分鐘),則採用自適應心跳計算出來的最大心跳作為固定值。
如果在執行過程中,發生了心跳失敗,則進行重連。同時將心跳間隔調整為斷線前間隔減去 20s,重新走自適應心跳;如果連續 5 次均失敗,則以初始心跳 180s 繼續測試。
Alarm 對齊策略
對於 Android 系統而言,為了減少頻繁喚醒系統導致的電量損耗,提供了 Alarm 對齊喚醒
機制:把一定時間段內的多次 Alarm 喚醒合併成一次,減少系統被喚醒次數,增加待機時間。
而我們的心跳包就是需要在定時結束後自動觸發一次心跳包的傳送,因此,在 Mars 裡面的心跳時間也是按照 Alarm 對齊時間來做心跳間隔,減少電量損耗。
其他
對於微信心跳策略感興趣的話可以閱讀文末的參考文獻,程式碼可以參考 smart_heartbeat 。
VI. 長連線資料協議及加密
長連線傳遞的是二進位制資料,前後端可以自行協商每個位元組要存放的內容即可。當然,也可以考慮採用一些通用協議:比如 SMTP、ProtoBuf 等序列化方案。
參考文章: 《一個基於 TCP/Socket/">WebSockets 的超級精簡的長連線訊息協議》 .
另外,在資料加密方面,可以結合非對稱加密演算法 RSA 和對稱加密演算法 AES 來對資料進行加密傳輸。
這一點不是本文的重點,不做過多贅述。
VII. 長連線通道建設及容災
上面講了長連線的優勢,那我們該如何搭建整個長連線通道呢?這裡我們以美團的長連線通道為例子進行說明,各大廠的方案也是類似的。

上面是一個簡圖,大體流程如下:
- 客戶端與代理長連伺服器建立長連線,代理伺服器可全國多地部署,在建立長連時可以選擇最近的伺服器 IP 就近接入;
- 長連線建立好後,客戶端對要傳送的二進位制資料進行加密並傳輸;
- 代理伺服器收到後,可以通過內部專線或普通 Http 請求來訪問業務伺服器;
- 如果長連接出現問題導致不可用,為保障客戶端執行,需要立即降級成普通 Http 短連或者 UDP 通道。
小結
本文結合了國內大廠如騰訊、美團等長連線框架,針對長連線這個技術點做了完整的介紹和剖析,如有不對或疑問,歡迎留言。
謝謝。
wingjay