TCP 知識自我匯總 (三次握手和四次握手,流量控制以及擁塞控制原理)

1.TCP基本認識

什么是 TCP ?

TCP 是面向連接的、可靠的、基于字節(jié)流的傳輸層通信協(xié)議。因為 TCP 是一個工作在傳輸層可靠數(shù)據(jù)傳輸?shù)姆眨艽_保接收端接收的網(wǎng)絡包是無損壞、無間隔、非冗余和按序的。

建立一個 TCP 連接是需要客戶端與服務器端達成上述三個信息的共識。

Socket:由 IP 地址和端口號組成,序列號:用來解決亂序問題等,窗口大小:用來做流量控制

源地址和目的地址的字段(32位)是在 IP 頭部中,作用是通過 IP 協(xié)議發(fā)送報文給對方主機。

源端口和目的端口的字段(16位)是在 TCP 頭部中,作用是告訴 TCP 協(xié)議應該把報文發(fā)給哪個進程


序列號:在建立連接時由計算機生成的隨機數(shù)作為其初始值,通過 SYN 包傳給接收端主機,每發(fā)送一次數(shù)據(jù),就「累加」一次該「數(shù)據(jù)字節(jié)數(shù)」的大小。用來解決網(wǎng)絡包亂序問題。

確認應答號:指下一次「期望」收到的數(shù)據(jù)的序列號,發(fā)送端收到這個確認應答以后可以認為在這個序號以前的數(shù)據(jù)都已經(jīng)被正常接收。用來解決不丟包的問題。

控制位:

ACK:該位為 1 時,「確認應答」的字段變?yōu)橛行?,TCP 規(guī)定除了最初建立連接時的 SYN 包之外該位必須設置為 1 。

RST:該位為 1 時,表示 TCP 連接中出現(xiàn)異常必須強制斷開連接。

SYC:該位為 1 時,表示希望建立連,并在其「序列號」的字段進行序列號初始值的設定。

FIN:該位為 1 時,表示今后不會再有數(shù)據(jù)發(fā)送,希望斷開連接。當通信結(jié)束希望斷開連接時,通信雙方的主機之間就可以相互交換 FIN 位置為 1 的 TCP 段。


2.TCP與UDP區(qū)別

UDP 和 TCP 有什么區(qū)別呢?分別的應用場景是?

UDP 不提供復雜的控制機制,利用 IP 提供面向「無連接」的通信服務。

UDP 協(xié)議真的非常簡單,頭部只有 8 個字節(jié)( 64 位),UDP 的頭部格式如下:


1. 連接

TCP 是面向連接的傳輸層協(xié)議,傳輸數(shù)據(jù)前先要建立連接。UDP 是不需要連接,即刻傳輸數(shù)據(jù)。

2. 服務對象

TCP 是一對一的兩點服務,即一條連接只有兩個端點。UDP 支持一對一、一對多、多對多的交互通信

3. 可靠性

TCP 是可靠交付數(shù)據(jù)的,數(shù)據(jù)可以無差錯、不丟失、不重復、按需到達。UDP 是盡最大努力交付,不保證可靠交付數(shù)據(jù)。

4. 擁塞控制、流量控制

TCP 有擁塞控制和流量控制機制,保證數(shù)據(jù)傳輸?shù)陌踩浴?

UDP 則沒有,即使網(wǎng)絡非常擁堵了,也不會

影響 UDP 的發(fā)送速率。

5. 首部開銷

TCP 首部長度較長,會有一定的開銷,首部在沒有使用「選項」字段時是 20 個字節(jié),如果使用了「選項」字段則會變長的。

UDP 首部只有 8 個字節(jié),并且是固定不變的,開銷較小。

TCP 和 UDP 應用場景:

由于 TCP 是面向連接,能保證數(shù)據(jù)的可靠性交付,因此經(jīng)常用于:

FTP 文件傳輸,HTTP / HTTPS

由于 UDP 面向無連接,它可以隨時發(fā)送數(shù)據(jù),再加上UDP本身的處理既簡單又高效,因此經(jīng)常用于:

包總量較少的通信,如 DNS 、SNMP 等。 視頻、音頻等多媒體通信和廣播通信

為什么 UDP 頭部沒有「首部長度」字段,而 TCP 頭部有「首部長度」字段呢?

原因是 TCP 有可變長的「選項」字段,而 UDP 頭部長度則是不會變化的,無需多一個字段去記錄 UDP 的首部長度。

為什么 UDP 頭部有「包長度」字段,而 TCP 頭部則沒有「包長度」字段呢?

先說說 TCP 是如何計算負載數(shù)據(jù)長度:TCP數(shù)據(jù)長度 = IP總長度 - IP首部長度 - TCP首部長度

TCP 三次握手過程和狀態(tài)變遷

TCP 是面向連接的協(xié)議,所以使用 TCP 前必須先建立連接,而建立連接是通過三次握手而進行的

一開始,客戶端和服務端都處于 CLOSED 狀態(tài)。先是服務端主動監(jiān)聽某個端口,處于 LISTEN 狀態(tài)


第一個報文:SYN報文
客戶端會隨機初始化序號(client_isn),將此序號置于 TCP 首部的序號字段中,同時把 SYN 標志位置為 1 。表示 SYN 報文。接著把第一個 SYN 報文發(fā)送給服務端,表示向服務端發(fā)起連接,該報文不包含應用層數(shù)據(jù),之后客戶端處于SYN-SENT 狀態(tài)。


第二個報文 —— SYN + ACK 報文
服務端收到客戶端的 SYN 報文后,首先服務端也隨機初始化自己的序號(server_isn),將此序號填入 TCP 首部的「序號」字段中,其次把 TCP 首部的「確認應答號」字段填入 client_isn + 1, 接著把 SYN 和 ACK 標志位置為1。最后把該報文發(fā)給客戶端,該報文也不包含應用層數(shù)據(jù),之后服務端處于SYN-RCVD 狀態(tài)。


第三個報文 —— ACK 報文
客戶端收到服務端報文后,還要向服務端回應最后一個應答報文,首先該應答報文 TCP 首部 ACK 標志位置為 1 ,其次「確認應答號」字段填入 server_isn + 1,最后把報文發(fā)送給服務端,這次報文可以攜帶客戶到服務器的數(shù)據(jù),之后客戶端處于 ESTABLISHED 狀態(tài)。

服務器收到客戶端的應答報文后,也進入 ESTABLISHED 狀態(tài)。

從上面的過程可以發(fā)現(xiàn)第三次握手是可以攜帶數(shù)據(jù)的,前兩次握手是不可以攜帶數(shù)據(jù)的,這也是面試常問的題。

一旦完成三次握手,雙方都處于 ESTABLISHED 狀態(tài),此致連接就已建立完成,客戶端和服務端就可以相互發(fā)送數(shù)據(jù)了。

如何在 Linux 系統(tǒng)中查看 TCP 狀態(tài)?

TCP 的連接狀態(tài)查看,在 Linux 可以通過 netstat -napt 命令查看。


為什么是三次握手?不是兩次、四次?

接下來以三個方面分析三次握手的原因:

三次握手才可以阻止歷史重復連接的初始化(主要原因)

三次握手才可以同步雙方的初始序列號

三次握手才可以避免資源浪費

原因一:避免歷史連接

三次握手的首要原因是為了防止舊的重復連接初始化造成混亂。

客戶端連續(xù)發(fā)送多次 SYN 建立連接的報文,在網(wǎng)絡擁堵等情況下:

一個「舊 SYN 報文」比「最新的 SYN 」 報文早到達了服務端;

那么此時服務端就會回一個 SYN + ACK 報文給客戶端;

客戶端收到后可以根據(jù)自身的上下文,判斷這是一個歷史連接(序列號過期或超時),那么客戶端就會發(fā)送 RST 報文給服務端,表示中止這一次連接。

如果是兩次握手連接,就不能判斷當前連接是否是歷史連接,三次握手則可以在客戶端(發(fā)送方)準備發(fā)送第三次報文時,客戶端因有足夠的上下文來判斷當前連接是否是歷史連接:

如果是歷史連接(序列號過期或超時),則第三次握手發(fā)送的報文是 RST 報文,以此中止歷史連接;如果不是歷史連接,則第三次發(fā)送的報文是 ACK 報文,通信雙方就會成功建立連接;所以, TCP 使用三次握手建立連接的最主要原因是防止歷史連接初始化了連接。

原因二:同步雙方初始序列號

TCP 協(xié)議的通信雙方, 都必須維護一個「序列號」, 序列號是可靠傳輸?shù)囊粋€關鍵因素,它的作用:

接收方可以去除重復的數(shù)據(jù);

接收方可以根據(jù)數(shù)據(jù)包的序列號按序接收;

可以標識發(fā)送出去的數(shù)據(jù)包中, 哪些是已經(jīng)被對方收到的;

序列號在 TCP 連接中占據(jù)著非常重要的作用,所以當客戶端發(fā)送攜帶「初始序列號」的 SYN 報文的時候,需要服務端回一個 ACK 應答報文,表示客戶端的 SYN 報文已被服務端成功接收,那當服務端發(fā)送「初始序列號」給客戶端的時候,依然也要得到客戶端的應答回應,這樣一來一回,才能確保雙方的初始序列號能被可靠的同步。

原因三:避免資源浪費

如果只有「兩次握手」,當客戶端的 SYN 請求連接在網(wǎng)絡中阻塞,客戶端沒有接收到ACK 報文,就會重新發(fā)送 SYN ,由于沒有第三次握手,服務器不清楚客戶端是否收到了自己發(fā)送的建立連接的 ACK 確認信號,所以每收到一個 SYN 就只能先主動建立一個連接,這會造成什么情況呢?

如果客戶端的 SYN 阻塞了,重復發(fā)送多次 SYN 報文,那么服務器在收到請求后就會建立多個冗余的無效鏈接,造成不必要的資源浪費。

MTU 與 MSS

MTU:一個網(wǎng)絡包的最大長度,以太網(wǎng)中一般為 1500 字節(jié);

MSS:除去 IP 和 TCP 頭部之后,一個網(wǎng)絡包所能容納的 TCP 數(shù)據(jù)的最大長度;

如果TCP 的整個報文(頭部 + 數(shù)據(jù))交給 IP 層進行分片,會有什么異常呢?

當 IP 層有一個超過 MTU 大小的數(shù)據(jù)(TCP 頭部 + TCP 數(shù)據(jù))要發(fā)送,那么 IP 層就要進行分片,把數(shù)據(jù)分片成若干片,保證每一個分片都小于 MTU。把一份 IP 數(shù)據(jù)報進行分片以后,由目標主機的 IP 層來進行重新組裝后,在交給上一層 TCP 傳輸層。

這看起來井然有序,但這存在隱患的,那么當如果一個 IP 分片丟失,整個 IP 報文的所有分片都得重傳。

因為 IP 層本身沒有超時重傳機制,它由傳輸層的 TCP 來負責超時和重傳。

當接收方發(fā)現(xiàn) TCP 報文(頭部 + 數(shù)據(jù))的某一片丟失后,則不會響應 ACK 給對方,那么發(fā)送方的 TCP 在超時后,就會重發(fā)「整個 TCP 報文(頭部 + 數(shù)據(jù))」。

因此,可以得知由 IP 層進行分片傳輸,是非常沒有效率的。

所以,為了達到最佳的傳輸效能 TCP 協(xié)議在建立連接的時候通常要協(xié)商雙方的 MSS 值,當 TCP 層發(fā)現(xiàn)數(shù)據(jù)超過 MSS 時,則就先會進行分片,當然由它形成的 IP 包的長度也就不會大于 MTU ,自然也就不用 IP 分片了。

什么是 SYN 攻擊?如何避免 SYN 攻擊?

SYN 攻擊

我們都知道 TCP 連接建立是需要三次握手,假設攻擊者短時間偽造不同 IP 地址的SYN 報文,服務端每接收到一個 SYN 報文,就進入SYN_RCVD 狀態(tài),但服務端發(fā)送出去的 ACK + SYN 報文,無法得到未知 IP 主機的 ACK 應答,久而久之就會占滿服務端的 SYN 接收隊列(未連接隊列),使得服務器不能為正常用戶服務。


避免 SYN 攻擊方式一

其中一種解決方式是通過修改 Linux 內(nèi)核參數(shù),控制隊列大小和當隊列滿時應做什么處理。

當網(wǎng)卡接收數(shù)據(jù)包的速度大于內(nèi)核處理的速度時,會有一個隊列保存這些數(shù)據(jù)包??刂圃撽犃械淖畲笾等缦聟?shù):

net.core.netdev_max_backlog

SYN_RCVD 狀態(tài)連接的最大個數(shù):

net.ipv4.tcp_max_syn_backlog

超出處理能時,對新的 SYN 直接回 RST,丟棄連接:

net.ipv4.tcp_abort_on_overflow

四次揮手都做什么?

TCP的連接是全雙工的,所以連接的拆除需要單獨將兩個通道分別拆除,而四次揮手所做的事情就是拆除兩條通道釋放資源

TCP 提供了連接的一端結(jié)束他的發(fā)送后,還能接收來自另一端數(shù)據(jù)的能力,也就是所謂的半關閉。


這里以Client作為主動發(fā)起端,Server作為被動關閉端。

第一步,Client主動發(fā)起一個Req給Server,里面包含F(xiàn)IN標識位=1,CLient的Seq序列號N,表示的是當前Client在該連接上的當前序列號。

第二步,Server端在收到這個含有FIN的Req消息之后,校驗無誤之后會立馬回復ACK消息給CLient端,消息內(nèi)部包含ACK標志位為1,同時Seq號碼是FIN的請求消息的Seq號+1。此時的Sever同時會主動發(fā)個結(jié)束標識給Server上面的應用層程序,應用層程序可以決定是立馬結(jié)束,還是等到服務其上面的該連接中的數(shù)據(jù)處理完了之后,在發(fā)送FIN消息給Client來關掉另外的一半連接。

第三步,Server端在處理完該連接上面的Pending住的數(shù)據(jù)之后,應用程序會close這個連接。Client會主動發(fā)起FIN的Req消息給Client端。消息內(nèi)部帶有,F(xiàn)IN=1的結(jié)束符標識位,以及Server端的Seq序列號。

第四步,Client端在收到對應的FIN消息之后,會主動通知應用層程序,告知這個連接現(xiàn)在需要關閉了。然后,Client會回復ACK消息給Server,以便斷開另外一個方向的通道,這個消息包含ACK=1的標識位和FIN的REQ帶過來的Seq+1。

為什么揮手需要四次?

因為TCP是一個全雙工協(xié)議,必須單獨拆除每一條信道。4次揮手的目的是終止數(shù)據(jù)傳輸,并回收資源,此時兩個端點兩個方向的序列號已經(jīng)沒有了任何關系,必須等待兩方向都沒有數(shù)據(jù)傳輸時才能拆除虛鏈路,不像初始化時那么簡單,發(fā)現(xiàn)SYN標志就初始化一個序列號并確認SYN的序列號。因此必須單獨分別在一個方向上終止該方向的數(shù)據(jù)傳輸。

再來回顧下四次揮手雙方發(fā) FIN 包的過程,就能理解為什么需要四次了。

關閉連接時,客戶端向服務端發(fā)送 FIN 時,僅僅表示客戶端不再發(fā)送數(shù)據(jù)了但是還能接收數(shù)據(jù)。

服務器收到客戶端的 FIN 報文時,先回一個 ACK 應答報文,而服務端可能還有數(shù)據(jù)需要處理和發(fā)送,等服務端不再發(fā)送數(shù)據(jù)時,才發(fā)送 FIN 報文給客戶端來表示同意現(xiàn)在關閉連接。

從上面過程可知,服務端通常需要等待完成數(shù)據(jù)的發(fā)送和處理,所以服務端的 ACK 和FIN 一般都會分開發(fā)送,從而比三次握手導致多了一次。

四次揮手的狀態(tài)轉(zhuǎn)換?

Client端:

ESTABLISHED---發(fā)送FIN給Server-->FIN_WAIT_1---接收到Server端的FIN對應的ACK-->FIN_WAIT_2---收到Server端發(fā)送過來的FIN消息-->FIIN_WAIT--2MSL之后會進入-->CLOSED

Server端:

ESTABLISHED---接收到Client端的FIN->CLOSED_WAIT--Server端的應用程序關閉發(fā)送FIN--> LAST_ACK---收到Client對于FIN的ACK響應-->FIIN_WAIT---->CLOSED

最簡單的理解

一:建立TCP連接:三次握手協(xié)議


客戶端:我要對你講話,你能聽到嗎;

服務端:我能聽到;而且我也要對你講話,你能聽到嗎;

客戶端:我也能聽到。

…….

互相開始通話

……..? ?

二:關閉TCP連接:四次握手協(xié)議

客戶端:我說完了,我要閉嘴了;

服務端:我收到請求,我要閉耳朵了;

(客戶端收到這個確認,于是安心地閉嘴了。)

…….

? ? 服務端還沒傾訴完自己的故事,于是繼續(xù)嘮嘮叨叨向客戶端說了半天,直到說完為止

…….

服務端:我說完了,我也要閉嘴了;

客戶端:我收到請求,我要閉耳朵了;(事實上,客戶端為了保證這個確認包成功送達,等待了兩個最大報文生命周期后,才閉上耳朵。)

(服務端收到這個確認,于是安心地閉嘴了。)

TCP如何流量控制

TCP 為了防止發(fā)送方無腦的發(fā)送數(shù)據(jù),導致接收方緩沖區(qū)被填滿,所以就有了滑動窗口的機制,它可利用接收方的接收窗口來控制發(fā)送方要發(fā)送的數(shù)據(jù)量,也就是流量控制。

接收窗口是由接收方指定的值,存儲在 TCP 頭部中,它可以告訴發(fā)送方自己的 TCP 緩沖空間區(qū)大小,這個緩沖區(qū)是給應用程序讀取數(shù)據(jù)的空間:

如果應用程序讀取了緩沖區(qū)的數(shù)據(jù),那么緩沖空間區(qū)的就會把被讀取的數(shù)據(jù)移除

如果應用程序沒有讀取數(shù)據(jù),則數(shù)據(jù)會一直滯留在緩沖區(qū)。

接收窗口的大小,是在 TCP 三次握手中協(xié)商好的,后續(xù)數(shù)據(jù)傳輸時,接收方發(fā)送確認應答 ACK 報文時,會攜帶當前的接收窗口的大小,以此來告知發(fā)送方。

假設接收方接收到數(shù)據(jù)后,應用層能很快的從緩沖區(qū)里讀取數(shù)據(jù),那么窗口大小會一直保持不變,過程如下:


但是現(xiàn)實中服務器會出現(xiàn)繁忙的情況,當應用程序讀取速度慢,那么緩存空間會慢慢被占滿,于是為了保證發(fā)送方發(fā)送的數(shù)據(jù)不會超過緩沖區(qū)大小,則服務器會調(diào)整窗口大小的值,接著通過 ACK 報文通知給對方,告知現(xiàn)在的接收窗口大小,從而控制發(fā)送方發(fā)送的數(shù)據(jù)大小。


零窗口通知與窗口探測

假設接收方處理數(shù)據(jù)的速度跟不上接收數(shù)據(jù)的速度,緩存就會被占滿,從而導致接收窗口為 0,當發(fā)送方接收到零窗口通知時,就會停止發(fā)送數(shù)據(jù)。

如下圖,可以接收方的窗口大小在不斷的收縮至 0:

窗口大小在收縮

接著,發(fā)送方會定時發(fā)送窗口大小探測報文,以便及時知道接收方窗口大小的變化。

以下圖 Wireshark 分析圖作為例子說明:

零窗口 與 窗口探測

發(fā)送方發(fā)送了數(shù)據(jù)包 1 給接收方,接收方收到后,由于緩沖區(qū)被占滿,回了個零窗口通知;

發(fā)送方收到零窗口通知后,就不再發(fā)送數(shù)據(jù)了,直到過了 3.4 秒后,發(fā)送了一個 TCP Keep-Alive 報文,也就是窗口大小探測報文;

當接收方收到窗口探測報文后,就立馬回一個窗口通知,但是窗口大小還是 0;

發(fā)送方發(fā)現(xiàn)窗口還是 0,于是繼續(xù)等待了 6.8(翻倍) 秒后,又發(fā)送了窗口探測報文,接收方依然還是回了窗口為 0 的通知;

發(fā)送方發(fā)現(xiàn)窗口還是 0,于是繼續(xù)等待了 13.5(翻倍) 秒后,又發(fā)送了窗口探測報文,接收方依然還是回了窗口為 0 的通知;

可以發(fā)現(xiàn),這些窗口探測報文以 3.4s、6.5s、13.5s 的間隔出現(xiàn),說明超時時間會翻倍遞增。

TCP 的擁塞控制原理


什么是 TCP 擁塞控制

TCP 擁塞控制的目標是最大化利用網(wǎng)絡上瓶頸鏈路的帶寬。

Reno

Reno 被許多教材(例如:《計算機網(wǎng)絡——自頂向下的方法》)所介紹,適用于低延時、低帶寬的網(wǎng)絡,它將擁塞控制的過程分為四個階段:慢啟動、擁塞避免、快重傳和快恢復,對應的狀態(tài)如下所示:

慢啟動階段思路是不要一開始就發(fā)送大量的數(shù)據(jù),先探測一下網(wǎng)絡的擁塞程度,也就是說由小到大逐漸增加擁塞窗口的大小,在沒有出現(xiàn)丟包時每收到一個 ACK 就將擁塞窗口大小加一(單位是 MSS,最大單個報文段長度),每輪次發(fā)送窗口增加一倍,呈指數(shù)增長,若出現(xiàn)丟包,則將擁塞窗口減半,進入擁塞避免階段;

當窗口達到慢啟動閾值或出現(xiàn)丟包時,進入擁塞避免階段,窗口每輪次加一,呈線性增長;當收到對一個報文的三個重復的 ACK 時,認為這個報文的下一個報文丟失了,進入快重傳階段,要求接收方在收到一個失序的報文段后就立即發(fā)出重復確認(為的是使發(fā)送方及早知道有報文段沒有到達對方,可提高網(wǎng)絡吞吐量約20%)而不要等到自己發(fā)送數(shù)據(jù)時捎帶確認;

快重傳完成后進入快恢復階段,將慢啟動閾值修改為當前擁塞窗口值的一半,同時擁塞窗口值等于慢啟動閾值,然后進入擁塞避免階段,重復上述過程。

BBR 算法不將出現(xiàn)丟包或時延增加作為擁塞的信號,而是認為當網(wǎng)絡上的數(shù)據(jù)包總量大于瓶頸鏈路帶寬和時延的乘積時才出現(xiàn)了擁塞,所以 BBR 也稱為基于擁塞的擁塞控制算法(Congestion-Based Congestion Control),其適用網(wǎng)絡為高帶寬、高時延、有一定丟包率的長肥網(wǎng)絡,可以有效降低傳輸時延,并保證較高的吞吐量,

BBR 算法周期性地探測網(wǎng)絡的容量,交替測量一段時間內(nèi)的帶寬極大值和時延極小值,將其乘積作為作為擁塞窗口大小,使得擁塞窗口始的值始終與網(wǎng)絡的容量保持一致。

在有一定丟包率的網(wǎng)絡鏈路上充分利用帶寬。降低網(wǎng)絡鏈路上的 buffer 占用率,從而降低延遲。

基于丟包的擁塞控制:將丟包視為出現(xiàn)擁塞,采取緩慢探測的方式,逐漸增大擁塞窗口,當出現(xiàn)丟包時,將擁塞窗口減小,如 Reno、Cubic 等。

基于時延的擁塞控制:將時延增加視為出現(xiàn)擁塞,延時增加時增大擁塞窗口,延時減小時減小擁塞窗口,如 Vegas、FastTCP 等。

基于鏈路容量的擁塞控制:實時測量網(wǎng)絡帶寬和時延,認為網(wǎng)絡上報文總量大于帶寬時延乘積時出現(xiàn)了擁塞,如 BBR。

基于學習的擁塞控制:沒有特定的擁塞信號,而是借助評價函數(shù),基于訓練數(shù)據(jù),使用機器學習的方法形成一個控制策略,如 Remy。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容