1. 程式人生 > 其它 >計算機網路-TCP篇

計算機網路-TCP篇

TCP篇

之前的總結文章:TCP簡單版本介紹-三次握手等

基本認識

TCP 是⾯向連線的(⼀定是「⼀對⼀」才能連線)、可靠的、基於位元組流的傳輸層通訊協議。

RFC 793 是如何定義「連線」的:⽤於保證可靠性和流量控制維護的某些狀態資訊,這些資訊的組合,包括Socket、序列號和窗⼝⼤⼩稱為連線。

建⽴⼀個 TCP 連線是需要客戶端與伺服器端達成上述三個資訊的共識。

Socket:由 IP 地址和端⼝號組成

序列號:⽤來解決亂序問題等

窗⼝⼤⼩:⽤來做流量控制

TCP 四元組可以唯⼀的確定⼀個連線,四元組包括如下:

格式

序列號:在建⽴連線時由計算機⽣成的隨機數作為其初始值,通過 SYN 包傳給接收端主機,每傳送⼀次資料,就「累加」⼀次該「資料位元組數」的⼤⼩。⽤來解決⽹絡包亂序問題。

確認應答號:指下⼀次「期望」收到的資料的序列號,傳送端收到這個確認應答以後可以認為在這個序號以前的資料都已經被正常接收。⽤來解決不丟包的問題。

控制位

ACK:該位為 1 時,「確認應答」的欄位變為有效,TCP 規定除了最初建⽴連線時的 SYN 包之外該位必須設定為 1 。

RST:該位為 1 時,表示 TCP 連線中出現異常必須強制斷開連線。

SYN:該位為 1 時,表示希望建⽴連線,並在其「序列號」的欄位進⾏序列號初始值的設定。

FIN:該位為 1 時,表示今後不會再有資料傳送,希望斷開連線。當通訊結束希望斷開連線時,通訊雙⽅的
主機之間就可以相互交換 FIN 位為 1 的 TCP 段。

TCP 和 UDP 區別

  1. 連線
    TCP 是⾯向連線的傳輸層協議,傳輸資料前先要建⽴連線。

UDP 是不需要連線,即刻傳輸資料。

  1. 服務物件

TCP 是⼀對⼀的兩點服務,即⼀條連線只有兩個端點。

UDP ⽀持⼀對⼀、⼀對多、多對多的互動通訊

  1. 可靠性

TCP 是可靠交付資料的,資料可以⽆差錯、不丟失、不重複、按需到達。

UDP 是盡最⼤努⼒交付,不保證可靠交付資料。

  1. 擁塞控制、流量控制

TCP 有擁塞控制和流量控制機制,保證資料傳輸的安全性。

UDP 則沒有,即使⽹絡⾮常擁堵了,也不會影響 UDP 的傳送速率。

  1. ⾸部開銷

TCP ⾸部⻓度較⻓,會有⼀定的開銷,⾸部在沒有使⽤「選項」欄位時是 20 個位元組,如果使⽤了「選項」欄位則會變⻓的。

UDP ⾸部只有 8 個位元組,並且是固定不變的,開銷較⼩。

  1. 傳輸⽅式

TCP 是流式傳輸,沒有邊界,但保證順序和可靠。

UDP 是⼀個包⼀個包的傳送,是有邊界的,但可能會丟包和亂序。

  1. 分⽚不同

TCP 的資料⼤⼩如果⼤於 MSS ⼤⼩,則會在傳輸層進⾏分⽚,⽬標主機收到後,

也同樣在傳輸層組裝 TCP資料包,如果中途丟失了⼀個分⽚,只需要傳輸丟失的這個分⽚。

UDP 的資料⼤⼩如果⼤於 MTU ⼤⼩,則會在 IP 層進⾏分⽚,⽬標主機收到後,在 IP 層組裝完資料,

接著再傳給傳輸層,但是如果中途丟了⼀個分⽚,在實現可靠傳輸的 UDP 時則就需要重傳所有的資料包,這樣傳輸效率⾮常差,所以通常 UDP 的報⽂應該⼩於 MTU。

TCP 和 UDP 應⽤場景

由於 TCP 是⾯向連線,能保證資料的可靠性交付,因此經常⽤於:

  • FTP ⽂件傳輸
  • HTTP / HTTPS

由於 UDP ⾯向⽆連線,它可以隨時傳送資料,再加上UDP本身的處理既簡單⼜⾼效,因此經常⽤於:

  • 包總量較少的通訊,如 DNS 、 SNMP 等
  • 視訊、⾳頻等多媒體通訊
  • ⼴播通訊

TCP 連線建⽴

TCP 是⾯向連線的協議,所以使⽤ TCP 前必須先建⽴連線,⽽建⽴連線是通過三次握⼿來進⾏的。

TCP 的連線狀態檢視,在 Linux 可以通過 netstat -napt 命令檢視。

為什麼是三次握⼿?不是兩次、四次?

Socket、序列號和窗⼝⼤⼩稱為連線。

所以,重要的是為什麼三次握⼿才可以初始化Socket、序列號和窗⼝⼤⼩並建⽴ TCP 連線。

接下來以三個⽅⾯分析三次握⼿的原因:

  • 三次握⼿才可以阻⽌重複歷史連線的初始化(主要原因)
  • 三次握⼿才可以同步雙⽅的初始序列號
  • 三次握⼿才可以避免資源浪費

阻⽌重複歷史連線的初始化

同步雙⽅初始序列號

四次握⼿其實也能夠可靠的同步雙⽅的初始化序號,但由於第⼆步和第三步可以優化成⼀步,

所以就成了「三次握⼿」。

避免資源浪費

兩次握⼿會造成訊息滯留情況下,伺服器重複接受⽆⽤的連線請求 SYN 報⽂,⽽造成重複分配資源。

一些問題

為什麼客戶端和服務端的初始序列號 ISN 是不相同的?

如果⼀個已經失效的連線被重⽤了,但是該舊連線的歷史報⽂還殘留在⽹絡中,

如果序列號相同,那麼就⽆法分辨出該報⽂是不是歷史報⽂,

如果歷史報⽂被新的連線接收了,則會產⽣資料錯亂。

每次建⽴連線前重新初始化⼀個序列號主要是為了通訊雙⽅能夠根據序號將不屬於本連線的報⽂段丟棄。

另⼀⽅⾯是為了安全性,防⽌⿊客偽造的相同序列號的 TCP 報⽂被對⽅接收。

初始序列號 ISN 是如何隨機產⽣的?

起始 ISN 是基於時鐘的,每 4 毫秒 + 1,轉⼀圈要 4.55 個⼩時。

RFC1948 中提出了⼀個較好的初始化序列號 ISN 隨機⽣成演算法。

ISN = M + F (localhost, localport, remotehost, remoteport)

既然 IP 層會分⽚,為什麼 TCP 層還需要 MSS 呢?

MTU :⼀個⽹絡包的最⼤⻓度,以太⽹中⼀般為 1500 位元組;

MSS :除去 IP 和 TCP 頭部之後,⼀個⽹絡包所能容納的 TCP 資料的最⼤⻓度;

當 IP 層有⼀個超過 MTU ⼤⼩的資料(TCP 頭部 + TCP 資料)要傳送,那麼 IP 層就要進⾏分⽚,

把資料分⽚成若⼲⽚,保證每⼀個分⽚都⼩於 MTU。把⼀份 IP 資料報進⾏分⽚以後,

由⽬標主機的 IP 層來進⾏重新組裝後,再交給上⼀層 TCP 傳輸層。

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

因為 IP 層本身沒有超時重傳機制,它由傳輸層的 TCP 來負責超時和重傳。
當接收⽅發現 TCP 報⽂(頭部 + 資料)的某⼀⽚丟失後,則不會響應 ACK 給對⽅,

那麼傳送⽅的 TCP 在超時後,就會重發「整個 TCP 報⽂(頭部 + 資料)」。

因此,可以得知由 IP 層進⾏分⽚傳輸,是⾮常沒有效率的。

所以,為了達到最佳的傳輸效能 TCP 協議在建⽴連線的時候通常要協商雙⽅的 MSS 值,

當 TCP 層發現數據超過MSS 時,則就先會進⾏分⽚,

當然由它形成的 IP 包的⻓度也就不會⼤於 MTU ,⾃然也就不⽤ IP 分⽚了。

經過 TCP 層分⽚後,如果⼀個 TCP 分⽚丟失後,進⾏重發時也是以 MSS 為單位,

⽽不⽤重傳所有的分⽚,⼤⼤增加了重傳的效率。

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

SYN 攻擊

我們都知道 TCP 連線建⽴是需要三次握⼿,假設攻擊者短時間偽造不同 IP 地址的 SYN 報⽂,

服務端每接收到⼀個 SYN 報⽂,就進⼊ SYN_RCVD 狀態,但服務端傳送出去的 ACK + SYN 報⽂,

⽆法得到未知 IP 主機的ACK 應答,

久⽽久之就會佔滿服務端的 SYN 接收佇列(未連線佇列),使得伺服器不能為正常⽤戶服務。

避免 SYN 攻擊⽅式⼀

其中⼀種解決⽅式是通過修改 Linux 核心引數,控制佇列⼤⼩和當佇列滿時應做什麼處理。

當⽹卡接收資料包的速度⼤於核心處理的速度時,會有⼀個佇列儲存這些資料包。

控制該佇列的最⼤值如下引數:

  • SYN_RCVD 狀態連線的最⼤個數:

    net.core.netdev_max_backlog

    net.ipv4.tcp_max_syn_backlog

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

避免 SYN 攻擊⽅式⼆

正常流程:

收到攻擊之後:

TCP 連線斷開

你可以看到,每個⽅向都需要⼀個 FIN 和⼀個 ACK,因此通常被稱為四次揮⼿。

這⾥⼀點需要注意是:主動關閉連線的,才有 TIME_WAIT 狀態。

為什麼揮⼿需要四次?

再來回顧下四次揮⼿雙⽅發 FIN 包的過程,就能理解為什麼需要四次了。

  • 關閉連線時,客戶端向服務端傳送 FIN 時,僅僅表示客戶端不再發送資料了但是還能接收資料。
  • 伺服器收到客戶端的 FIN 報⽂時,先回⼀個 ACK 應答報⽂,⽽服務端可能還有資料需要處理和傳送,等服務端不再發送資料時,才傳送 FIN 報⽂給客戶端來表示同意現在關閉連線。

從上⾯過程可知,服務端通常需要等待完成資料的傳送和處理,

所以服務端的 ACK 和 FIN ⼀般都會分開發送,從⽽⽐三次握⼿導致多了⼀次。

為什麼 TIME_WAIT 等待的時間是 2MSL?

MSL 是 Maximum Segment Lifetime,報⽂最⼤⽣存時間,它是任何報⽂在⽹絡上存在的最⻓時間,

超過這個時間報⽂將被丟棄。

因為 TCP 報⽂基於是 IP 協議的,⽽ IP 頭中有⼀個 TTL 欄位,是 IP 資料報可以經過的最⼤路由數,每經過⼀個處理他的路由器此值就減 1,當此值為 0 則資料報將被丟棄,同時傳送 ICMP 報⽂通知源主機。

MSL 與 TTL 的區別: MSL 的單位是時間,⽽ TTL 是經過路由跳數。

所以 MSL 應該要⼤於等於 TTL 消耗為 0 的時間,以確保報⽂已被⾃然消亡。

TIME_WAIT 等待 2 倍的 MSL

⽐較合理的解釋是: ⽹絡中可能存在來⾃傳送⽅的資料包,當這些傳送⽅的資料包被接收⽅處理後⼜會向對⽅傳送響應,所以⼀來⼀回需要等待 2 倍的時間。

2MSL 的時間是從客戶端接收到 FIN 後傳送 ACK 開始計時的。

如果在 TIME-WAIT 時間內,因為客戶端的 ACK沒有傳輸到服務端,客戶端⼜接收到了服務端重發的 FIN 報⽂,那麼 2MSL 時間將重新計時。

在 Linux 系統⾥ 2MSL 預設是 60 秒,那麼⼀個 MSL 也就是 30 秒。Linux 系統停留在 TIME_WAIT 的時
間為固定的 60 秒。

為什麼需要 TIME_WAIT 狀態?

主動發起關閉連線的⼀⽅,才會有 TIME-WAIT 狀態。

需要 TIME-WAIT 狀態,主要是兩個原因:

  • 防⽌具有相同「四元組」的「舊」資料包被收到;
  • 保證「被動關閉連線」的⼀⽅能被正確的關閉,即保證最後的 ACK 能讓被動關閉⽅接收,從⽽幫助其正常關閉;

原因⼀:防⽌舊連線的資料包

TCP 就設計出了這麼⼀個機制,經過 2MSL 這個時間,⾜以讓兩個⽅向上的資料包都被丟棄,

使得原來連線的資料包在⽹絡中都⾃然消失,再出現的資料包⼀定都是新建⽴連線所產⽣的。

原因⼆:保證連線正確關閉

TIME-WAIT 作⽤是等待⾜夠的時間以確保最後的 ACK 能讓被動關閉⽅接收,從⽽幫助其正常關閉。

TIME_WAIT 過多有什麼危害

過多的 TIME-WAIT 狀態主要的危害有兩種:

  • 第⼀是記憶體資源佔⽤;
  • 第⼆是對端⼝資源的佔⽤,⼀個 TCP 連線⾄少消耗⼀個本地端⼝;

第⼆個危害是會造成嚴重的後果的,要知道,端⼝資源也是有限的,⼀般可以開啟的端⼝為 32768~61000 ,也可以通過如下引數設定指定

如果發起連線⼀⽅的 TIME_WAIT 狀態過多,佔滿了所有端⼝資源,則會導致⽆法建立新連線。

如何優化 TIME_WAIT?

更新中…………

Socket 程式設計

所以,監聽的 socket 和真正⽤來傳送資料的 socket,是「兩個」 socket,⼀個叫作監聽 socket,⼀個叫作已完成連線 socket。

成功連線建⽴之後,雙⽅開始通過 read 和 write 函式來讀寫資料,就像往⼀個⽂件流⾥⾯寫東⻄⼀樣。

listen 時候引數 backlog 的意義?

Linux核心中會維護兩個佇列:

  • 未完成連線佇列(SYN 佇列):接收到⼀個 SYN 建⽴連線請求,處於 SYN_RCVD 狀態;
  • 已完成連線佇列(Accpet 佇列):已完成 TCP 三次握⼿過程,處於 ESTABLISHED 狀態;

accept 發⽣在三次握⼿的哪⼀步?

客戶端連線服務端時,傳送了什麼?

客戶端 connect 成功返回是在第⼆次握⼿,服務端 accept 成功返回是在三次握⼿成功之後。

正所謂知道的越多,不知道的也越多。

參考:圖解網路

我這裡只是一個自己的學習筆記,大家有興趣一定去看原文!!! 謝謝大家的閱讀!!

大家有興趣一定去看原文,這只是我自己的一個筆記總結!!
大家有興趣一定去看原文,這只是我自己的一個筆記總結!!
大家有興趣一定去看原文,這只是我自己的一個筆記總結!!