TCP 概念
- TCP 屬于網絡分層中的傳輸層協定,介于會話層和網絡層中間
- TCP 協定是用于主機到主機的通信協定,是面向連接配接的端到端的可靠協定,提供可靠位元組流傳輸和對上層應用提供連接配接服務
- TCP 主要通過檢驗和、序列号、确認應答、重發控制、連接配接管理以及視窗控制等機制來實作可靠性
- TCP 對位元組流的内容不作任何解釋,對位元組流的解釋由 TCP 連接配接雙方的應用層進行解釋
- TCP 連接配接的建立和關閉的過程通過三次握手和四次揮手實作,具體細節可以參考上一篇文章TCP 連接配接狀态及相關指令學習
TCP 和 UDP 的差別
- TCP 是面向連接配接的、可靠的流協定,通過“順序控制”或“重發控制”等來提高可靠性
- UDP 是不具有可靠性的資料報協定,雖然可以確定發送消息的大小,卻不能保證消息一定會到達,需要應用層根據自己的需要進行重發處理
- TCP 用于在傳輸層有必要實作可靠傳輸的情況,UDP 主要用于那些對高速傳輸和實時性有較高要求的通信或廣播通信
端口号
- 端口号用來識别同一台主機中進行通信的不同應用程式
- 傳輸層協定正是利用這些端口号識别本機中正在進行通信的應用程式,并準确地将資料傳輸
- IP 位址 + 端口号 + 協定來唯一識别一個應用程式,也就是不同的協定可以使用相同的端口
- 知名端口号:分布在 0~1023 之間,例如 HTTP、FTP、TELNET 等廣為使用的應用協定中所使用的端口号就是固定的
- 端口号在 1024~49151 之間,這些端口号可以用于任何的通信用途
- 一般情況下伺服器需要确認端口号,但是用戶端在連接配接服務端時完全不用使用者自己指定,由作業系統動态配置設定 49152~65535 之間的端口号
TCP 的連接配接隊列
- 正等待連接配接請求的一端有一個固定長度的連接配接隊列,該隊列中儲存着已被 TCP 接受的連接配接(即三次握手已經完成),但還沒有被應用層所接受
- TCP 每接受一個連接配接,也就是完成三次握手就将其放入這個隊列,而應用層每接受一個連接配接時将其從該隊列中移出
- 應用層将指明該隊列的最大長度,這個值通常稱為積壓值(backlog),它的取值範圍是 0~5 之間的整數
- 當一個連接配接請求(即 SYN )到達時,該 TCP 監聽端口的連接配接隊列中還有空間,TCP 子產品将對 SYN 進行确認并完成連接配接的建立
- 如果對于新的連接配接請求,連接配接隊列中已沒有空間,TCP 将不理會收到的 SYN,也不發回任何封包段(即不發回 RST),客戶的主動打開最終将逾時
MSS(Maximum Segment Size)
- 在建立 TCP 連接配接的同時會計算兩端之間的傳輸的資料包大小,該資料包的大小被稱其為 MSS(最大消息長度)
- MSS 的大小不包含 TCP Header 和 TCP Option,隻包含 TCP Payload
- 最理想的情況是,MSS 正好是 IP 中不會被分片處理的最大資料長度,減少 IP 層資料分片和重傳的消耗
- TCP 在傳送大量資料時,是以 MSS 的大小将資料進行分割發送,進行重發時也是以 MSS 為機關
- 兩端的主機在發出建立連接配接的請求時,會在 TCP 首部中寫入 MSS 選項,告訴對方自己的接口能夠适應的 MSS 的大小,然後會在兩者之間選擇一個較小的值進行使用
通信序列号
- 序列号是按照順序給發送資料的每一個位元組(8位位元組)都标上号碼的編号
- 接收端查詢接收資料 TCP 首部中的序列号和資料的長度,将自己下一步應該接收的序列号作為确認應答發送回去
- 通過序列号和确認應答号,TCP 能夠識别是否已經接收資料,又能夠判斷是否需要接收,進而實作可靠傳輸
- 為了解決序列号沖突問題,TCP 每個連接配接都從不同的序列号開始,這個序号的起始序号是随着時間而變化的
TCP 标志位
TCP 報頭資訊中有六個控制位:URG,ACK,PSH,RST,SYN,FIN:
- URG:緊急标志位,如果該位設定為 1,目前封包需要接收端立即處理,并且目前封包不需要經過接收端的緩沖區,直接越過緩沖區,傳遞給接收端的應用層
- ACK:确認标志用于确認資料包的成功接收,每次發送一個資料包都要進行确認
- PSH:
- 通過允許客戶應用程式通知其 TCP 設定 PSH 标志,客戶程序通知 TCP 在向伺服器發送一個封包段時不要因等待額外資料而使已送出資料在緩存中滞留
- PSH 和 URG 不同之處在于:目前的資料還會被發送到接收端的緩沖區,并重新整理緩沖區,将目前緩沖區中所有資料都傳遞給上一層的應用層
- PSH 标志位所表達的是發送方通知接收方傳輸層應該盡快的将這個封包段交給應用層
- PSH 标志通常設定在檔案的最後一段,以防止緩沖區死鎖,當用于通過代理發送 HTTP 或其他類型的請求時也可以看到,確定請求得到适當和有效的處理
- RST:如果主機收到無法比對的用戶端請求,則主機将自動拒絕該請求,并産生 RST 标志回應。産生 RST 的情況主要由以下兩種情況:
- 到不存在端口的連接配接請求
- 在探活過程中,如果對方已經奔潰或者重新開機,異常終止一個連接配接,會産生 RST
- 在半打開連接配接上發送資料
- SYN:TCP 三次握手中,表示建立連接配接的标記
- FIN:TCP 四次揮手時,表示關閉連接配接的标記
Nagle 算法
- Nagle 算法要求一個 TCP 連接配接在任意時刻最多隻能有一個沒有被 ACK 确認的小段。所謂“小段”指的是小于 MSS 的資料塊。
- Nagle 算法就是為了盡可能發送大塊資料,避免網絡中充斥着許多小資料塊,進而減少大量小包的發送。
- 該算法的優越之處在于它是自适應的:确認到達得越快,資料也就發送得越快,并且可以發送更少的分組
- Nagle算法的實作規則如下:
- 如果包長度達到 MSS,則允許發送;
- 如果該包含有 FIN,則允許發送;
- 如果該包設定了 TCP_NODELAY 選項,則允許發送;
- 未設定 TCP_CORK 選項時,若所有發出去的小資料包均被确認,則允許發送;
- 上述條件都未滿足,但發生了逾時(一般為200ms),則立即發送。
重發機制
- TCP 重發逾時是指在重發資料之前,等待确認應答到來的那個特定時間間隔
- 如果超過這個時間仍未收到确認應答,發送端将進行資料重發
- 最理想的是,找到一個最小時間,它能保證“确認應答一定能在這個時間内傳回”
- 自适應重傳算法(Adaptive Retransmission Algorithm):
估計往返時間,需要 TCP 通過采樣 RTT 的時間,然後進行權重平均,算出一個值,
而且這個值還是要不斷變化的,因為網絡狀況不斷地變化。
除了采樣 RTT,還要采樣 RTT 的波動範圍,計算出一個估計的逾時時間
- 在 BSD 的 Unix 以及 Windows 系統中,逾時都以 0.5 秒為機關進行控制,是以重發逾時都是 0.5 秒的整數倍。不過,最初其重發逾時的預設值一般設定為6秒左右
- 資料被重發之後若還是收不到确認應答,則進行再次發送。此時,等待确認應答的時間将會以 2 倍、4 倍的指數函數延長
- 資料也不會被無限、反複地重發,達到一定重發次數之後,如果仍沒有任何确認應答傳回,就會判斷為網絡或對端主機發生了異常,強制關閉連接配接,并且通知應用通信異常強行終止
- TCP 重傳時不一定要重傳相同的封包段,可以進行重新分組而發一個更大的封包段
滑動視窗
概念:
如果 TCP 以 1 個段為機關,每發送一個段進行一次确認應答的處理,這樣的傳輸方式有一個缺點,就是包的往返時間越長通信性能就越低。為解決這個問題,TCP 引入了滑動視窗這個概念,确認應答不再是以每個分段,而是以更大的機關進行确認,轉發時間将會被大幅地縮短。也就是說,通過滑動視窗的機制,發送端主機在發送了一個段以後不必要一直等待确認應答,而是可以繼續發送。
視窗大小:
- 視窗大小是指無需等待确認應答而可以繼續發送資料的最大值
- 接收方在收到 ACK 時順帶将視窗大小傳回給發送方
- 接收方每次 ACK 的回來的視窗大小不一定是固定的,ACK 隻是表示接受到了資料,但是可能這些資料應用程式根本來不及處理,還存在 TCP 緩存區裡面,此時滑動視窗的大小就是剩餘緩存區的大小
- 有些情況下可能緩存區已經滿了,這時接收方在 ACK 時告訴發送方通告視窗大小為 0,無法接受資料,後面會等待緩存區釋放以後,接收方再發送一個 ACK (更新視窗),此時并不确認任何資料,隻是用來更新視窗
- 如果設定為 0 的話,發送方也會定時發送視窗探測資料包,看是否有機會調整視窗的大小
累積确認應答(cumulative acknowledgment):
為了保證不丢包,接收方對于發送的包都要進行應答,但是這個應答也不是一個一個來的,而是會應答某個之前的 ID,表示這個 ID 之前的所有包都收到了,這種模式稱為累計确認或者累計應答。通過累積确認應答減少傳輸次數,提高傳輸效率。
延時發送 ACK:
通常 TCP 在接收到資料時并不立即發送 ACK,相反它推遲發送,以便将 ACK 與需要沿該方向發送的資料一起發送(有時稱這種現象為資料捎帶 ACK),絕大多數實作采用的時延為 200 ms,也就是說,TCP 将以最大 200 ms 的時延等待是否有資料一起發送
滑動視窗中的重發控制:
- 當滑動視窗在一定程度較大時,即使有少部分的确認應答丢失也不會重發,可以通過下一個确認應答進行确認
- 當接收方收到一個序号大于下一個所期望的封包段時,就會檢測到資料流中的一個間隔,于是它就會發送備援的 ACK,而當用戶端收到三個備援的 ACK 後,就會在定時器過期之前,重傳丢失的封包段,這種機制比之前提到的逾時管理更加高效,是以也被稱為高速重發控制
TCP 擁塞控制
TCP 網絡實際傳輸中,由于包的數量較多,很可能出現網絡擁塞的現象,為此 TCP 提供了四種控制擁塞的方法:
TCP 慢啟動:
所謂慢啟動,也就是 TCP 連接配接剛建立,一點一點地提速,試探一下網絡的承受能力,以免直接擾亂了網絡通道的秩序:
(1)連接配接建好的開始先初始化擁塞視窗 cwnd 大小為 1,表明可以傳一個 MSS 大小的資料。
(2)每當收到一個 ACK,cwnd 大小加一,每當過了一個往返延遲時間 RTT,cwnd 大小直接翻倍,乘以 2,呈指數讓升。
(3)還有一個 ssthresh(slow start threshold),是一個上限,發送方取擁塞視窗與通告視窗中的最小值作為發送上限,當 cwnd >= ssthresh 時,就會進入下面講到的 “擁塞避免算法” 階段
擁塞避免算法:
如同前邊說的,當擁塞視窗大小 cwnd 大于等于慢啟動門檻值 ssthresh 後,就進入擁塞避免算法階段:
(1)每收到一個 ACK,則 cwnd = cwnd + 1 / cwnd
(2)每當過了一個往返延遲時間 RTT,cwnd 大小加一,呈線性增長
(3)過了慢啟動門檻值後,擁塞避免算法可以避免視窗增長過快導緻視窗擁塞,而是緩慢的增加調整到網絡的最佳值。
(4)算法通過觀察到新分組進入網絡的速率應該與另一端傳回确認的速率相同而進行工作
擁塞狀态時的算法:
該算法是指 TCP 進入擁塞狀态後該怎麼處理,一般認為丢包的情況下就進入擁塞狀态了,丢包有兩種判斷方式:
- 逾時重傳
- 高速重發控制,也就是收到三個重複确認 ACK
進入擁塞狀态後,比較早期的 TCP 處理流程如下:
(1)由于發生丢包,将慢啟動門檻值 ssthresh 設定為目前 cwnd 的一半,即 ssthresh = cwnd / 2。
(2)然後 cwnd 重置為 1。
(3)由擁塞狀态再次進入慢啟動過程
由于一丢包就要一切重來,導緻 cwnd 又重置為 1,十分不利于網絡資料的穩定傳遞,後面算法又進行了優化:
- cwnd 大小縮小為目前的一半
- ssthresh 設定為縮小後的 cwnd 大小
- 然後進入下一個算法階段:快速恢複算法
快速恢複算法:
- cwnd = cwnd + 3 * MSS,加 3 * MSS 的原因是因為收到 3 個重複的 ACK
- 重傳高速重發控制指定的資料包
- 如果再收到需要高速重發的包,那麼 cwnd 大小增加一
- 如果收到新的 ACK,表明重傳的包成功了,那麼退出快速恢複算法。将 cwnd 設定為 ssthresh,然後進入擁塞避免算法
TCP BBR 擁塞算法:
TCP BBR 算法是 google 公司研究出來的擁塞控制算法,相對于傳統的擁塞控制算法,TCP BBR 算法緻力于解決兩個問題:
- 在有一定丢包率的網絡鍊路上充分利用帶寬。
- 降低網絡鍊路上的 buffer 占用率,進而降低延遲。
TCP BBR 不再使用丢包作為擁塞的信号,也不使用 “加性增,乘性減” 來維護發送視窗大小,而是分别估計極大帶寬和極小延遲,把它們的乘積作為發送視窗大小。它企圖找到一個平衡點,就是通過不斷地加快發送速度,将管道填滿,但是不要填滿中間裝置的緩存,因為這樣時延會增加,在這個平衡點可以很好的達到高帶寬和低延遲時間的平衡。
TCP 中的定時器
TCP 一共有四種不同的定時器:
重傳定時器:
TCP 發送封包段時,建立該特定封包段的重傳計時器,可能發生兩種情況:
- 若在計時器截止時間到之前收到了對此特定封包段的确認 ACK,則撤銷此計時器
- 若在收到了對此特定封包段的确認之前計時器截止期到,則重傳此封包段,并将計時器複位
堅持定時器:
- 堅持定時器專門用于對付滑動視窗大小為零視窗通知而設立。
- 當發送端收到零視窗的确認時,就啟動堅持計時器,當堅持計時器截止期到時,發送端 TCP 就發送一個特殊的封包段,叫探測封包段,這個封包段隻有一個位元組的資料。
- 探測封包段有序号,但序号永遠不需要确認,甚至在計算對其他部分資料的确認時這個序号也被忽略。探測封包段提醒接收端 TCP,确認已丢失,必須重傳。
保活定時器:
- 假定客戶打開了到伺服器的連接配接,傳送了一些資料,然後就保持靜默了,也許這個客戶出故障了。在這種情況下,這個連接配接将永遠地處于打開狀态。
- 為了防止出現這種狀态,TCP 使用保活計時器來檢查連接配接狀态。
- 要解決這種問題,在大多數的實作中都是使伺服器設定保活計時器,每當伺服器收到客戶的資訊,就将計時器複位,逾時通常預設設定為 2 小時。
- 若伺服器過了 2 小時還沒有收到客戶的資訊,它就發送探測封包段,若發送了 9 個探測封包段,每一個相隔 75 秒,還沒有響應就假定客戶出了故障,因而就終止該連接配接。
- keepalive 相關參數配置:
- tcp_keepalive_time:預設兩小時,在沒有資料傳輸的情況下,逾時多久開始發送探測封包
- tcp_keepalive_probes:探活次數,預設為 9 次
- tcp_keepalive_intal:重試的時間間隔
2MSL 定時器:
- 當 TCP 關閉連接配接時,并不認為這個連接配接就真正關閉了,在時間等待期間,連接配接還處于一種中間過渡狀态。
- 2MSL 定時器的設定主要是為了確定發送的最後一個 ACK 封包段能夠到達對方,最後一次揮手資料很有可能丢失,維持這個狀态主要為了可以繼續重發 ACK。
- 2MSL 定時器階段,該 TCP 連接配接處于 TIME—WAIT 狀态,通常為2MSL(封包段壽命的兩倍)。
關于 Socket
- 一個 TCP 連接配接對應一個 Socket
- 一個 Socket 的唯一辨別是: {SRC-IP, SRC-PORT, DEST-IP, DEST-PORT, PROTOCOL}
- 監聽的 Socket 和真正用來傳資料的 Socket 是兩個,一個叫作監聽 Socket,一個叫作已連接配接 Socket
- 一個處于監聽狀态的 TCP 服務可以同時接受來自多個用戶端的 Socket,TCP Socket 就是一個檔案流,Socket 在 Linux 中就是以檔案的形式存在的
- 不同程序可以監聽同一個端口,如果他們的協定(TCP/UDP)不同
- 一個程序可以打開和關閉多個 Socket
- 子程序可以繼承所有的檔案描述符(FD)從父程序上,是以不同的程序或者線程之間如果有父子關系,可以使用同一個Socket
- 一個處于監聽狀态的 TCP 服務隻需要一個監聽端口,但可以建立多個 Socket
- 伺服器一個端口可以建立的 socket 連接配接數理論上是沒有上限的,取決于系統的記憶體大小和可以建立的檔案描述符的上限,可以通過修改檔案描述符上限進行設定
參考文獻:
- TCP 連接配接狀态及相關指令學習
- 一篇文章帶你熟悉 TCP/IP 協定(網絡協定篇二)
- TCP 擁塞控制算法
- Linux Kernel 4.9 中的 BBR 算法與之前的 TCP 擁塞控制相比有什麼優勢?
- TCP Nagle算法&&延遲确認機制
- TCP标志位
- 詳解TCP四種定時器和四個定時器