歡迎閱讀「程序員cxuan」 的文章,從今日後,你就是個人讀者了。git
個人 github bestJavaer 已經收錄此文章,目錄在程序員
github.com/crisxuan/be…github
但願你能夠給我一個 star 哦!算法
這一篇文章是計算機網絡連載文章的第四篇,歷史文章請閱讀api
計算機網絡基礎知識總結服務器
下面開始本篇文章。微信
運輸層
位於應用層和網絡層之間,是 OSI 分層體系中的第四層,同時也是網絡體系結構的重要部分。運輸層主要負責網絡上的端到端通訊。markdown
運輸層爲運行在不一樣主機上的應用程序之間的通訊起着相當重要的做用。下面咱們就來一塊兒探討一下關於運輸層的協議部分
計算機網絡的運輸層很是相似於高速公路,高速公路負責把人或者物品從一端運送到另外一端,而計算機網絡的運輸層則負責把報文從一端運輸到另外一端,這個端指的就是 端系統
。在計算機網絡中,任意一個能夠交換信息的介質均可以稱爲端系統,好比手機、網絡媒體、電腦、運營商等。
在運輸層運輸報文的過程當中,會遵照必定的協議規範,好比一次傳輸的數據限制、選擇什麼樣的運輸協議等。運輸層實現了讓兩個互不相關的主機進行邏輯通訊
的功能,看起來像是讓兩個主機相連同樣。
運輸層協議是在端系統中實現的,而不是在路由器中實現的。路由只是作識別地址並轉發的功能。這就好比快遞員送快遞同樣,固然是要由地址的接受人也就是 xxx 號樓 xxx 單元 xxx 室的這我的來判斷了!
TCP 如何判斷是哪一個端口的呢?
還記得數據包的結構嗎,這裏來回顧一下
數據包通過每層後,該層協議都會在數據包附上包首部,一個完整的包首部圖如上所示。
在數據傳輸到運輸層後,會爲其附上 TCP 首部,首部包含着源端口號和目的端口號。
在發送端,運輸層將從發送應用程序進程接收到的報文轉化成運輸層分組
,分組在計算機網絡中也稱爲 報文段(segment)
。運輸層通常會將報文段進行分割,分割成爲較小的塊,爲每一塊加上運輸層首部並將其向目的地發送。
在發送過程當中,可選的運輸層協議(也就是交通工具) 主要有 TCP
和 UDP
,關於這兩種運輸協議的選擇及其特性也是咱們着重探討的重點。
在 TCP/IP 協議中可以實現傳輸層功能的,最具表明性的就是 TCP 和 UDP。提起 TCP 和 UDP ,就得先從這兩個協議的定義提及。
TCP 叫作傳輸控制協議(TCP,Transmission Control Protocol)
,經過名稱能夠大體知道 TCP 協議有控制傳輸的功能,主要體如今其可控,可控就表示着可靠,確實是這樣的,TCP 爲應用層提供了一種可靠的、面向鏈接的服務,它可以將分組可靠的傳輸到服務端。
UDP 叫作 用戶數據報協議(UDP,User Datagram Protocol)
,經過名稱能夠知道 UDP 把重點放在了數據報上,它爲應用層提供了一種無需創建鏈接就能夠直接發送數據報的方法。
怎麼計算機網絡中的術語對一個數據的描述這麼多啊?
在計算機網絡中,在不一樣層之間會有不一樣的描述。咱們上面提到會將運輸層的分組稱爲報文段,除此以外,還會將 TCP 中的分組也稱爲報文段,然而將 UDP 的分組稱爲數據報,同時也將網絡層的分組稱爲數據報
可是爲了統一,通常在計算機網絡中咱們統一稱 TCP 和 UDP 的報文爲 報文段
,這個就至關因而約定,到底如何稱呼不用過多糾結啦。
在 TCP 或者 UDP 發送具體的報文信息前,須要先通過一扇 門
,這個門就是套接字(socket)
,套接字向上鏈接着應用層,向下鏈接着網絡層。在操做系統中,操做系統分別爲應用和硬件提供了接口(Application Programming Interface)
。而在計算機網絡中,套接字一樣是一種接口,它也是有接口 API 的。
使用 TCP 或 UDP 通訊時,會普遍用到套接字的 API,使用這套 API 設置 IP 地址、端口號,實現數據的發送和接收。
如今咱們知道了, Socket 和 TCP/IP 沒有必然聯繫,Socket 的出現只是方便了 TCP/IP 的使用,如何方便使用呢?你能夠直接使用下面 Socket API 的這些方法。
方法 | 描述 |
---|---|
create() | 建立一個 socket |
bind() | 套接字標識,通常用於綁定端口號 |
listen() | 準備接收鏈接 |
connect() | 準備充當發送者 |
accept() | 準備做爲接收者 |
write() | 發送數據 |
read() | 接收數據 |
close() | 關閉鏈接 |
套接字的主要類型有三種,下面咱們分別介紹一下
數據報套接字(Datagram sockets)
:數據報套接字提供一種無鏈接
的服務,並且並不能保證數據傳輸的可靠性。數據有可能在傳輸過程當中丟失或出現數據重複,且沒法保證順序地接收到數據。數據報套接字使用UDP( User DatagramProtocol)協議
進行數據的傳輸。因爲數據報套接字不能保證數據傳輸的可靠性,對於有可能出現的數據丟失狀況,須要在程序中作相應的處理。流套接字(Stream sockets)
:流套接字用於提供面向鏈接、可靠的數據傳輸服務。可以保證數據的可靠性、順序性。流套接字之因此可以實現可靠的數據服務,緣由在於其使用了傳輸控制協議,即 TCP(The Transmission Control Protocol)協議
原始套接字(Raw sockets)
: 原始套接字容許直接發送和接收 IP 數據包,而無需任何特定於協議的傳輸層格式,原始套接字能夠讀寫內核沒有處理過的 IP 數據包。在計算機網絡中,要想實現通訊,必須至少須要兩個端系統,至少須要一對兩個套接字才行。下面是套接字的通訊過程。
套接字描述符
。就像使用文件描述符來訪問文件同樣,套接字描述符用來訪問套接字。
listen
表示客戶端願意等待鏈接的意願,listen 必須在 accept api 以前調用。connect
發起與服務器的鏈接請求。accept
API 接受客戶端鏈接請求,服務器必須先成功調用 bind 和 listen 後,再調用 accept api。close
API 釋放套接字獲取的全部系統資源。雖然套接字 API 位於應用程序層和傳輸層之間的通訊模型中,可是套接字 API 不屬於通訊模型。套接字 API 容許應用程序與傳輸層和網絡層進行交互。
在往下繼續聊以前,咱們先播放一個小插曲,簡單聊一聊 IP。
IP
是Internet Protocol(網際互連協議)
的縮寫,是 TCP/IP 體系中的網絡層
協議。設計 IP 的初衷主要想解決兩類問題
IP 是整個 TCP/IP 協議族的核心,也是構成互聯網的基礎。爲了實現大規模網絡的互通互聯,IP 更加註重適應性、簡潔性和可操做性,並在可靠性作了必定的犧牲。IP 不保證分組的交付時限和可靠性,所傳送分組有可能出現丟失、重複、延遲或亂序等問題。
咱們知道,TCP 協議的下一層就是 IP 協議層,既然 IP 不可靠,那麼如何保證數據可以準確無誤地到達呢?
這就涉及到 TCP 傳輸機制的問題了,咱們後面聊到 TCP 的時候再說。
在聊端口號前,先來聊一聊文件描述以及 socket 和端口號的關係
爲了方便資源的使用,提升機器的性能、利用率和穩定性等等緣由,咱們的計算機都有一層軟件叫作操做系統,它用於幫咱們管理計算機可使用的資源,當咱們的程序要使用一個資源的時候,能夠向操做系統申請,再由操做系統爲咱們的程序分配和管理資源。一般當咱們要訪問一個內核設備或文件時,程序能夠調用系統函數,系統就會爲咱們打開設備或文件,而後返回一個文件描述符fd(或稱爲ID,是一個整數),咱們要訪問該設備或文件,只能經過該文件描述符。能夠認爲該編號對應着打開的文件或設備。
而當咱們的程序要使用網絡時,要使用到對應的操做系統內核的操做和網卡設備,因此咱們能夠向操做系統申請,而後系統會爲咱們建立一個套接字 Socket,並返回這個 Socket 的ID,之後咱們的程序要使用網絡資源,只要向這個 Socket 的編號 ID 操做便可。而咱們的每個網絡通訊的進程至少對應着一個 Socket。向 Socket 的 ID 中寫數據,至關於向網絡發送數據,向 Socket 中讀數據,至關於接收數據。並且這些套接字都有惟一標識符——文件描述符 fd。
端口號是 16
位的非負整數,它的範圍是 0 - 65535 之間,這個範圍會分爲三種不一樣的端口號段,由 Internet 號碼分配機構 IANA 進行分配
一臺計算機上能夠運行多個應用程序,當一個報文段到達主機後,應該傳輸給哪一個應用程序呢?你怎麼知道這個報文段就是傳遞給 HTTP 服務器而不是 SSH 服務器的呢?
是憑藉端口號嗎?當報文到達服務器時,是端口號來區分不一樣應用程序的,因此應該藉助端口號來區分。
舉個例子反駁一下 cxuan,假如到達服務器的兩條數據都是由 80 端口發出的你該如何區分呢?或者說到達服務器的兩條數據端口同樣,協議不一樣,該如何區分呢?
因此僅憑端口號來肯定某一條報文顯然是不夠的。
互聯網上通常使用 源 IP 地址、目標 IP 地址、源端口號、目標端口號 來進行區分。若是其中的某一項不一樣,就被認爲是不一樣的報文段。這些也是多路分解和多路複用
的基礎。
在實際通訊以前,須要先肯定一下端口號,肯定端口號的方法分爲兩種:
標準既定的端口號是靜態分配的,每一個程序都會有本身的端口號,每一個端口號都有不一樣的用途。端口號是一個 16 比特的數,其大小在 0 - 65535 之間,0 - 1023 範圍內的端口號都是動態分配的既定端口號,例如 HTTP 使用 80 端口來標識,FTP 使用 21 端口來標識,SSH 使用 22 來標識。這類端口號有一個特殊的名字,叫作 周知端口號(Well-Known Port Number)
。
第二種分配端口號的方式是一種動態分配法,在這種方法下,客戶端應用程序能夠徹底不用本身設置端口號,憑藉操做系統進行分配,操做系統能夠爲每一個應用程序分配互不衝突的端口號。這種動態分配端口號的機制即便是同一個客戶端發起的 TCP 鏈接,也能識別不一樣的鏈接。
咱們上面聊到了在主機上的每一個套接字都會分配一個端口號,當報文段到達主機時,運輸層會檢查報文段中的目的端口號,並將其定向到相應的套接字,而後報文段中的數據經過套接字進入其所鏈接的進程。下面咱們來聊一下什麼是多路複用和多路分解的概念。
多路複用和多路分解分爲兩種,即無鏈接
的多路複用(多路分解)和面向鏈接
的多路複用(多路分解)
開發人員會編寫代碼肯定端口號是周知端口號仍是時序分配的端口號。假如主機 A 中的一個 10637 端口要向主機 B 中的 45438 端口發送數據,運輸層採用的是 UDP
協議,數據在應用層產生後,會在運輸層中加工處理,而後在網絡層將數據封裝獲得 IP 數據報,IP 數據包經過鏈路層盡力而爲的交付給主機 B,而後主機 B 會檢查報文段中的端口號判斷是哪一個套接字的,這一系列的過程以下所示
UDP 套接字就是一個二元組,二元組包含目的 IP 地址和目的端口號。
因此,若是兩個 UDP 報文段有不一樣的源 IP 地址和/或相同的源端口號,可是具備相同的目的 IP 地址和目的端口號,那麼這兩個報文會經過套接字定位到相同的目的進程。
這裏思考一個問題,主機 A 給主機 B 發送一個消息,爲何還須要知道源端口號呢?好比我給妹子表達出我對你有點意思的信息,妹子還須要知道這個信息是從個人哪一個器官發出的嗎?知道是我這我的對你有點意思不就完了?其實是須要的,由於妹子若是要表達出她對你也有點意思,她是否是可能會親你一口,那她得知道往哪親吧?
這就是,在 A 到 B 的報文段中,源端口號會做爲 返回地址
的一部分,即當 B 須要回發一個報文段給 A 時,B 須要從 A 到 B 中的源端口號取值,以下圖所示
若是說無鏈接的多路複用和多路分解指的是 UDP 的話,那麼面向鏈接的多路複用與多路分解指的是 TCP 了,TCP 和 UDP 在報文結構上的差異是,UDP 是一個二元組而 TCP 是一個四元組,即源 IP 地址、目標 IP 地址、源端口號、目標端口號 ,這個咱們上面也提到了。當一個 TCP 報文段從網絡到達一臺主機時,這個主機會根據這四個值拆解到對應的套接字上。
上圖顯示了面向鏈接的多路複用和多路分解的過程,圖中主機 C 向主機 B 發起了兩個 HTTP 請求,主機 A 向主機 C 發起了一個 HTTP 請求,主機 A、B、C 都有本身惟一的 IP 地址,當主機 C 發出 HTTP 請求後,主機 B 可以分解這兩個 HTTP 鏈接,由於主機 C 發出請求的兩個源端口號不一樣,因此對於主機 B 來講,這是兩條請求,主機 B 可以進行分解。對於主機 A 和主機 C 來講,這兩個主機有不一樣的 IP 地址,因此對於主機 B 來講,也可以進行分解。
終於,咱們開始了對 UDP 協議的探討,淦起!
UDP 的全稱是 用戶數據報協議(UDP,User Datagram Protocol)
,UDP 爲應用程序提供了一種無需創建鏈接
就能夠發送封裝的 IP 數據包的方法。若是應用程序開發人員選擇的是 UDP 而不是 TCP 的話,那麼該應用程序至關於就是和 IP 直接打交道的。
從應用程序傳遞過來的數據,會附加上多路複用/多路分解的源和目的端口號字段,以及其餘字段,而後將造成的報文傳遞給網絡層,網絡層將運輸層報文段封裝到 IP 數據報中,而後盡力而爲的交付給目標主機。最關鍵的一點就是,使用 UDP 協議在將數據報傳遞給目標主機時,發送方和接收方的運輸層實體間是沒有握手
的。正由於如此,UDP 被稱爲是無鏈接
的協議。
UDP 協議通常做爲流媒體應用、語音交流、視頻會議所使用的傳輸層協議,咱們你們都知道的 DNS 協議底層也使用了 UDP 協議,這些應用或協議之因此選擇 UDP 主要是由於如下這幾點
速度快
,採用 UDP 協議時,只要應用進程將數據傳給 UDP,UDP 就會將此數據打包進 UDP 報文段並馬上傳遞給網絡層,而後 TCP 有擁塞控制的功能,它會在發送前判斷互聯網的擁堵狀況,若是互聯網極度阻塞,那麼就會抑制 TCP 的發送方。使用 UDP 的目的就是但願實時性。無須創建鏈接
,TCP 在數據傳輸以前須要通過三次握手的操做,而 UDP 則無須任何準備便可進行數據傳輸。所以 UDP 沒有創建鏈接的時延。若是使用 TCP 和 UDP 來比喻開發人員:TCP 就是那種凡事都要設計好,沒設計不會進行開發的工程師,須要把一切因素考慮在內後再開幹!因此很是靠譜
;而 UDP 就是那種上來直接乾乾幹,接到項目需求立刻就開幹,也無論設計,也無論技術選型,就是幹,這種開發人員很是不靠譜
,可是適合快速迭代開發,由於能夠立刻上手!無鏈接狀態
,TCP 須要在端系統中維護鏈接狀態
,鏈接狀態包括接收和發送緩存、擁塞控制參數以及序號和確認號的參數,在 UDP 中沒有這些參數,也沒有發送緩存和接受緩存。所以,某些專門用於某種特定應用的服務器當應用程序運行在 UDP 上,通常能支持更多的活躍用戶分組首部開銷小
,每一個 TCP 報文段都有 20 字節的首部開銷,而 UDP 僅僅只有 8 字節的開銷。這裏須要注意一點,並非全部使用 UDP 協議的應用層都是
不可靠
的,應用程序能夠本身實現可靠的數據傳輸,經過增長確認和重傳機制。因此使用 UDP 協議最大的特色就是速度快。
下面來一塊兒看一下 UDP 的報文結構,每一個 UDP 報文分爲 UDP 報頭和 UDP 數據區兩部分。報頭由 4 個 16 位長(2 字節)字段組成,分別說明該報文的源端口、目的端口、報文長度和校驗值。
源端口號(Source Port)
:這個字段佔據 UDP 報文頭的前 16 位,一般包含發送數據報的應用程序所使用的 UDP 端口。接收端的應用程序利用這個字段的值做爲發送響應的目的地址。這個字段是可選項,有時不會設置源端口號。沒有源端口號就默認爲 0 ,一般用於不須要返回消息的通訊中。目標端口號(Destination Port)
: 表示接收端端口,字段長爲 16 位長度(Length)
: 該字段佔據 16 位,表示 UDP 數據報長度,包含 UDP 報文頭和 UDP 數據長度。由於 UDP 報文頭長度是 8 個字節,因此這個值最小爲 8,最大長度爲 65535 字節。校驗和(Checksum)
:UDP 使用校驗和來保證數據安全性,UDP 的校驗和也提供了差錯檢測功能,差錯檢測用於校驗報文段從源到目標主機的過程當中,數據的完整性是否發生了改變。發送方的 UDP 對報文段中的 16 比特字的和進行反碼運算,求和時遇到的位溢出都會被忽略,好比下面這個例子,三個 16 比特的數字進行相加 這些 16 比特的前兩個和是
而後再將上面的結果和第三個 16 比特的數進行相加
最後一次相加的位會進行溢出,溢出位 1 要被捨棄,而後進行反碼運算,反碼運算就是將全部的 1 變爲 0 ,0 變爲 1。所以 1000 0100 1001 0101 的反碼就是 0111 1011 0110 1010,這就是校驗和,若是在接收方,數據沒有出現差錯,那麼所有的 4 個 16 比特的數值進行運算,同時也包括校驗和,若是最後結果的值不是 1111 1111 1111 1111 的話,那麼就表示傳輸過程當中的數據出現了差錯。
下面來想一個問題,爲何 UDP 會提供差錯檢測的功能?
這實際上是一種 端到端
的設計原則,這個原則說的是要讓傳輸中各類錯誤發生的機率下降到一個能夠接受的水平。
文件從主機A傳到主機B,也就是說AB主機要通訊,須要通過三個環節:首先是主機A從磁盤上讀取文件並將數據分組成一個個數據包packet,,而後數據包經過鏈接主機A和主機B的網絡傳輸到主機B,最後是主機B收到數據包並將數據包寫入磁盤。在這個看似簡單其實很複雜的過程當中可能會因爲某些緣由而影響正常通訊。好比:磁盤上文件讀寫錯誤、緩衝溢出、內存出錯、網絡擁擠等等這些因素都有可能致使數據包的出錯或者丟失,因而可知用於通訊的網絡是不可靠的。
因爲實現通訊只要通過上述三個環節,那麼咱們就想是否在其中某個環節上增長一個檢錯糾錯機制來用於對信息進行把關呢?
網絡層確定不能作這件事,由於網絡層的最主要目的是增大數據傳輸的速率,網絡層不須要考慮數據的完整性,數據的完整性和正確性交給端系統去檢測就好了,所以在數據傳輸中,對於網絡層只能要求其提供儘量好的數據傳輸服務,而不可能寄但願於網絡層提供數據完整性的服務。
UDP 不可靠的緣由是它雖然提供差錯檢測的功能,可是對於差錯沒有恢復能力更不會有重傳機制。
UDP 是一種沒有複雜的控制,提供無鏈接通訊服務的一種協議,換句話說,它將部分控制部分交給應用程序去處理,本身只提供做爲傳輸層協議最基本的功能。
而與 UDP 不一樣的是,一樣做爲傳輸層協議,TCP 協議要比 UDP 的功能多不少。
TCP
的全稱是 Transmission Control Protocol
,它被稱爲是一種面向鏈接(connection-oriented)
的協議,這是由於一個應用程序開始向另外一個應用程序發送數據以前,這兩個進程必須先進行握手
,握手是一個邏輯鏈接,並非兩個主機之間進行真實的握手。
這個鏈接是指各類設備、線路或者網絡中進行通訊的兩個應用程序爲了相互傳遞消息而專有的、虛擬的通訊鏈路,也叫作虛擬電路。
一旦主機 A 和主機 B 創建了鏈接,那麼進行通訊的應用程序只使用這個虛擬的通訊線路發送和接收數據就能夠保證數據的傳輸,TCP 協議負責控制鏈接的創建、斷開、保持等工做。
TCP 鏈接是全雙工服務(full-duplex service)
的,全雙工是什麼意思?全雙工指的是主機 A 與另一個主機 B 存在一條 TCP 鏈接,那麼應用程數據就能夠從主機 B 流向主機 A 的同時,也從主機 A 流向主機 B。
TCP 只能進行 點對點(point-to-point)
鏈接,那麼所謂的多播
,即一個主機對多個接收方發送消息的狀況是不存在的,TCP 鏈接只能鏈接兩個一對主機。
TCP 的鏈接創建須要通過三次握手,這個咱們下面再說。一旦 TCP 鏈接創建後,主機之間就能夠相互發送數據了,客戶進程經過套接字傳送數據流。數據一旦經過套接字後,它就由客戶中運行的 TCP 協議所控制。
TCP 會將數據臨時存儲到鏈接的發送緩存(send buffer)
中,這個 send buffer 是三次握手之間設置的緩存之一,而後 TCP 在合適的時間將發送緩存中的數據發送到目標主機的接收緩存中,實際上,每一端都會有發送緩存和接收緩存,以下所示
主機之間的發送是以 報文段(segment)
進行的,那麼什麼是 Segement 呢?
TCP 會將要傳輸的數據流分爲多個塊(chunk)
,而後向每一個 chunk 中添加 TCP 標頭,這樣就造成了一個 TCP 段也就是報文段。每個報文段能夠傳輸的長度是有限的,不能超過最大數據長度(Maximum Segment Size)
,俗稱 MSS
。在報文段向下傳輸的過程當中,會通過鏈路層,鏈路層有一個 Maximum Transmission Unit
,最大傳輸單元 MTU, 即數據鏈路層上所能經過最大數據包的大小,最大傳輸單元一般與通訊接口有關。
那麼 MSS 和 MTU 有啥關係呢?
由於計算機網絡是分層考慮的,這個很重要,不一樣層的稱呼不同,對於傳輸層來講,稱爲報文段而對網絡層來講就叫作 IP 數據包,因此,MTU 能夠認爲是網絡層可以傳輸的最大 IP 數據包,而 MSS(Maximum segment size)能夠認爲是傳輸層的概念,也就是 TCP 數據包每次可以傳輸的最大量。
在簡單聊了聊 TCP 鏈接後,下面咱們就來聊一下 TCP 的報文段結構,以下圖所示
TCP 報文段結構相比 UDP 報文結構多了不少內容。可是前兩個 32 比特的字段是同樣的。它們是 源端口號
和 目標端口號
,咱們知道,這兩個字段是用於多路複用和多路分解的。另外,和 UDP 同樣,TCP 也包含校驗和(checksum field)
,除此以外,TCP 報文段首部還有下面這些
32 比特的序號字段(sequence number field)
和 32 比特的確認號字段(acknowledgment number field)
。這些字段被 TCP 發送方和接收方用來實現可靠的數據傳輸。
4 比特的首部字段長度字段(header length field)
,這個字段指示了以 32 比特的字爲單位的 TCP 首部長度。TCP 首部的長度是可變的,可是一般狀況下,選項字段爲空,因此 TCP 首部字段的長度是 20 字節。
16 比特的 接受窗口字段(receive window field)
,這個字段用於流量控制。它用於指示接收方可以/願意接受的字節數量
可變的選項字段(options field)
,這個字段用於發送方和接收方協商最大報文長度,也就是 MSS 時使用
6 比特的 標誌字段(flag field)
, ACK
標誌用於指示確認字段中的值是有效的,這個報文段包括一個對已被成功接收報文段的確認;RST
、SYN
、FIN
標誌用於鏈接的創建和關閉;CWR
和 ECE
用於擁塞控制;PSH
標誌用於表示馬上將數據交給上層處理;URG
標誌用來表示數據中存在須要被上層處理的 緊急 數據。緊急數據最後一個字節由 16 比特的緊急數據指針字段(urgeent data pointer field)
指出。通常狀況下,PSH 和 URG 並無使用。
TCP 的各類功能和特色都是經過 TCP 報文結構來體現的,在聊完 TCP 報文結構以後,咱們下面就來聊一下 TCP 有哪些功能及其特色了。
TCP 報文段首部中兩個最重要的字段就是 序號
和 確認號
,這兩個字段是 TCP 實現可靠性的基礎,那麼你確定好奇如何實現可靠性呢?要了解這一點,首先咱們得先知道這兩個字段裏面存了哪些內容吧?
一個報文段的序號就是數據流的字節編號 。由於 TCP 會把數據流分割成爲一段一段的字節流,由於字節流自己是有序的,因此每一段的字節編號就是標示是哪一段的字節流。好比,主機 A 要給主機 B 發送一條數據。數據通過應用層產生後會有一串數據流,數據流會通過 TCP 分割,分割的依據就是 MSS,假設數據是 10000 字節,MSS 是 2000 字節,那麼 TCP 就會把數據拆分紅 0 - 1999 , 2000 - 3999 的段,依次類推。
因此,第一個數據 0 - 1999 的首字節編號就是 0 ,2000 - 3999 的首字節編號就是 2000 。。。。。。
而後,每一個序號都會被填入 TCP 報文段首部的序號字段中。
至於確認號的話,會比序號要稍微麻煩一些。這裏咱們先拓展下幾種通訊模型。
單工、半雙工、全雙工通訊以下圖所示
TCP 是一種全雙工的通訊協議,所以主機 A 在向主機 B 發送消息的過程當中,也在接受來自主機 B 的數據。主機 A 填充進報文段的確認號是指望從主機 B 收到的下一字節的序號。稍微有點繞,咱們來舉個例子看一下。好比主機 A 收到了來自主機 B 發送的編號爲 0 - 999 字節的報文段,這個報文段會寫入序號中,隨後主機 A 指望可以從主機 B 收到 1000 - 剩下的報文段,所以,主機 A 發送到主機 B 的報文段中,它的確認號就是 1000 。
這裏再舉出一個例子,好比主機 A 在發送 0 - 999 報文段後,指望可以接受到 1000 以後的報文段,可是主機 B 卻給主機 A 發送了一個 1500 以後的報文段,那麼主機 A 是否還會繼續進行等待呢?
答案顯然是會的,由於 TCP 只會確認流中至第一個丟失字節爲止的字節,由於 1500 雖然屬於 1000 以後的字節,可是主機 B 沒有給主機 A 發送 1000 - 1499 之間的字節,因此主機 A 會繼續等待。
在瞭解完序號和確認號以後,咱們下面來聊一下 TCP 的發送過程。下面是一個正常的發送過程
TCP 經過確定的確認應答(ACK)
來實現可靠的數據傳輸,當主機 A將數據發出以後會等待主機 B 的響應。若是有確認應答(ACK),說明數據已經成功到達對端。反之,則數據極可能會丟失。
以下圖所示,若是在必定時間內主機 A 沒有等到確認應答,則認爲主機 B 發送的報文段已經丟失,並進行重發。
主機 A 給主機 B 的響應可能因爲網絡抖動等緣由沒法到達,那麼在通過特定的時間間隔後,主機 A 將從新發送報文段。
主機 A 沒有收到主機 B 的響應還多是由於主機 B 在發送給主機 A 的過程當中丟失。
如上圖所示,由主機 B 返回的確認應答,因爲網絡擁堵等緣由在傳送的過程當中丟失,並無到達主機 A。主機 A 會等待一段時間,若是在這段時間內主機 A 仍沒有等到主機 B 的響應,那麼主機 A 會從新發送報文段。
那麼如今就存在一個問題,若是主機 A 給主機 B 發送了一個報文段後,主機 B 接受到報文段發送響應,此刻因爲網絡緣由,這個報文段並未到達,等到一段時間後主機 A 從新發送報文段,而後此時主機 B 發送的響應在主機 A 第二次發送後失序到達主機 A,那麼主機 A 應該如何處理呢?
TCP RFC 並未爲此作任何規定,也就是說,咱們能夠本身決定如何處理失序到達的報文段。通常處理方式有兩種
通常來講一般採起的作法是第二種。
前面咱們介紹了 TCP 是以數據段的形式進行發送,若是通過一段時間內主機 A 等不到主機 B 的響應,主機 A 就會從新發送報文段,接受到主機 B 的響應,再會繼續發送後面的報文段,咱們如今看到,這一問一答的形式還存在許多條件,好比響應未收到、等待響應等,那麼對崇尚性能的互聯網來講,這種形式的性能應該不會很高。
那麼如何提高性能呢?
爲了解決這個問題,TCP 引入了 窗口
這個概念,即便在往返時間較長、頻次不少的狀況下,它也能控制網絡性能的降低,聽起來很牛批,那它是如何實現的呢?
以下圖所示
咱們以前每次請求發送都是以報文段的形式進行的,引入窗口後,每次請求均可以發送多個報文段,也就是說一個窗口能夠發送多個報文段。窗口大小就是指無需等待確認應答就能夠繼續發送報文段的最大值。
在這個窗口機制中,大量使用了 緩衝區
,經過對多個段同時進行確認應答的功能。
以下圖所示,發送報文段中高亮部分便是咱們提到的窗口,在窗口內,便是沒有收到確認應答也能夠把請求發送出去。不過,在整個窗口的確認應答沒有到達以前,若是部分報文段丟失,那麼主機 A 將仍會重傳。爲此,主機 A 須要設置緩存來保留這些須要重傳的報文段,直到收到他們的確認應答。
在滑動窗口之外的部分是還沒有發送的報文段和已經接受到的報文段,若是報文段已經收到確認則不可進行重發,此時報文段就能夠從緩衝區中清除。
在收到確認的狀況下,會將窗口滑動到確認應答中確認號的位置,如上圖所示,這樣能夠順序的將多個段同時發送,用以提升通訊性能,這種窗口也叫作 滑動窗口(Sliding window)
。
報文段的發送和接收,必然伴隨着報文段的丟失和重發,窗口也是一樣如此,若是在窗口中報文段發送過程當中出現丟失怎麼辦?
首先咱們先考慮確認應答沒有返回的狀況。在這種狀況下,主機 A 發送的報文段到達主機 B,是不須要再進行重發的。這和單個報文段的發送不同,若是發送單個報文段,即便確認應答沒有返回,也要進行重發。
窗口在必定程度上比較大時,即便有少部分確認應答的丟失,也不會從新發送報文段。
咱們知道,若是在某個狀況下因爲發送的報文段丟失,致使接受主機未收到請求,或者主機返回的響應未到達客戶端的話,會通過一段時間重傳報文。那麼在使用窗口的狀況下,報文段丟失會怎麼樣呢?
以下圖所示,報文段 0 - 999 丟失後,可是主機 A 並不會等待,主機 A 會繼續發送餘下的報文段,主機 B 發送的確認應答卻一直是 1000,同一個確認號的應答報文會被持續不斷的返回,若是發送端主機在連續 3 次收到同一個確認應答後,就會將其所對應的數據重發,這種機制要比以前提到的超時重發更加高效,這種機制也被稱爲 高速重發控制
。這種重發的確認應答也被稱爲 冗餘 ACK(響應)
。
主機 B 在沒有接收到本身指望序列號的報文段時,會對以前收到的數據進行確認應答。發送端則一旦收到某個確認應答後,又連續三次收到一樣的確認應答,那麼就會認爲報文段已經丟失。須要進行重發。使用這種機制能夠提供更爲快速的重發服務。
前面聊的是傳輸控制,下面 cxuan 再和你聊一下 流量控制
。咱們知道,在每一個 TCP 鏈接的一側主機都會有一個 socket 緩衝區,緩衝區會爲每一個鏈接設置接收緩存和發送緩存,當 TCP 創建鏈接後,從應用程序產生的數據就會到達接收方的接收緩衝區中,接收方的應用程序並不必定會立刻讀區緩衝區的數據,它須要等待操做系統分配時間片。若是此時發送方的應用程序產生數據過快,而接收方讀取接受緩衝區的數據相對較慢的話,那麼接收方中緩衝區的數據將會溢出
。
可是還好,TCP 有 流量控制服務(flow-control service)
用於消除緩衝區溢出的狀況。流量控制是一個速度匹配服務,即發送方的發送速率與接受方應用程序的讀取速率相匹配。
TCP 經過使用一個 接收窗口(receive window)
的變量來提供流量控制。接受窗口會給發送方一個指示到底還有多少可用的緩存空間。發送端會根據接收端的實際接受能力來控制發送的數據量。
接收端主機向發送端主機通知本身能夠接收數據的大小,發送端會發送不超過這個限度的數據,這個大小限度就是窗口大小,還記得 TCP 的首部麼,有一個接收窗口,咱們上面聊的時候說這個字段用於流量控制。它用於指示接收方可以/願意接受的字節數量。
那麼只知道這個字段用於流量控制,那麼如何控制呢?
發送端主機會按期發送一個窗口探測包
,這個包用於探測接收端主機是否還可以接受數據,當接收端的緩衝區一旦面臨數據溢出的風險時,窗口大小的值也隨之被設置爲一個更小的值通知發送端,從而控制數據發送量。
下面是一個流量控制示意圖
發送端主機根據接收端主機的窗口大小進行流量控制。由此也能夠防止發送端主機一次發送過大數據致使接收端主機沒法處理。
如上圖所示,當主機 B 收到報文段 2000 - 2999 以後緩衝區已滿,不得不暫時中止接收數據。而後主機 A 發送窗口探測包,窗口探測包很是小僅僅一個字節。而後主機 B 更新緩衝區接收窗口大小併發送窗口更新通知給主機 A,而後主機 A 再繼續發送報文段。
在上面的發送過程當中,窗口更新通知可能會丟失,一旦丟失發送端就不會發送數據,因此窗口探測包會隨機發送,以免這種狀況發生。
在繼續介紹下面有意思的特性以前,咱們先來把關注點放在 TCP 的鏈接管理
上,由於沒有 TCP 鏈接,也就沒有後續的一系列 TCP 特性什麼事兒了。假設運行在一臺主機上的進程想要和另外一臺主機上的進程創建一條 TCP 鏈接,那麼客戶中的 TCP 會使用下面這些步驟與服務器中的 TCP 創建鏈接。
首先,客戶端首先向服務器發送一個特殊的 TCP 報文段。這個報文段首部不包含應用層數據,可是在報文段的首部中有一個 SYN 標誌位
被置爲 1。所以,這個特殊的報文段也能夠叫作 SYN 報文段。而後,客戶端隨機選擇一個初始序列號(client_isn)
,並將此數字放入初始 TCP SYN 段的序列號字段中,SYN 段又被封裝在 IP 數據段中發送給服務器。
一旦包含 IP 數據段到達服務器後,服務端會從 IP 數據段中提取 TCP SYN 段,將 TCP 緩衝區和變量分配給鏈接,而後給客戶端發送一個鏈接所容許的報文段。這個鏈接所容許的報文段也不包括任何應用層數據。然而,它卻包含了三個很是重要的信息。
這些緩衝區和變量的分配使 TCP 容易受到稱爲 SYN 泛洪的拒絕服務攻擊。
client_isn + 1
。初始序號(server_isn)
,並將其放置到 TCP 報文段首部的序號字段中。若是用大白話解釋下就是,我收到了你發起創建鏈接的 SYN 報文段,這個報文段具備首部字段 client_isn。我贊成創建該鏈接,我本身的初始序號是 server_isn。這個容許鏈接的報文段被稱爲 SYNACK 報文段
第三步,在收到 SYNACK 報文段後,客戶端也要爲該鏈接分配緩衝區和變量。客戶端主機向服務器發送另一個報文段,最後一個報文段對服務器發送的響應報文作了確認,確認的標準是客戶端發送的數據段中確認號爲 server_isn + 1,由於鏈接已經創建,因此 SYN 比特被置爲 0 。以上就是 TCP 創建鏈接的三次數據段發送過程,也被稱爲 三次握手
。
一旦完成這三個步驟,客戶和服務器主機就能夠相互發送報文段了,在之後的每個報文段中,SYN 比特都被置爲 0 ,整個過程描述以下圖所示
在客戶端主機和服務端主機創建鏈接後,參與一條 TCP 鏈接的兩個進程中的任何一個都能終止 TCP 鏈接。鏈接結束後,主機中的緩存和變量將會被釋放。假設客戶端主機想要終止 TCP 鏈接,它會經歷以下過程
客戶應用進程發出一個關閉命令,客戶 TCP 向服務器進程發送一個特殊的 TCP 報文段,這個特殊的報文段的首部標誌 FIN 被設置爲 1 。當服務器收到這個報文段後,就會向發送方發送一個確認報文段。而後,服務器發送它本身的終止報文段,FIN 位被設置爲 1 。客戶端對這個終止報文段進行確認。此時,在兩臺主機上用於該鏈接的全部資源都被釋放了,以下圖所示
在一個 TCP 鏈接的生命週期內,運行在每臺主機中的 TCP 協議都會在各類 TCP 狀態(TCP State)
之間進行變化,TCP 的狀態主要有 LISTEN、SYN-SEND、SYN-RECEIVED、ESTABLISHED、FIN-WAIT-一、FIN-WAIT-二、CLOSE-WAIT、CLOSING、LAST-ACK、TIME-WAIT 和 CLOSED 。這些狀態的解釋以下
LISTEN
: 表示等待任何來自遠程 TCP 和端口的鏈接請求。SYN-SEND
: 表示發送鏈接請求後等待匹配的鏈接請求。SYN-RECEIVED
: 表示已接收併發送鏈接請求後等待鏈接確認,也就是 TCP 三次握手中第二步後服務端的狀態ESTABLISHED
: 表示已經鏈接已經創建,能夠將應用數據發送給其餘主機上面這四種狀態是 TCP 三次握手所涉及的。
FIN-WAIT-1
: 表示等待來自遠程 TCP 的鏈接終止請求,或者等待先前發送的鏈接終止請求的確認。
FIN-WAIT-2
: 表示等待來自遠程 TCP 的鏈接終止請求。
CLOSE-WAIT
: 表示等待本地用戶的鏈接終止請求。
CLOSING
: 表示等待來自遠程 TCP 的鏈接終止請求確認。
LAST-ACK
: 表示等待先前發送給遠程 TCP 的鏈接終止請求的確認(包括對它的鏈接終止請求的確認)。
TIME-WAIT
: 表示等待足夠的時間以確保遠程 TCP 收到其鏈接終止請求的確認。
CLOSED
: 表示鏈接已經關閉,無鏈接狀態。
上面 7 種狀態是 TCP 四次揮手,也就是斷開連接所設計的。
TCP 的鏈接狀態會進行各類切換,這些 TCP 鏈接的切換是根據事件進行的,這些事件由用戶調用:OPEN、SEND、RECEIVE、CLOSE、ABORT 和 STATUS。涉及到 TCP 報文段的標誌有 SYN、ACK、RST 和 FIN ,固然,還有超時。
咱們下面加上 TCP 鏈接狀態後,再來看一下三次握手和四次揮手的過程。
下圖畫出了 TCP 鏈接創建的過程。假設圖中左端是客戶端主機,右端是服務端主機,一開始,兩端都處於CLOSED(關閉)
狀態。
被動打開(passive open)
。而後服務端進程處於 LISTEN
狀態,等待客戶端鏈接請求。connect
發起主動打開(active open)
,向服務器發出鏈接請求,請求中首部同步位 SYN = 1,同時選擇一個初始序號 sequence ,簡寫 seq = x。SYN 報文段不容許攜帶數據,只消耗一個序號。此時,客戶端進入 SYN-SEND
狀態。SYN-RECEIVED(同步收到)
狀態。ESTABLISHED (已鏈接)
狀態ESTABLISHED
狀態。TCP 創建一個鏈接須要三個報文段,釋放一個鏈接卻須要四個報文段。
數據傳輸結束後,通訊的雙方能夠釋放鏈接。數據傳輸結束後的客戶端主機和服務端主機都處於 ESTABLISHED 狀態,而後進入釋放鏈接的過程。
TCP 斷開鏈接須要歷經的過程以下
客戶端應用程序發出釋放鏈接的報文段,並中止發送數據,主動關閉 TCP 鏈接。客戶端主機發送釋放鏈接的報文段,報文段中首部 FIN 位置爲 1 ,不包含數據,序列號位 seq = u,此時客戶端主機進入 FIN-WAIT-1(終止等待 1)
階段。
服務器主機接受到客戶端發出的報文段後,即發出確認應答報文,確認應答報文中 ACK = 1,生成本身的序號位 seq = v,ack = u + 1,而後服務器主機就進入 CLOSE-WAIT(關閉等待)
狀態,這個時候客戶端主機 -> 服務器主機這條方向的鏈接就釋放了,客戶端主機沒有數據須要發送,此時服務器主機是一種半鏈接的狀態,可是服務器主機仍然能夠發送數據。
客戶端主機收到服務端主機的確認應答後,即進入 FIN-WAIT-2(終止等待2)
的狀態。等待客戶端發出鏈接釋放的報文段。
當服務器主機沒有數據發送後,應用進程就會通知 TCP 釋放鏈接。這時服務端主機會發出斷開鏈接的報文段,報文段中 ACK = 1,序列號 seq = w,由於在這之間可能已經發送了一些數據,因此 seq 不必定等於 v + 1。ack = u + 1,在發送完斷開請求的報文後,服務端主機就進入了 LAST-ACK(最後確認)
的階段。
客戶端收到服務端的斷開鏈接請求後,客戶端須要做出響應,客戶端發出斷開鏈接的報文段,在報文段中,ACK = 1, 序列號 seq = u + 1,由於客戶端從鏈接開始斷開後就沒有再發送數據,ack = w + 1,而後進入到 TIME-WAIT(時間等待)
狀態,請注意,這個時候 TCP 鏈接尚未釋放。必須通過時間等待的設置,也就是 2MSL
後,客戶端纔會進入 CLOSED
狀態,時間 MSL 叫作最長報文段壽命(Maximum Segment Lifetime)
。
服務端主要收到了客戶端的斷開鏈接確認後,就會進入 CLOSED 狀態。由於服務端結束 TCP 鏈接時間要比客戶端早,而整個鏈接斷開過程須要發送四個報文段,所以釋放鏈接的過程也被稱爲四次揮手。
我上面只是簡單提到了一下 TIME-WAIT 狀態和 2MSL 是啥,下面來聊一下這兩個概念。
MSL
是 TCP 報文段能夠存活或者駐留在網絡中的最長時間。RFC 793 定義了 MSL 的時間是兩分鐘,可是具體的實現還要根據程序員來指定,一些實現採用了 30 秒的這個最大存活時間。
那麼爲何要等待 2MSL
呢?
主要是由於兩個理由
LAST-ACK
狀態等待客戶端響應。這時候服務器會重傳一次 FINACK 斷開鏈接報文,客戶端接收後再從新確認,重啓定時器。若是客戶端不是 2MSL ,在客戶端發送 ACK 後直接關閉的話,若是報文丟失,那麼雙方主機會沒法進入 CLOSED 狀態。已失效
的報文段。客戶端在發送最後一個 ACK 以後,再通過通過 2MSL,就可使本連接持續時間內所產生的全部報文段都從網絡中消失。從保證在關閉鏈接後不會有還在網絡中滯留的報文段去騷擾服務器。這裏注意一點:在服務器發送了 FIN-ACK 以後,會當即啓動超時重傳計時器。客戶端在發送最後一個 ACK 以後會當即啓動時間等待計時器。
說好的 RST
、SYN
、FIN
標誌用於鏈接的創建和關閉,那麼 SYN 和 FIN 都現身了,那 RST 呢?也是啊,咱們上面探討的都是一種理想的狀況,就是客戶端服務器雙方都會接受傳輸報文段的狀況,還有一種狀況是當主機收到 TCP 報文段後,其 IP 和端口號不匹配的狀況。假設客戶端主機發送一個請求,而服務器主機通過 IP 和端口號的判斷後發現不是給這個服務器的,那麼服務器就會發出一個 RST
特殊報文段給客戶端。
所以,當服務端發送一個 RST 特殊報文段給客戶端的時候,它就會告訴客戶端沒有匹配的套接字鏈接,請不要再繼續發送了。
上面探討的是 TCP 的狀況,那麼 UDP 呢?
使用 UDP 做爲傳輸協議後,若是套接字不匹配的話,UDP 主機就會發送一個特殊的 ICMP 數據報。
下面咱們來討論一下什麼是 SYN 洪泛攻擊。
咱們在 TCP 的三次握手中已經看到,服務器爲了響應一個收到的 SYN,分配並初始化變量鏈接和緩存,而後服務器發送一個 SYNACK 做爲響應,而後等待來自於客戶端的 ACK 報文。若是客戶端不發送 ACK 來完成最後一步的話,那麼這個鏈接就處在一個掛起的狀態,也就是半鏈接狀態。
攻擊者一般在這種狀況下發送大量的 TCP SYN 報文段,服務端繼續響應,可是每一個鏈接都完不成三次握手的步驟。隨着 SYN 的不斷增長,服務器會不斷的爲這些半開鏈接分配資源,致使服務器的鏈接最終被消耗殆盡。這種攻擊也是屬於 Dos
攻擊的一種。
抵禦這種攻擊的方式是使用 SYN cookie
,下面是它的工做流程介紹
SYN Cookie
,用於緩存 SYN 請求。而後,服務器會發送帶着 SYN Cookie 的 SYNACK 分組。有一點須要注意的是,服務器不會記憶這個 Cookie 或 SYN 的其餘狀態信息。有了 TCP 的窗口控制後,使計算機網絡中兩個主機之間再也不是以單個數據段的形式發送了,而是可以連續發送大量的數據包。然而,大量數據包同時也伴隨着其餘問題,好比網絡負載、網絡擁堵等問題。TCP 爲了防止這類問題的出現,使用了 擁塞控制
機制,擁塞控制機制會在面臨網絡擁塞時遏制發送方的數據發送。
擁塞控制主要有兩種方法
端到端的擁塞控制
: 由於網絡層沒有爲運輸層擁塞控制提供顯示支持。因此即便網絡中存在擁塞狀況,端系統也要經過對網絡行爲的觀察來推斷。TCP 就是使用了端到端的擁塞控制方式。IP 層不會向端系統提供有關網絡擁塞的反饋信息。那麼 TCP 如何推斷網絡擁塞呢?若是超時或者三次冗餘確認就被認爲是網絡擁塞,TCP 會減少窗口的大小,或者增長往返時延來避免。網絡輔助的擁塞控制
: 在網絡輔助的擁塞控制中,路由器會向發送方提供關於網絡中擁塞狀態的反饋。這種反饋信息就是一個比特信息,它指示鏈路中的擁塞狀況。下圖描述了這兩種擁塞控制方式
若是你看到這裏,那我就暫定認爲你瞭解了 TCP 實現可靠性的基礎了,那就是使用序號和確認號。除此以外,另一個實現 TCP 可靠性基礎的就是 TCP 的擁塞控制。若是說
TCP 所採用的方法是讓每個發送方根據所感知到的網絡的擁塞程度來限制發出報文段的速率,若是 TCP 發送方感知到沒有什麼擁塞,則 TCP 發送方會增長髮送速率;若是發送方感知沿着路徑有阻塞,那麼發送方就會下降發送速率。
可是這種方法有三個問題
- TCP 發送方如何限制它向其餘鏈接發送報文段的速率呢?
- 一個 TCP 發送方是如何感知到網絡擁塞的呢?
- 當發送方感知到端到端的擁塞時,採用何種算法來改變其發送速率呢?
咱們先來探討一下第一個問題,TCP 發送方如何限制它向其餘鏈接發送報文段的速率呢?
咱們知道 TCP 是由接收緩存、發送緩存和變量(LastByteRead, rwnd,等)
組成。發送方的 TCP 擁塞控制機制會跟蹤一個變量,即 擁塞窗口(congestion window)
的變量,擁塞窗口表示爲 cwnd
,用於限制 TCP 在接收到 ACK 以前能夠發送到網絡的數據量。而接收窗口(rwnd)
是一個用於告訴接收方可以接受的數據量。
通常來講,發送方未確認的數據量不得超過 cwnd 和 rwnd 的最小值,也就是
LastByteSent - LastByteAcked <= min(cwnd,rwnd)
因爲每一個數據包的往返時間是 RTT,咱們假設接收端有足夠的緩存空間用於接收數據,咱們就不用考慮 rwnd 了,只專一於 cwnd,那麼,該發送方的發送速率大概是 cwnd/RTT 字節/秒
。經過調節 cwnd,發送方所以能調整它向鏈接發送數據的速率。
一個 TCP 發送方是如何感知到網絡擁塞的呢?
這個咱們上面討論過,是 TCP 根據超時或者 3 個冗餘 ACK 來感知的。
當發送方感知到端到端的擁塞時,採用何種算法來改變其發送速率呢 ?
這個問題比較複雜,且容我娓娓道來,通常來講,TCP 會遵循下面這幾種指導性原則
帶寬探測
,帶寬探測說的是 TCP 能夠經過調節傳輸速率來增長/減少 ACK 到達的次數,若是出現丟包事件,就會減少傳輸速率。所以,爲了探測擁塞開始出現的頻率, TCP 發送方應該增長它的傳輸速率。而後慢慢使傳輸速率下降,進而再次開始探測,看看擁塞開始速率是否發生了變化。在瞭解完 TCP 擁塞控制後,下面咱們就該聊一下 TCP 的 擁塞控制算法(TCP congestion control algorithm)
了。TCP 擁塞控制算法主要包含三個部分:慢啓動、擁塞避免、快速恢復,下面咱們依次來看一下
當一條 TCP 開始創建鏈接時,cwnd 的值就會初始化爲一個 MSS 的較小值。這就使得初始發送速率大概是 MSS/RTT 字節/秒
,好比要傳輸 1000 字節的數據,RTT 爲 200 ms ,那麼獲得的初始發送速率大概是 40 kb/s 。實際狀況下可用帶寬要比這個 MSS/RTT 大得多,所以 TCP 想要找到最佳的發送速率,能夠經過 慢啓動(slow-start)
的方式,在慢啓動的方式中,cwnd 的值會初始化爲 1 個 MSS,而且每次傳輸報文確認後就會增長一個 MSS,cwnd 的值會變爲 2 個 MSS,這兩個報文段都傳輸成功後每一個報文段 + 1,會變爲 4 個 MSS,依此類推,每成功一次 cwnd 的值就會翻倍。以下圖所示
發送速率不可能會一直增加,增加總有結束的時候,那麼什麼時候結束呢?慢啓動一般會使用下面這幾種方式結束髮送速率的增加。
若是在慢啓動的發送過程出現丟包的狀況,那麼 TCP 會將發送方的 cwnd 設置爲 1 並從新開始慢啓動的過程,此時會引入一個 ssthresh(慢啓動閾值)
的概念,它的初始值就是產生丟包的 cwnd 的值 / 2,即當檢測到擁塞時,ssthresh 的值就是窗口值的一半。
第二種方式是直接和 ssthresh 的值相關聯,由於當檢測到擁塞時,ssthresh 的值就是窗口值的一半,那麼當 cwnd > ssthresh 時,每次翻番均可能會出現丟包,因此最好的方式就是 cwnd 的值 = ssthresh ,這樣 TCP 就會轉爲擁塞控制模式,結束慢啓動。
慢啓動結束的最後一種方式就是若是檢測到 3 個冗餘 ACK,TCP 就會執行一種快速重傳並進入恢復狀態。
當 TCP 進入擁塞控制狀態後,cwnd 的值就等於擁塞時值的一半,也就是 ssthresh 的值。因此,沒法每次報文段到達後都將 cwnd 的值再翻倍。而是採用了一種相對保守
的方式,每次傳輸完成後只將 cwnd 的值增長一個 MSS
,好比收到了 10 個報文段的確認,可是 cwnd 的值只增長一個 MSS。這是一種線性增加模式,它也會有增加逾值,它的增加逾值和慢啓動同樣,若是出現丟包,那麼 cwnd 的值就是一個 MSS,ssthresh 的值就等於 cwnd 的一半;或者是收到 3 個冗餘的 ACK 響應也能中止 MSS 增加。若是 TCP 將 cwnd 的值減半後,仍然會收到 3 個冗餘 ACK,那麼就會將 ssthresh 的值記錄爲 cwnd 值的一半,進入 快速恢復
狀態。
在快速恢復中,對於使 TCP 進入快速恢復狀態缺失的報文段,對於每一個收到的冗餘 ACK,cwnd 的值都會增長一個 MSS 。當對丟失報文段的一個 ACK 到達時,TCP 在下降 cwnd 後進入擁塞避免狀態。若是在擁塞控制狀態後出現超時,那麼就會遷移到慢啓動狀態,cwnd 的值被設置爲 1 個 MSS,ssthresh 的值設置爲 cwnd 的一半。
若是你能用心看到這裏,我相信你定會有所收穫。
這篇文章寫的時間很長,圖中不少樣式和配色都是精挑細選,若是你仔細閱讀,能夠看到個人用心良苦。
若是你以爲文章寫的還不錯,歡迎你幫助 cxuan 擴散一下,這將是我繼續更新的動力,切忌不要白嫖,會讓本身變得廉價,好的文章值得分享。
請記得給我一個贊哦!
另外,我本身肝了六本 PDF,微信搜索 程序員cxuan 關注公衆號後,在後臺回覆 cxuan ,領取所有 PDF,這些 PDF。