在各種網路異常情況的背後,tcp是怎麼處理的?又是怎樣把處理結果反饋給上層應用的?本文就來討論這個問題。分為兩個場景來討論
建立連線時的異常情況
1 正常情況下
經過三次握手,客戶端連線成功,服務端有乙個新連線到來。
2 客戶端連線了服務端未監聽的埠
在這種情況下,服務端會對收到的syn回應乙個rst(rfc 793 3.4),客戶端收到rst之後,終止連線,並進入closed狀態。
客戶端的connect返回econnrefused 111 /* connection refused */。
3 客戶端與伺服器之間的網路不通,這又分兩種情況
connect返回主機不可達。具體資訊在不同系統上不一樣,比如linux上的定義是ehostunreach 113 /* no route to host */。明顯給出了乙個不可訪問的位址(例如,訪問乙個不存在的本地網路位址,或者dns解析失敗會導致這種情況。
connect返回連線超時。這種情況下,客戶端傳送的syn丟失在網路中,沒有得到確認,客戶端的tcp會超時重發syn。以ubuntu 12.04為例,重發syn的時間,系列是:0,1,3,7,15,31,63(2n-1-1)。即傳送7個syn後等待乙個超時時間(例如:127秒),如果在這段時間內仍然沒有收到ack,則connect返回超時。
在這兩種情況下, 服務端的狀態沒有變化,對服務端來講什麼也沒發生。
4 建立連線的過程中包丟失
三次握手傳送的包系列是syn > syn-ack > ack
syn丟失。這種情況就是3種的第2種情況。
syn-ack丟失。從客戶端的角度來講以前面一種情況類似。從服務端的角度來講,由listen狀態進入syn_revd狀態。服務端的tcp會重發syn-ack,直到超
時。syn攻擊正是利用這一原理,攻擊方偽造大量的syn包傳送到伺服器,伺服器對收到的syn包不斷回應syn-ack,直到超時。這會浪費服務
器大量的資源,甚至導致奔潰。對服務端的應用層來講,什麼也沒有發生。因為tcp只有在經過3次握手之後才回通知應用層,有新的連線到來。
ack丟失。這對服務端來講與2相同。對於客戶端來講,由syn_sent狀態進入了establised狀態,即連線成功了。連線成功後客戶端就可以傳送資料了。
但實際上資料是傳送不到服務端的(我們假設客戶端收到syn-ack之後,客戶端與服務端之間的網路就斷開了),客戶端傳送出去的資料得不
到確認,一般重發3次左右就會處於等待ack的狀態(win7)。而ubuntu 12.10下,呼叫send會返回成功,直到tcp的緩衝被填滿(測試環
境:區域網,感覺這個不是很合理,按照書上所說:應該是使用「指數退避」進行重傳 -- tcp/ip協議詳解, 大概是我的測試環境中有nat所致
吧)。最終,客戶端產生乙個復位訊號並終止連線。返回給應用程式的結果是connection time out(errno: 110)
連線建立成功後出現的異常情況
1 客戶端與伺服器的網路斷開,雙方不再傳送資料
這樣,雙方都不知道網路已經不通,會一直保持establishded狀態,除非開啟了so_keepalive選項。
3 網路斷開,一方等待著另一方傳送資料
這種情況下,等待資料的一方將一直等待下去。接收方無法直接知道網路已經斷開,一般是設定乙個超時時間,超時時間到就判斷為網路已斷開。傳送
資料的一方的反應如2所述。
4 一方crash,另一方繼續傳送/接收資料
這依賴於tcp協議棧對crash的反應。與系統相關性很大,例如:
在windows下:按ctrl+c結束程式,會傳送rst段。而在linux下,按ctrl+c結束程式,會呼叫close。
在wind7下,如果沒有呼叫close而結束程式,tcp會傳送rst。而ubuntu12.10上,則會傳送fin段。
1.crash的一端傳送fin,相當於呼叫了close
沒有crash的一端接收資料,具體的反應與系統有關,例如
linux 3.8.0-29-generic呼叫recv返回-1,errno被設定為22,invalid argument,而linux3.3.6-030306-generic呼叫recv返回0.在tcp內部,呼叫recv時,傳送fin,終止連線(linux)。
windows情況以此不同,recv返回0,表示對方呼叫了shutdown。tcp內部傳送乙個rst。
但共同點是recv都會立即返回失敗。
沒有crash的一端傳送資料
第一次呼叫send返回成功,資料會被傳送到crash的一端,crash的一端會回應乙個rst,再次呼叫send返回-1, errno被設定為32, broken pipe。 注意:這會向應用程式傳送sigpipe訊號,你的程式會莫名其妙退出。這是因為程式對sigpipe的預設處理就是結束程式。
這是編寫伺服器程式是最需要注意的乙個問題。最簡單的處理方法是忽略該訊號 -- signal(sigpipe,sig_ign);
windows下行為是一樣的, 不同的是返回的錯誤是10053 - wsaeconnaborted, 由於軟體錯誤,造成乙個已經建立的連線被取消。
共同點第一次send成功,之後就出錯。
2.crash的一端傳送rst
沒有crash的一端接收資料
呼叫recv返回-1,errno被設定為104, connection reset by peer。在tcp內部,當收到rst時,把錯誤號設為econnreset。
沒有crash的一端傳送資料
呼叫send返回-1,errno被設定為104, connection reset by peer。在tcp內部,當收到rst時,把錯誤號設為econnreset
3.crash的一端即沒傳送fin也沒傳送rst
沒有crash的一端接收資料
呼叫recv會一直阻塞等待資料到來
沒有crash的一端傳送資料
重傳一定次數後,返回connection time out。
5 一端關閉連線
這種情況與一端crash並傳送fin 的情況相同,參看4.1
總結 上面分析的目的是:當程式出現網路異常時,能夠知道問題的原因在哪?
作為開發者,我們主要關心應用層面的返回狀態。一般出錯的地方是呼叫connect, recv, send的時候。
下面做乙個總結
connect函式返回狀態及其原因
recv函式返回狀態及其原因
send函式返回狀態及其原因
各種不同步的狀態,都是通過傳送rst來恢復的,理解這些狀況的關鍵在於理解何時產生rst,以及在各種狀態下,對rst段如何處理
深入理解Socket的讀寫
對於linux網路程式設計,有很多坑需要我們去踩。在這個時候,我們才會知道理論知識的重要性。無論是哪種語言,網路程式設計都可以寫成厚厚的一本書。舉個例子,比如 當網路斷掉,我們呼叫write去往socket中寫入資料,為什麼返回正常寫入呢?所以有空多看看 tcp ip詳解 unix網路程式設計 等經...
深入理解C語言 深入理解指標
關於指標,其是c語言的重點,c語言學的好壞,其實就是指標學的好壞。其實指標並不複雜,學習指標,要正確的理解指標。指標也是一種變數,占有記憶體空間,用來儲存記憶體位址 指標就是告訴編譯器,開闢4個位元組的儲存空間 32位系統 無論是幾級指標都是一樣的 p操作記憶體 在指標宣告時,號表示所宣告的變數為指...
mysql 索引深入理解 深入理解MySql的索引
為什麼索引能提高查詢速度 先從 mysql的基本儲存結構說起 mysql的基本儲存結構是頁 記錄都存在頁裡邊 各個資料頁可以組成乙個雙向鍊錶每個資料頁中的記錄又可以組成乙個單向鍊錶 每個資料頁都會為儲存在它裡邊兒的記錄生成乙個頁目錄,在通過主鍵查詢某條記錄的時候可以在頁目錄中使用二分法快速定位到對應...