我們知道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); ...... }