目錄
一. UDP
1.1 UDP協議段格式
1.2 UDP傳輸的特點
1.3 面向數據報
1.4 UDP緩沖區
1.5 報文的理解
二. TCP?
2.1 TCP協議段格式
?2.2 確認應答機制(ACK)
2.3 超時重傳機制
2.4 連接管理機制
?為什么要三次握手?
三次?四次握手?
四次揮手?
2.5 理解TIME_WAIT狀態
?解決 TIME_WAIT 狀態引起的 bind 失敗的方法
2.6 滑動窗口
?部分問題?
?2.7 流量控制
2.8 擁塞控制
?2.9 延遲應答
?2.10 面向字節流
?2.11 粘包問題
?用UDP實現可靠傳輸(面試)
一. UDP
1.1 UDP協議段格式
16 位 UDP 長度, 表示整個數據報(UDP 首部+UDP 數據)的最大長度;
如果校驗和出錯, 就會直接丟棄;?
1.2 UDP傳輸的特點
類似于寄信
? 無連接: 知道對端的 IP 和端口號就直接進行傳輸, 不需要建立連接;
? 不可靠: 沒有確認機制, 沒有重傳機制; 如果因為網絡故障該段無法發到對方, UDP 協議層也不會給應用層返回任何錯誤信息;
? 面向數據報: 不能夠靈活的控制讀寫數據的次數和數量;?
1.3 面向數據報
?應用層交給 UDP 多長的報文, UDP 原樣發送, 既不會拆分, 也不會合并;
用 UDP 傳輸 100 個字節的數據:
? 如果發送端調用一次 sendto, 發送 100 個字節, 那么接收端也必須調用對應的 一次 recvfrom, 接收 100 個字節; 而不能循環調用 10 次 recvfrom, 每次接收 10 個字 節;
1.4 UDP緩沖區
? UDP 沒有真正意義上的 發送緩沖區. 調用 sendto 會直接交給內核, 由內核將數 據傳給網絡層協議進行后續的傳輸動作;
? UDP 具有接收緩沖區. 但是這個接收緩沖區不能保證收到的 UDP 報的順序和 發送 UDP 報的順序一致; 如果緩沖區滿了, 再到達的 UDP 數據就會被丟棄; ?
UDP 的 socket 既能讀, 也能寫, 這個概念叫做 全雙工
1.5 報文的理解
在OS的內部肯定會存在多個報文,OS要管理這些報文就需要結構體。
所以無論是在應用層,傳輸層,網絡層還是數據鏈路層都是一群的sk_buff在發揮作用。分用和解包的原理也就是data指針的移動問題。?
二. TCP?
2.1 TCP協議段格式
? 源/目的端口號: 表示數據是從哪個進程來, 到哪個進程去;
? 32位序號和32位確認序號:一個是對對方報文的確認,一個是自己報文的序號。(捎帶應答)
? 4 位 TCP 報頭長度: 表示該 TCP 頭部有多少個 32 位 bit(有多少個 4 字節); 所以 TCP 頭部最大長度是 15 * 4 = 60
? 6 位標志位(發送的報文可能是請求連接,斷開鏈接,發送數據等類型的報文,所以需要標志位來分開這些不同類型的報文):
????????○ URG: 緊急指針是否有效(與16位緊急指針共同使用)
????????○ ACK: 確認號是否有效
????????○ PSH: 提示接收端應用程序立刻從 TCP 緩沖區把數據讀走
????????○ RST: 對方要求重新建立連接; 我們把攜帶 RST 標識的稱為復位報文段
????????
????????○ SYN: 請求建立連接; 我們把攜帶 SYN 標識的稱為同步報文段
????????○ FIN: 通知對方, 本端要關閉了, 我們稱攜帶 FIN 標識的為結束報文段
? 16 位校驗和: 發送端填充, CRC 校驗. 接收端校驗不通過, 則認為數據有問題. 此 處的檢驗和不光包含 TCP 首部, 也包含 TCP 數據部分.
? 16 位緊急指針: 標識哪部分數據是緊急數據;
在TCP協議段報頭中不存在報文大小,為什么只有報頭大小?(而UDP里面的16位UDP長度表示的就是報頭和有效載荷的大小):
TCP 是面向字節流的協議,在操作系統內部,發送的多個 TCP 數據段會被視為連續的字節流,接收方收到數據后會將其放入接收緩沖區,由上層應用自行去分析和組裝數據,因此 TCP 難以界定一個報文的完整長度,也就不需要在報頭中設置專門的報文大小字段。
?在到達數據鏈路層準備傳輸的時候,sk_buff轉換為物理幀格式,并觸發硬件傳輸。到達后然后一步步往上解包,到達接收緩沖區時就只有有效載荷了。(報頭和有效載荷不會出現粘一起的情況,可能會這樣的是有效載荷與有效載荷)解決的話是靠應用層去解決
?2.2 確認應答機制(ACK)
?TCP 將每個字節的數據都進行了編號. 即為序列號
?
每一個 ACK 都帶有對應的確認序列號, 意思是告訴發送者, 我已經收到了哪些數據; 下 一次你從哪里開始發.?
2.3 超時重傳機制
? 主機 A 發送數據給 B 之后, 可能因為網絡擁堵等原因, 數據無法到達主機 B;
? 如果主機 A 在一個特定時間間隔內沒有收到 B 發來的確認應答, 就會進行重發;?
?如果是應答丟了,客戶端就無法確切的知道報文是否是丟失了,可能正常的,只是沒有應答。也可能真的丟了。這個時候就會有超時機制了,超時重傳。所以主機 B 會收到很多重復數據. 那么 TCP 協議需要能夠識別出那些包是重復的包, 并 且把重復的丟棄掉. 這時候我們可以利用前面提到的序列號, 就可以很容易做到去重的效果.
? 最理想的情況下, 找到一個最小的時間, 保證 "確認應答一定能在這個時間內返 回".
? 但是這個時間的長短, 隨著網絡環境的不同, 是有差異的.
? 如果超時時間設的太長, 會影響整體的重傳效率;
? 如果超時時間設的太短, 有可能會頻繁發送重復的包;
網絡好的時候等待的時間就長一點,不好的時候就短一點。TCP 為了保證無論在任何環境下都能比較高性能的通信, 因此會動態計算這個最大超 時時間.?
? Linux 中(BSD Unix 和 Windows 也是如此), 超時以 500ms 為一個單位進行控 制, 每次判定超時重發的超時時間都是 500ms 的整數倍.
? 如果重發一次之后, 仍然得不到應答, 等待 2*500ms 后再進行重傳.
? 如果仍然得不到應答, 等待 4*500ms 進行重傳. 依次類推, 以指數形式遞增.
? 累計到一定的重傳次數, TCP 認為網絡或者對端主機出現異常, 強制關閉連接.?
2.4 連接管理機制
注意:
- connect 發起三次握手:是客戶端發起和完成三次握手的觸發接口。
- accept 不參與三次握手:僅在三次握手完成后,從內核的已完成連接隊列中獲取連接,是連接建立后的處理接口。
?為什么要三次握手?
1.以最小成本確定雙方通信意愿
2.驗證全雙工,我們兩個所處的網絡是流暢的,能夠支持全雙工。
三次?四次握手?
三次握手其實也就是四次握手捎帶應答,中間的ACK和SYN合在一起了。服務器面對客戶端的鏈接請求都要無腦接受。
四次揮手?
四次揮手實際上也可以壓縮為三次揮手,不過是C和S兩端同時要有斷開鏈接的意愿。
但畢竟是4次揮手,客戶端關閉了,但是服務端還想往客戶端發消息就走不通了,因為把文件描述符關閉了。可以用shutdown?
它可以只關閉文件描述符的讀端,寫端。使全雙工變為半雙工。(正常使用close就行,這里了解)
注意:
主動斷開鏈接的一方,要進入一個狀態叫做TIME_WAIT,即便是四次揮手完成,過一段時間才會CLOSED。因為先斷開連接的一方,在收到對方發來的FIN后,直接發送ACK的話,自己這一端的四次揮手已經完成了。
2.5 理解TIME_WAIT狀態
現在做一個測試,首先啟動 server,然后啟動 client,然后用 Ctrl-C 使 server 終止,這時馬 上再運行 server, 結果是:
這是因為,雖然 server 的應用程序終止了,但 TCP 協議層的連接并沒有完全斷開,因此不 能再次監 聽同樣的 server 端口. 我們用 netstat 命令查看一下:
? TCP 協議規定,主動關閉連接的一方要處于 TIME_ WAIT 狀態,等待兩個 MSL(maximum segment lifetime)的時間后才能回到 CLOSED 狀態.
? 我們使用 Ctrl-C 終止了 server, 所以 server 是主動關閉連接的一方, 在 TIME_WAIT 期間仍然不能再次監聽同樣的 server 端口;
? MSL 在 RFC1122 中規定為兩分鐘,但是各操作系統的實現不同, 在 Centos7 上 默認配置的值是 60s;
? 可以通過 cat /proc/sys/net/ipv4/tcp_fin_timeout 查看 msl 的值;?
?? MSL 是 TCP 報文的最大生存時間, 因此 TIME_WAIT 持續存在 2MSL 的話
? 就能保證在兩個傳輸方向上的尚未被接收或遲到的報文段都已經消失(否則服務器立刻重啟, 可能會收到來自上一個進程的遲到的數據, 但是這種數據很可能是錯誤的);(在TIME_WAIT狀態下,這也是我們在關閉服務器后,當前的ip和端口號會bind失敗的原因。假設歷史上有data還沒有到達,如果我們可以使用歷史的端口號,重新建立鏈接的話,上一次鏈接還沒有到達的data此時就會到達,這個data 是我們不需要的)
? 同時也是在理論上保證最后一個報文可靠到達(假設最后一個 ACK 丟失, 那么服務器會再重發一個 FIN. 這時雖然客戶端的進程不在了, 但是 TCP 連接還在, 仍然可以重發 LAST_ACK);
?解決 TIME_WAIT 狀態引起的 bind 失敗的方法
在 server 的 TCP 連接沒有完全斷開之前不允許重新監聽, 某些情況下可能是不合理的
? 服務器需要處理非常大量的客戶端的連接(每個連接的生存時間可能很短, 但是每秒都有很大數量的客戶端來請求).
? 這個時候如果由服務器端主動關閉連接(比如某些客戶端不活躍, 就需要被服務器端主動清理掉), 就會產生大量 TIME_WAIT 連接.
? 由于我們的請求量很大, 就可能導致 TIME_WAIT 的連接數很多, 每個連接都會占用一個通信五元組(源 ip, 源端口, 目的 ip, 目的端口, 協議). 其中服務器的 ip 和端 口和協議是固定的. 如果新來的客戶端連接的 ip 和端口號和 TIME_WAIT 占用的鏈 接重復了, 就會出現問題.
使用 setsockopt()設置 socket 描述符的 選項 SO_REUSEADDR 為 1, 表示允許創建端 口號相同但 IP 地址不同的多個 socket 描述符?
2.6 滑動窗口
一次向對方發送多少數據由滑動窗口決定。
?對每一個發送的數據段, 都要給一個 ACK 確認應答. 收 到 ACK 后再發送下一個數據段. 這樣做有一個比較大的缺點, 就是性能較差. 尤其是數據往返的時間較長的時候.
既然這樣一發一收的方式性能較低, 那么我們一次發送多條數據, 就可以大大的提高性 能(其實是將多個段的等待時間重疊在一起了).?
? 窗口大小指的是無需等待確認應答而可以繼續發送數據的最大值. 上圖的窗口 大小就是 4000 個字節(四個段).
? 發送前四個段的時候, 不需要等待任何 ACK, 直接發送;
? 收到第一個 ACK 后, 滑動窗口向后移動, 繼續發送第五個段的數據; 依次類推;
? 操作系統內核為了維護這個滑動窗口, 需要開辟發送緩沖區來記錄當前還有哪些數據沒有應答; 只有確認應答過的數據, 才能從緩沖區刪掉;
? 窗口越大, 則網絡的吞吐率就越高;
?
?部分問題?
可以向左滑動嗎?
不可以,左邊是已發送已確認的,而且確認序號一定是在增加的。
滑動窗口可以變大嗎?可以變小嗎?可以不變嗎?可以為0嗎?
全都可以,假如對方的接收緩沖區只剩2000個字節,上層直接把緩沖區讀取完了,此時緩沖區為空了,滑動窗口大小就會變大。變小也是同樣的道理,發送一次之后,接收緩沖區只剩2000個字節,滑動窗口的大小不能超過接收緩沖區的剩余空間的大小。
丟包了怎么辦?滑動窗口會不會跳過報文進行應答?
?情況一:
這種情況下, 部分 ACK 丟了并不要緊, 因為可以通過后續的 ACK 進行確認;?
情況二:
?
? 當某一段報文段丟失之后, 發送端會一直收到 1001 這樣的 ACK, 就像是在提醒 發送端 "我想要的是 1001" 一樣;
? 如果發送端主機連續三次收到了同樣一個 "1001" 這樣的應答, 就會將對應的數 據 1001 - 2000 重新發送;?
? 這個時候接收端收到了 1001 之后, 再次返回的 ACK 就是 7001 了(因為 2001 - 7000)接收端其實之前就已經收到了, 被放到了接收端操作系統內核的接收緩沖區中; 這種機制被稱為 "高速重發控制"(也叫 "快重傳").
超時重傳和快重傳都是丟包問題的重傳機制。超時重傳是保底,快重傳是提升效率的?
?2.7 流量控制
接收端處理數據的速度是有限的. 如果發送端發的太快, 導致接收端的緩沖區被打滿, 這 個時候如果發送端繼續發送, 就會造成丟包, 繼而引起丟包重傳等等一系列連鎖反應.
因此 TCP 支持根據接收端的處理能力, 來決定發送端的發送速度. 這個機制就叫做流量 控制(Flow Control);
? 接收端將自己可以接收的緩沖區大小放入 TCP 首部中的 "窗口大小" 字段, 通 過 ACK 端通知發送端;
? 窗口大小字段越大, 說明網絡的吞吐量越高;
? 接收端一旦發現自己的緩沖區快滿了, 就會將窗口大小設置成一個更小的值通 知給發送端;
? 發送端接受到這個窗口之后, 就會減慢自己的發送速度;
? 如果接收端緩沖區滿了, 就會將窗口置為 0; 這時發送方不再發送數據, 但是需 要定期發送一個窗口探測數據段, 使接收端把窗口大小告訴發送端.
?接收端如何把窗口大小告訴發送端呢? 回憶我們的 TCP 首部中, 有一個 16 位窗口字段, 就是存放了窗口大小信息;
那么問題來了, 16 位數字最大表示 65535, 那么 TCP 窗口最大就是 65535 字節么?
實際上, TCP 首部 40 字節選項中還包含了一個窗口擴大因子 M, 實際窗口大小是 窗口 字段的值左移 M 位;
2.8 擁塞控制
?雖然 TCP 有了滑動窗口這個大殺器, 能夠高效可靠的發送大量的數據. 但是如果在剛開 始階段就發送大量的數據, 仍然可能引發問題.
因為網絡上有很多的計算機, 可能當前的網絡狀態就已經比較擁堵. 在不清楚當前網絡 狀態下, 貿然發送大量的數據, 是很有可能引起雪上加霜的.
TCP 引入 慢啟動 機制, 先發少量的數據, 探探路, 摸清當前的網絡擁堵狀態, 再決定按 照多大的速度傳輸數據;
? 此處引入一個概念稱為擁塞窗口。(一個臨界值,臨界值以內網絡較大概率不擁堵,以外網絡可能擁堵)
? 發送開始的時候, 定義擁塞窗口大小為 1;
? 每次收到一個 ACK 應答, 擁塞窗口加 1;
? 每次發送數據包的時候, 將擁塞窗口和接收端主機反饋的窗口大小做比較, 取較 小的值作為實際發送的窗口;?(滑動窗口=min(對方的win大小,擁塞窗口))
但是指數增長到后面就太快了:
? 為了不增長的那么快, 因此不能使擁塞窗口單純的加倍.
? 此處引入一個叫做慢啟動的閾值
? 當擁塞窗口超過這個閾值的時候, 不再按照指數方式增長, 而是按照線性方式增 長?
? 當 TCP 開始啟動的時候, 慢啟動閾值等于窗口最大值;
? 在每次超時重發的時候, 慢啟動閾值會變成原來的一半, 同時擁塞窗口置回 1;
少量的丟包, 我們僅僅是觸發超時重傳; 大量的丟包, 我們就認為網絡擁塞; 當 TCP 通信開始后, 網絡吞吐量會逐漸上升; 隨著網絡發生擁堵, 吞吐量會立刻下降; 擁塞控制, 歸根結底是 TCP 協議想盡可能快的把數據傳輸給對方, 但是又要避免給網絡 造成太大壓力的折中方案.
?2.9 延遲應答
如果接收數據的主機立刻返回 ACK 應答, 這時候返回的窗口可能比較小.
? 假設接收端緩沖區為 1M. 一次收到了 500K 的數據; 如果立刻應答, 返回的窗口 就是 500K;
? 但實際上可能處理端處理的速度很快, 10ms 之內就把 500K 數據從緩沖區消費 掉了;
? 在這種情況下, 接收端處理還遠沒有達到自己的極限, 即使窗口再放大一些, 也 能處理過來;
? 如果接收端稍微等一會再應答, 比如等待 200ms 再應答, 那么這個時候返回的 窗口大小就是 1M;
那么所有的包都可以延遲應答么? 肯定也不是;
? 數量限制: 每隔 N 個包就應答一次;
? 時間限制: 超過最大延遲時間就應答一次;?
具體的數量和超時時間, 依操作系統不同也有差異; 一般 N 取 2, 超時時間取 200ms;?
?2.10 面向字節流
創建一個 TCP 的 socket, 同時在內核中創建一個 發送緩沖區 和一個 接收緩沖區;
? 調用 write 時, 數據會先寫入發送緩沖區中;
? 如果發送的字節數太長, 會被拆分成多個 TCP 的數據包發出;
? 如果發送的字節數太短, 就會先在緩沖區里等待, 等到緩沖區長度差不多了, 或 者其他合適的時機發送出去;
? 接收數據的時候, 數據也是從網卡驅動程序到達內核的接收緩沖區;
? 然后應用程序可以調用 read 從接收緩沖區拿數據;
? 另一方面, TCP 的一個連接, 既有發送緩沖區, 也有接收緩沖區, 那么對于這一 個連接, 既可以讀數據, 也可以寫數據. 這個概念叫做 全雙工
由于緩沖區的存在, TCP 程序的讀和寫不需要一一匹配, 例如:
? 寫 100 個字節數據時, 可以調用一次 write 寫 100 個字節, 也可以調用 100 次 write, 每次寫一個字節;
? 讀 100 個字節數據時, 也完全不需要考慮寫的時候是怎么寫的, 既可以一次 read 100 個字節, 也可以一次 read 一個字節, 重復 100 次;?
?2.11 粘包問題
? 首先要明確, 粘包問題中的 "包" , 是指的應用層的數據包.
? 在 TCP 的協議頭中, 沒有如同 UDP 一樣的 "報文長度" 這樣的字段, 但是有一 個序號這樣的字段.
? 站在傳輸層的角度, TCP 是一個一個報文過來的. 按照序號排好序放在緩沖區 中.
? 站在應用層的角度, 看到的只是一串連續的字節數據.
? 那么應用程序看到了這么一連串的字節數據, 就不知道從哪個部分開始到哪個 部分, 是一個完整的應用層數據包.
那么如何避免粘包問題呢? 歸根結底就是一句話, 明確兩個包之間的邊界.
? 對于定長的包, 保證每次都按固定大小讀取即可; 例如上面的 Request 結構, 是 固定大小的, 那么就從緩沖區從頭開始按 sizeof(Request)依次讀取即可;
? 對于變長的包, 可以在包頭的位置, 約定一個包總長度的字段, 從而就知道了包的結束位置;
? 對于變長的包, 還可以在包和包之間使用明確的分隔符(應用層協議, 是程序猿自己來定的, 只要保證分隔符不和正文沖突即可);?
對于 UDP 協議來說, 是否也存在 "粘包問題" 呢?
? 對于 UDP, 如果還沒有上層交付數據, UDP 的報文長度仍然在. 同時, UDP 是一 個一個把數據交付給應用層. 就有很明確的數據邊界.
? 站在應用層的站在應用層的角度, 使用 UDP 的時候, 要么收到完整的 UDP 報 文, 要么不收. 不會出現"半個"的情況.?
?用UDP實現可靠傳輸(面試)
引入TCP機制就行了,引入確認序號,確認應答,超時重傳等等。。