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不支持多播)。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章