TCP的十一種狀態與三次握手分析(有圖)


我們知道TCP是面向連接的,我們只知道有連接斷開,其實內部還有一些比較復雜的狀態。去了解各個狀態之間的切換有助於我們更加深入的了解TCP。下面我們就來分析各個狀態。

1、如下圖示(圖源百度)圖中顯示出了10種狀態。

我們假定斷開時是client主動斷開的。
對於server來說狀態有:closed -> listen -> syn_recv -> enstablished -> close_wait -> last_ack -> closed
對於client來說狀態有:closed -> syn_send -> enstablished -> fin_wait1 -> fin_wait2 -> time_wait

2.結合三次握手進行分析狀態
我們知道三次握手的這樣: 
client 發送 syn x到server
server 回復syn y和ack x+1
client再回復 ack y+1
下面分析上面三步與狀態的關系:
1)首先,剛創建的socket都是closed狀態,server調用listen之后進入listen狀態
2)接着、客戶端調用connnect。(TCP協議會完成三次握手,client發送第一個syn之后就進入syn_send狀態。與此同時,server收到syn並回復syn和ack,server進入syn_recv狀態)
3)之后、client和server都進入了enstablisted狀態。之后就可以互相發送數據了。

 

關於TCP協議頭中的確認號ack的理解可以參考:http://www.cnblogs.com/xcywt/p/8075623.html

關於Wireshark的理解可以參考:http://www.cnblogs.com/xcywt/p/8025113.html

結合Wireshark抓包分析三次握手:

 這里設置的過濾器,先清空當前捕獲的包,在瀏覽器打開博客園。

假設TCPa -> TCPb

第一次:如下圖,發送一個請求 syn,序號為0

第二次:B回復,發送syn,ack為0+1

第三次,A回復,ack = y + 1。其中y為之前B發送過來的序號,這里過來的序號的0,所以ack = 1;


3.結合斷開時四次握手進行分析狀態

如圖(圖源百度,侵刪)

我們知道四次握手是這樣的(假定是client先close 的):
client調用close。TCP協議會發送FIN x給server
server收到FIN x之后,會回復ack x+1
接着、server調用close,給客戶端發送FIN y
最后,客戶端回復ack y+1
分析與狀態的關系:
1)client調用close,發送了FINx。client進入fin_wait1狀態。server收到並回復ack,server進入close_wait狀態。然后client會收到ack,進入fin_wait2狀態
2)server接着調用close,給client發送了fin,server則進入了了last_ack狀態。
3)client收到FIN 之后,回復ack。client進入time_wait狀態。server收到ack之后,進入closed狀態。
client在保持了2個MSL之后就進入closed狀態)

4.注意事項
1)client進入time_wait狀態之后,會保持在這個狀態2MSL。目的是為了確保發送過去的ack可以被收到(因為后面已經沒有數據可以發送了)。
2)連接過程是狀態的改變,促使狀態的改變是用戶的調用。所以切換狀態不一定是用戶的調用。(比如,server進入close_wait狀態,純粹是TCP協議做好的,用戶並沒有調用什么接口)
3)關於退出時的分析,存在一個主動一個被動關系。上面分析的client主動,則client會出現fin_wait1、fin_wait2、time_wait狀態。server會出現close_wait、last_ack狀態。
如果是server主動斷開的,則關系剛剛反過來了。server先進入fin_wait1狀態,然后是fin_wait2狀態。后面整個就反過來了。

5.關於closing狀態的出現(這里就是第十一種狀態)
通過上面的分析我們知道,client主動退出時,先給server發送了一個FIN。接着會收到一個ack確認這個FIN。
如果沒收到ack,而是收到server發來的FIN y。那么這時候則進入closing狀態。
這種情況是怎么出現的呢:那就是雙方幾乎同時closer一個socket。這是雙方都正在關閉socket連接。這種情況出現的幾率很小

6.為什么連接需要三次握手,斷開需要四次握手。
首先我們知道,TCP協議是去全雙工的。可以在發送的同時進行接收數據。
假定是主機A和主機B進行通信,斷開時是A主動斷開的。
1)三次握手:第一次握手表明A可以發數據給B。但是無法保證B發給A的數據可以被收到。所以B也需要發送SYN給A,A對它進行回應,才保證了B也可以發數據給A。
個人理解可以把三步拆分為四步理解:
a)主機A給B發送SYN
b)主機B回復ack --- 這時表明A可以發數據給B
c)主機B發送SYN給A
d)主機A回復ack --- 這時表明B也可以發送數據給A
只不過協議中,把中間兩步放在一步進行了。

2)四次握手,就像下面這樣理解:
a)主機A給B發送FIN,表示對B說“我要斷開了”
b)主機B回復ack進行確認,表示對A說“嗯,我知道了,你可以斷開了”
c)然后B發送FIN給A,表示對A說“A,我也要斷開了”
d)A回復ack進行確認,表示對B說:“嗯,知道了,你斷開吧”
前兩步對A進行斷開,后兩步對B進行斷開。
那么為什么不能把中間兩步進行合並呢,因為無法保證被斷開的一方的數據已經傳送完畢了。
就拿上面的例子來說,假如A斷開了通知B,但是B還有數據沒有發送完畢,如果立即斷開(調用close發送FIN),就無法保證數據的可靠性。
如果等數據發送完畢再將fin和ack一起發過去,n那么A就會長時間處於fin_wait1狀態。這樣就比較不好了。

實際情況中我們可能會在server忘記關閉客戶端的socket。那么client就會一直處於fin_wait2狀態,越來越多的fin_wait2狀態會導致系統崩潰。所以我們需要在編程中要注意在什么情況下要關閉雙方的socket。

 

7.其他知識
什么時候會收到SIGPIPE這個信號呢?
當server關閉一個連接之后,client接着發送數據,第一次發送會收到一個RST的響應。如果接着發送則系統會發出一個SIGPIPE信號給client。
系統默認的處理是將應用程序退出。實際編程中,我們可以捕獲這個信號,讓應用程序不退出

 如下(偽代碼):

#include"signal.h"
void sig_recvpipe(int sig)
{
}

int main()
{
    ......
    signal(SIGPIPE, sig_recvpipe);
    ......
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM