先仍給出一段測試代碼,同CLOSE_WAIT狀態分析一文測試代碼,main函數如下.測試後,會發現兩邊有大量的TIME_WAIT連接.
int main(int argc, char* argv[]){
int sender = 0;
int ret = 0;
int listen_sock = 0;
if(argc !=2){
printf("usage: ./test 1/n");
return -1;
}
sender = atoi(argv[1]);
if(sender){
while(1){
ret = socket_connect("10.224.55.145",8765);
if(ret < 0){
return -1;
}else{
close(ret);
}
}
}else{
listen_sock = socket_listen(8765,100);
if(listen_sock < 0){
return -1;
}else{
while(1){
ret = socket_accept(listen_sock,NULL);
if(ret < 0){
return -1;
}else{
close(ret);
}
}
}
}
return 0;
}
CLOSE_WAIT狀態分析一文已經介紹了一個通常的TCP連接終止的過程.我們以CLIENT端斷開連接重新介紹下:
CLIENT--- FIN --- SERVER
SERVER--- ACK --- CLIENT
SERVER--- FIN --- CLIENT
CLIENT--- ACK --- SERVER
步驟III,SERVER發送FIN報文後進入LAST_ACK狀態.步驟IV,CLIENT接收到FIN報文併發出ACK報文後進入TIME_WAIT狀態.(當SERVER收到ACK報文後,也即可以進入到CLOSED可用狀態了).
下面先解釋一下TIME_WAIT狀態存在的原因.
MSL(最大分段生存期)指明TCP報文在Internet上最長生存時間,每個具體的TCP實現都必須選擇一個確定的MSL值.TIME_WAIT 狀態最大保持時間是2*MSL.
TCP報文在傳送過程中可能因爲路由故障被迫緩衝延遲,選擇非最優路徑等等,結果發送方TCP機制開始超時重傳.前一個TCP報文可以稱爲"漫遊TCP重複報文",後一個TCP報文可以稱爲"超時重傳TCP重複報文".假設最終的ACK並未被SERVER端收到,則SERVER端將重發FIN,client必須維護TCP狀態信息以便可以重發最終的ACK,否則會發送RST,然後server認爲發生錯誤.TCP實現必須可靠地終止連接的兩個方向(全雙工關閉),client必須進入TIME_WAIT 狀態,因爲client可能面臨重發最終ACK的情形.
如果 TIME_WAIT 狀態保持時間不足夠長(比如小於2MSL),第一個連接就正常終止了, 第二個擁有相同相關五元組的連接出現,而第一個連接的重複報文到達,干擾了第二 個連接.TCP實現必須防止某個連接的重複報文在連接終止後出現,所以讓TIME_WAIT 狀態保持時間足夠長(2MSL),連接相應方向上的TCP報文要麼完全響應完畢,要麼被 丟棄.建立第二個連接的時候,不會混淆.
先調用close()的一方會進入TIME_WAIT狀態.
我們寫server程序總是在調用bind()之前設置SO_REUSEADDR套接字選項.這個套接字選項通知內核,如果端口忙,但TCP狀態位於 TIME_WAIT,可以重用端口.如果端口忙,而TCP狀態位於其他狀態,重用端口時依舊得到一個錯誤信息.如果你的服務程序停止後想立即重啓,而新套接字依舊使用同一端口,此時 SO_REUSEADDR 選項非常有用.必須意識到,會出現上面說的問題,當接收到的連接相關五元組的(協議,本地地址,本地端口,遠程地址,遠程端口)相同時,程序有可能收到非期望數據.
不過一般的端口分配算法能保證五元組中的遠程端口是不一樣的.客戶端的端口分配一般是自動的,且一般都是從上一次分配的端口號+1開始分配的.所以我們要知道的就是理論上有這種可能,事實上是很不可能.