1. 程式人生 > >TIME-WAIT狀態和reuse問題

TIME-WAIT狀態和reuse問題

前言

上一篇看了TCP的三次握手與四次揮手,記得四次揮手,主動斷開連線的一方最後一個狀態就是TIME-WAIT狀態,並且一定是主動斷開連線的一方,它可能使socket能陷入一種時間比較長的狀態,過多的TIME-WAIT會影響新socket的建立。那麼TIME-WAIT為什麼會存在?它的作用又是什麼呢?
TCP連線和斷開連線狀態轉換圖:
這裡寫圖片描述

TIME-WAIT

從上圖可以看到,客戶端連線在收到伺服器的結束報文段(6)之後,並沒有直接進入CLOSED狀態,而是轉移到TIME-WAIT狀態。此狀態一般要等待2倍的MSL(報文段的最大生存時間)的時間,才能完全關閉。RFC 1122建議值為2min。
TIME-WAIT狀態存在的原因有兩點:

  1. 可靠地終止TCP連線
  2. 確保新TCP連線和老TCP連線不會相互干擾

原因1:可靠地終止TCP連線
其實就是確保“主動關閉”一端最後發出的ACK到達“被動關閉“的一端。假如用於確認伺服器結束報文段6的TCP報文段7丟失了,那麼伺服器將會重發結束報文段。而客戶端也就是主動斷開的一端需要停留在某個狀態重複收到結束報文段。假如客戶端傳送完ACK報文段後直接進入CLOSED狀態,而最後的ACK報文段又丟失了,伺服器程序會不斷重發結束報文段,客戶端則以復位報文段來回應伺服器,伺服器會認為這是個錯誤,因為它現在在等的是ACK報文段。
原因2:確保新TCP連線和老TCP連線不會相互干擾
Linux下,一個TCP埠不能被同時開啟兩次以上。當一個TCP連線處於TIME-WAIT狀時,我們將無法立即使用該連線佔用的埠來建立一個新連線。如果不存在TIME-WAIT狀態,,應用程式再建立一個一樣的連線,這時新連線又可能受到舊連線TCP報文段,這顯然是不應該發生。
還有剛說過TIME-WAIT狀態會維持2MSL的時間,而TCP報文段的最大生存時間是MSL,所以確保了網路上傳輸方向上的未被接受的、遲到的TCP報文段都已經消失。這也為什麼要維持2MSL時間。

然而有時候我們又希望避免TIME-WAIT狀態,當程式退出後我們可以立即重啟它。例如百度騰訊這些大公司,如果真的哪天伺服器崩掉了,必定是需要立即重啟,要不然損失可就大了。
這裡我用自己寫的一個select伺服器測試,發現先Ctrl+C之後,再次重新啟動伺服器,就會出錯,這裡就可以推測出,這裡就是因為處在TIME-WAIT狀態,埠被佔用著,所以重新啟動失敗。那麼怎麼解決這個問題呢?
這裡寫圖片描述

reuse問題

上圖可以看到地址已經被用了
那麼我們如何解決這個問題呢?
可以使用使用setsockopt函式

setsockopt函式

int setsockopt(int sockfd, int
level, int optname, const void *optval, socklen_t optlen);
//可以在socket函式和bind函式之間加入這個函式
int on=1;
setsockopt(lisetn_sock,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))

SO_REUSEADDR是什麼意思呢?

這個套接字通知核心,如果埠忙,而TCP處在TIME-WAIT狀態時,可以重用埠。如果埠忙,而TCP不在TIME-WAIT,在其他狀態時,仍然會得到一個錯誤資訊,指明地址已在使用中。
另外SO_REUSEADDR選項提供的功能:

  1. SO_REUSEADDR允許啟動一個監聽伺服器並捆綁其眾所周知埠,即使以前建立的將此埠用做他們的本地埠的連線仍存在。這通常是重啟監聽伺服器時出現,若不設定此選項,則bind時將出錯。
  2. SO_REUSEADDR允許在同一埠上啟動同一伺服器的多個例項,只要每個例項捆綁一個不同的本地IP地址即可。對於TCP,我們根本不可能啟動捆綁相同IP地址和相同埠號的多個伺服器。
  3. SO_REUSEADDR允許單個程序捆綁同一埠到多個套介面上,只要每個捆綁指定不同的本地IP地址即可。這一般不用於TCP伺服器。
  4. SO_REUSEADDR允許完全重複的捆綁:當一個IP地址和埠繫結到某個套介面上時,還允許此IP地址和埠捆綁到另一個套介面上。一般來說,這個特性僅在支援多播的系統上才有,而且只對UDP套介面而言(TCP不支援多播)。