【讀】這一次,讓我們再深入一點 - TCP協議

_高洋_發表於2018-01-01

這是關於網路系列的第三篇文章,接下來會有更多精彩內容.敬請期待! 讓我們一起乘風破浪!

前言

上篇我們瞭解了關於UDP協議的相關知識,這裡我們繼續討論運輸層的TCP協議.該篇篇幅較長,希望你能耐心的讀下去並有所收穫.

TCP概述

TCP本身比較複雜, 也比較重要, 這裡先簡單瞭解下.慢慢來!

TCP的主要特點

  • 面向連線.應用程式在使用TCP之前, 必須建立TCP連線. 在傳送完資料之後,再釋放連線.
  • 點對點通訊.連線了兩端的socket.
  • 提供可靠交付的服務. 通過TCP連線傳送的資料, 無差錯, 不丟失, 不重複且按序到達.
  • 全雙工.雙發可以在任何時候傳送資料.
  • 面向位元組流.
    流是指流入到程式或從程式流出的位元組序列.
    面向位元組流的含義是:雖然應用程式和TCP互動的是大小不等的資料塊,但TCP把這些資料看成無結構的位元組流.TCP只保證傳送方發出的位元組流和接收方接到的位元組流相同.

TCP的連線

每條TCP連線有兩個端點(正是TCP提供點對點通訊的體現).其連線的兩個端點稱為套接字(socket).套接字是由IP地址和埠組成, 中間使用冒號隔開.如192.168.0.1:80.

TCP可靠傳輸的工作原理

TCP報文最終還是要交付到網際層的IP協議手中,而IP協議不提供可靠的服務,TCP必須自己才去措施保證服務的可靠.下面先了解下相關的理論基礎.

停止等待協議

停止等待協議意為每傳送完一個分組就停止傳送,等待對方的確認,在收到對方確認後再傳送下一個分組(TCP提供全雙工的通訊,為了簡單這裡只考慮A做為傳送方,B作為接收方).

無差錯情況

使用下圖來理解下:

【讀】這一次,讓我們再深入一點 - TCP協議
在沒有差錯情況下(如上圖a情況),A傳送分組M1,發完就暫停,等待B的確認.B收到了M1就向A傳送確認. A收到對M1的確認後傳送M2..M3.這是停止等待協議在無差錯時的表現.

出現差錯

上圖b是傳輸過程中出現差錯的情況.B接受到M1檢測出了差錯,丟棄了M1,其他什麼也不做了(也可能M1根本沒有到達B).A在指定時間內沒有收到對M1的確認,就認為M1丟失,需要重傳,這就叫做超時重傳.要實現超時重傳就要在傳送完分組後設定一個超時計時器,若在計時器到期之前收到了確認報文,就撤銷計時器.這裡需要注意以下內容:

  • A在傳送完分組之後,需要暫時保留髮送出去的分組,以便實現超時重傳.在收到確認後才能清除該分組.
  • 分組和確認分組都需要進行編號 ,以便區分.
  • 超時計時器的時間應當比分組傳輸的往返時間更長一些.關於時間如何選擇,後面會進一步瞭解.

確認丟失和確認遲到

確認丟失和確認遲到也是可能出現的情況.如下:

【讀】這一次,讓我們再深入一點 - TCP協議

  • 確認丟失
    如上圖a. B傳送出的對M1的確認丟失了.A在規定時間內沒有接收到確認,無法知道是自己發出的分組出錯,丟失,或B的確認丟失.A需要在計時器到期後重傳M1.這時B又收到了M1,需要

    • 丟棄該分組M1,不向上層交付.
    • 向A傳送確認.以免A再次傳送M1.
  • 確認遲到
    如上圖b. 由於網路延遲等原因,B發出的對M1的確認沒能在指定時間內到達A,而是在以後的某個時間到達了.這時A會收到重複 的確認(因為A會超時重傳).對於這樣的確認,A只需丟棄.對於重傳的M1,B也需要確認,並丟棄M1.

上述的確認和重傳機制,就是TCP實現可靠傳輸的依據.

通道利用率

停止等待協議簡單,但是通道的利用率太低,如下圖:

【讀】這一次,讓我們再深入一點 - TCP協議

  • Td是傳送分組所需時間, 等於分組長度除以傳送速度.
  • RTT是傳送的分組到達對方使用的時間對方確認分組回來使用的時間之和.
  • Ta是傳送確認分組使用的時間, 等於確認分組的長度除以傳送速度.

因為僅在Td的時間內是用來傳送有用資料的,通道利用率可以使用下面的演算法來粗略估計:

【讀】這一次,讓我們再深入一點 - TCP協議

現假定1200km的通道往返時間RTT=20ms.分組長度1200bit,傳送速率1 Mbit/s.忽略其他處理時間和Ta(一般Ta遠小於Td).可算出U = 5.66%. 通道的利用率超低!隨著傳送速率的增加,這個數字還會下降!

連續ARQ協議

為了提高通道了利用率, 可以採用流水線的方式傳輸, 這句涉及得到連續ARQ協議.

【讀】這一次,讓我們再深入一點 - TCP協議
連續ARQ協議中,傳送方需要維持一個 傳送視窗,它可以使在視窗內的連續多個分組的資料連續傳送出去,不必等待對方的確認.如下圖:

【讀】這一次,讓我們再深入一點 - TCP協議
圖中的傳送視窗大小為5, a中的5個分組可以連續的傳送不用等待確認.在傳送方每收到一個確認,就將傳送視窗向前(向前指向著時間增大的方向,向後指向時間減少的方向)滑動一個位置.如b所示.此時可以傳送第6個分組了.

接收方一般採取累積確認的方式, 接收方不必對每個分組逐個傳送確認,而是在收到幾個分組後,對按序到達的最後一個分組傳送確認, 這就表示到這個分組為止的所有分組都以正確收到.這樣的優點是即使確認丟失也不必重傳,缺點是不能向傳送方反映出接收方已經正確收到的所有分組資訊.

例如,傳送方傳送了5個分組,而中間3個分組丟失.這時接收方只能對前兩個分組確認.傳送方無法知道後3個分組的下落,只好把後3個都重傳一次.這就叫做Go-back-N,表示需要在退回來傳送以傳送的N個分組.

滑動視窗是TCP的精華所在,後面再詳細的說明.

TCP報文首部

在深入瞭解TCP之前, 瞭解TCP報文首部是必要的.下面一起來了解下其首部的具體內容.

雖然TCP是面向位元組流的, 但TCP傳送的資料單元是報文. 一個TCP報文包括首部和資料兩個部分(IP報文和UDP報文也是由首部和資料組成),TCP的功能也是依靠首部各個欄位的.

【讀】這一次,讓我們再深入一點 - TCP協議

從上圖可以看到, TCP的首部是由固定的20位元組加上後面的選項部分(4n位元組,n 需要為整數).

  • 目的埠和源埠, 各佔2位元組.基於埠複用和分用(在上篇提到過這個概念).
  • 序號, 佔4位元組. 範圍是[0, 2^32-1].序號達到最大值後, 又回到0從新開始.因為TCP是面向位元組流的, 在TCP中傳送的每一個位元組都按序編號. 整個要傳送的位元組流的起始序號在連線建立時確定.首部中的該欄位指的是本報文段所傳送的資料的第一個位元組的序號.例如: 一個報文段的序號為301, 而攜帶資料100位元組; 可以確定的是,本報文段資料的第一個位元組序號為301, 最後一個位元組的序號為400, 下一個報文段的序號應該是401.
  • 確認號, 佔4位元組.表示期望收到對方下一個報文段的資料第一個位元組的序號.例如,B正確收到A發過來的一個報文段,其序號值為501,而資料長度是200位元組(也就是說該報文段的資料位元組序號從501到700).這表明B正確收到了A傳送的到序號700為止的資料, 因此B期望收到A的下一個資料的序號是701, 於是B將發給A的確認報文的確認號置為701.請記住,確認號為N,表明序號為N-1的所有資料都以正確收到.
  • 資料偏移, 佔4bit. 它指出TCP報文段的資料起始處距離TCP報文段的起始處有多遠(單位4位元組).其實它指出了TCP報文首部的長度是多少.由於TCP報文首部還有不確定的選項部分,該欄位的存在是必要的.最大偏移為15 * 4位元組=60位元組(也確定了TCP首部的最大位元組數), 去掉首部固定的20位元組, 即選項部分最大為40位元組.
  • 保留, 佔6bit. 暫未使用,目前置為0.
  • 緊急URG(URGent), 佔1bit. 當URG=1時,表明後面的緊急指標有效.它表明該報文中有緊急資料,需要優先傳送.
  • 確認ACK(ACKnowledgment), 佔1bit.當ACK=1時,確認號欄位才有效.
  • 推送PSH(Push),佔1bit. 當兩個應用程式進行互動式通訊時, 在一端的應用程式希望在鍵入一個命令後立即的到對方的響應.這種情況下,TCP可以將PSH置為1,並立即建立一個報文傳送出去,接收方在收到PUS=1的報文後,儘快的向上交付.
  • 復位RST(ReSet), 佔1bit. 當RST=1時,表明TCP連線出現嚴重錯誤,必須釋放連線,重新建立.還可以使用RST=1來拒絕一個非法的報文段或拒絕開啟一個連線.
  • 同步SYN(SYNChronization), 佔1bit. 在建立連線時使用同步序號.當SYN=1ACK=0時表明這是一個請求建立連線的報文. 若對方同意建立連線,則響應報文中SYN和ACK都應該是1.可以發現,當SYN=1時說明該報文是用來建立連線的(請求建立連線報文,或同意建立連線報文).
  • 終止FIN, 佔1bit.用來釋放連線,當FIN=1時,表明此報文的傳送方資料已經傳送完畢,要求釋放連線.
  • 視窗, 佔2位元組.值的範圍是[0, 2^16-1]之間的整數.指出了接收方目前可以接受資料的大小, 傳送方在傳送資料時必須考慮到這點.例如,A(作為接收方)發出了一個確認報文,確認號為701(這表明前700個編號資料都正確接收),視窗欄位為1000. 這表明A的接收快取空間還可以接收從編號701到1700的1000位元組資料.總之,視窗明確指出了允許傳送方傳送的資料數量.
  • 校驗和, 佔2位元組. 包含首部和資料兩部分的校驗.這裡不對校驗的方法做深入討論.
  • 緊急指標, 佔2位元組.僅在URG=1才有效.它指出緊急資料的位元組數量(緊急資料在該報文的資料部分最前方).即使視窗為0,也可以傳送緊急資料.
  • 選項, 長度可變,最長40位元組.

TCP可靠傳輸的實現

為了方便說明,下面的討論基於A傳送資料,B接收給出確認.

以位元組為單位的滑動視窗

假定A收到了B的確認報文,該報文首部的視窗欄位值為20位元組,確認號欄位為31(這表明B正確接收了前30位元組的資料, 期望收到編號為31開始的資料).那麼A可以根據此資訊構造自己的傳送視窗.如下:

【讀】這一次,讓我們再深入一點 - TCP協議
說明如下:

  • A此時可以將視窗內的資料都連續的傳送出去, 在未收到確認之前,該資料需要保留,以便超時重傳使用.

  • A的傳送視窗的大小受B確認報文中的視窗欄位值的影響(現在A的傳送視窗大小為20).A的傳送視窗大小不能超過B指定的視窗大小.

  • A的傳送視窗後沿後面部分的資料是以收到確認的,可以不再保留.前沿前面的資料是不能傳送的,對方B沒有足夠的快取區接收.

  • A的傳送視窗的後沿變化情況有兩種:

    • 不動.說明沒有收到新的確認.
    • 向前移動.說明收到了新的確認.
    • 不可能出現向後移動,若向後移動代表確認過的報文需要再次確認,這是不存在的.
  • A的傳送視窗的前沿通常不斷向前移動,但可能不動:

    • 未收到新的確認,B通知的視窗大小也未變化.
    • 收到新的確認,但B的視窗縮小,剛好使A的傳送視窗不動.

    若A收到確認後,得知B的接收視窗小於現在的傳送視窗, 這時需要A傳送視窗的前沿向後移動! 這是TCP標準強烈不建議的.因為A可能再收到該確認之前,已經將傳送視窗的資料傳送出去,現在又不允許傳送(資料不在視窗中就是不允許傳送的意思).將會出錯.

假定現在A傳送了31~41的資料,但未收到確認,傳送視窗位置不變,42~50表示未傳送的.如下圖a示 :

【讀】這一次,讓我們再深入一點 - TCP協議
從上面的情況可以看出,描述一個傳送視窗的狀態需要如上圖的三個指標:P1,P2,P3,它們都指向位元組的序號.

  • 小於P1的表示已傳送且收到確認的部分,大於等於P3的表示不允許傳送的.
  • P3-P1表示了A的傳送視窗
  • P2-P1表示傳送但未確認的位元組數
  • P3-P2表示允許傳送但未傳送的位元組,或者稱為可用視窗或有效視窗.

對於上圖B的接收視窗:

  • B的接收視窗大小為20(從31~50).30及之前的資料時已經傳送確認且交付上層的,可用不用保留了.序號31~50的資料是允許接收或希望接收的資料.
  • 假設現在B收到了序號32和33的資料,但未接收到序號31的資料,B只能給出確認號為31的確認,不能是32或33.

現在假定B接收到了31號資料 ,並將序號31~33的資料交付上層,然後刪除這些資料,給A發出確認報文(確認號為34).將視窗向前移動3個序號 ,視窗大小任然為20.

現在B又收到了未按序到達的37,38和40的資料,B選擇先暫存.

A在收到B的確認後,將傳送視窗向前移動3個序號,P2指標不動(P2表示了A可以傳送但未傳送的起始序號),現在A可以傳送的資料增多了,直到53.

上面的情況如下:

【讀】這一次,讓我們再深入一點 - TCP協議

現在A選擇繼續傳送資料,將A中的待傳送資料全都傳送出去.如下:

【讀】這一次,讓我們再深入一點 - TCP協議
此時,A並沒有收到B確認,自己的有效視窗為0,不再繼續傳送資料. 也許在超時計時器到期後A任然未收到B的確認,A會重新傳送資料,直到收到B的確認後.

通過上面的描述,我們瞭解了TCP中滑動視窗的工作模式,希望你能理解.

超時重傳時間的選擇

超時重傳是保證TCP可靠的重要舉措, 這個時間時如何確定的呢?

TCP採用了自適應的演算法: 它會記錄一個報文發出去的時間,和接收到相應確認的時間.這兩個時間之差就是報文段的往返時間RTT.TCP還會保留RTT的加權平均RTTs.當第一次獲取到RTT時,RTTs的值也是這個,以後每次獲取到RTT後,就會重新計算RTTs:

新的RTTs = (1-x) *  舊RTTs + x * 新RTT值
複製程式碼

超時重傳時間(RTO, retransmissionTime-Out)應略大於RTTs,具體更詳細的計算,這裡就不再展開.

選擇確認SACK

若接收方收到的報文無差錯,只是未按序到達,中間缺少了一些, 那麼能否只讓傳送方只重傳缺少的資料呢? 選擇確認是一種方法.

假如,接收方收到的資料如下:

【讀】這一次,讓我們再深入一點 - TCP協議

  • 1~1000, 1501~3000, 3501~4500已經正確收到
  • 中間多個部分缺失

若想傳送方只傳遞缺失部分,需要告知接收方接收資料的情況.接收方可以將這些邊界告知傳送方.例如圖中的L1, R1, L2, R2...

若選擇使用選擇確認,需要在TCP連線建立時彼此商量好,在TCP報文首部的選項部分來說明這些邊界值.

L1, R1, L2, R2這些也是報文序號中的一個,因此每一個需要4位元組.在接收方存在多個不連續資料時,報文首部的選項欄位的大小(40位元組)是不夠用的.所以重傳時大多還是選擇對未確認的資料重傳.

基於滑動視窗的流量控制

流量控制,控制的流量是傳送方發出的流量,不至於發的資料太多,接收方來不及接收.TCP基於滑動視窗很容易實現流量控制.借用下圖理解下:

【讀】這一次,讓我們再深入一點 - TCP協議

  • 在建立連線時,接收方(B),告訴了傳送方(A):我的接收視窗是400(單位位元組).

  • 圖中的ACK為TCP首部的ACK欄位,ack為首部的確認號欄位.

  • 流量控制體現在:rwnd=300, rwnd=100, rwnd=0.在確認報文的視窗欄位設定了傳送方能夠發出的資料多少,從而控制流量.注意只有到首部的ACK欄位值為1,視窗欄位的值才有效.

  • 假設在B傳送了rwnd=0之後,過段時間由於自己又希望接收到資料,於是發出rwnd=400的報文,但是該報文丟失了,這樣A依然無法傳送資料,B希望接收但接收不到資料.

    為解決該問題,TCP為每個連結都設有一個持續計時器.只要接收到對方視窗為0的通知,就啟動持續計時器.在計時器到期後,就傳送探測報文,對方可以在該報文的確認中告知當前的視窗值.若視窗任然為0,那麼就重新設定計時器,若不為0,那麼上述的問題就解決了.

是不是在理解了滑動視窗的基礎上,流量控制應該很簡單了吧!

擁塞控制

擁塞是指對網路某一資源(頻寬,快取等)的需求超過了可提供的部分,從而使網路中傳送的資料不能按時到達,網路效能變差的情況.

擁塞控制就是防止過多的資料注入到網路中,這樣網路中的資源壓力就小了.

流量控制和擁塞控制似乎很相似,但是他們不同.前者立足於接收和傳送者雙方的情況;而後者注重的是資料量對網路環境的影響.

TCP 控制擁塞的方法

TCP採用慢開始,擁塞避免,快重傳,快恢復.

  • 慢開始.當連線建立開始傳遞資料時,由於不清楚網路狀況,先將較小資料傳送出去,有小到大的增加傳送視窗.比如先傳送一個位元組資料,等收到確認後再傳送2個位元組...4個等.每經過一次確認就將傳送視窗加倍.
  • 擁塞避免思路是在經過確認後,每次將傳送視窗增加1,而不是像慢開始那樣成倍增加.

通過下圖理解:

【讀】這一次,讓我們再深入一點 - TCP協議

  • 圖中的ssthresh為慢開始門限,在慢開始的作用下,傳送視窗成倍增加,不可能沒有上限,這個上限就是慢開始門限.慢開始門限以下使用慢開始控制傳送視窗,而在慢開始門限以上使用避免擁塞方法.
  • 橫軸,傳送輪次意為:連續發出的資料,再經過確認後,稱為一個輪次. 縱軸,擁塞視窗,就是傳送視窗.
  • 標註1之前(傳送視窗值未達到慢開始門限),採用慢開始方法控制傳送視窗;到達門限值後,為避免擁塞採用避免擁塞方法控制傳送視窗.
  • 傳送視窗還繼續增大,直到標註的2,網路出現超時,傳送方判斷出現了擁塞,將慢開始門限設定為傳送視窗值的一半,同時將傳送視窗設為1,進入慢開始.
  • 當再次到達慢開始門限時(標註3),執行避免擁塞控制,直到標註4.此時出現了對一個報文3次確認的情況(如圖中標註3-ACK).
    • 在個別報文丟失(而不是網路擁塞),傳送方收不到確認,誤以為網路擁塞.
    • 快重傳可以讓傳送方儘早知道報文丟失.它要求接收方要對收到的資料儘快確認.即使收到了未按序到達的資料,也要對之前確認過的報文再次確認.這樣就不會超時,也不會造成傳送方誤解網路擁塞.
    • 上圖示註的3-ACK就是連續的3次重複確認.
  • 到標註4後,傳送方知道了網路未出現擁塞, 便啟用快恢復控制,將門限值調整為傳送視窗的一半,傳送視窗也減半.開始避免擁塞控制.當然這只是一種快恢復的方法.

下面是TCP擁塞控制的流程圖:

【讀】這一次,讓我們再深入一點 - TCP協議

通過上面簡單瞭解了TCP擁塞控制的方法,更詳細的不再深入.通過擁塞控制和之前瞭解的確認報文首部的視窗值可以知道,傳送方的傳送視窗大小是擁塞控制的視窗和確認報文首部的視窗值中較小的一個.

連線管理

TCP是基於連線的.那麼連線是如何建立的?答案就是3次握手:

【讀】這一次,讓我們再深入一點 - TCP協議

  • 伺服器是處於監聽狀態的,以便及時發現客戶端建立連線的需求。
  • 客戶端TCP程式主動發出Flag段SYN=1,報文序列號seq=x的報文段(A),請求建立連線。狀態變為SYN-SENT(同步已傳送)。
  • 伺服器收到對應報文段(A)後,會發出確認報文段(B)。該報文(B)的Flag段的SYN和ACK都是1,確認號ack=x+1(意為對A的確認),同時設定自己的初始序列號seq=y。狀態由LISTEN(監聽)變為SYN-RCVD(同步收到)。
  • 客戶端收到伺服器的確認後,還需向伺服器傳送確認。報文段(C)的Flag的ACK=1,確認號ack=y+1(意為對B的確認),序列號seq=x+1。狀態變為ESTABLISHED(已建立連線)。伺服器在收到報文段後狀態也變為ESTABLISHED。
  • 客戶端的最後確認是必要的,可以防止以失效的請求建立連線報文突然到達伺服器而產生錯誤。

下面再來看看連線是如何釋放的:

【讀】這一次,讓我們再深入一點 - TCP協議

  • 在連線建立完成,資料傳輸完畢後,通訊的雙發都是可以釋放連線的。但通常情況,伺服器都是被動的一端,客戶端才知道,自己是不是真的沒有資料要傳送了。
  • 在資料傳輸過程中,客戶端和伺服器都處於已建立連線的狀態。
  • 客戶端TCP程式先發出連線釋放報文(A),並停止傳送資料。該報文的Flag位的FIN=1,seq=u(等於其上一個序號+1)。客戶端進入FIN-WAIT-1狀態,等待伺服器確認。
  • 伺服器接收到釋放連線報文段後發出確認報文(B)Flag位ACK=1,確認號ack=u+1,序列號seq=v(等於其上一個序號+1)。伺服器進入WAIT(關閉等待)狀態,這條TCP連線處於半關閉狀態,也就是客戶端到伺服器方向的通道被關閉。
  • 客戶端在收到伺服器的確認報文(B)後,進入FIN-WAIT-2狀態,等待伺服器發出連線關閉的報文。
  • 若伺服器也沒有其他資料需要傳送,就會向客戶端發出釋放連線的報文(C),Flag端FIN=1,ACK=1,確認號ack=u+1(和報文B一樣),序列號seq=w(伺服器可能在發出報文B之後又傳送了資料,w的值可能不是v+1)。伺服器進入LAST-ACK狀態,等待客戶端的確認。
  • 客戶端在收到伺服器釋放連線報文(C)後,發出確認報文(D)。Flag段ACK=1,確認號ack=w+1,序列號seq=u+1(報文A的seq+1)。客戶端進入TIME-WAIT狀態。現在TCP連線並沒有釋放掉,必須等待2倍MSL(最長報文段壽命Maximum Segment Lifetime,該時長是由時間等待計時器設定)後才能關閉。
  • 伺服器收到報文D後,就可以關閉連線。
  • 為何要等待2MSL,客戶端才能關閉連線
    • 保證客戶端發出的報文D能夠到達伺服器。在報文D丟失的情況下,伺服器未收到客戶端的響應,所以會觸發TCP的超時重傳,而客戶端可以在2MSL時間內收到重傳的報文,並對之響應且重新啟動2MSL計時器。最終,該連線可以正常關閉。
    • 防止失效的連線請求報文出現。可以保證在建立該TCP連線中發出的報文都在網路中消失。
  • 關於TCP的保活計時器:在客戶端伺服器之間的TCP連線建立後,客戶端突然故障,伺服器也就無法再收到來自該客戶端的任何報文,為了使伺服器不會白白等待客戶端而使用的措施是保活計時器。伺服器每次收到客戶端的資料就會重置保活計時器,時間2小時。若2小時未收到客戶端的任何資料,伺服器傳送探測報文,每隔75秒一次,若連續10個探測報文都沒有客戶端的響應,該連線就會被伺服器關閉。

結語

到這裡,說明你有足夠的耐心!獎勵下自己!從上面可以看出,TCP的滑動視窗是比較重要的,後面的流量控制和擁塞控制都是基於滑動視窗的.再後序文章中我們再一起繼續學習.

  • 部分圖片來源於網路,如有侵權,請告知。
  • 如有錯誤,還請指出。共勉!
  • 您的喜歡是最大的讚賞。

相關文章