TCP的三路握手和四路揮手及其臨界條件(結合系統調用)

一、TCP的三路握手:


其實這張圖已經說得很清楚了,客戶端應用程序調用connect導致TCP發送一個SYN報文段,服務器端有一個監聽套接字,該監聽套接字收到SYN後,在待連接套接字

隊列中插入一項,然後發送SYN和對客戶端確認的ACK(注意到ACK序列號總是目前等待接受的序列號相同,SYN佔一個字節,所以在SYN的序列號J的基礎上加1得到

ACK的序列號,如果是其他數據報文段,那麼報文段實際長度爲多少,確認序列號就在該報文段的序列號基礎上加多少)。客戶端接收到該SYN+ACK以後,connect就

成功返回,同時向服務端發送ACK。服務端接收到客戶端發送的者三路握手中最後一個ACK之後,就將該連接從待連接套接字隊列移到已連接套接字隊列,等待accept

從已連接套接字隊列中取出。注意到accept總是從對連接套接字隊列執行pop操作,因此accept得到的總是三路握手已完成,連接已建立的套接字,可以說即使不調用

accept,這個已連接的套接字也已經存在於系統中。那麼如果客戶端在三路握手完成之後,accept調用之前crash掉怎麼辦,有些系統對accept之前crash掉的連接在

內核層面已經解決,所以accept不會看到這種狀態的出現,另一些對已經crash掉的連接調用accept則返回ECONNABORT錯誤,因此,最保險的做法是檢查

ECONNABORT錯誤,如果檢查到該錯誤,直接進行下一次accept就行。

二、TCP的四路揮手:



從這個圖可以看到客戶端調用close,導致TCP發送FIN主動發起結束連接的第一次揮手,同時進入FIN_WAIT1狀態,服務器端接收到這個FIN之後發送ACK同時

進入到CLOSE_WAIT狀態,客戶端接受到服務器對自己發送的FIN確認之後進入FIN_WAIT2狀態,直到服務器程序也調用close導致TCP發送FIN,服務器進入

LAST_ACK狀態,客戶端接收到這個FIN之後,發送對服務器端ACK的確認,同時進入TIME_WAIT狀態。注意到由於TCP的延遲確認機制,如果服務器接收到

客戶端的FIN後,及時調用close,會使得對客戶端的確認ACK和服務器自己的FIN同時發送,四路握手變爲三路。首先看這個TIME_WAIT狀態的必要性,第一,

假定客戶端發送給服務器的最後一個ACK丟包(這是完全有可能的),此時服務器會不斷重傳最後一個FIN,而客戶端已經沒有關於這個連接的任何信息,因此

會導致服務器處於錯誤狀態。第二,如果客戶端另一個進程馬上佔用掉剛剛關閉的套接字端口號,此時服務器在上一個連接中發送的數據由於網絡擁塞發生延時,

剛好到達該端口,被新的連接讀取,就會出現串話現象。因此,這個TIME_WAIT狀態一般持續2MSL時長,以保證上一個連接的所有報文都已發送完畢。和連接

操作永遠是由客戶端來主動發起的不同,主動關閉操作也可以由服務器來進行(例如WEB服務器),因此當服務器應當避免TIME_WAIT的出現,或者縮短TIME_WAIT

的時延,因爲每一個TIME_WAIT都是沒有釋放資源的連接,此狀態過多會導致服務器資源消耗嚴重,而且由於服務器必要時需要極短時間內重啓,TIME_WAIT也

會使得服務器的短時間內重啓失敗。

三、TCP連接中的一些臨界情況:

(1)A,B兩個主機上的進程a,b已經通過TCP建立連接c,然後拔掉B主機的網線,此時a進程會處於何種狀態?

如果拔掉B的網線後a進程永遠不通過c連接對b進程發送數據,那麼a進程就永遠不會知道這件事的發生,A主機上爲a,b兩個進程建立的連接將會永遠存在,這就好像

a,b兩個人只能通過有線電話聯繫,突然有一天連接到b的電話線斷了,那麼只要a不給b打電話,他就永遠不知道b的電話線斷了。這裏有一個服務器編程中需要注意的

問題是,如果服務器程序一直只是監聽客戶端的請求並作出回覆,那麼如果客戶端在連接建立之後出現這種斷網、斷電等情況,服務器將永遠不會覺察到客戶端掛掉的

狀態,爲掛掉的客戶端建立的連接c將會永遠存在(存在的連接是要耗費資源的)。那麼如果a進程通過c給b進程發送數據呢?這時候TCP發送該數據,由於收不到b的確認,因此不斷重傳直到超時,(或者收到某個中間路由器回覆的目的不可達),此時TCP就知道b已經掛了,就可以通知應用程序處理這個事件。這也是TCP中KEEPALIVE存在的意

義(如果一個連接上較長時間沒有接受和發送數據,設置了KEEPALIVE選項的TCP會發送保活報文段,收到確認就當什麼事兒也沒有,如果超時或者收到destion 

unreachable,就通知應用程序處理該事件。那麼如果拔掉網線後馬上連接,而且保證此時a,b兩個進程沒有互相發送數據,會發生什麼?答案是一切正常,就好像

a,b兩個人的斷掉的時候互相之間沒有打過電話,等到他們打電話時,電話線已經被電信部門修好了,那麼a,b就永遠不知道電話線斷掉的這個事情。

(2)A,B兩個主機上的進程a,b已經通過TCP建立連接c,b進程一直在忙別的事情(比如阻塞在別的IO上面),在此期間a進程調用了close,會發生什麼?

如果b進程在忙完別的事情後馬上讀取c連接上的數據,那麼讀到FIN並調用close正常關閉連接。如果b進程還要往c連接上寫數據會發送什麼?第一次寫數據是可以

正常進行的,因爲TCP是雙向連接,因此b接收到a的FIN會認爲a不會再發送數據,但並不以爲着不能向a寫數據,a進程接收到b發送來的(非期望的)數據後,會給

b進程發送一個RST,只要b進程的下一次寫操作發生在接收到a的RST之前,寫操作一直會正常進行。知道接收到a的RST之後,在對a進行寫操作,會返回返回EPIPE錯誤,

同時出發SIG_PIPE信號(默認終止進程),因此服務器程序一般要忽略SIG_PIPE信號,並對EPIPE錯誤進行處理。

(3)A,B兩個主機上的進程a,b已經通過TCP建立連接c,此時B主機突然斷電宕機,然後馬上重啓(假定b程序是開機自動啓動的服務器程序),此時a進程往b進程寫數據

會發生什麼?由於B的宕機,b進程不會再crash時給a發送FIN,所以a進程在給b進程寫數據之前是不會感知到這一現象,等到B主機接收到a進程發來的數據時(這是可以

的,因爲B主機已經重啓),b進程由於crash導致關於a,b之間的連接c的任何信息都已不存在,所以B主機找不到這樣一個連接,因此會讓a進程重新連接,a進程返回

ECONNREST錯誤。

四、TCP連接的狀態轉換圖:



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章