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

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

前言

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

TCP概述

TCP自己比較複雜, 也比較重要, 這裏先簡單瞭解下.慢慢來!bash

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做爲接收方).socket

無差錯狀況

使用下圖來理解下:post

在沒有差錯狀況下(如上圖a狀況),A發送分組M1,發完就暫停,等待B的確認.B收到了M1就向A發送確認. A收到對M1的確認後發送M2..M3.這是 中止等待協議在無差錯時的表現.

出現差錯

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

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

確認丟失和確認遲到

確認丟失和確認遲到也是可能出現的狀況.以下:學習

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

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

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

信道利用率

中止等待協議簡單,可是信道的利用率過低,以下圖:

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

由於僅在Td的時間內是用來傳送有用數據的,信道利用率可使用下面的算法來粗略估計:

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

連續ARQ協議

爲了提升信道了利用率, 能夠採用流水線的方式傳輸, 這句涉及獲得連續ARQ協議.

連續ARQ協議中,發送方須要維持一個 發送窗口,它可使在窗口內的連續多個分組的數據連續發送出去,沒必要等待對方的確認.以下圖:

圖中的 發送窗口大小爲5, a中的5個分組能夠連續的發送不用等待確認.在發送方每收到一個確認,就將發送窗口向前(向前指向着時間增大的方向,向後指向時間減小的方向)滑動一個位置.如b所示.此時能夠發送第6個分組了.

接收方通常採起累積確認的方式, 接收方沒必要對每一個分組逐個發送確認,而是在收到幾個分組後,對按序到達的最後一個分組發送確認, 這就表示到這個分組爲止的全部分組都以正確收到.這樣的優勢是即便確認丟失也沒必要重傳,缺點是不能向發送方反映出接收方已經正確收到的全部分組信息.

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

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

TCP報文首部

在深刻了解TCP以前, 瞭解TCP報文首部是必要的.下面一塊兒來了解下其首部的具體內容.

雖然TCP是面向字節流的, 但TCP傳送的數據單元是報文. 一個TCP報文包括首部和數據兩個部分(IP報文和UDP報文也是由首部和數據組成),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能夠根據此信息構造本身的發送窗口.以下:

說明以下:

  • 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示 :

從上面的狀況能夠看出,描述一個發送窗口的狀態須要如上圖的三個指針: 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.

上面的狀況以下:

如今A選擇繼續發送數據,將A中的待發送數據全都發送出去.以下:

此時,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

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

假如,接收方收到的數據以下:

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

若想發送方只傳遞缺失部分,須要告知接收方接收數據的狀況.接收方能夠將這些邊界告知發送方.例如圖中的L1, R1, L2, R2...

若選擇使用選擇確認,須要在TCP鏈接創建時彼此商量好,在TCP報文首部的選項部分來講明這些邊界值.

L1, R1, L2, R2這些也是報文序號中的一個,所以每個須要4字節.在接收方存在多個不連續數據時,報文首部的選項字段的大小(40字節)是不夠用的.因此重傳時大多仍是選擇對未確認的數據重傳.

基於滑動窗口的流量控制

流量控制,控制的流量是發送方發出的流量,不至於發的數據太多,接收方來不及接收.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,而不是像慢開始那樣成倍增長.

經過下圖理解:

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

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

經過上面簡單瞭解了TCP擁塞控制的方法,更詳細的再也不深刻.經過擁塞控制和以前瞭解的確認報文首部的窗口值能夠知道,發送方的發送窗口大小是擁塞控制的窗口和確認報文首部的窗口值中較小的一個.

鏈接管理

TCP是基於鏈接的.那麼鏈接是如何創建的?答案就是3次握手:

  • 服務器是處於監聽狀態的,以便及時發現客戶端創建鏈接的需求。
  • 客戶端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程序先發出鏈接釋放報文(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的滑動窗口是比較重要的,後面的流量控制和擁塞控制都是基於滑動窗口的.再後序文章中咱們再一塊兒繼續學習.

  • 部分圖片來源於網絡,若有侵權,請告知。
  • 若有錯誤,還請指出。共勉!
  • 您的喜歡是最大的讚揚。
相關文章
相關標籤/搜索