目前TCP/IP已經成為網路的主導技術。通過對TCP底層實現的分析,對TCP/IP編程中一個長期使人困惑的問題----網路連接中斷的即時檢測—進行深入的分析,並提出相應的解決方案。
0引言
作為現代網路的主導技術,TCP/IP編程看起來非常簡單,但在經曆了最初的高效率後,往往會在細節面前停滯不前,這常常是因為對TCP協議底層細節的缺乏瞭解所導致的。
TCP是連線導向協議,而UDP是無連線協定,許多初學者發現可以沒有任何資料流通過一個閒置TCP串連,如果TCP串連的雙方都沒有向對方發送資料,則在兩個TCP模組之間不交換任何資訊。這意味著可以啟動一個客戶與伺服器建立一個串連,然後離去數小時至數個星期串連依然保持。中間路由器可以崩潰和重啟,電話線可以被掛斷再連通,只要兩端的主機沒有被重啟,則串連依然保持建立。
因此,初次接觸TCP/IP協議組的程式員感到很迷惑:TCP中並沒有可以在其他網路通訊協定中發現的串連階段的輪詢,甚至發現TCP不給應用程式提供既時的網路連接中斷的通知。一些程式員據此斷定TCP不適用於一般的應用程式到應用程式的通訊。TCP為什麼不提供通知呢。
1原理分析
TCP通常被稱為可靠的協議,即“TCP保證發送資料的傳輸”,這通常會產生誤解:TCP不會出錯。事實是只要雙方保持串連,TCP就能保證資料的正確傳輸,但是當串連中斷時,就會產生問題,原因有3個:1)永久的或暫時的網路紊亂;2)對等方應用程式崩潰;3)對等方主機崩潰,當出現以上問題時,會使雙方應用程式不能互相通訊,而其中一個應用程式卻不能立刻意識到。發送資料給對等方的應用程式可能在知道TCP在放棄重發之前才會發現串連中斷,。如果應用程式沒有發送資料,可能永遠不會發現網路已經中斷。例如應用程式可能是一個正在等待對等方發出下一個請求的伺服器,因為用戶端不能和伺服器通訊,下一個請求永遠不會到達,甚至用戶端的TCP放棄並撤銷串連,導致用戶端中斷,伺服器也沒有意識到這一點。
其他的通訊協定如SNA和X.25,當串連中斷時會給應用程式提供通知。比如簡單的直接點對點專有連結複雜的任何協議都必須使用一種輪詢協議來連續地測試對等方是否存在。輪詢-選擇協議可能會採用顯式地發送“你有要發送給我的任何資料嗎?”諸如此類的訊息的形式,或者它們會採用後台靜態幀的形式來檢測虛擬線路是否仍然存在。每一個輪詢訊息都會消耗網路資源,而這些資源本來可以用於“承載”資料的傳輸。
對可獲得的網路頻寬的消耗是TCP不提供網路中斷立即通知的一個原因。因為大多數的應用程式不需要即時的通知,所以沒有必要以降低頻寬的代價來提供這個功能。需要以一種及時的方式知道對等方不可到達的應用程式可以實現它們自己的機制來發現網路中斷,如後面介紹的那樣。
TCP/IP設計中使用的一個基本原則是終端對終端參數[Saltzerelal.1984],該參數應用到網路上時可以表述為所有的智能應當儘可能地接近串連的終端點,而網路本身應當相對沒有智能。這就是為什麼TCP自己處理錯誤控制而不是依靠網路來提供它的原因。當這個原則應用到監控對等應用程式之間的串連時,應用程式應當提供它自己需要的功能,而不是不管應用程式是否需要這個功能都由下層提供。
TCP不提供及時串連中斷通知的最重要的原因是:網路突然中斷時仍可以維持通訊的能力。TCP最早是美國國防部發起的一項研究的成果,它要求提供一個遇到戰爭或自然災害引起的網路中斷時仍然可以維持電腦之間可靠的通訊的網路通訊協定。通常網路紊亂是暫時的,路由器也可能找到串連的另一條路徑。通過允許串連的暫時中斷,甚至在終端應用程式意識到中斷之前TCP就已經處理好了紊亂。
2解決方案
2.1方案一:使用TCPKeep-alive機制
人們希望知道串連是否中斷了,因此許多TCP的具體實現提供了一個稱作Keep-alive的機制用於檢測死串連,但是它並不經常用於應用程式。如果應用程式啟用Keep-alive機制時,TCP就會在串連已經空閑了一段時間間隔後發送一個特殊的段給對等方。如果對等方主機可到達而且對等方應用程式仍然運行,對等方TCP就會響應一個ACK應答。在這種情況下,TCP發送Keep-alive重設空閑時間為零,並且應用程式不會收到訊息交換的任何通知。
如果對等方主機可以到達但是對等方應用程式沒有運行,對等方TCP就響應RST訊息,發送Keep-alive訊息的TCP撤銷串連並返回ECONNRESET錯誤給應用程式。這通常是對等方主機崩潰後重起的結果,因為如果僅僅是對等方應用程式中斷或崩潰了,對等方TCP可能已經發送FIN訊息了。
如果對等方主機沒有響應ACK或RST訊息,發送Keep-alive訊息的TCP繼續發送Keep-alive探詢訊息,直到它認為對等方不可到達或已經崩潰了。這時它就撤銷串連並通知應用程式ETIMEDOUT錯誤,如果路由器已經返回主機或網路不可到底的ICMP訊息的話,就返回EHOSTUNREACH或ENERUNREACH錯誤。
通過Keep-alive機制,TCP提供了協議層面的網路中斷通知功能,但這種機制有很多問題以至於很少用於應用程式。
首先,Keep-alive並不是TCP規範中的一部分,長期以來,是否在TCP中提供Keep-alive機制一直是有爭論的話題,因此,Keep-alive不是所有的TCP實現都提供,而且實現細節也有所不同。
需要即時通知網路中斷的應用程式使用Keep-alive功能的第二個問題是和時間間隔有關的。RFC1122[Braden1989]認為,如果TCP實現了Keep-alive,保活間隔必須是可配置的,但是其預設值必須不小於兩個小時,之後它才能發送Keep-alive探詢訊息。那麼因為對等方的ACK訊息並不是可靠地遞交,它必須在放棄串連之前重複發送探詢訊息。4.4BSD具體實現在撤銷串連之前以75秒的時間間隔發送9個探詢訊息。
這意味著BSD派生的具體實現,大約需要2小時11分鐘15秒才能發現串連已經中斷了。這個時間值只有在我們認識Keep-alive是用於釋放被死串連佔有的資源時才有意義。例如,當用戶端串連到伺服器而用戶端主機崩潰時就有可能發生這樣的串連。如果沒有Keep-alive機制,伺服器就會永遠等待用戶端的下一個請求,這是因為它永遠沒有接收到FIN訊息。(因為基於PC系統的使用者僅僅是關閉電腦和modem而不是正確地關閉應用程式,所以這種情況正越來越普遍。)
由於2小時的時間對於即時檢測幾乎沒有意義,因此一些具體實現允許改變一個或兩個時間間隔值,但因為保活間隔時間是系統級的變數,這些值的改變影響系統上所有的TCP串連,這是Keep-alive作為一個串連監控機制沒有實際使用的主要原因:預設的時間段太長了,如果改變了預設值,它們就失去了清除長時間死串連這一最初的意義。
Keep-alive的另一個問題是它們不僅僅檢測死串連,同時也撤銷它們。這有可能是應用程式所希望的,但是也有可能不是應用程式所希望的。
2.2方案二:使用heartbeat檢測
第二種方案是在應用程式層來實現對串連中斷的檢測,其基本思想是象Keep-alive一樣,定時向對等方發送探針,由於是在應用程式層實現,可以根據應用程式靈活掌握探測時間。實際上,邊界網關協議BGP就是通過定期發送Keep-alive報文給其鄰站來檢測TCP串連對端的鏈路或主機失敗,兩個報文之間的時間間隔建議值為30秒。這種在應用程式層實現的對串連中斷的檢測通常稱為“heartbeat檢測”。
以上演算法原則儘管是在TCP上討論,但一樣適用於UDP。這種方案的最大優點在於提供了最大的靈活性。
2.3方案三:利用TCP-KEEPALIVE通訊端選項
第三種方案是使用新的POSIX1003.1g通訊端選項TCP-KEEPALIVE,它允許在每一個串連的基礎上指定逾時時間間隔,但是它沒有廣泛地實現,因此應用中使用的不多。在Linuxkernel2.4之後的TCP實現中,可以這樣設定socket的探針間隔:
#ifdefTCP_KEEPALIVE
intsecs=120;/*2minutes*/
setsockopt(s,IPPROTO_TCP,TCP_KEEPALIVE,&secs,sizeof(secs));
#endif
這種方案的缺點是,由於TCP-KEEPALIVE通訊端選項是比較新的POSIX特性,不是所有TCP實現都給予支援,因此存在移植性問題;同時,由於是在傳輸層進行探測,靈活性不如在應用程式層的實現。
3總結
綜上所述,對於TCP串連中斷的檢測,原理上都是通過定時向對等方發送探針資料來進行檢測,不同方案的區別在於實現於不同層次,應用中可以根據需要進行不同的選擇,關鍵在於對TCP串連的原理要透徹理解。
原文地址:http://www.guigu.org/news/guiguvip/201206117802.html