1. 程式人生 > >這一篇TCP總結請收下

這一篇TCP總結請收下

## 前言 很高興遇見你~ TCP這些東西,基本每個程式猿都或多或少是掌握的了。雖然感覺在實際開發中沒有什麼用武之處,但,面試他要問啊 ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f75b6ed0719542b6b7cc48ccb2436555~tplv-k3u1fbpfcp-zoom-1.image) 而最近大傢伙過完年,也都在準備春招,我也一樣。閱讀了一些okHttp原始碼之後,又屁顛屁顛地跑回來重新把tcp、http這些東西給重新學了一遍。okHttp基本都是這些協議的實現,而理解原始碼的基礎是,理解tcp、http。 重新看了一遍tcp之後,我把這些東西給總結了下來,也就有了這篇文章。 計算機網路的知識特點就是:瑣碎。靠背誦“面試八股文”估計沒多久就忘了。TCP是計算機網路運輸層的一個協議,所以首先要對計網分層結構以及運輸層有一定的理解。然後是TCP的四個重點:面向連線、可靠傳輸原理、流量控制和擁塞控制,最後再補充一點粘包和拆包的知識。 ## 計網分層結構 考慮最簡單的情況:兩臺主機之間的通訊。這個時候只需要一條網線把兩者連起來,規定好彼此的硬體介面,如都用USB、電壓10v、頻率2.4GHz等,**這一層就是物理層,這些規定就是物理層協議** 。 ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/36ff02218adb4875a2797947656f3d1f~tplv-k3u1fbpfcp-zoom-1.image) --- 我們當然不滿足於只有兩臺電腦連線,因此我們可以使用交換機把多個電腦連線起來,如下圖: ![image.png](https://i.loli.net/2021/02/22/lkJOApL9uqGYb3P.png) 這樣連線起來的網路,稱為區域網,也可以稱為乙太網(乙太網是區域網的一種)。在這個網路中,我們需要標識每個機器,這樣才可以指定要和哪個機器通訊。這個標識就是硬體地址MAC。硬體地址隨機器的生產就被確定,永久性唯一。在區域網中,我們需要和另外的機器通訊時,只需要知道他的硬體地址,交換機就會把我們的訊息傳送到對應的機器。 這裡我們可以不管底層的網線介面如何傳送,把物理層抽離,在他之上建立一個新的層次,這就是**資料鏈路層** 。 ---- 我們依然不滿足於區域網的規模,需要把所有的區域網聯絡起來,這個時候就需要用到路由器來連線兩個區域網: ![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8ca1bd0d723249ec8f9f7243bb63b6c8~tplv-k3u1fbpfcp-zoom-1.image) 但是如果我們還是使用硬體地址來作為通訊物件的唯一標識,那麼當網路規模越來越大,需要記住所有機器的硬體地址是不現實的;同時,一個網路物件可能會頻繁更換裝置,這個時候硬體地址表維護起來更加複雜。這裡使用了一個新的地址來標記一個網路物件:**IP地址** 。 >通過一個簡單的寄信例子來理解IP地址。 > >我住在北京市,我朋友A住在上海市,我要給朋友A寫信: > >1. 寫完信,我會在信上寫好我朋友A的地址,並放到北京市郵局(給資訊附加目標IP地址,併發送給路由器) >2. 郵局會幫我把信運輸到上海市當地郵局(資訊會經過路由傳遞到目標IP區域網的路由器) >3. 上海市當地路由器會幫我把信交給朋友A(區域網內通訊) > >因此,這裡IP地址就是一個網路接入地址(朋友A的住址),我只需要知道目標IP地址,路由器就可以把訊息給我帶到。**在區域網中,就可以動態維護一個MAC地址與IP地址的對映關係,根據目的IP地址就可以尋找到機器的MAC地址進行傳送** 。 這樣我們不需管理底層如何去選擇機器,我們只需要知道IP地址,就可以和我們的目標進行通訊。這一層就是**網路層**。網路層的核心作用就是 **提供主機之間的邏輯通訊** 。這樣,在網路中的所有主機,在邏輯上都連線起來了,上層只需要提供目標IP地址和資料,網路層就可以把訊息傳送到對應的主機。 ---- 一個主機有多個程序,程序之間進行不同的網路通訊,如邊和朋友開黑邊和女朋友聊微信。我的手機同時和兩個不同機器進行通訊。那麼當我的手機收到資料時,如何區分是微信的資料,還是王者的資料?那麼就必須在網路層之上再新增一層:**運輸層** : ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4edeaef5e49b433499ba7d82c9ae42cb~tplv-k3u1fbpfcp-zoom-1.image) 運輸層通過socket(套接字),將網路資訊進行進一步的拆分,不同的應用程序可以獨立進行網路請求,互不干擾。這就是運輸層的最本質特點:**提供程序之間的邏輯通訊** 。這裡的程序可以是主機之間,也可以是同個主機,所以在android中,socket通訊也是程序通訊的一種方式。 ---- 現在不同的機器上的應用程序之間可以獨立通訊了,那麼我們就可以在計算機網路上開發出形形式式的應用:如web網頁的http,檔案傳輸ftp等等。這一層稱為**應用層**。 應用層還可以進一步拆分出表示層、會話層,但他們的本質特點都沒有改變:**完成具體的業務需求** 。和下面的四層相比,他們並不是必須的,可以歸屬到應用層中。 --- 最後對計網分層進行小結: ![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/85749e94d2a7498387348363379fb332~tplv-k3u1fbpfcp-zoom-1.image) 1. 最底層物理層,負責兩個機器之間通過硬體的直接通訊; 2. 資料鏈路層使用硬體地址在區域網中進行定址,實現區域網通訊; 3. 網路層通過抽象IP地址實現主機之間的邏輯通訊; 4. 運輸層在網路層的基礎上,對資料進行拆分,實現應用程序的獨立網路通訊; 5. 應用層在運輸層的基礎上,根據具體的需求開發形形式式的功能。 這裡需要注意的是,分層並不是在物理上的分層,而是邏輯上的分層。通過對底層邏輯的封裝,使得上層的開發可以直接依賴底層的功能而無需理會具體的實現,簡便了開發。 這種分層的思路,也就是責任鏈設計模式,通過層層封裝,把不同的職責獨立起來,更加方便開發、維護等等。okHttp中的攔截器設計模式,也是這種責任鏈模式。 ## 運輸層 本文主要是講解TCP,這裡需要增加一些運輸層的知識。 #### 本質:提供程序通訊 ![yqs3gx.md.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1d45e0ba28eb4f6891be3ffe67bf4830~tplv-k3u1fbpfcp-zoom-1.image) 在運輸層之下的網路層,是不知道該資料包屬於哪個程序,他只負責資料包的接收與傳送。運輸層則負責接收不同程序的資料交給網路層,同時把網路層的資料拆分交給不同的程序。從上往下匯聚到網路層,稱為**多路複用**,從下往上拆分,稱為**多路拆分** 。 運輸層的表現,受網路層的限制。這很好理解,網路層是運輸層的底層支援。所以運輸層是無法決定自己頻寬、時延等的上限。但可以基於網路層開發更多的特性:如可靠傳輸。網路層只負責盡力把資料包從一端傳送到另一端,而不保證資料可以到達且完整。 #### 底層實現:socket 前面講到,最簡單的運輸層協議,就是提供程序之間的獨立通訊 ,但底層的實現,是**socket之間的獨立通訊** 。在網路層中,IP地址是一個主機邏輯地址,而在運輸層中,socket是一個程序的邏輯地址;當然,一個程序可以擁有多個socket。應用程序可以通過監聽socket,來獲取這個socket接受到的訊息。 > 舉個例子來理解socket。如下圖 > > ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3cf63872198a474db6671d4d77644319~tplv-k3u1fbpfcp-zoom-1.image) > > 每一個主機可以建立很多個socket來接收資訊。如主機A的微信程序,想要傳送給主機B的微信,那麼他只需要傳送給主機B的socketC,主機B的微信就會從socketC中取到訊息。(當然實際的流程不是這樣的,我們的訊息需要經過微信後臺伺服器,這裡只是舉例子) > > 同理,主機B的QQ,想要傳送訊息給主機A的QQ,那麼只需要把訊息傳送給socketB,主機A的QQ就可以拿到訊息了。 socket並不是一個實實在在的東西,而是運輸層抽象出來的一個物件。運輸層增加了**埠**這個概念,來區分不同的socket。埠可以理解為一個主機上有很多的網路通訊口,每個埠都有一個埠號,埠的數量由運輸層協議確定。 不同的運輸層協議對socket有不同的定義方式。在UDP協議中,使用目標IP+目標埠號來定義一個socket;在TCP中使用目標IP+目標埠號+源IP+源埠號來定義一個socket。我們只需要在運輸層報文的頭部附加上這些資訊,目標主機就會知道我們要傳送給哪個socket,對應監聽該socket的程序就可獲得資訊。 #### 運輸層協議 運輸層的協議就是大名鼎鼎的TCP和UDP。其中,UDP是最精簡的運輸層協議,只實現了程序間的通訊;而TCP在UDP的基礎上,實現了可靠傳輸、流量控制、擁塞控制、面向連線等等特性,同時也更加複雜。 當然除此之外,還有更多更優秀的運輸層協議,但目前廣為使用的,就是TCP和UDP。UDP在後面也會總結到。 ## TCP協議首部 TCP協議,表現在報文上,就是會在應用層傳輸下來的資料前附加上一個TCP首部,這個首部附加了TCP資訊,先來整體看一下這個首部的結構: ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0e8f0ed8546d43aa94691f3c0743ef4f~tplv-k3u1fbpfcp-zoom-1.image) 這張圖是來自我大學老師的課件, 非常好用,所以一直拿來學習。最下面部分表示了報文之間的關係,TCP資料部分就是應用層傳下來的資料。 TCP首部固定長度是20位元組,下面還有4位元組是可選的。內容很多,但其中有一些我們比較熟悉的:源埠,目標埠。嗯?socket不是還需要IP進行定位嗎?IP地址在網路層被附加了。其他的內容後面都會慢慢講解,作為一篇總結文章,這裡放出查閱表,方便複習: | 頭部引數 | 位元組數 | 作用 | | ---------------------- | ---------- | ------------------------------------------------------------ | | 源埠和目的埠欄位 | 各佔兩位元組 | socket是通過埠號和IP號來進行定義,這裡表示發出訊息的主機埠以及接收訊息的目標主機埠 | | 序號欄位 | 4 位元組 | TCP 連線中傳送的資料流中的每一個位元組都編上一個序號。序號欄位的值則指的是本報文段所傳送的資料的第一個位元組的序號。長度4位元組,所以序號的範圍是【0,2^32 - 1】 | | 確認號欄位 | 4位元組 | 是期望收到對方的下一個報文段的資料的第一個位元組的序號。 | | 資料偏移(即首部長度) | 4位 | 指出 TCP 報文段的**資料起始處**距離 TCP **報文段的起始處**有多遠。“資料偏移”的單位是 32 位(以 4 位元組為計算單位) | | 保留欄位 | 6位 | 保留為今後使用,但目前應置為 0 | | 緊急 URG | 1位 | 當 URG =1 時,表明緊急指標欄位有效。它告訴系統此報文段中有緊急資料,應儘快傳送(相當於高優先順序的資料) | | 確認 ACK | 1位 | 只有當 ACK=1 時確認號欄位才有效。當 ACK = 0 時,確認號無效。當收到報文需要向傳送方傳送確認報時設定該標誌位為1。 | | 推送 PSH | 1位 | 接收 TCP 收到 PSH = 1 的報文段,就儘快地交付接收應用程序,而不再等到整個快取都填滿了後再向上交付。 | | 復位 RST | 1位 | 當 RST =1 時,表明 TCP 連線中出現嚴重差錯(如由於主機崩潰或其他原因),必須釋放連線,然後再重新建立運輸連線。 | | 同步 SYN | 1位 | 同步 SYN = 1 表示這是一個連線請求或連線接受報文。 | | 終止 FIN | 1位 | 用來釋放一個連線。FIN = 1 表明此報文段的傳送端的資料已傳送完畢,並要求釋放運輸連線 | | 視窗欄位 | 2位元組 | 傳送方接收快取區剩下的**位元組** 數,注意單位是位元組。 | | 檢驗和 | 2位元組 | 檢驗和欄位檢驗的範圍包括首部和資料這兩部分。在計算檢驗和時,要在 TCP 報文段的前面加上 12 位元組的偽首部。主要是檢驗報文是否發生了錯誤,如某個‘1’變成了‘0’。 | | 緊急指標欄位 | 2位元組 | 指出在本報文段中緊急資料共有多少個位元組(緊急資料放在本報文段資料的最前面) | | 選項欄位 | 長度不定 | TCP 最初只規定了一種選項,即最大報文段長度 MSS。MSS 告訴對方 TCP:“我的快取所能接收的報文段的**資料欄位**的最大長度是 MSS 個位元組。” | | 填充欄位 | 不定 | 這是為了使整個首部長度是 4 位元組的整數倍。 | 選項欄位中包含以下其他選項: | 選項 | 作用 | | ------------ | ------------------------------------------------------------ | | 視窗擴大選項 | 佔 3 位元組,其中有一個位元組表示移位值 S。新的視窗值等於 TCP 首部中的視窗位數增大到 (16 + S),相當於把視窗值向左移動 S 位後獲得實際的視窗大小 | | 時間戳選項 | 佔 10 位元組,其中最主要的欄位時間戳值欄位(4 位元組)和時間戳回送回答欄位(4 位元組),主要是用於計算資料報在網路中傳輸的往返時間。 | | 選擇確認選項 | 接收方收到了和前面的位元組流不連續的兩個位元組塊,需要告訴傳送方目前已經接收到的資料報範圍。每一個段需要兩個邊界,一個邊界需要4位元組來表示,選項欄位最長是40位元組,所以最多可以表示4個已接收的欄位。 | 講完下面內容,再回來看這些欄位就熟悉了。 ## TCP面向位元組流特性 TCP並不是把應用層傳輸過來的資料直接加上首部然後傳送給目標,而是把資料看成一個**位元組** 流,給他們標上序號之後分部分發送。這就是TCP的 **面向位元組流** 特性: ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/387107cfb2b943f398ee54e0c525856b~tplv-k3u1fbpfcp-zoom-1.image) - TCP會以流的形式從應用層讀取資料並存放在自己的傳送快取區中,同時為這些**位元組**標上序號 - TCP會從傳送方緩衝區選擇適量的位元組組成TCP報文,通過網路層傳送給目標 - 目標會讀取位元組並存放在自己的接收方緩衝區中,並在合適的時候交付給應用層 面向位元組流的好處是無需一次儲存過大的資料佔用太多記憶體,壞處是無法知道這些位元組代表的意義,例如應用層傳送一個音訊檔案和一個文字檔案,對於TCP來說就是一串位元組流,沒有意義可言,這會導致粘包以及拆包問題,後面講。 ## 可靠傳輸原理 前面講到,TCP是可靠傳輸協議,也就是,一個數據交給他,他肯定可以完整無誤地傳送到目標地址,除非網路炸了。他實現的網路模型如下: ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/09b6212f30834a4baf8dade637c2a9dc~tplv-k3u1fbpfcp-zoom-1.image) 對於應用層來說,他就是一個可靠傳輸的底層支援服務;而運輸層底層採用了網路層的不可靠傳輸。雖然在網路層甚至資料鏈路層就可以使用協議來保證資料傳輸的可靠性,但這樣網路的設計會更加複雜、效率會隨之降低。把資料傳輸的可靠性保證放在運輸層,會更加合適。 可靠傳輸原理的重點總結一下有:**滑動視窗、超時重傳、累積確認、選擇確認、連續ARQ** 。 #### 停止等待協議 要實現可靠傳輸,最簡便的方法就是:我傳送一個數據包給你,然後你跟我回復收到,我繼續傳送下一個資料包。傳輸模型如下: ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ba9a274a1f9e4a67ad67c828d59f608a~tplv-k3u1fbpfcp-zoom-1.image) 這種“一來一去”的方法來保證傳輸可靠就是**停止等待協議**(stop-and-wait)。不知道還記不記得前面TCP首部有一個ack欄位,當他設定為1的時候,表示這個報文是一個確認收到報文。 然後再來考慮一種情況:丟包。網路環境不可靠,導致每一次傳送的資料包可能會丟失,如果機器A傳送了資料包丟失了,那麼機器B永遠接收不到資料,機器A永遠在等待。解決這個問題的方法是:**超時重傳** 。當機器A發出一個數據包時便開始計時,時間到還沒收到確認回覆,就可以認為是發生了丟包,便再次傳送,也就是重傳。 但重傳會導致另一種問題:如果原先的資料包並沒有丟失,只是在網路中待的時間比較久,這個時候機器B會受到兩個資料包,那麼機器B是如何辨別這兩個資料包是屬於同一份資料還是不同的資料?這就需要前面講過的方法:**給資料位元組進行編號**。這樣接收方就可以根據資料的位元組編號,得出這些資料是接下來的資料,還是重傳的資料。 在TCP首部有兩個欄位:序號和確認號,他們表示傳送方資料第一個位元組的編號,和接收方期待的下一份資料的第一個位元組的編號。前面講到TCP是面向位元組流,但是他並不是一個位元組一個位元組地傳送,而是一次擷取一整段。擷取的長度受多種因素影響,如快取區的資料大小、資料鏈路層限制的幀大小等。 #### 連續ARQ協議 停止等待協議已經可以滿足可靠傳輸了,但有一個致命缺點:**效率太低**。傳送方傳送一個數據包之後便進入等待,這個期間並沒有幹任何事,浪費了資源。解決的方法是:**連續傳送資料包**。模型如下: ![](https://i.bmp.ovh/imgs/2021/02/b5280361111e9d11.png) 和停止等待最大的不同就是,他會源源不斷地傳送,接收方源源不斷收到資料之後,逐一進行確認回覆。這樣便極大地提高了效率。但同樣,帶來了一些額外的問題: 傳送是否可以無限傳送直到把緩衝區所有資料傳送完?不可以。因為需要考慮接收方緩衝區以及讀取資料的能力。如果傳送太快導致接收方無法接受,那麼只是會頻繁進行重傳,浪費了網路資源。所以傳送方傳送資料的範圍,需要考慮到接收方緩衝區的情況。這就是TCP的**流量控制** 。解決方法是:**滑動視窗** 。基本模型如下: ![](https://i.bmp.ovh/imgs/2021/02/cc06bfa390900de1.png) - 傳送方需要根據接收方的緩衝區大小,設定自己的可傳送視窗大小,處於視窗內的資料表示可傳送,之外的資料不可傳送。 - 當視窗內的資料接收到確認回覆時,整個視窗會往前移動,直到傳送完成所有的資料 在TCP的首部有一個視窗大小欄位,他表示接收方的剩餘緩衝區大小,讓傳送方可以調整自己的傳送視窗大小。通過滑動視窗,就可以實現TCP的流量控制,不至於傳送太快,導致太多的資料丟失。 --- 連續ARQ帶來的第二個問題是:網路中充斥著和傳送資料包一樣資料量的確認回覆報文,因為每一個傳送資料包,必須得有一個確認回覆。提高網路效率的方法是:**累積確認** 。接收方不需要逐個進行回覆,而是累積到一定量的資料包之後,告訴傳送方,在此資料包之前的資料全都收到。例如,收到 1234,接收方只需要告訴傳送方我收到4了,那麼傳送方就知道1234都收到了。 第三個問題是:如何處理丟包情況。在停止等待協議中很簡單,直接一個超時重傳就解決了。但,連續ARQ中不太一樣。例如:接收方收到了 123 567,六個位元組,編號為4的位元組丟失了。按照累積確認的思路,只能傳送3的確認回覆,567都必須丟掉,因為傳送方會進行重傳。這就是**GBN(go-back-n)** 思路。 但是我們會發現,只需要重傳4即可,這樣不是很浪費資源,所以就有了:**選擇確認SACK** 。在TCP報文的選項欄位,可以設定已經收到的報文段,每一個報文段需要兩個邊界來進行確定。這樣傳送方,就可以根據這個選項欄位只重傳丟失的資料了。 #### 可靠傳輸小結 到這裡關於TCP的可靠傳輸原理就已經介紹的差不多。最後進行一個小結: - 通過連續ARQ協議與傳送-確認回覆模式來保證每一個數據包都到達接收方 - 通過給位元組編號的方法,來標記每一個數據是屬於重傳還是新的資料 - 通過超時重傳的方式,來解決資料包在網路中丟失的問題 - 通過滑動視窗來實現流量控制 - 通過累積確認+選擇確認的方法來提高確認回覆與重傳的效率 當然,這只是可靠傳輸的冰山一角,感興趣可以再深入去研究(和麵試官聊天已經差不多了[狗頭])。 ## 擁塞控制 擁塞控制考慮的是另外一個問題:**避免網路過分擁擠導致丟包嚴重,網路效率降低** 。 > 拿現實的交通舉例子: > > 高速公路同一時間可通行的汽車數量是一定的,當節假日時,就會發生嚴重的堵車。在TCP中,資料包超時,會進行重傳,也就是會進來更多的汽車,這時候更堵,最後導致的結果就是:丟包-重傳-丟包-重傳。最後整個網路癱瘓了。 這裡的擁塞控制和前面的流量控制不是一個東西,流量控制是擁塞控制的手段:為了避免擁塞,必須對流量進行控制。擁塞控制目的是:限制每個主機的傳送的資料量,避免網路擁塞效率下降。就像廣州等地,限制車牌號出行是一個道理。不然大家都堵在路上,誰都別想走。 擁塞控制的解決方法是流量控制,流量控制的實現是滑動視窗,所以**擁塞控制最終也是通過限制傳送方的滑動視窗大小來限制流量** 。當然,擁塞控制的手段不只是流量控制,導致擁塞的因素有:路由器快取、頻寬、處理器處理速度等等。提升硬體能力(把4車道改成8車道)是其中一個方法,但畢竟硬體提升是有瓶頸的,沒辦法不斷提升,還是需要從tcp本身來增加演算法,解決擁塞。 擁塞控制的重點有4個:**慢開始、快恢復、快重傳、擁塞避免**。這裡依舊獻祭出大學老師的ppt圖片: ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e806869b1d8f4970929b8c68bde41d26~tplv-k3u1fbpfcp-zoom-1.image) Y軸表示的是傳送方視窗大小,X軸表示的是傳送的輪次(不是位元組編號)。 - 最開始的時候,會把視窗設定一個較小的值,然後每輪變為原來的兩倍。這是慢開始。 - 當視窗值到達ssthresh值,這個值是需要通過實時網路情況設定的一個視窗限制值,開始進入擁塞避免,每輪把視窗值提升1,慢慢試探網路的底線。 - 如果發生了資料超時,表示極可能發生了擁塞,然後回到慢開始,重複上面的步驟。 - 如果收到三個相同的確認回覆,表示現在網路的情況不太好,把ssthresh的值設定為原來的一半,繼續擁塞避免。這部分稱為快恢復。 - 如果收到丟包資訊,應該儘快把丟失的包重傳一次,這是快重傳。 - 當然,視窗的最終上限是不能無限上漲的,他不能超過接收方的快取區大小。 通過這個演算法,就可以在很大程度上,避免網路擁擠。 除此之外,還可以讓路由器在快取即將滿的時候,告知傳送方我快滿了,而不是等到出現了超時再進行處理,這是**主動佇列管理AQM**。此外還有很多方法,但是上面的演算法是重點。 ## 面向連線 這一小節講的就是無人不曉的TCP三次握手與四次揮手這些,經過前面的內容,這一小節其實已經很好理解。 TCP是面向連線的,那連線是什麼?**這裡的連線並不是實實在在的連線,而是通訊雙方彼此之間的一個記錄** 。TCP是一個全雙工通訊,也就是可以互相傳送資料,所以雙方都需要記錄對方的資訊。根據前面的可靠傳輸原理,TCP通訊雙方需要為對方準備一個接收緩衝區可以接收對方的資料、記住對方的socket知道怎麼傳送資料、記住對方的緩衝區來調整自己的視窗大小等等,這些記錄,就是一個連線。 在運輸層小節中講到,運輸層雙方通訊的地址是採用socket來定義的,TCP也不例外。TCP的每一個連線只能有兩個物件,也就是兩個socket,而不能有三個。所以socket的定義需要源IP、源埠號、目標IP、目標埠號四個關鍵因素,才不會發生混亂。 > 假如TCP和UDP一樣只採用目標IP+目標埠號來定義socket,那麼就會出現多個傳送方同時傳送到同一個目標socket的情況。這個時候TCP無法區分這些資料是否來自不同的傳送方,就會導致出現錯誤。 既然是連線,就有兩個關鍵要點:建立連線、斷開連線。 #### 建立連線 建立連線的目的就是交換彼此的資訊,然後記住對方的資訊。所以雙方都需要傳送彼此的資訊給對方: ![](https://i.bmp.ovh/imgs/2021/02/3e467693f1aae75f.png) 但前面的可靠傳輸原理告訴我們,資料在網路中傳輸是不可靠的,需要對方給予我們一個確認回覆,才可以保證訊息正確到達。如下圖: ![](https://i.bmp.ovh/imgs/2021/02/995b340650cfdc67.png) 機器B的確認收到和機器B資訊可以進行合併,減少次數;而且傳送機器B給機器A本身就代表了機器B已經收到了訊息,所以最後的示例圖是: ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9279e7a0194d4798abe826d9fcb138f2~tplv-k3u1fbpfcp-zoom-1.image) 步驟如下: 1. 機器A傳送syn包向機器B請求建立TCP連線,並附加上自身的接收緩衝區資訊等,機器A進入SYN_SEND狀態,表示請求已經發送正在等待回覆; 2. 機器B收到請求之後,根據機器A的資訊記錄下來,並建立自身的接收快取區,向機器A傳送syn+ack的合成包,同時自身進入SYN_RECV狀態,表示已經準備好了,等待機器A 的回覆就可以向A傳送資料; 3. 機器A收到回覆之後記錄機器B 的資訊,傳送ack資訊,自身進入ESTABLISHED狀態,表示已經完全準備好了,可以進行傳送和接收; 4. 機器B收到ACK資料之後,進入ESTABLISHED狀態。 三次訊息的傳送,稱為三次握手。 #### 斷開連線 斷開連線和三次握手類似,直接上圖: ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6911f63ef5a2484f9a8cdc41da850c2c~tplv-k3u1fbpfcp-zoom-1.image) 1. 機器A傳送完資料之後,向機器B請求斷開連線,自身進入FIN_WAIT_1狀態,表示資料傳送完成且已經發送FIN包(FIN標誌位為1); 2. 機器B收到FIN包之後,回覆ack包表示已經收到,但此時機器B可能還有資料沒傳送完成,自身進入CLOSE_WAIT狀態,表示對方已傳送完成且請求關閉連線,自身傳送完成之後可以關閉連線; 3. 機器B資料傳送完成之後,傳送FIN包給機器B ,自身進入LAST_ACK狀態,表示等待一個ACK包即可關閉連線; 4. 機器A收到FIN包之後,知道機器B也傳送完成了,回覆一個ACK包,並進入TIME_WAIT狀態 > TIME_WAIT狀態比較特殊。當機器A收到機器B的FIN包時,理想狀態下,確實是可以直接關閉連線了;但是: > > 1. 我們知道網路是不穩定的,可能機器B 傳送了一些資料還沒到達(比FIN包慢); > 2. 同時回覆的ACK包可能丟失了,機器B會重傳FIN包; > > 如果此時機器A馬上關閉連線,會導致資料不完整、機器B無法釋放連線等問題。所以此時機器A需要等待2個報文生存最大時長,確保網路中沒有任何遺留報文了,再關閉連線 5. 最後,機器A等待兩個報文存活最大時長之後,機器B 接收到ACK報文之後,均關閉連線,進入CLASED狀態 雙方之間4次互相傳送報文來斷開連線的過程,就是**四次揮手**。 現在,對於為什麼握手是三次揮手是四次、一定要三次/四次嗎、為什麼要停留2msl再關閉連線等等這些問題,就都解決了。 ## UDP協議 運輸層協議除了TCP,還有大名鼎鼎的UDP。如果說TCP憑藉他完善穩定的功能獨樹一幟,那UDP就是精簡主義亂拳打死老師傅。 UDP只實現了運輸層最少的功能:程序間通訊。對於應用層傳下來的資料,UDP只是附加一個首部就直接交給網路層了。UDP的頭部非常簡單,只有三部分: - 源埠、目標埠:埠號用來區分主機的不同程序 - 校驗碼:用於校驗資料包在傳輸的過程中沒有出現錯誤,例如某個1變成了0 - 長度:報文的長度 所以UDP的功能也只有兩個:校驗資料報是否發生錯誤、區分不同的程序通訊。 但,TCP的功能雖然多,但同時也是要付出相對應的代價。例如面向連線的特性,在建立和斷開連線的時候會有開銷;擁塞控制的特性,會限制傳輸的上限等等。下面來羅列一下UDP的優缺點: #### UDP的缺點 - 無法保證訊息完整、正確到達,UDP是一個不可靠的傳輸協議; - 缺少擁塞控制容易互相競爭資源導致網路系統癱瘓 #### UDP的優點 - 效率更快;不需要建立連線以及擁塞控制 - 連線更多的客戶;沒有連線狀態,不需要為每個客戶建立快取等 - 分組首部位元組少,開銷小;TCP首部固定首部是20位元組,而UDP只有8位元組;更小的首部意味著更大比例的資料部分 - 在一些需要高效率允許可限度誤差的場景下可以使用。如直播場景,並不需要保證每個資料包都完整到達,允許一定的丟包率,這個時候TCP的可靠特性反而成為了累贅;精簡的UDP更高的效率是更加適合的選擇 - 可以進行廣播;UDP並不是面向連線的,所以可以同時對多個程序進行傳送報文 #### UDP適用場景 UDP適用於對傳輸模型需要應用層高度自定義、允許出現丟包、需要高效率的場景、需要廣播;例如 - 視屏直播 - DNS - RIP路由選擇協議 ## 其他補充 #### 分塊傳輸 我們可以發現,運輸層在傳輸資料的時候,並不是把整個資料包加個首部直接傳送過去,而是會拆分成多個報文分開發送;那他這樣做原因是什麼? 有讀者可能會想到:資料鏈路層限制了資料長度只能有1460。那資料鏈路層為什麼要這麼限制?他的本質原因就是:**網路是不穩定的**。如果報文太長,那麼極有可能在傳輸一般的時候突然中斷了,這個時候就要整個資料重傳,效率就降低了。把資料拆分成多個數據報,那麼當某個資料報丟失,只需要重傳該資料報即可。 那是不是拆分得越細越好?報文中資料欄位長度太低,會使得首部的佔比太大,這樣首部就會成為網路傳輸最大的負擔了。例如1000位元組,每個報文首部是40位元組,如果拆分成10個報文,那麼只需要傳輸400位元組的首部;而如果拆分成1000個,那麼需要傳輸40000位元組的首部,效率就極大地降低了。 #### 路由轉換 先看下圖: ![](https://i.bmp.ovh/imgs/2021/02/f631d7cd9844ac2b.png) - 正常情況下,主機A的資料包可以又 1-3-6-7路徑進行傳送 - 如果路由3壞掉了,那麼可以從 1-4-6-7進行傳送 - 如果4也壞掉了,那麼只能從2-5-6-7傳送 - 如果5壞掉了,那麼就中斷線路了 可以看出來,使用路由轉發的好處是:**提高網路的容錯率**,本質原因依舊是**網路是不穩定的** 。即使壞掉幾個路由器,網路依舊暢通。但是如果壞掉路由器6那就直接導致主機A和主機B無法通訊,所以要避免這種核心路由器的存在。 使用路由的好處還有:**分流**。如果一條線路太擁堵,可以從別的路線進行傳輸,提高效率。 #### 粘包與拆包 在面向位元組流那一小節講過,TCP不懂這些資料流的意義,他只知道從應用層拿到資料流,切割成一份份報文,然後傳送給目標物件。而如果應用層傳輸下來的是兩個資料包,那麼極有可能出現這種情況: ![](https://i.bmp.ovh/imgs/2021/02/92258863626ef377.png) - 應用層需要向目標程序傳送兩份資料,一份音訊,一份文字 - TCP只知道接收到一個流,並把流拆分成4段進行傳送 - 中間第二個報文的資料就出現兩個檔案的資料混在一起,這就是粘包 - 目標程序應用層在接收到資料之後,需要把這些資料拆分成正確的兩個檔案,就是拆包 粘包與拆包都是應用層需要解決的問題,可以在每個檔案的最後附加上一些特殊的位元組,如換行符;或者控制每個報文只包含一個檔案的資料,不足的用0補充等等。 #### 惡意攻擊 TCP的面向連線特點可能會被惡意的人利用,對伺服器進行攻擊。 前面我們知道,當我們向一個主機發送syn包請求建立連線時,伺服器會為我們建立緩衝區等,然後向我們返回syn+ack報文;如果我們偽造IP和埠,向一個伺服器進行海量的請求,會使得伺服器建立了大量的建立一半的TCP連線,使得其無法正常響應使用者的請求,導致伺服器癱瘓。 解決的方法可以有限制IP的建立連線數、讓建立一半的tcp連線在更短的時間內自行關閉、延緩接收緩衝區記憶體的分配等等。 #### 長連線 我們向伺服器的每一次請求都需要建立一個TCP連線,伺服器返回資料之後就會關閉連線;如果在短時間內有大量的請求,那麼頻繁建立TCP連線關閉TCP連線是一個很浪費資源的行為。所以我們可以讓TCP連線不要關閉,在這個期間進行請求,提高效率。 需要注意長連線維持時間、建立條件等,避免被惡意利用建立大量的長連線,消耗殆盡伺服器的資源。 ## 最後 以前學習的時候覺得這些東西好像沒什麼卵用,貌似就是用來考試的。事實上,在沒應用到的時候,對這些知識很難有更深層次的認知,例如現在我看上面的總結,很多隻是表面上的認知,不知道他背後代表的真正含義。 但當我學習的更加廣泛、深入,會對這些知識有越來越深刻的認識。有那麼幾個瞬間覺得:哦原來那個東西是這樣運用,那個東西是這樣的啊,原來學了是真的有用。 現在可能學了之後沒有什麼感覺,但是當用到或者學到相關的應用時,會有一個頓悟感,會瞬間收穫很多。 覺得有幫助留個贊鼓勵一下作者吧~ > 全文到此,原創不易,覺得有幫助可以點贊收藏評論轉發。 > 筆者才疏學淺,文章有錯誤或有不同觀點歡迎評論區交流。 > 如需轉載請評論區或私信告知即可。 > > 另外歡迎光臨筆者的個人部落格:[傳送門](https://qwerhuan.gi