Linux TCP 伺服器程式設計(五):埠重用及TIME_WAIT
上一節說到設定套接字選項的問題,我們這一節主要處理上一節說的“Address already in use”問題,這個問題通過設定設定一個套接字選項來解決。
這個錯誤提示是在bind函式呼叫時發生的,bind函式經常遇到這個問題:試圖繫結一個已經在使用的埠。但是我們已經明確關閉程序了,這是有套接字狀態TIME_WAIT引起的,該狀態在套接字關閉後幾分鐘仍保留,在TIME_WAIT狀態退出之後,套接字被刪除,該地址才能重新繫結而不出問題。
首先介紹一下設定套接字選項函式setsockopt():
函式原型:
#include <sys/socket.h> int setsockopt(int sockfd,int level, int optname, const void optval, socklen_t optlen);
sockfd - 開啟的描述符。
level - 解釋選項程式碼或特定協議的程式碼,如SLO_SOCKET。
optname - 套接字選項,如SO_REUSEADDR。
optval - 某變數指標。
optlen - 某變數長度。 我們只需在bind函式之前呼叫該函式即可.
int on = 1;
setsockopt(sockfd, SOL_SOCKET, SO_REUSERADDR, &on, sizeof(on);
重新編譯,執行,發現可以解決該問題。
接下來說說TIME_WAIT:
主動關閉的socket端會進入TIME_WAIT狀態,並且持續2MSL時間長度,MSL就是maximum segment lifetime(最大分節生命期),這是一個IP資料包能在網際網路上生存的最長時間,超過這個時間將在網路中消失。MSL在RFC 1122上建議是2分鐘,而源自berkeley的TCP實現傳統上使用30秒,因而,TIME_WAIT狀態一般維持在1-4分鐘。
IP頭部有一個TTL,最大值255。儘管TTL的單位不是秒(根本和時間無關),我們仍需 假設,TTL為255的TCP報文在Internet上生存時間不能超過MSL。 TCP報文在傳送過程中可能因為路由故障被迫緩衝延遲、選擇非最優路徑等等,結果 傳送方TCP機制開始超時重傳。前一個TCP報文可以稱為"漫遊TCP重複報文",後一個 TCP報文可以稱為"超時重傳TCP重複報文",作為面向連線的可靠協議,TCP實現必須 正確處理這種重複報文,因為二者可能最終都到達。
一個通常的TCP連線終止可以用圖描述如下:
client server
FIN M
close -----------------> (被動關閉)
ACK M+1
<-----------------
FIN N
<----------------- close
ACK N+1
----------------->
為什麼需要 TIME_WAIT 狀態? 假設最終的ACK丟失,server將重發FIN,client必須維護TCP狀態資訊以便可以重發
最終的ACK,否則會發送RST,結果server認為發生錯誤。TCP實現必須可靠地終止連 接的兩個方向(全雙工關閉),client必須進入 TIME_WAIT 狀態,因為client可能面 臨重發最終ACK的情形。
TIME_WAIT狀態存在的理由:
1)可靠地實現TCP全雙工連線的終止
在進行關閉連線四路握手協議時,最後的ACK是由主動關閉端發出的,如果這個最終的ACK丟失,伺服器將重發最終的FIN,因此客戶端必須維護狀態資訊允許它重發最終的ACK。如果不維持這個狀態資訊,那麼客戶端將響應RST分節,伺服器將此分節解釋成一個錯誤。因而,要實現TCP全雙工連線的正常終止,必須處理終止序列四個分節中任何一個分節的丟失情況,主動關閉的客戶端必須維持狀態資訊進入TIME_WAIT狀態。
2)允許老的重複分節在網路中消逝
TCP分節可能由於路由器異常而“迷途”,在迷途期間,TCP傳送端可能因確認超時而重發這個分節,迷途的分節在路由器修復後也會被送到最終目的地,這個原來的迷途分節就稱為lost duplicate。在關閉一個TCP連線後,馬上又重新建立起一個相同的IP地址和埠之間的TCP連線,後一個連線被稱為前一個連線的化身(incarnation),那麼有可能出現這種情況,前一個連線的迷途重複分組在前一個連線終止後出現,從而被誤解成從屬於新的化身。為了避免這個情況,TCP不允許處於TIME_WAIT狀態的連線啟動一個新的化身,因為TIME_WAIT狀態持續2MSL,就可以保證當成功建立一個TCP連線的時候,來自連線先前化身的重複分組已經在網路中消逝。