【Linux網路編程筆記】TCP短串連產生大量TIME_WAIT導致無法對外建立新TCP串連的原因及解決方案—基礎知識篇

來源:互聯網
上載者:User

      最近遇到一個線上警示:伺服器出現大量TIME_WAIT導致其無法與下遊模組建立新HTTP串連,在解決過程中,通過查閱經典教材和技術文章,加深了對TCP網路問題的理解。作為筆記,記錄於此。
        備忘:本文主要介紹TCP編程中涉及到的眾多基礎知識,關於實際工程中對由TIME_WAIT引發的不能建立新串連問題的解決方案將在下篇筆記中給出。

1. 實際問題
        初步查看發現,無法對外建立TCP串連時,線上伺服器存在大量處於TIME_WAIT狀態的TCP串連(最多的一次為單機10w+,其中引起警示的那個模組產生的TIME_WAIT約2w),導致其無法跟下遊模組建立新TCP串連。
        TIME_WAIT涉及到TCP釋放串連過程中的狀態遷移,也涉及到具體的socket api對TCP狀態的影響,下面開始逐步介紹這些概念。

2. TCP狀態遷移
       連線導向的TCP協議要求每次peer間通訊前建立一條TCP串連,該串連可抽象為一個4元組(four-tuple,有時也稱socket pair):(local_ip, local_port, remote_ip,remote_port),這4個元素唯一地代表一條TCP串連。
       1)TCP Connection Establishment
       TCP建立串連的過程,通常又叫“三向交握”(three-way handshake),可用來示意:
       

      可對做如下解釋:
        a. client向server發送SYN並約定初始包序號(sequence number)為J;
        b. server發送自己的SYN並表明初始包序號為K,同時,針對client的SYNJ返回ACKJ+1(註:J+1表示server期望的來自該client的下一個包序為J+1);
        c. client收到來自server的SYN+ACK後,發送ACKK+1,至此,TCP建立成功。
        其實,在TCP建立時的3次握手過程中,還要通過SYN包商定各自的MSS,timestamp等參數,這涉及到協議的細節,本文旨在拋磚引玉,不再展開。

           2)TCPConnection Termination
       與建立串連的3次握手相對應,釋放一條TCP串連時,需要經過四步互動(又稱“四次揮手”),如所示:
        
         可對做如下解釋:
       a. 串連的某一方先調用close()發起主動關閉(active close),該api會促使TCP傳輸層向remotepeer發送FIN包,該包表明發起active close的application不再發送資料(特別注意:這裡“不再發送資料”的承諾是從應用程式層角度來看的,在TCP傳輸層,還是要將該application對應的核心tcp send buffer中當前尚未發出的資料發到鏈路上)。                remote peer收到FIN後,需要完成被動關閉(passive close),具體分為兩步:
       b. 首先,在TCP傳輸層,先針對對方的FIN包發出ACK包(主要ACK的包序是在對方FIN包序基礎上加1);
       c. 接著,應用程式層的application收到對方的EOF(end-of-file,對方的FIN包作為EOF傳給應用程式層的application)後,得知這條串連不會再有來自對方的資料,於是也調用close()關閉串連,該close會促使TCP傳輸層發送FIN。
       d. 發起主動關閉的peer收到remote peer的FIN後,發送ACK包,至此,TCP串連關閉。
       注意1:TCP串連的任一方均可以首先調用close()以發起主動關閉,以client自動發起關閉做說明,而不是說只能client發起自動關閉。
       注意2:上面給出的TCP建立/釋放串連的流程說明中,未考慮由於各種原因引起的重傳、擁塞控制等協議細節,感興趣的同學可以查看各種TCP RFC Documents ,比如TCP RFC793。

 

        3)TCP StateTransition Diagram
       上面介紹了TCP建立、釋放串連的過程,此處對TCP狀態機器的遷移過程做總體說明。將TCP RFC793中描述的TCP狀態機器遷移圖摘出如下(引用自這裡):
     
          TCP狀態機器共含11個狀態,狀態間在各種socket apis的驅動下進行遷移,雖然此圖看起來錯綜複雜,但對於有一定TCP網路編程經驗的同學來說,理解起來還是比較容易的。限於篇幅,本文不準備展開詳述,想瞭解具體遷移過程的新手同學,建議閱讀《Linux Network Programming Volume1》第2.6節。

 

3. TIME_WAIT狀態
        
經過前面的鋪墊,終於要講到與本文主題相關的內容了。 ^_^
        從TCP狀態遷移圖可知,只有首先調用close()發起主動關閉的一方才會進入TIME_WAIT狀態,而且是必須進入(圖中左下角所示的3條狀態遷移線最終均要進入該狀態才能回到初始的CLOSED狀態)。
        還可看到,進入TIME_WAIT狀態的TCP串連需要經過2MSL才能回到初始狀態,其中,MSL是指Max
Segment Lifetime,即資料包在網路中的最大存留時間。每種TCP協議的實現方法均要指定一個合適的MSL值,如RFC1122給出的建議值為2分鐘,又如Berkeley體系的TCP實現通常選擇30秒作為MSL值。這意味著TIME_WAIT的典型期間為1-4分鐘。
       TIME_WAIT狀態存在的原因主要有兩點:
    
  1)為實現TCP這種全雙工系統(full-duplex)串連的可靠釋放
       參考本文前面給出的TCP釋放串連4次揮手,假設發起active close的一方(圖中為client)發送的ACK(4次互動的最後一個包)在網路中丟失,那麼由於TCP的重傳機制,執行passiveclose的一方(圖中為server)需要重發其FIN,在該FIN到達client(client是active close發起方)之前,client必須維護這條串連的狀態(儘管它已調用過close),具體而言,就是這條TCP串連對應的(local_ip, local_port)資源不能被立即釋放或重新分配。直到romete peer重發的FIN達到,client也重發ACK後,該TCP串連才能恢複初始的CLOSED狀態。如果activeclose方不進入TIME_WAIT以維護其串連狀態,則當passive close方重發的FIN達到時,active close方的TCP傳輸層會以RST包響應對方,這會被對方認為有錯誤發生(而事實上,這是正常的關閉串連過程,並非異常)。
        2)為使舊的資料包在網路因到期而消失
       為說明這個問題,我們先假設TCP協議中不存在TIME_WAIT狀態的限制,再假設當前有一條TCP串連:(local_ip, local_port, remote_ip,remote_port),因某些原因,我們先關閉,接著很快以相同的四元組建立一條新串連。本文前面介紹過,TCP串連由四元組唯一標識,因此,在我們假設的情況中,TCP協議棧是無法區分前後兩條TCP串連的不同的,在它看來,這根本就是同一條串連,中間先釋放再建立的過程對其來說是“感知”不到的。這樣就可能發生這樣的情況:前一條TCP串連由local peer發送的資料到達remote peer後,會被該remot peer的TCP傳輸層當做當前TCP串連的正常資料接收並向上傳遞至應用程式層(而事實上,在我們假設的情境下,這些舊資料到達remote peer前,舊串連已斷開且一條由相同四元組構成的新TCP串連已建立,因此,這些舊資料是不應該被向上傳遞至應用程式層的),從而引起資料錯亂進而導致各種無法預知的詭異現象。作為一種可靠的傳輸協議,TCP必須在協議層面考慮並避免這種情況的發生,這正是TIME_WAIT狀態存在的第2個原因。
       具體而言,local peer主動調用close後,此時的TCP串連進入TIME_WAIT狀態,處於該狀態下的TCP串連不能立即以同樣的四元組建立新串連,即發起active close的那方佔用的local port在TIME_WAIT期間不能再被重新分配。由於TIME_WAIT狀態期間為2MSL,這樣保證了舊TCP串連雙工鏈路中的舊資料包均因到期(超過MSL)而消失,此後,就可以用相同的四元組建立一條新串連而不會發生前後兩次串連資料錯亂的情況。

 4. socket api: close() 和 shutdown()
       
由前面內容可知,對一條TCP串連而言,首先調用close()的一方會進入TIME_WAIT狀態,除此之外,關於close()還有一些細節需要說明。
       對一個tcp socket調用close()的預設動作是將該socket標記為已關閉並立即返回到調用該api進程中。此時,從應用程式層來看,該socket fd不能再被進程使用,即不能再作為read或write的參數。而從傳輸層來看,TCP會嘗試將目前send buffer中積壓的資料發到鏈路上,然後才會發起TCP的4次揮手以徹底關閉TCP串連。
       調用close()是關閉TCP串連的正常方式,但這種方式存在兩個限制,而這正是引入shutdown()的原因:
       1)close()其實只是將socket fd的引用計數減1,只有當該socket fd的引用計數減至0時,TCP傳輸層才會發起4次握手從而真正關閉串連。而shutdown則可以直接發起關閉串連所需的4次握手,而不用受到引用計數的限制;
       2)close()會終止TCP的雙工鏈路。由於TCP串連的全雙工系統特性,可能會存在這樣的應用情境:local peer不會再向remote peer發送資料,而remote peer可能還有資料需要發送過來,在這種情況下,如果local peer想要通知remote peer自己不會再發送資料但還會繼續收資料這個事實,用close()是不行的,而shutdown()可以完成這個任務。

       close()和shutdown()的具體調用方法可以man查看,此處不再贅述。

       以上就是本文要分析和解決的“由於TIME_WAIT太多導致無法對外建立新串連”問題所需要掌握的基礎知識。下一篇筆記會在本文基礎上介紹這個問題具體的解決方案。^_^

【參考資料】
1.《Linux Network Programming Volume 1》. Chapter 2 && Chapter 4
2. TCP RFC 793
3. Online Document: TCP StateTransition Diagram


================ EOF ===============

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.