TCP(Transmission Control Protocol,傳輸控制協議) 是計算機網絡的的重要組成部分,也是網絡編程的重要內容,還有咱們平時接觸最多的 HTTP 也是基於 TCP 實現的。TCP 能夠說是最重要的傳輸層協議,既然如此,做爲開發人員,就有必要把 TCP 的核心概念和原理搞清楚。除此以外,諸如三次握手、四次揮手、滑動窗口和擁塞控制這些概念更是高頻面試題,這就更有理由深刻學習一下 TCP 了,本文就爲你們詳細梳理一下 TCP 的核心概念和原理。注:因爲本文圖片較多,標註有 by HYN 的圖片爲做者自制,其它來自於網絡或參考資料,侵刪面試
第一部分先爲你們介紹一下 TCP 的主要概念,並講解一下 TCP 的三個重要特性——1. 面向鏈接;2. 基於字節流;3. 可靠性。算法
關於網絡分層的概念實在是老生常談了,下圖就是兩種經典的分層模型,能夠看到 TCP 在網絡分層中的位置。編程
網絡分層模型
本文重點對 TCP 進行介紹,從圖中能夠看到 TCP 位於傳輸層,並且構建於網絡層的 IP 協議之上,對於 TCP 最多見的介紹就是 「TCP 是一種面向鏈接的、可靠的、基於字節流的傳輸層通訊協議」,那這三個形容詞到底是什麼意思呢?緩存
面向鏈接意味着兩個使用 TCP 的應用 (一般是一個客戶端和一個服務器) 在彼此交換數據以前必須先創建一個 TCP 鏈接。這一過程與打電話很類似,先撥號響鈴,等對方應答後再說明是誰。詳細的三次握手、四次揮手過程將在第二部分——鏈接管理部分進行介紹。服務器
TCP 鏈接雙方的數據交換格式是以字節 (byte,1byte = 8 bit)構成的有序但無結構的字節流。TCP 不在字節流中插入記錄標識符,這被稱爲字節流服務(byte stream service)。若是一方的應用程序先傳 10 字節,又傳 20 字節,再傳 50 字節 ,鏈接的另外一方將沒法瞭解發方每次發送了多少字節。收方能夠分 4 次接收這 80 個字節,每次接收 20 字節。一端將字節流放到 TCP 鏈接上,一樣的字節流將出如今 TCP 鏈接的另外一端。另外,TCP 對字節流的內容不做任何解釋,TCP 沒法知道傳輸的數據字節流是二進制數據,仍是 ASCI I 字符。cookie
若是以爲上面這段話比較抽象的話,能夠拿 TCP 的字節流和 UDP 的報文 (message) 進行比較(UDP:User Datagram Protocol,用戶數據報協議,和 TCP 同爲傳輸層的協議,後面會提供二者的全面對比)。TCP 的字節流相似於自來水,鏈接雙方都有緩衝區,能夠類比成蓄水池,發送方的發送頻率和每次的發送量沒有固定要求,接收方也能夠自由決定本身的接收頻率和每次的接收量,只要把全部的數據接收完畢便可。而 UDP 的數據報則相似於瓶裝水 (好比農夫山泉),發送方發送一瓶,接收方就要相應地接收一瓶。網絡
下圖描述了 TCP 鏈接中數據的傳輸過程以及 TCP 在整個過程當中所扮演的角色。併發
TCP 在網絡數據傳輸中的位置和角色
按照圖中的流程,好比咱們在瀏覽B站,在 TCP 鏈接創建以後,客戶端的應用層協議能夠向 TCP 發送無特殊格式的字節流,TCP 會將這些字節打包成報文段(segment),報文段大小視狀況而定,這些報文段會被網絡層的 IP 封裝成 IP 數據報(IP Datagram),而後通過網絡傳輸給服務器,而接下來服務器的操做至關於客戶端的逆操做,先從 IP 數據報中拆分出 TCP 報文段,再把 TCP 報文段還原成字節流併發送給上層的應用層協議。服務器向客戶端發送數據的流程也是同樣的,發送方和接收方的角色互換便可。ide
上面屢次提到了報文段的概念,其結構很是重要,後面的鏈接過程和擁塞控制等內容也要用到相關概念,先在這裏介紹一下。函數
TCP 報文段結構
圖的上半部分顯示 TCP 報文段被封裝在 IP 數據報中,圖的下半部分則顯示了 TCP 報文段和 TCP 首部的結構,TCP 首部的固定數據有20字節,加上選項部分最大可達60字節,而有效數據部分則是被打包的應用層數據。下面介紹一下 TCP 首部的結構:
控制位 (Control Bits):在三次握手和四次揮手中會常常看到 SYN、ACK 和 FIN 的身影,一共有 6 個標誌位,它們表示的意義以下:
咱們都知道 TCP 是具備可靠性的通訊協議,它主要經過如下方式確保可靠性,這裏先了解一下可靠性的原理,其中細節部分後文會講:
上面爲你們介紹了 TCP 最重要的三個特色,在本文第一部分的最後,再來看看 TCP 和 UDP 的對比吧。
UDP | TCP | |
---|---|---|
是否鏈接 | 無鏈接 | 面向鏈接 |
是否可靠 | 不可靠,沒有確認機制、流量控制和擁塞控制 | 可靠,有確認機制、流量控制和擁塞控制 |
鏈接對象個數 | 支持一對一,一對多,多對一和多對多交互通訊 | 只支持一對一通訊 |
傳輸方式 | 面向報文 | 面向字節流 |
首部開銷 | 首部開銷小,固定8字節 | 首部開銷較大,最小20字節,最大60字節 |
適用場景 | 適用於實時應用(IP電話、視頻會議、直播等) | 適用於要求可靠傳輸的應用,如文件傳輸等 |
這個問題簡直太經典了,若是你在面試中只被問到了一個關於 TCP 的問題,那大機率就是關於三次握手的問題。TCP 的重要特性之一就是面向鏈接,鏈接雙方在發送數據以前必須經歷握手的階段,那具體的過程是怎樣的呢?先來看圖,你們最好能夠動手簡單畫畫這個圖,固然還有後文四次揮手的圖,幫助加深記憶。
三次握手過程
如圖所示,雙方之間的三個藍色箭頭就表示了三次握手過程當中所發生的數據交換:
常見面試題 1: TCP 創建鏈接爲何要三次握手而不是兩次?
答:網上大多數資料對這個問題的回答只有簡單的一句:防止已過時的鏈接請求報文忽然又傳送到服務器,於是產生錯誤,這既不夠全面也不夠具體。下面給出比較詳細而全面的回答:
在雙方兩次握手便可創建鏈接的狀況下,假設客戶端發送 A 報文段請求創建鏈接,因爲網絡緣由形成 A 暫時沒法到達服務器,服務器接收不到請求報文段就不會返回確認報文段,客戶端在長時間得不到應答的狀況下從新發送請求報文段 B,此次 B 順利到達服務器,服務器隨即返回確認報文並進入 ESTABLISHED 狀態,客戶端在收到 確認報文後也進入 ESTABLISHED 狀態,雙方創建鏈接並傳輸數據,以後正常斷開鏈接。此時姍姍來遲的 A 報文段纔到達服務器,服務器隨即返回確認報文並進入 ESTABLISHED 狀態,可是已經進入 CLOSED 狀態的客戶端沒法再接受確認報文段,更沒法進入 ESTABLISHED 狀態,這將致使服務器長時間單方面等待,形成資源浪費。
第一次握手:客戶端只是發送處請求報文段,什麼都沒法確認,而服務器能夠確認本身的接收能力和對方的發送能力正常;
第二次握手:客戶端能夠確認本身發送能力和接收能力正常,對方發送能力和接收能力正常;
第三次握手:服務器能夠確認本身發送能力和接收能力正常,對方發送能力和接收能力正常;
可見三次握手才能讓雙方都確認本身和對方的發送和接收能力所有正常,這樣就能夠愉快地進行通訊了。
TCP 實現了可靠的數據傳輸,緣由之一就是 TCP 報文段中維護了序號字段和確認序號字段,也就是圖中的 seq 和 ack,經過這兩個字段雙方均可以知道在本身發出的數據中,哪些是已經被對方確認接收的。這兩個字段的值會在初始序號值得基礎遞增,若是是兩次握手,只有發起方的初始序號能夠獲得確認,而另外一方的初始序號則得不到確認。
常見面試題2: TCP 創建鏈接爲何要三次握手而不是四次?
答:相比上個問題而言,這個問題就簡單多了。由於三次握手已經能夠確認雙方的發送接收能力正常,雙方都知道彼此已經準備好,並且也能夠完成對雙方初始序號值得確認,也就無需再第四次握手了。
常見面試題3: 有一種網絡攻擊是利用了 TCP 創建鏈接機制的漏洞,你瞭解嗎?這個問題怎麼解決?
答:在三次握手過程當中,服務器在收到了客戶端的 SYN 報文段後,會分配並初始化鏈接變量和緩存,並向客戶端發送 SYN + ACK 報文段,這至關因而打開了一個「半開鏈接 (half-open connection)」,會消耗服務器資源。若是客戶端正常返回了 ACK 報文段,那麼雙方能夠正常創建鏈接,不然,服務器在等待一分鐘後會終止這個「半開鏈接」並回收資源。這樣的機制爲 SYN洪泛攻擊 (SYN flood attack)提供了機會,這是一種經典的 DoS攻擊 (Denial of Service,拒絕服務攻擊),所謂的拒絕服務攻擊就是經過進行攻擊,使受害主機或網絡不能提供良好的服務,從而間接達到攻擊的目的。在 SYN 洪泛攻擊中,攻擊者發送大量的 SYN 報文段到服務器請求創建鏈接,可是卻不進行第三次握手,這會致使服務器打開大量的半開鏈接,消耗大量的資源,最終沒法進行正常的服務。
解決方法:SYN Cookies,如今大多數主流操做系統都有這種防護系統。SYN Cookies 是對 TCP 服務器端的三次握手作一些修改,專門用來防範 SYN 洪泛攻擊的一種手段。它的原理是,在服務器接收到 SYN 報文段並返回 SYN + ACK 報文段時,再也不打開一個半開鏈接,也不分配資源,而是根據這個 SYN 報文段的重要信息 (包括源和目的 IP 地址,端口號可一個祕密數),利用特定散列函數計算出一個 cookie 值。這個 cookie 做爲將要返回的SYN + ACK 報文段的初始序列號(ISN)。當客戶端返回一個 ACK 報文段時,服務器根據首部字段信息計算 cookie,與返回的確認序號(初始序列號 + 1)進行對比,若是相同,則是一個正常鏈接,而後分配資源並創建鏈接,不然拒絕創建鏈接。
這是 TCP 創建鏈接的特殊狀況,有時會出現兩臺機器同時執行主動打開的狀況,不過幾率很是小,這種狀況你們僅做了解便可。在這種狀況下就無所謂發送方和接收方了,雙放均可以稱爲客戶端和服務器,同時打開的過程以下:
同時打開的過程
如圖所示,雙方在同一時刻發送 SYN 報文段,並進入 SYN-SENT 狀態,在收到 SYN 後,狀態變爲 SYN-RECEIVED,同時它們都再發送一個 SYN + ACK 的報文段,狀態都變爲 ESTABLISHED,鏈接成功創建。在此過程當中雙方一共交換了4個報文段,比三次握手多一個。
創建一個鏈接須要三次握手,而終止一個鏈接要通過 4次握手。這由 TCP 的半關閉( half-close) 形成的。既然一個 TCP 鏈接是全雙工 (即數據在兩個方向上能同時傳遞), 所以每一個方向必須單獨地進行關閉。這原則就是當一方完成它的數據發送任務後就能發送一個 FIN 來終止這個方向鏈接。當一端收到一個 FIN,它必須通知應用層另外一端已經終止了數據傳送。理論上客戶端和服務器均可以發起主動關閉,可是更多的狀況下是客戶端主動發起。
四次揮手過程
四次揮手詳細過程以下:
常見面試題1: 爲何 TCP 關閉鏈接爲何要四次而不是三次?
答:服務器在收到客戶端的 FIN 報文段後,可能還有一些數據要傳輸,因此不能立刻關閉鏈接,可是會作出應答,返回 ACK 報文段,接下來可能會繼續發送數據,在數據發送完後,服務器會向客戶單發送 FIN 報文,表示數據已經發送完畢,請求關閉鏈接,而後客戶端再作出應答,所以一共須要四次揮手。
常見面試題2: 客戶端爲何須要在 TIME-WAIT 狀態等待 2MSL 時間才能進入 CLOSED 狀態?
答:按照常理,在網絡正常的狀況下,四個報文段發送完後,雙方就能夠關閉鏈接進入 CLOSED 狀態了,可是網絡並不老是可靠的,若是客戶端發送的 ACK 報文段丟失,服務器在接收不到 ACK 的狀況下會一直重發 FIN 報文段,這顯然不是咱們想要的。所以客戶端爲了確保服務器收到了 ACK,會設置一個定時器,並在 TIME-WAIT 狀態等待 2MSL 的時間,若是在此期間又收到了來自服務器的 FIN 報文段,那麼客戶端會從新設置計時器並再次等待 2MSL 的時間,若是在這段時間內沒有收到來自服務器的 FIN 報文,那就說明服務器已經成功收到了 ACK 報文,此時客戶端就能夠進入 CLOSED 狀態了。
以前在介紹 TCP 創建鏈接的時候會有一種特殊狀況,那就是同時打開,與之對應地, TCP 關閉時也會有一種特殊狀況,那就是同時關閉,這種狀況僅做了解便可,流程圖以下:
同時關閉過程
這種狀況下,雙方應用層同時發出關閉命令,這將致使雙方各發送一個 FIN,兩端均從 ESTABLISHED 變爲 FIN_WAIT_1,兩個 FIN 通過網絡傳送後分別到達另外一端。收到 FIN 後,狀態由 FIN_WAIT_1 變遷到 CLOSING,併發送最後的 ACK,當收到最後的 ACK 時,爲確保對方也收到 ACK,狀態變化爲 TIME_WAIT,並等待 2MSL 時間,若是一切正常,隨後會進入 CLOSED 狀態。
TCP 鏈接雙方的主機都爲該鏈接設置了發送緩存和接收緩存,這些緩存起到了蓄水池的做用,咱們確定不能把上層應用程序發來的數據一古腦兒發送到網絡中,而是利用發送緩存將其緩存起來,而後再按必定的速率經過網絡發送給對方,而接收緩存的做用是把對方傳來的數據先緩存起來,等到己方應用程序有空的時候再來取走數據。示意圖以下:
TCP 緩存示意圖
在此過程當中,若是接收方應用程序讀取數據的速度小於發送方的數據發送速度,將致使接收方的接收緩存溢出,形成數據丟失,這顯然不是咱們想看到的。所以 TCP 爲應用程序提供了流量控制服務 (flow-control service),以消除發送方使接收方的接收緩存溢出的可能性。簡單來講流量控制的目的就是協調發送方的數據發送速度,使其與接收方的數據處理速度相匹配,避免數據丟失,那麼如何實現流量控制呢?
顧名思義就是發送方在發送一個數據包後就中止發送,等待對方響應 ACK,而後才能繼續發送數據。這種模式的具體實現爲 Positive Acknowledgment With Retransmission (PAR),意爲帶重傳的確定確認協議,其實現方式以下圖所示:
PAR 示意圖
這種實現很簡單,發送方在發送數據包 (圖中的msg)時會設置一個計時器,而後等待接收方的 ACK,接收方在收到數據後會返回 ACK 做爲應答,發送方在收到 ACK 後會發送下一個數據包。若是因爲網絡緣由形成數據包或者 ACK 丟失時,計時器會超時,而後發送方會從新發送未被確認的數據包。能夠看到,這種模式雖然能夠確保數據傳輸的可靠性,可是有個致命的缺點,那就是效率過低?若是是你,你會怎麼對這個方案進行優化呢?
既然每次發送只一個數據包效率過低,那就多發送幾個,而後給這些數據包編上號,接收端必須對每個包進行確認,這樣設備 A 一次多發送幾個片斷,而沒必要等候 ACK,同時接收端也要告知它可以收多少,這樣發送端發起來也有個限制,固然還須要保證順序性,不要亂序,對於亂序的情況,咱們能夠容許等待必定狀況下的亂序,好比說先緩存提早到的數據,而後去等待須要的數據,若是必定時間沒來就丟掉亂序的數據,來保證順序性,這樣的話,數據傳輸效率就能夠大大提升。不過 TCP 也沒有采用這種方案,而是在此基礎上實現更加複雜的滑動窗口。
首先給你們推薦一個視頻,講得很不錯 https://www.bilibili.com/vide...
咱們能夠把發送方的發送緩存中的字節分爲如下四類,每一個編號對應一個字節:
發送緩存中的字節分類
發送窗口
發送窗口:圖中的黑色框就是發送方的發送窗口,其大小由兩個因素決定:一、接收方的提供的窗口大小 (TCP 報文段首部中的 window 字段),發送方在三次握手階段首次獲得這個值,以後的通訊過程當中接收方會根據本身的可用緩存對這個值進行動態調整;二、發送方會根據網絡狀況維護一個擁塞窗口變量 (後文介紹)。發送窗口的大小取這兩個值的最小值。對於發送方來講,發送窗口分爲兩部分,分別是已經發送的部分(已經發送了,可是沒有收到ACK)和可用窗口,接收端容許發送可是沒有發送的那部分稱爲可用窗口。
接收窗口:對於接收端也是有一個接收窗口的,相似發送端,接收端的數據有3個分類,由於接收端並不須要等待ACK因此它沒有相似的接收並確認了的分類,狀況以下
滑動窗口的滑動過程
累積確認概念:TCP 並非每個報文段都會回覆一個 ACK ,可能會對兩個報文段發送一個ACK,也可能會對多個報文段發送 1 個 ACK,這稱爲累積確認。好比說發送方有 1/2/3 3 個報文段,先發送了2,3 兩個報文段,可是接收方指望收到1報文段,這個時候 2/3 報文段就只能放在緩存中等待報文1的空洞被填上,若是報文段1一直不來,報文2/3也將被丟棄,若是報文1來了,那麼會發送一個 ACK 對第3個報文段進行確認,就表明對這三個報文段所有進行了確認。
下面舉例說明一下窗口滑動的過程:
接下來就是重複上述過程,直到 TCP 字節流的全部數據發送完畢。在這個過程當中,接收方會根據本身接收緩存的剩餘空間動態調整窗口值,對發送方進行流量控制。文字描述可能不夠直觀,你們能夠參考上文推薦的視頻。另外推薦一個動圖演示的網站 動畫地址,能夠觀看滑動窗口的動態效果,以下圖 (此演示未考慮丟包的狀況):
滑動窗口動畫效果
當數據從一個大的管道 (好比一個快速局域網)向一個較小的管道 (好比較慢的廣域網)發送的時候就會發生擁塞,還有一種狀況就是當多個輸入流到達一個路由器,而路由器的輸出流小於這些輸入流的總和時,也會發生擁塞。舉個例子就好理解了,第一種狀況就好像源源不斷的車流從八車道進入四車道,若是不進行控制,必然形成道路擁堵;第二種狀況相似於不少車輛匯入十字路口,若是進的速度大於出的速度,再不加以控制,必然也會形成擁堵。因而 TCP 提供了響應的機制來應對這種狀況,也就是 TCP 的擁塞控制。
TCP 一共使用了四種算法來實現擁塞控制:一、慢開始 (slow-start);二、擁塞避免 (congestion avoidance);三、快速重傳 (fast retransmit);四、快速恢復 (fast recovery)。
這裏先介紹一下擁塞窗口 (congestion window,簡寫爲 cwnd)的概念:擁塞窗口是由發送方根據網絡情況維護的一個變量,用於控制本身的數據發送速率。前文提到了發送方的發送窗口受兩個變量約束,一是接收方通告的窗口大小值,二就是發送方自身的擁塞窗口,實際的發送窗口大小取兩者最小值。
在早期的 TCP Tahoe 版本中,只用到了前兩種算法,如圖所示:
慢開始和擁塞避免
如圖所示,在剛開始,TCP 採用慢開始算法。慢開始不是指擁塞窗口的增加速度慢(增加速度是指數增加,很是快),而是指 TCP 開始發送設置 cwnd=1。思路就是不要一開始就發送大量的數據,先探測一下網絡的擁塞程度,也就是說由小到大 逐漸增長擁塞窗口的大小。這裏用報文段的個數的擁塞窗口大小舉例說明慢啓動算法,實時擁塞窗口大小是以字節爲單位的。爲了防止 cwnd 增加過大引發網絡擁塞,設置一個慢開始門限(slow start threshold,簡寫爲 ssthresh) ,
當cnwd < ssthresh,使用慢開始算法
當 cnwd = ssthresh,既可以使用慢開始算法,也可使用擁塞避免算法
當 cnwd > ssthresh,使用擁塞避免算法
當擁塞窗口大小達到初始 ssthresh 值時,轉而採用擁塞避免算法。擁塞避免並不是徹底可以避免擁塞,是說在擁塞避免階段將擁塞窗口控制爲按線性規律增加,使網絡比較不容易出現擁塞,思路:讓擁塞窗口 cwnd 緩慢地增大,即每通過一個往返時間 RTT 就把發送方的擁塞窗口加一。不管是在慢開始階段仍是在擁塞避免階段,只要發送方判斷網絡出現擁塞(其根據就是沒有收到確認,雖然沒有收到確承認能是其餘緣由的分組丟失,可是由於沒法斷定,因此都當作擁塞來處理),就把慢開始門限設置爲出現擁塞時的發送窗口大小的一半。而後把擁塞窗口設置爲 1,執行慢開始算法。
TCP Reno 應用了四種算法
有時候的發送方未收到某個報文段的確認也並必定就說明必定是出現了網絡擁塞,也多是其餘緣由,因此直接執行慢開始算法會影響總體效率,後來的 TCP Reno 版本解決了這一問題,那就是採用快速重傳和快速恢復算法。
快速重傳要求接收方在收到一個失序的報文段後就當即發出重複確認(爲的是使發送方及早知道有報文段沒有到達對方),而不要等到本身發送數據時捎帶確認。快重傳算法規定,發送方只要一連收到三個重複確認就應當當即重傳對方還沒有收到的報文段,而沒必要繼續等待設置的重傳計時器時間到期。因爲不須要等待設置的重傳計時器到期,能儘早重傳未被確認的報文段,能提升整個網絡的吞吐量。
當發送方連續收到三個重複確認時,就執行「乘法減少」算法,把 ssthresh 門限減半。 可是接下去並不執行慢開始算法。考慮到若是網絡出現擁塞的話就不會收到好幾個重複的確認,因此發送方如今認爲網絡可能沒有出現擁塞。因此此時不執行慢開始算法,而是將 cwnd 設置爲 ssthresh 的大小, 而後執行擁塞避免算法。
咱們知道 TCP 是以字節流的方式傳輸數據,傳輸的最小單位爲一個報文段(segment)。TCP 首部 中有個選項 (Options)的字段,常見的選項爲 MSS (Maximum Segment Size最大消息長度),它是收發雙方協商通訊時每個報文段所能承載的最大有效數據的長度。數據鏈路層每次傳輸的數據有個最大限制MTU (Maximum Transmission Unit),通常是1500比特,超過這個量要分紅多個報文段,MSS 則是這個最大限制減去 TCP 的首部,光是要傳輸的數據的大小,通常爲1460比特。換算成字節,也就是180多字節。
MSS = MTU - Header
TCP 爲提升性能,發送端會將須要發送的數據發送到發送緩存,等待緩存滿了以後,再將緩存中的數據發送到接收方。同理,接收方也有接收緩存這樣的機制,來接收數據。
上面這些是發生 TCP 粘包和拆包的前提,下面是具體的緣由:
本文爲你們梳理了 TCP 的核心概念和原理,也分享了一些高頻面試題,但願對你有幫助。儘管文章字數超過了一萬字,可是 TCP 的不少細節還遠遠沒有介紹到,若是想進一步瞭解,能夠參考一下下面參考資料中提到的幾本書。
參考資料: