一般長連線都會附帶著心跳保活機制,直觀的理由就是為了確保這個連線一直有效,但是以前卻沒有往深層次去想為什麼要這麼做。本文嘗試進行一下梳理。
一般網際網路應用是「請求
/響應」模式,即只能客戶端主動發起請求,服務端被動響應請求(因為
nat的原因,服務端無法主動找到客戶端)。對於一些需要伺服器主動下發資料到客戶端的場景,如果使用「請求
/響應」模式,那麼只能由客戶端輪詢的方式來服務端拉資料,這樣不僅占用網路頻寬,還給通訊雙方增加壓力。如果客戶端先主動發起建立長連線,然後一直保持連線有效,那麼服務端相當於有一條主動通往客戶端的通道。
tcp長連線是指通訊雙方在三次握手之後一直不關閉
socket
而維持著這個連線,然後在需要通訊時,可以立刻傳送
tcp資料流,不需要每次通訊前都經過三次握手和四次揮手的流程。這樣做的優點有: (
1)節省連線建立和終止的開銷,提高網路頻寬利用率。乙個典型的例子是
支援的pipeline。
(2)支援雙向通訊,比如實時通訊或者訊息推送之類的服務。
那麼,如何保證長連線一直有效?如果網路鏈路中間某個節點斷了,雙方是否能感知到?
(1)nat
ipv4
環境下,客戶端一般位於
nat之後,
nat裝置(比如家裡的路由器)會對所有網路連線建立乙個
nat對映表。裝置的儲存空間必定有限,所以需要對
nat對映條目有一定的淘汰策略(比如
lru)來保證新的網路連線能正常建立。所以會淘汰一些老的不活躍連線。一旦連線被淘汰,那麼伺服器就無法通過主動
push
的方式到達客戶端了。 (
2)系統故障
正常情況下即使應用程式不
close
連線,在程序退出時,作業系統也會進一步做資源**(即幫忙發最後的
fin請求並完成四次揮手過程)。但是如果作業系統自己也崩潰了,這個事就永遠沒人去做,這就意味著如果不傳送資料對端永遠不可能知道這個連線已關閉。 (
3)斷電或者網線中斷
通訊雙方斷電、中間節點斷網。比如家裡的路由器斷電了,那麼同樣,通訊雙方都無法知道這件事,如果過一會路由器又恢復了,連線其實還是有效的。
眾所周知,
tcp位於
ip層之上,其實
tcp連線只是存在於通訊雙方之間的虛擬連線,資料報最終還是要封裝成
ip包路由出去的,中間的節點其實並不知道
tcp的存在,所以才有前面說的各種無法感知的場景。
而之所以說
tcp能感知連線斷開,是指正常情況下,任何一方只要正常關閉連線,協議棧會向對方傳送乙個
fin包,此時對方
read
呼叫會返回長度
0來標誌這個連線關閉。一般情況下,即使主動關閉方應用程序不顯示關閉連線而直接退出程序,作業系統也會幫忙做善後工作完成四次揮手過程。這種情況下對端使能馬上感知連線關閉的。
然而,現實往往很骨感,當出現前面羅列的異常情況時,
read
呼叫並不會返回任何資訊。只有當
write
時,服務端才能從作業系統的錯誤碼來判斷各種異常。但是問題是伺服器往客戶端主動下發資料的頻率可能並不高,或者是服務本身要求
push
成功率要盡量高。所以需要有一種手段來盡快發現各種連線異常並促使客戶端重新建立連線,這便是應用層心跳。
(1)keepalive
預設配置一般探測間隔比較長,難以滿足應用層的靈敏度要求。即使通過修改配置也會有第二點問題。
net.ipv4.tcp_keepalive_intvl = 75
net.ipv4.tcp_keepalive_probes = 9
net.ipv4.tcp_keepalive_time = 7200
說明:這是
linux
預設的配置函式,含義是
7200s
發一次探測包,如果成功收到
ack則結束,應用程式無感知;如果超時則每隔
75s重試,重試
9次判斷連線異常,此時關閉本地連線並把
errno
設定為etimeout
;如果對方系統奔潰並恢復了,則會收到
rst響應,此時關閉本地連線並把
errno
設定為econnreset。
(2)配置keepalive
可以修改作業系統全域性的設定,但在高併發場景下會增加系統負擔。一種解決方案是程式針對某個描述符進行單獨設定,但這樣依然無法避免第三點的問題。 (
3)keepalive
是協議棧層面的事情,即使正常也無法說明應用層正常。比如應用層程式死鎖、負載很高等,這種情況下協議棧還能正常對
keepalive
請求作出響應,但應用程式已經無法正常服務了。使用應用層心跳能夠讓客戶端及時感知各種服務異常並重新選擇正常節點進行連線。
關於tcp
的三次握手和四次揮手過程,參考資料2
三次握手及狀態(參考資料2)
四次揮手及狀態
(參考資料2)
關於read
、write
系統呼叫在各種異常情況下的表現(一般結合
epoll
使用):
(1)read
:當對端程序關閉
socket
或者程序異常退出,
epoll
會返回可讀事件並且
read
返回長度是
0。本端能夠第一時間關閉連線。
(2)read
:當發生前文第二點的異常時,如果本端沒有資料需要傳送,那麼
read
不會有任何返回。除非啟用了
keepalive
(etimeout
)或者使用應用層心跳才能發現這種問題並關閉連線。
(3)write:
當對端程序關閉
socket
或者程序異常退出,如果繼續傳送資料,那麼本端會收到
rst響應並設定
errno
為econnreset
。如果繼續傳送資料則會收到協議棧的
sigpipe
訊號,如果不捕獲這個訊號,預設處理是退出程序,所以一般服務程序都會捕獲這個訊號並忽略它。
(4)write
:當發生前文第二點的異常時,返回錯誤並且
errno
為etimedout
或者ehostunreach。
IM為什麼需要心跳
對於客戶端而言,使用 tcp 長連線來實現業務的最大驅動力在於 在當前連線可用的情況下,每一次請求都只是簡單的資料傳送和接受,免去了 dns 解析,連線建立等時間,大大加快了請求的速度,同時也有利於接受伺服器的實時訊息。但前提是連線可用。nat超時是影響tcp連線壽命的乙個重要因素 尤其是國內 所以...
長連線(輪詢,心跳,socket)
概念 表示客戶端和伺服器端保持聯絡,客戶端向伺服器端一直獲取資料,或者是伺服器端一直向客戶端推送資料。基本的長連線方式 輪詢表示客服端利用定時器原理 setinterval 間隔時間內向伺服器請求最新資料。心跳原理跟輪詢類似,但是心跳在一段時間內沒有向到伺服器請求,就會認為客戶端狀態異常。socke...
長連線 心跳保活機制
而在實現長連線方式時,存在很多效能問題,如 長連線保活 今天,我將 手把手教大家實現自適應的心跳保活機制,從而能高效維持長連線 確保實時性 避免短時間內重複連線所造成的通道資源 網路資源的浪費 可是,長連線會存在斷開的情況,而 斷開原因 主要是 長連線所在程序被殺死 nat超時 網路狀態發生變化 其...