簡體   English   中英

為什么套接字連接被阻止並且TCP內核不斷重傳[ACK]數據包

[英]Why Socket Connection Blocked and TCP Kernel Keeps Retransmitting [ACK] packets

我們面臨的一個問題是,從一段時間以后,特定的套接字連接將被阻止,客戶端的tcp內核將繼續轉發[ACK]數據包。

拓撲流程如下:

   Client A ←→ Switch A ← Router A:NAT ← .. Internet .. 
               → Router B:NAT → Switch B ←→ Server B

以下是WireShark捕獲的數據包:
A)服務器

1. 8013 > 6757 [PSH, ACK] Seq=56 Ack=132 Win=5840 Len=55     
2. 6757 > 8013 [ACK] Seq=132 Ack=111 Win=65425 Len=0     

B)客戶

//lines 3 and 4 are exactly the same as line 1 and 2      
3. 8013 > 13000 [PSH, ACK] Seq=56 Ack=132 Win=5840 Len=55      
4. 13000 > 8013 [ACK] Seq=132 Ack=111 Win=65425 Len=0     
5. 13000 > 8013 [PSH, ACK] Seq=132 Ack=111 Win=65425 Len=17     

[TCP Retransmission]          
6. 13000 > 8013 [PSH, ACK] Seq=132 Ack=111 Win=65425 Len=17         

8013是服務器端口,6757是客戶端NAT端口。

為什么即使服務器已經收到一個[ACK]數據包(請參見數據包2),TCP內核為什么仍繼續發送[ACK]數據包以告知客戶端它已接收到數據包1(請參見數據包4、5和6)? 發生問題時,連接雙方均不會關閉套接字。

數據包6之后,連接斷開,我們無法再通過該套接字將任何內容發送到服務器。

         psuedocode:  
         //client
         serverAddr.port =htons(8013) ;
         serverAddr.ip = inet_addr(publicIPB);
         connect(fdA, serverAddr,...);         

         //server
         listenfd = socket(,SO_STREAM,);
         localAddr.port = htons(8013);
         localAddr.ip = inet_addr(INADDR_ANY);
         bind(localAddr...)
         listen(listenfd, 100);

         ...
         //using select model
         select(fdSet, NULL, NULL, NULL);
         for(...)
         {
         if (FD_ISSET(listenfd))
            {
            ...
              }
         ...
         }

更新
UP1。 這是重現該問題的具體步驟

  1. 給定三台計算機,分別為PC1,PC2和PC3。 這三個都在RouterA后面,而服務器在RouterB后面。

  2. 給定兩個用戶U1和U2。 U1從PC1登錄,U2從PC3登錄。 U1和U2都將在自身和服務器之間建立tcp連接。 現在,U1能夠通過其tcp連接將數據發送到Server,然后Server將所有數據中繼到U2。 到目前為止,一切正常。

    表示與U1和服務器之間的TCP連接的服務器端點相對應的套接字號:U1-OldSocketFd

  3. 不要注銷U1,然后拔下PC1的電纜。 然后,U1從PC2登錄,現在它與服務器建立了新的TCP連接。

    表示與U1和服務器之間的TCP連接的服務器端點相對應的套接字號:U1-NewSocketFd

    從服務器端,當它使用U1更新其Session時,它將調用close(U1-OldSocketFd)

4.1。 在第3步之后大約30秒,我們發現U1無法通過其新的TCP連接將任何數據發送到服務器。

4.2。 在第3步中,如果服務器沒有立即調用close(U1-OldSocketFd) (在U1和Server之間建立了相同的第二個新連接),而是服務器在70-80秒內調用了close(U1-OldSocketFd) ,則一切正常。

UP2。 路由器B在端口8013上使用端口轉發。
UP3。 運行服務器的Linux操作系統的一些參數。

    net.ipv4.tcp_tw_reuse = 1
    net.ipv4.tcp_tw_recycle = 1

數據包1(與3相同)和數據包2(與4相同)通過后,您的客戶端似乎正在向服務器(數據包5)傳輸17個字節的數據。 我不知道第一次交換數據包之后會有多少個數據包5出現,所以我不知道會在多少時間后發生。 您的偽代碼沒有澄清它,因為它只顯示套接字初始化,它沒有顯示哪一方在什么時候嘗試傳輸什么數據。 在這種情況下, 梯形圖可能對表示協議交換很有用。

在任何情況下,服務器顯然都不會確認這17個字節的數據,因此它們將再次傳輸(數據包6)。

除非您對網絡,防火牆或NAT路由器有任何問題,或者其他原因導致丟包,否則應該沒有任何理由可以使服務器接收TCP交換的較早部分,但顯然不能接收5或6包再次,在先前的數據交換和數據包5之間是否經過了大量時間(例如,足夠的時間使NAT路由器,防火牆或負載平衡器使連接失效)?

根據您重現該問題和UPD3的步驟,可能是由於

net.ipv4.tcp_tw_recycle = 1

原因是內核正在嘗試在到期時間之前回收TIME_WAIT連接(這要感謝tw_recycle)。

該答案說明了tw_reuse和tw_recycle的行為方式(此處關注NAT部分)。

根據重現步驟並觀察4-1和4-2,當您立即調用fclose()時,連接進入TIME_WAIT狀態,從那里tw_recycle可以繼續進行,並假定由於此端已關閉連接,因此套接字可以回收利用。 從服務器的角度來看,由於連接來自同一主機,因此tw_recycle啟動。

當您改為在調用fclose()之前等待時,由於未從服務器的POV觸發斷開連接,因此它將假定連接仍處於活動狀態,這阻止了tw_recycle進入,有可能/可能會強迫創建全新的連接。

根據1 ,為避免受到協議POV的侵害,您有2種情況:

  • 禁用tw_reuse和tw_recycle
  • 啟用tw_reuse,啟用TCP時間戳,禁用tw_recycle

鑒於您的網絡拓撲,tw_recycle可能總是會觸發無連接性條件。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM