1. 程式人生 > 其它 >js 樹的各種騷操作,生成樹,父子鏈,樹推平

js 樹的各種騷操作,生成樹,父子鏈,樹推平

TCP服務的特點

傳輸層協議主要有兩個: TCP協議和UDP協議。TCP協議相對於UDP協議的特點是:面向連線、位元組流和可靠傳輸。

使用TCP協議通訊的雙方必須先建立連線,然後才能開始資料的讀寫。雙方都必須為該連線分配必要的核心資源,以管理連線的狀態和連線上資料的傳輸。TCP連線是全雙工的,即雙方的資料讀寫可以通過一個連線進行。完成資料交換之後,通訊雙方都必須斷開連線以釋放系統資源。

TCP協議的這種連線是一對一的,所以基於廣播和多播(目標是多個主機地址)的應用程式不能使用TCP服務。而無連線協議UDP則非常適合於廣播和多播。

位元組流服務和資料報服務的這種區別對應到實際程式設計中,則體現為通訊雙方是否必須執行相同次數的讀、寫操作(當然,這只是表現形式)。當傳送端應用程式連續執行多次寫操作時,TCP模組先將這些資料放入TCP傳送緩衝區中。當TCP模組真正開始傳送資料時,傳送緩衝區中這些等待發送的資料可能被封裝成一個或多個TCP報文段發出。因此,TCP模組傳送出的TCP報文段的個數和應用程式執行的寫操作次數之間沒有固定的數量關係。

當接收端收到一個或多個TCP報文段後,TCP模組將它們攜帶的應用程式資料按照TCP報文段的序號(見後文)依次放人TCP接收緩衝區中,並通知應用程式讀取資料。接收端應用程式可以一次性將TCP接收緩衝區中的資料全部讀出,也可以分多次讀取,這取決於使用者指定的應用程式讀緩衝區的大小。因此,應用程式執行的讀操作次數和TCP模組接收到的TCP報文段個數之間也沒有固定的數量關係。

綜上所述,傳送端執行的寫操作次數和接收端執行的讀操作次數之間沒有任何數量關係,這就是位元組流的概念:應用程式對資料的傳送和接收是沒有邊界限制的。UDP則不然。傳送端應用程式每執行一次寫操作,UDP模組就將其封裝成-一個 UDP資料報併發送之。接收端必須及時針對每一個UDP資料報執行讀操作(通過recvfrom系統呼叫),否則就會丟包(這經常發生在較慢的伺服器,上)。並且,如果使用者沒有指定足夠的應用程式緩衝區來讀取UDP資料,則UDP資料將被截斷。


TCP傳輸是可靠的。首先,TCP 協議採用傳送應答機制,即傳送端傳送的每個TCP報文段都必須得到接收方的應答,才認為這個TCP報文段傳輸成功。其次,TCP協議採用超時重傳機制,傳送端在傳送出一個TCP報文段之後啟動定時器,如果在定時時間內未收到應答,它將重發該報文段。最後,因為TCP報文段最終是以IP資料報傳送的,而IP資料報到達接收端可能亂序、重複,所以TCP協議還會對接收到的TCP報文段重排、整理,再交付給應用層。.UDP協議則和IP協議一樣,提供不可靠服務。它們都需要上層協議來處理資料確認和超時重傳。

TCP頭部結構

TCP固定頭部結構

  • 16位埠號(port number):告知主機該報文段是來自哪裡(源埠)以及傳給哪個上層協議或應用程式(目的埠)的。進行TCP通訊時,客戶端通常使用系統自動選擇的臨時埠號,而伺服器則使用知名服務埠號。1.3 節中提到過,所有知名服務使用的埠號都定義在/etc/services檔案中。

  • 32位序號( sequence number):一次TCP通訊(從TCP連線建立到斷開)過程中某一個傳輸方向上的位元組流的每個位元組的編號。假設主機A和主機B進行TCP通訊,A傳送給B的第一個TCP報文段中,序號值被系統初始化為某個隨機值ISN ( Initial Sequence Number,初始序號值)。那麼在該傳輸方向上(從A到B),後續的TCP報文段中序號值將被系統設定成ISN加上該報文段所攜帶資料的第一個位元組在整個位元組流中的偏移。例如,某個TCP報文段傳送的資料是位元組流中的第1025~2048位元組,那麼該報文段的序號值就是ISN+1025.另外一個傳輸方向(從B到A)的TCP報文段的序號值也具有相同的含義。

  • 32位確認號(acknowledgement number):用作對另一方傳送來的TCP報文段的響應。其值是收到的TCP報文段的序號值加1.假設主機A和主機B進行TCP通訊,那麼A傳送出的TCP報文段不僅攜帶自己的序號,而且包含對B傳送來的TCP報文段的確認號。反之,B傳送出的TCP報文段也同時攜帶自己的序號和對A傳送來的報文段的確認號。

  • 4位頭部長度(header length):標識該TCP頭部有多少個32bit字(4位元組)。因為4位最大能表示15,所以TCP頭部最長是60位元組。

  • 6位標誌位包含如下幾項:

    • URG標誌,表示緊急指標(urgent pointer)是否有效。
    • ACK標誌,表示確認號是否有效。我們稱攜帶ACK標誌的TCP報文段為確認報文段。
    • PSH標誌,提示接收端應用程式應該立即從TCP接收緩衝區中讀走資料,為接收後
      續資料騰出空間(如果應用程式不將接收到的資料讀走,它們就會--直停留在TCP接收緩衝區中)。
    • RST標誌,表示要求對方重新建立連線。我們稱攜帶RST標誌的TCP報文段為復位報文段。
    • SYN標誌,表示請求建立-一個連線。我們稱攜帶SYN標誌的TCP報文段為同步報文段。
    • FIN標誌,表示通知對方本端要關閉連線了。我們稱攜帶FIN標誌的TCP報文段為結束報文段。
  • 16位視窗大小( window size):是TCP流量控制的-一個手段。這裡說的視窗,指的是接收通告視窗(Receiver Window, RWND)。它告訴對方本端的TCP接收緩衝區還能容納多少位元組的資料,這樣對方就可以控制傳送資料的速度。

  • 16位校驗和(TCP checksum):由傳送端填充,接收端對TCP報文段執行CRC演算法以檢驗TCP報文段在傳輸過程中是否損壞。注意,這個校驗不僅包括TCP頭部,也包括資料部分。這也是TCP可靠傳輸的-一個重要保障。

  • 16位緊急指標(urgent pointer):是-一個正的偏移量。它和序號欄位的值相加表示最後一個緊急資料的下一-位元組的序號。因此,確切地說,這個欄位是緊急指標相對當前序號的偏移,不妨稱之為緊急偏移。TCP 的緊急指標是傳送端向接收端傳送緊急資料的方法。

TCP頭部選項

TCP頭部的最後-一個選項欄位(options) 是可變長的可選資訊。這部分最多包含40位元組,因為TCP頭部最長是60位元組(其中還包含前面討論的20位元組的固定部分)。典型的TCP頭部選項結構如圖所示。

選項的第一個欄位kind說明選項的型別。有的TCP選項沒有後面兩個欄位,僅包含1位元組的kind欄位。第二個欄位length ( 如果有的話)指定該選項的總長度,該長度包括kind欄位和length欄位佔據的2位元組。第三個欄位info (如果有的話)是選項的具體資訊。常見的TCP選項有7種,如圖所示。

  • kind=0是選項表結束選項。
  • kind=1是空操作(nop)選項,沒有特殊含義,一般用於將TCP選項的總長度填充為4位元組的整數倍。
  • kind =2是最大報文段長度選項。TCP連線初始化時,通訊雙方使用該選項來協商最大報文段長度(Max Segment Size, MSS)。 TCP模組通常將MSS設定為(MTU-40) 位元組(減掉的這40位元組包括20位元組的TCP頭部和20位元組的IP頭部)。這樣攜帶TCP報文段的IP資料報的長度就不會超過MTU (假設TCP頭部和IP頭部都不包含選項欄位,並且這也是一般情況),從而避免本機發生IP分片。對乙太網而言,MSS值是1460 (1500 -40)位元組。
  • kind=3是視窗擴大因子選項。TCP連線初始化時,通訊雙方使用該選項來協商接收通告視窗的擴大因子。在TCP的頭部中,接收通告視窗大小是用16位表示的,故最大為65535位元組,但實際上TCP模組允許的接收通告視窗大小遠不止這個數(為了提高TCP通訊的吞吐量)。視窗擴大因子解決了這個問題。假設TCP頭部中的接收通告視窗大小是N,視窗擴大因子(移位數)是M,那麼TCP報文段的實際接收通告視窗大小是N乘2^M,或者說N左移M位。注意,M的取值範圍是0~14。我們可以通過修改/proc/sys/net/ipv4/tcp_window_scaling核心變數來啟用或關閉視窗擴大因子選項。

和MSS選項-樣,視窗擴大因子選項只能出現在同步報文段中,否則將被忽略。但同步報文段本身不執行視窗擴大操作,即同步報文段頭部的接收通告視窗大小就是該TCP報文段的實際接收通告視窗大小。當連線建立好之後,每個資料傳輸方向的視窗擴大因子就固定不變了。

  • kind=4是選擇性確認( Selective Acknowledgment, SACK) 選項。TCP通訊時,如果某個TCP報文段丟失,則TCP模組會重傳最後被確認的TCP報文段後續的所有報文段,這樣原先已經正確傳輸的TCP報文段也可能重複傳送,從而降低了TCP效能。SACK技術正是為改善這種情況而產生的,它使TCP模組只重新發送丟失的TCP報文段,不用傳送所有未被確認的TCP報文段。選擇性確認選項用在連線初始化時,表示是否支援SACK技術。我們可以通過修改/proc/sys/net/ipv4/tcp_ sack核心變數來啟用或關閉選擇性確認選項。
  • kind=5是SACK實際工作的選項。該選項的引數告訴傳送方本端已經收到並快取的不連續的資料塊,從而讓傳送端可以據此檢查並重發丟失的資料塊。每個塊邊沿(edge of block)引數包含一個4位元組的序號。其中塊左邊沿表示不連續塊的第一個資料的序號, 而塊右邊沿則表示不連續塊的最後一個數據的序號的下一個序號。這樣一對引數(塊左邊沿和塊右邊沿)之間的資料是沒有收到的。因為一個塊資訊佔用8位元組,所以TCP頭部選項中實際上最多可以包含4個這樣的不連續資料塊(考慮選項型別和長度佔用的2位元組)。kind=8是時間截選項。該選項提供了較為準確的計算通訊雙方之間的迴路時間( Round Trip Time, RTT)的方法,從而為TCP流量控制提供重要資訊。我們可以通過修改/proc/sys/net/ipv4/tcp_ timestamps 核心變數來啟用或關閉時間戳選項。

TCP連線的建立和關閉


第1個TCP報文段包含SYN標誌,因此它是一個同步報文段,即ernest-laptop (客戶端)向Kongming20 (伺服器)發起連線請求。同時,該同步報文段包含一個ISN值535734930的字號。

第2個TCP報文段也是同步報文段,表示Kongming20同意與ernest-laptop建立連線。同時它傳送自己的ISN值為2159701207的序號,並對第1個同步報文段進行確認。確認值是535734931,即第1個同步報文段的序號值加1,序號值是用來標識TCP資料流中的每一位元組的。但同步報文段比較特殊,即使它並沒有攜帶任何應用程式資料,它也要佔用一個序號值。

第3個TCP報文段是ernest- laptop對第2個同步報文段的確認。至此,TCP連線就建立起來了。建立TCP連線的這3個步驟被稱為TCP三次握手。

後面4個TCP報文段是關閉連線的過程。第4個TCP報文段包含FIN標誌,因此它是-個結束報文段,即ermestlaptop要求關閉連線。結束報文段和同步報文段一樣, 也要佔用一個序號值。Kongming20 用TCP報文段5來確認該結束報文段。緊接著Kongming20傳送自己的結束報文段6, ernest-laptop 則用TCP報文段7給予確認。實際上,僅用於確認目的的確認報文段5是可以省略的,因為結束報文段6也攜帶了該確認資訊。確認報文段5是否出現在連線斷開的過程中,取決於TCP的延遲確認特性。

在連線的關閉過程中,因為ermest-laptop先發送結束報文段,故稱ernest-laptop執行主動關閉,而稱Kongming20執行被動關閉。

一般而言,TCP連線是由客戶端發起,並通過三次握手建立(特殊情況是所謂同時開啟的。TCP連線的關閉過程相對複雜一些。可能是客戶端執行主動關閉,比如前面的例子;也可能是伺服器執行主動關閉,比如伺服器程式被中斷而強制關閉連線;還可能是同時關閉(和同時開啟一樣,非常少見)。

半關閉狀態

TCP連線是全雙工的,所以它允許兩個方向的資料傳輸被獨立關閉。換言之,通訊的一端可以傳送結束報文段給對方,告訴它本端已經完成了資料的傳送,但允許繼續接收來自對方的資料,直到對方也傳送結束報文段以關閉連線。TCP連線的這種狀態稱為半關閉( half close)狀態,如圖所示。

請注意,在圖中,伺服器和客戶端應用程式判斷對方是否已經關閉連線的方法是:read系統呼叫返回0 (收到結束報文段)。當然,Linux 還提供其他檢測連線是否被對方關閉的方法,socket網路程式設計介面通過shutdown函式提供了對半關閉的支援。

連線超時

如果客戶端訪問一個距離很遠的伺服器,或者由於網路繁忙,導致伺服器對於客戶端傳送的同步報文段沒有應答。TCP模組會進行一共5次重連操作,這是由/proc/sys/net/ipv4/tcp. syn, retries 核心變數所定義的。每次重連的超時時間都增加一-倍。在5次重連均失敗的情況下,TCP模組放棄連線並通知應用程式。

TCP狀態轉移

服務端的狀態

伺服器通過listen系統呼叫進人LISTEN狀態,被動等待客戶蟎連線,因此執行的是所謂的被動開啟。伺服器- -旦監聽到某個連線請求(收到同步報文段),就將該連線放人核心等待佇列中,並向客戶端傳送帶SYN標誌的確認報文段。此時該連線處於SYN_RCVD狀態。如果伺服器成功地接收到客戶端傳送回的確認報文段,則該連線轉移到ESTABLISHED狀態。ESTABLISHED狀態是連線雙方能夠進行雙向資料傳輸的
狀態。

當客戶端主動關閉連線時(通過close或shutdown系統呼叫向伺服器傳送結束報文段),伺服器通過返回確認報文段使連線進人CLOSE_ WAIT狀態。這個狀態的含義很明確:等待伺服器應用程式關閉連線。通常,伺服器檢測到客戶端關閉連線後,也會立即給客戶端傳送一個結束報文段來關閉連線。這將使連線轉移到LAST_ _ACK狀態,以等待客戶端對結束報文段的最後一次確認。一旦確認完成,連線就徹底關閉了。

客戶端狀態

客戶端通過connct系統呼叫主動與伺服器建立連線。connect 系統呼叫首先給伺服器傳送一個同步報文段,使連線轉移到SYN, _SENT狀態。此後,connect 系統呼叫可能因為如下兩個原因失敗返回:

  • 如果connect連線的目標埠不存在(未被任何程序監聽),或者該埠仍被處於TIME WAIT狀態的連線所佔用(見後文),則伺服器將給客戶端傳送-一個復 位報文段,connect 呼叫失敗。
  • 如果目標埠存在,但connect在超時時間內未收到伺服器的確認報文段,則connect呼叫失敗。

connect呼叫失敗將使連線立即返回到初始的CLOSED狀態。如果客戶端成功收到伺服器的同步報文段和確認,則connect呼叫成功返回,連線轉移至ESTABLISHED狀態。當客戶端執行主動關閉時,它將伺服器傳送一個結束報文段,同時連線進入FIN_WAIT_1狀態。若此時客戶端收到伺服器專門用於確認目的的確認報文段,則連線轉移至FIN_ WAIT_ 2狀態。當客戶端處於FIN_ WAIT_ 2狀態時,伺服器處於CLOSEWAIT狀態,這一對狀態是可能發生半關閉的狀態。此時如果伺服器也關閉連線(傳送結束報文段),則客戶端將給予確認並進人TIME _WAIT狀態。

圖還給出了客戶端從FIN_ WAIT_ 1狀態直接進入TME WAIT狀態的一條線路(不經過FINWAIT2狀態),前提是處於FIN__WAIT_1狀態的伺服器直接收到帶確認資訊的結束報文段(而不是先收到確認報文段,再收到結束報文段)。處於FIN_WAIT2狀態的客戶端需要等待伺服器傳送結束報文段,才能轉移至TIME_ WAIT狀態,否則它將一直停 留在這個狀態。如果不是為了在半關閉狀態下繼續接收資料,連線長時間地停留在FIN_ WAIT 2狀態並無益處。連線停留在FIN_ WAIT_ 2狀態的情況可能發生在:客戶端執行半關閉後,未等伺服器關閉連線就強行退出了。此時客戶端連線由核心來接管,可稱之為孤兒連線(和孤兒程序類似)。Linux 為了防止孤兒連線長時間存留在核心中,定義了兩個核心變數: /proc/sys/netipv4/tcp_ max_orphans 和/proc/sys/net/ipv4/tcp_ fin_ timeout。 前者指定核心能接管的孤兒連線數目,後者指定孤兒連線在核心中生存的時間。

TIME_WAIT狀態

從圖來看,客戶端連線在收到伺服器的結束報文段(TCP報文段6)之後,並沒有直接進入CLOSED狀態,而是轉移到TIME_ WAIT狀態。在這個狀態,客戶端連線要等待一段 長為2MSL (Maximum Segment Life,報文段最大生存時間)的時間,才能完全關閉。MSL是TCP報文段在網路中的最大生存時間,標準文件RFC 1122的建議值是2 min.TIME_ WAIT狀態存在的原因有兩點:

  • 可靠地終止TCP連線。
  • 保證讓遲來的TCP報文段有足夠的時間被識別並丟棄。

第一個原因很好理解。假設圖中用於確認伺服器結束報文段6的TCP報文段7丟失,那麼伺服器將重發結束報文段。因此客戶端需要停留在某個狀態以處理重複收到的結束報文段(即向伺服器傳送確認報文段)。否則,客戶端將以復位報文段來回應伺服器,伺服器則認為這是-一個錯誤,因為它期望的是一個像TCP報文段7那樣的確認報文段。在Linux系統上,一個TCP埠不能被同時開啟多次(兩次及以上)。當一個TCP連線處於TIME_ WAIT狀態時,我們將無法立即使用該連線佔用著的埠來建立一個新連線。反過來思考,如果不存在TIME_ WAIT狀態,則應用程式能夠立即建立-一個和剛關閉的連線相似的連線(這裡說的相似,是指它們具有相同的IP地址和埠號)。這個新的、和原來相似的連線被稱為原來的連線的化身( incarnation)。新的化身可能接收到屬於原來的連線的、攜帶應用程式資料的TCP報文段(遲到的報文段),這顯然是不應該發生的。這就是TIME_WAIT狀態存在的第二個原因。

另外,因為TCP報文段的最大生存時間是MSL,所以堅持2MSL時間的TIME WAIT狀態能夠確保網路上兩個傳輸方向上尚未被接收到的、遲到的TCP報文段都已經消失(被中轉路由器丟棄)。因此,一個連線的新的化身可以在2MSL時間之後安全地建立,而絕對不會接收到屬於原來連線的應用程式資料,這就是TIME_WAIT狀態要持續2MSL時間的原因。

有時候我們希望避免TIME__WAIT狀態,因為當程式退出後,我們希望能夠立即重啟它。但由於處在TIME_ WAIT狀態的連線還佔用著埠,程式將無法啟動(直到2MSL超時時間結束)。

但如果是伺服器主動關閉連線後異常終止,則因為它總是使用同- -個知名服務埠號,所以連線的TIME WAIT狀態將導致它不能立即重啟。不過,我們可以通過socket選項so_REUSEADDR來強制程序立即使用處於TIME _WAIT狀態的連線佔用的埠。

復位報文段

在某些特殊條件下,TCP連線的一端會向另一端傳送攜帶RST標誌的報文段,即復位報文段,以通知對方關閉連線或重新建立連線。

訪問不存在的埠

當訪問一個不存在的埠時,將回應一個復位報文段。因為復位報文段的接收通告視窗大小為0,所以可以預見:收到復位報文段的一端應該關閉連線或者重新連線,而不能迴應這個復位報文段。實際上,當客戶端程式向伺服器的某個埠發起連線,而該埠仍被處於TIMEWAIT狀態的連線所佔用時,客戶端程式也將收到復位報文段。

異常終止連線

TCP提供了異常終止一個連線的方法,即給對方傳送- -個復位報文段。一旦傳送了復位報文段,傳送端所有排隊等待發送的資料都將被丟棄。應用程式可以使用socket選項sO_ LINGER來發送復位報文段,以異常終止-一個連線。我們將在第5章討論SO_ LINGER選項。

處理半開啟連線

考慮下面的情況:伺服器(或客戶端)關閉或者異常終止了連線,而對方沒有接收到結束報文段(比如發生了網路故障),此時,客戶端(或伺服器)還維持著原來的連線,而伺服器(或客戶端)即使重啟,也已經沒有該連線的任何資訊了。我們將這種狀態稱為半開啟狀態,處於這種狀態的連線稱為半開啟連線。如果客戶端(或伺服器)往處於半開啟狀態的連線寫人資料,則對方將回應-一個復位報文段。

TCP互動資料流

TCP報文段所攜帶的應用程式資料按照長度分為兩種:互動資料和成塊資料。互動資料僅包含很少的位元組。使用互動資料的應用程式( 或協議)對實時性要求高,比如telnet、ssh .等。成塊資料的長度則通常為TCP報文段允許的最大資料長度。使用成塊資料的應用程式(或協議)對傳輸效率要求高,比如ftp.本節我們討論互動資料流。

在一個telnet連線中,客戶端針對伺服器返回的資料所傳送的確認報文段都不搒帶任何應用程式資料(長度為0),而伺服器每次傳送的確認報文段都包含它需要傳送的應用程式資料。伺服器的這種處理方式稱為延遲確認,即它不馬上確認上次收到的資料,而是在一段延遲時間後檢視本端是否有資料需要傳送,如果有,則和確認資訊-一起發出。因為伺服器對客戶請求處理得很快,所以它傳送確認報文段的時候總是有資料一-起傳送。 延遲確認可以減少傳送TCP報文段的數量。而由於使用者的輸入速度明顯慢於客戶端程式的處理速度,所以客戶端的確認報文段總是不攜帶任何應用程式資料。前文曾提到,在TCP連線的建立和斷開過程中,也可能發生延遲確認。

上例是在本地迴路執行的結果,在區域網中也能得到基本相同的結果,但在廣域網就未必如此了。廣域網上的互動資料流可能經受很大的延遲,並且,搒帶互動資料的微小TCP報文段數量一般很多(一個按鍵輸人就導致一個TCP報文段),這些因素都可能導致擁塞發生。解決該問題的-一個簡單有效的方法是使用Nagle演算法。

Nagle演算法要求一個TCP連線的通訊雙方在任意時刻都最多隻能傳送一個未被確認的TCP報文段,在該TCP報文段的確認到達之前不能傳送其他TCP報文段。另一方面,傳送方在等待確認的同時收集本端需要傳送的微量資料,並在確認到來時以一個TCP報文段將它們全部發出。這樣就極大地減少了網路上的微小TCP報文段的數量。該演算法的另-一個優點在於其自適應性:確認到達得越快,資料也就傳送得越快。

TCP成塊資料流

當傳輸大量大塊資料的時候,傳送方會連續傳送多個TCP報文段,接收方可以一次確認所有這些報文段。那麼傳送方在收到上一次確認後,能連續傳送多少個TCP報文段呢?這是由接收通告視窗(還需要考慮擁塞視窗)的大小決定的。

另外一個值得注意的地方是,伺服器每傳送4個TCP報文段就傳送一個PSH標誌給客戶端,以通知客戶端的應用程式儘快讀取資料。不過這對伺服器來說顯然不是必需的,因為它知道客戶端的TCP接收緩衝區中還有空閒空間(接收通告視窗大小不為0)。

帶外資料

有些傳輸層協議具有帶外(Out Of Band, 0OB)資料的概念,用於迅速通告對方本端發生的重要事件。因此,帶外資料比普通資料(也稱為帶內資料)有更高的優先順序,它應該總是立即被髮送,而不論傳送緩衝區中是否有排隊等待發送的普通資料。帶外資料的傳輸可以使用一條獨立的傳輸層連線,也可以對映到傳輸普通資料的連線中。實際應用中,帶外資料的使用很少見,已知的僅有telnet、 ftp 等遠端非活躍程式。

UDP沒有實現帶外資料傳輸,TCP也沒有真正的帶外資料。不過TCP利用其頭部中的緊急指標標誌和緊急指標兩個欄位,給應用程式提供了一種緊急方式。TCP的緊急方式利用傳輸普通資料的連線來傳輸緊急資料。這種緊急資料的含義和帶外資料類似,因此後文也將TCP緊急資料稱為帶外資料。

我們先來介紹TCP傳送帶外資料的過程。假設一個程序已 經往某個TCP連線的傳送緩衝區中寫入了N位元組的普通資料,並等待其傳送。在資料被髮送前,該程序又向這個連線寫人了3位元組的帶外資料“abc"。此時,待發送的TCP報文段的頭部將被設定URG標誌,並且緊急指標被設定為指向最後一個帶外資料的下一位元組(進一步減去當前TCP報文段的序號值得到其頭部中的緊急偏移值),如圖所示。

由圖可見,傳送端一次傳送的多位元組的帶外資料中只有最後--位元組被當作帶外資料(字母c),而其他資料(字母a和b)被當成了普通資料。如果TCP模組以多個TCP報文段來發送圖所示TCP傳送緩衝區中的內容,則每個TCP報文段都將設定URG標誌,並且它們的緊急指標指向同一個位置( 資料流中帶外資料的下一個位置),但只有-一個TCP報文段真正搒帶帶外資料。

現在考慮TCP接收帶外資料的過程。TCP接收端只有在接收到緊急指標標誌時才檢查緊急指標,然後根據緊急指標所指的位置確定帶外資料的位置,並將它讀人一個特殊的快取中。這個快取只有1位元組,稱為帶外快取。如果上層應用程式沒有及時將帶外資料從帶外快取中讀出,則後續的帶外資料( 如果有的話)將覆蓋它。

前面討論的帶外資料的接收過程是TCP模組接收帶外資料的預設方式。如果我們給TCP連線設定了SO_OOBINLINE選項,則帶外資料將和普通資料一樣被TCP模組存放在TCP接收緩衝區中。此時應用程式需要像讀取普通資料一樣來讀取帶外資料。那麼這種情況下如何區分帶外資料和普通資料呢?顯然,緊急指標可以用來指出帶外資料的位置,socket程式設計介面也提供了系統呼叫來識別帶外資料。

TCP超時重傳

TCP服務必須能夠重傳超時時間內未收到確認的TCP報文段。為此,TCP模組為每個TCP報文段都維護-一個重傳定時器,該定時器在TCP報文段第一次被髮送時啟動。如果超時時間內未收到接收方的應答,TCP模組將重傳TCP報文段並重置定時器。至於下次重傳的超時時間如何選擇,以及最多執行多少次重傳,就是TCP的重傳策略。

Linux有兩個重要的核心引數與TCP超時重傳相關: /proc/sys/net/ipv4/tcp_retries1 和/proc/sys/net/ipv4/tcp_retries2。 前者指定在底層IP接管之前TCP最少執行的重傳次數,預設值是3。後者指定連線放棄前TCP最多可以執行的重傳次數,預設值是15(一般對應13 ~ 30 min)。

擁塞控制

TCP模組還有一一個重要的任務,就是提高網路利用率,降低丟包率,並保證網路資源對每條資料流的公平性。這就是所謂的擁塞控制。

TCP擁塞控制的四個部分:慢啟動(slow start)、擁塞避免(congestion avoidance).快速重傳(fast retransmit)和快速恢復(fast recovery).擁塞控制演算法在Linux下有多種實現,比如reno演算法、vegas 演算法和cubic演算法
等。它們或者部分或者全部實現了上述四個部分。/pos/s/s/tipv4/epo._congestion_control 檔案指示機器當前所使用的擁塞控制演算法。

擁塞控制的最終受控變數是傳送端向網路-次連續寫人 (收到其中第- -個數據的確認之前)的資料量,我們稱為SWND (Send Window,傳送視窗四)。不過,傳送端最終以TCP報文段來發送資料,所以SWND限定了傳送端能連續傳送的TCP報文段數量。這些TCP報文段的最大長度(僅指資料部分)稱為SMSS (Sender Maximum Segment Size,傳送者最大段大小),其值一般等於MSS.

傳送端需要合理地選擇SWND的大小。如果SWND太小,會引起明顯的網路延遲;反之,如果SWND 太大,則容易導致網路擁塞。前文提到,接收方可通過其接收通告視窗(RWND)來控制傳送端的SWND.但這顯然不夠,所以傳送端引入了一個稱為擁塞視窗(Congestion Window, CWND)的狀態變數。實際的SWND值是RWND和CWND中的較小者。圖中顯示了擁塞控制的輸人和輸出(可見,它是-一個閉環反饋控制)。

慢啟動和擁塞避免

TCP連線建立好之後,CWND將被設定成初始值IW (Initial Window),其大小為2~ 4個SMSS。但新的Linux核心提高了該初始值,以減小傳輸滯後。此時傳送端最多能傳送Iw位元組的資料。此後傳送端每收到接收端的一個確認,其CWND就按照下式增加:

CWND+= min (N,SMSS)

其中N是此次確認中包含的之前未被確認的位元組數。這樣--來,CWND將按照指數形;式擴大,這就是所謂的慢啟動。慢啟動演算法的理由是,TCP模組剛開始傳送資料時並不知道網路的實際情況,需要用一種試探的方式平滑地增加CWND的大小。

但是如果不施加其他手段,慢啟動必然使得CWND很快膨脹(可見慢啟動其實不慢)並最終導致網路擁塞。因此TCP擁塞控制中定義了另一個重要的狀態變數:慢啟動門限,(slow start threshold size, ssthresh)。 當CWND的大小超過該值時,TCP 擁塞控制將進人擁塞避免階段。

擁塞避免演算法使得CWND按照線性方式增加,從而減緩其擴大。RFC 5681中提到了如下兩種實現方式:

  • 每個RTT時間內按照上式 計算新的CWND,而不論該RTT時間內傳送端收到多少個確認。
  • 每收到一個對新資料的確認報文段,就按照下式來更新CWND.
CWND+= SMSS* SMSS/CWND

圖中粗略地描述了慢啟動和擁塞避免發生的時機和區別。該圖中,我們以SMSS為單位來顯示CWND (實際上它是以位元組為單位的),以次數為單位來顯示RTT,這只是為了方便討論問題。此外,我們假設當前的ssthresh是16SMSS大小(當然,實際的ssthresh 顯然遠不止這麼大)。

以上我們討論了傳送端在未檢測到擁塞時所採用的積極避免擁塞的方法。接下來介紹擁塞發生時(可能發生在慢啟動階段或者擁塞避免階段)擁塞控制的行為。不過我們先要搞清楚傳送端是如何判斷擁塞已經發生的。傳送端判斷擁塞發生的依據有如下兩個:

  • 傳輸超時,或者說TCP重傳定時器溢位。
  • 接收到重複的確認報文段。

擁塞控制對這兩種情況有不同的處理方式。對第一種情況仍然 使用慢啟動和擁塞避免。對第二種情況則使用快速重傳和快速恢復(如果是真的發生擁塞的話)。注意,第二種情況如果發生在重傳定時器溢位之後,則也被擁塞控制當成第一種情況來對待。如果傳送端檢測到擁塞發生是由於傳輸超時,即上述第一種情況,那麼它將執行重傳並做如下調整:

ssthresh=max ( FlightSize/2,2*SMSS)
CWMD<=SMSS

其中FlightSize是已經發送但未收到確認的位元組數。這樣調整之後,CWMD將小於SMSS,那麼也必然小於新的慢啟動門限值ssthresh (因為根據上式, 它一定不小於SMSS的2倍),故而擁塞控制再次進人慢啟動階段。

快速重傳和快速恢復

在很多情況下,傳送端都可能接收到重複的確認報文段,比如TCP報文段丟失,或者接收端收到亂序TCP報文段並重排之等。擁塞控制演算法需要判斷當收到重複的確認報文段時,網路是否真的發生了擁塞,或者說TCP報文段是否真的丟失了。具體做法是:傳送端如果連續收到3個重複的確認報文段,就認為是擁塞發生了。然後它啟用快速重傳和快速恢復演算法來處理擁塞,過程如下:

  1. 當收到第3個重複的確認報文段時,按照上式計算shresh,然後立即重傳丟失的報文段,並按照下式 設定CWND.
CWND=ssthresh+3*SMSS
  1. 每次收到1個重複的確認時,設定CWND=CWND+SMSS.此時傳送端可以傳送新的TCP報文段(如果新的CWND允許的話)。
  2. 當收到新資料的確認時,設定CWND=ssthresh (ssthresh 是新的慢啟動門限值,由第一步計算得到)。