在tcp鏈接的釋放過程中,由於存在TIME-WAIT階段,會影響其他程序在該埠建立tcp連接嗎?

在謝希仁著《計算機網路》(第六版)中講解tcp連接的釋放部分,提到了客戶端A在釋放連接最後階段一般需要經過2msl(書上說msl一般為兩分鐘)時間來確認服務端是否收到釋放請求的ack,之後才會釋放掉tcp連接。那這樣的話如果一個客戶端進程在x埠建立了tcp連接,那客戶端的x埠豈不是至少要4分鐘之後才能被其他進程所使用?這效率會不會太低了點,而且我平時使用socket編程的時候也沒有明顯感受到這一限制啊?求解答


TCP是一個邏輯連接,當客戶端主動發起斷開連接,伺服器端被動斷開,過程如下:

Client ------Server
FIN ----&>
&<------- FIN + ACK
ACK ----&>

最後這個對伺服器FIN方的ACK,客戶端發出之後無從知道是否到達伺服器,如果到達對方,對方不會再發送ACK;如果對方沒有收到,對方會超時重傳FIN。

如何選擇一個時間窗,可以確保知道對方收到或沒有收到,如果沒有收到,對方的重傳定時器通常會在幾秒之內重傳,即使對方的重傳再丟,也會在幾秒之內再次重傳,所以客戶端應該在幾秒之內接到對方的FIN的重傳。

以上討論的是來向的丟,那去向的丟最大能到多少呢?最大不會超過IP包TTL最大的生存時間,即255秒,一個IP包最多可以存活255秒的時間。

所以一旦客戶端發出自己的ACK之後,會等待255秒的時間,如果沒有收到對方的任何反應,可以安全推測對方收到了,可以安全地釋放本端的埠資源。

而現代的路由器在處理IP包時,處理時間遠遠小於1秒,所以對TCP包的存活時間做了一個修正,即等待240秒就可以安全釋放埠。

有一些操作系統可能只等待120、或60秒就可以釋放埠了。

為何那麼保守?
怕客戶端使用相同的埠號,重連原有的伺服器、埠號,那麼根據五元組,

新的連接 與舊的連接 五元組完全一樣,TCP會搞暈,可能會把新老數據弄混而出錯。

為了杜絕以上混亂狀況,需要主動斷開TCP的一方,再逗留240秒,確保TCP連接安全斷開。

可以重用(Reuse)客戶端埠嗎?
如果不重連老的伺服器原有埠,可以立馬使用該埠(無需等待),只要保證五元組與舊的連接不衝突即可。


5.2 SO_REUSEADDR 選項

設置該選項: public void setResuseAddress(boolean on) throws SocketException
讀取該選項: public boolean getResuseAddress() throws SocketException
這個選項與Socket 的選項相同, 用於決定如果網路上仍然有數據向舊的 ServerSocket 傳輸數據, 是否允許新的 ServerSocket 綁定到與舊的 ServerSocket 同樣的埠上. SO_REUSEADDR 選項的默認值與操作系統有關, 在某些操作系統中, 允許重用埠, 而在某些操作系統中不允許重用埠.

當 ServerSocket 關閉時, 如果網路上還有發送到這個 ServerSocket 的數據, 這個ServerSocket 不會立即釋放本地埠, 而是會等待一段時間, 確保接收到了網路上發送過來的延遲數據, 然後再釋放埠.

許多伺服器程序都使用固定的埠. 當伺服器程序關閉後, 有可能它的埠還會被佔用一段時間, 如果此時立刻在同一個主機上重啟伺服器程序, 由於埠已經被佔用, 使得伺服器程序無法綁定到該埠, 伺服器啟動失敗, 並拋出 BindException:

java.net.BindExcetpion: Address already in use: JVM_Bind

為了確保一個進程關閉了 ServerSocket 後, 即使操作系統還沒釋放埠, 同一個主機上的其他進程還可以立即重用該埠, 可以調用 ServerSocket 的 setResuseAddress(true) 方法:

if(!serverSocket.getReuseAddress()) serverSocket.setReuseAddress(true);

值得注意的是, serverSocket.setReuseAddress(true) 方法必須在 ServerSocket 還沒有綁定到一個本地埠之前調用, 否則執行 serverSocket.setReuseAddress(true) 方法無效. 此外, 兩個共用同一個埠的進程必須都調用 serverSocket.setResuseAddress(true) 方法, 才能使得一個進程關閉 ServerSocket 後, 另一個進程的 ServerSocket 還能夠立刻重用相同的埠.


對 tcp 連接來說,不存在埠被佔用的問題。平時說的埠被佔用說的是監聽埠,和連接不是一回事。


糾正一下高票答案,一般沒有人把五元組不同的連接叫做復用埠的吧,畢竟任何狀態下都可以同時使用……TIME_WAIT狀態下默認情況下的確是不允許相同五元組連接重新建立的,但是偶爾會出現埠不夠用的情況,考慮兩個固定的IP之間建立非常短的連接進行通信,默認TIME_WAIT有120s,當頻率很高的時候就可能用完所有源埠,導致無法建立連接。針對這個情況發展的新技術,可以讓TIME_WAIT狀態下的五元組立即重建,在Linux下面和socket選項SO_REUSEADDR以及系統參數tw_reuse_addr有關。
立即復用TIME_WAIT的原理在於巧妙選用一個初始序號,這個初始序號恰好在上一個連接結束時最後一個序號的後面,我們知道TCP判斷新老數據的準則是以當前序號為分界點,將整個序號空間(32位整數的循環)均等地分成兩半,向前(減)的一半是舊數據,向後(增)的一半是新數據,這樣如果系統允許,當收到新數據範圍的SYN的時候,就可以重新建立連接。舊連接里的數據大概率仍然落在新連接的舊數據區,因此不會產生干擾。這個選項稍微增加了連接出問題的風險,但是對一般的網路來說增加的風險是微不足道的。


不影響,不管有多少連接,服務端永遠只佔用監聽的那個埠,還有很多人以為服務端不能建立超過65535個客戶端連接,犯的跟你一樣的錯誤。
當然連接佔用的內存資源和文件對象不能被立即釋放掉,這個是有影響的。


隨便寫一個死循環,裡面,
connect
close。
服務端循環寫一個
accept
sleep 1
close
應該分分鐘就可以看到客戶端報錯。目前一般linux的可用埠範圍默認好像收3萬多,只要在第一個timewait超時前把這些埠都用上就行了
題主沒有明顯感覺估計是因為開發的項目不是單ip間高並發短連接的場景吧


客戶端connect的時候是內核分配的隨機埠,如果是客戶端,內核會幫你過濾掉處於TIME_WAIT狀態或其他正在工作中的狀態的鏈接佔用的埠,對於伺服器端,如果是伺服器端奔潰或強殺進程等Server端主動關閉的場景,則伺服器端監聽埠的相關的鏈接狀態也會處於TIME_WAIT狀態,而伺服器端一般會開啟SO_REUSEADDR,這樣可以解決綁定處於timewait狀態時相關的ip,port的Address already in use 錯誤導致進程起不來的問題


Socket = Remote Addr + Local Addr = Remote IP: Remote Port + Local IP: Local Port

- Local Address 相同,Remote Address 不同,同一進程

完全不影響。其他 Remote Address 和這個伺服器埠之間的通訊,TCP Connection 之間,或者說 Sockets 之間是不影響的。

- Local Address 相同,Remote Address 不同,不同進程

也就是 Reuse Local IP: Local Port

- 正在使用的 Local Address

同伺服器的其他進程,在一個正常的 bind/connect 請求里,本來你就是不可以去 bind 別的進程已經在用的 Socket Address。

- TIME_WAIT Local Address

但是當 x 進程使用的 Socket Address 真的如題目所說進入了 TIME_WAIT 狀態,那的確是會造成問題的,默認情況下幾分鐘內其他進程不能重新 bind 這個地址。為了解決這個問題,Socket API 允許你用 SO_REUSEADDR 去綁定一個已經在 TIME_WAIT 狀態的 Socket Address,但是仍然有限制,kernel 還是不允許你重新建立一個和上文 TCP Connection 四元組完全相同的連接。你可以其他地址正常通訊。另外你用這個選項去 bind 正常使用的地址,仍然會報錯(除非是特殊設定的)

- 特殊情況的 Local Address

古代的 Linux 網路模型里,同一個 Local Address 只能是同一個進程去監聽,所以要麼是來回切換,要麼是單個進程去分發包,各有各的問題,不展開細講。後來 Linux 有了 SO_REUSEPORT 和 SO_REUSEADDR,這個就允許不同的進程去共享偵聽同一個 TCP Socket Address,然後內核根據四元組分發給各個偵聽進程(同一個客戶端發起的一次 Connection 始終由同一個進程處理;雖然這個也有別的問題,但不細講)。這樣就實現了內核層面的埠復用和均衡。

- Local Address 相同,Remote Address 相同,不同進程

也就是 Reuse TCP Connection.

那如何重用完全相同的 TCP Connection 呢,首先,不要使用 net.ipv4.tcp_tw_recycle,這個在最近的 kernel 里已經移除,而且非常容易出錯。一個可能的選項是 net.ipv4.tcp_tw_reuse,它是基於 timestamp,也就是 Timestamp 更新的包,可以被處理(注意 TIME_WAIT 本來就是為了避免舊包造成混亂)。但要注意,它解決的是 outgoing connection,正常情況下你也不太應該在伺服器上使用這個選項。

&> Whether TCP should reuse an existing connection in the TIME-WAIT state for a new outgoing connection if the new timestamp is strictly bigger than the most recent timestamp recorded for the previous connection. This helps avoid from running out of available network sockets.

&> Allow to reuse TIME-WAIT sockets for new connections when it is safe from protocol viewpoint. Default value is 0. It should not be changed without advice/request of technical experts.

- 為什麼你應該不需要解決這個問題

TIME_WAIT 這個狀態,應該是發起 close 的一方才會出現。所以常規的伺服器本來就應該避免主動去 close tcp connection,尤其是還有可能要和同一個客戶端繼續通訊的情況下。應該等待客戶端去發起 close。


客戶端埠是隨機的,不會有什麼影響啊,每次都用的是新埠,服務端的確會有影響,服務端直接進程關掉然後重新打開進程會提示埠被佔用了,原因就是來確認ack


tcp連接是按四元組標識的,並不只看一端的埠


會,我猜樓主使用httpclient時候沒寫header添加keepalive,導致每次都建一個新tcp,然後timewait默認1分鐘,請求一多就把fd打滿了。。。。。。。。。。我為啥能猜到。。。。。因為。。。。。。


1.Msl可以設置30s 1分鐘都行 並不是說一定是2分鐘,原則上如果處於Time_wait狀態,在此2Msl時間內四元組暫時不可以被重新分配使用。
2.但是目前有Time_wait快速回收機制,也就是設置並不需要等待2Msl就可以繼續使用埠。
3.還有Time_wait重用機制,只要確保當前要佔用的埠下已經沒有殘留報文就可以再次使用。


tcp連接通過五元組(tcp/udp,dip,dport,sip,sport)來區別,以客戶端發起連接為例,伺服器地址和偵聽埠對應了其中的dip和dport,其中dport一般不可變(或者說基本上固定),dip可以有多個,客戶端會隨機選擇一個本地沒有使用的埠(sport),一般為1025-65535,並通過sip:sport的信息去訪問dip:dport,由於sport,sip,dip都有可能是多個,因此由於埠被佔用而導致訪問失敗的情況很少(基本不可能發生)。


推薦閱讀:

同一交換機的不同VLAN如何做到隔離?
交換機下能抓包嗎?
增加帶機量是選用一個好的路由器還是增加一個核心交換機?
只有一台電腦,可以DDOS網站嗎?
如何理解 TCP/IP, SPDY, WebSocket 三者之間的關係?

TAG:計算機網路 | 計算機科學 | TCP |