談談網路協議 – 傳輸層( Transport)
傳輸層( Transport)
- 傳輸層有2個協議
- TCP(Transmission Control Protocol),傳輸控制協議
- UDP(User Datagram Protocol),使用者資料報協議
UDP - 使用者資料報協議
UDP - 首部資料格式
-
UDP是無連線的,減少了建立和釋放連線的開銷
-
UDP盡最大能力交付,不保證可靠交付
- 因此不需要維護一些複雜的引數,首部只有8個位元組(TCP的首部至少20個位元組)
-
UDP長度(Length)
-
佔16位,首部的長度 + 資料的長度
UDP首部 - 檢驗和( Checksum)
- 檢驗和的計算內容:偽首部 + 首部 + 資料
- 偽首部:僅在計算檢驗和時起作用,並不會傳遞給網路層
埠( Port)
- UDP首部中埠是佔用2位元組
- 可以推測出埠號的取值範圍是:0~65535
- 客戶端的源埠是臨時開啟的隨機埠
- 防火牆可以設定開啟\關閉某些埠來提高安全性
- 常用命令列 netstat –an:檢視被佔用的埠
- netstat –anb:檢視被佔用的埠、佔用埠的應用程式
- telnet 主機 埠:檢視是否可以訪問主機的某個埠
- 安裝telnet:控制面板 – 程式 – 啟用或關閉Windows功能 – 勾選“Telnet Client” – 確定
UDP抓包
TCP - 傳輸控制協議
TCP - 首部資料格式
TCP首部 - 資料偏移
- 佔4位,取值範圍是0x0101~0x1111
- 乘以4:首部長度(Header Length)
- 首部長度是20~60位元組
TCP首部 - 保留
- 佔6位,目前全為0
首部小細節
-
有些資料中,TCP首部的保留(Reserved)欄位佔3位,標誌(Flags)欄位佔9位(前3位也都是0,沒什麼用)
- Wireshark中也是如此
-
UDP的首部中有個16位的欄位記錄了整個UDP報文段的長度(首部+資料)
- 但是,TCP的首部中僅僅有個4位的欄位記錄了TCP報文段的首部長度,並沒有欄位記錄TCP報文段的資料長度
-
分析
- UDP首部中佔16位的長度欄位是冗餘的,純粹是為了保證首部是32bit對齊
- TCP\UDP的資料長度,完全可以由IP資料包的首部推測出來
- 傳輸層的資料長度 = 網路層的總長度 – 網路層的首部長度 – 傳輸層的首部長度
TCP首部 - 檢驗和( Checksum)
- 跟UDP一樣,TCP檢驗和的計算內容:偽首部 + 首部 + 資料
- 偽首部:佔用12位元組,僅在計算檢驗和時起作用,並不會傳遞給網路層
TCP首部 - 標誌位( Flags)
-
URG(Urgent)
- 當URG=1時,緊急指標欄位才有效。表明當前報文段中有緊急資料,應優先儘快傳送,傳送應用程序就告訴傳送方的TCP有緊急資料要傳送。於是傳送方TCP就把緊急資料插入到本報文段資料的最前面,而緊急資料後面的資料仍是普通資料
-
ACK(Acknowledgment)
- 當ACK=1時,確認號欄位才有效
-
PSH(Push)
- 推送,當兩個應用程序進行互動式的通訊時,有時候一端的應用程序希望在鍵入一個命令後立即就能收到對方的響應。在這種情況下,TCP就可應使用推送(push)操作。這時,傳送方TCP把PSH置1,並立即建立一個報文段傳送出去。接收方TCP收到PSH=1的報文段,就儘快地交付接收應用程序,而不再等到整個快取都填滿了再向上交付
-
RST(Reset)
- 當RST=1時,表明連線中出現嚴重差錯,必須釋放連線,然後再重新建立連線
-
SYN(Synchronization)
- 當SYN=1、ACK=0時,表明這是一個建立連線的請求 若對方同意建立連線,則回覆SYN=1、ACK=1
-
FIN(Finish)
- 當FIN=1時,表明資料已經發送完畢,要求釋放連線
TCP首部 - 序號、確認號、視窗
- 序號(Sequence Number)
- 佔4位元組
- 首先,在傳輸過程的每一個位元組都會有一個編號 在建立連線後,序號代表:這一次傳給對方的TCP資料部分的第一個位元組的編號
- 確認號(Acknowledgment Number)
- 佔4位元組
- 在建立連線後,確認號代表:期望對方下一次傳過來的TCP資料部分的第一個位元組的編號
- 視窗(Window)
- 佔2位元組
- 這個欄位有流量控制功能,用以告知對方下一次允許傳送的資料大小(位元組為單位)
TCP - 四大要點
- 可靠傳輸
- 流量控制
- 擁塞控制
- 連線管理
- 3次握手
- 4次分手
TCP - 可靠傳輸
停止等待 協議
- ARQ(Automatic Repeat–reQuest),自動重傳請求
圖例分析
-
a) 正常無差錯情況
- 傳送一條,確認一條
-
b) 超時重傳
- 當A傳送給B時網路異常導致沒有傳送過去
- A會啟動一個定時器,在這個定時器執行(超時)之前如果一直沒有收到B的回覆,就會重新發送M1
-
c) 確認丟失
- A傳送M1給B,B收到了,B傳送M1的確認包給A沒傳送成功,此時A那邊超時重傳M1,B再次收到M1的資料包,會將上一次的M1丟棄,重傳確認M1給A
-
d) 確認遲到
- A發M1給到B,B收到,發M1的確認包給A,網路延遲一直沒到,A超時重傳M1給B,B收到後丟棄上一次重複的M1,再次重新發送確認M1的包給A,A收到,在此之後B傳送給A第一次的M1的確認包到達A,A收到後發現是已經確認過的,直接丟棄,什麼也不做
一些疑問
-
若有個包重傳了N次還是失敗,會一直持續重傳到成功為止麼?
-
這個取決於系統的設定,比如有些系統,重傳5次還未成功就會發送reset報文(RST)強制斷開TCP連線
-
連續ARQ協議 + 滑動視窗協議
- 連續ARQ
- 一次分組批量發多個包給對方,傳送完後,停止傳送,等待確認,對方收到後只需要發一個(最後那個包)確認包給接收方,如果有包丟失會執行後面的 選擇性確認 流程
- 滑動視窗
- 當批量的包發完後,並且回覆確認,會接著發下一批資料,整個過程類似視窗一樣往下滑,所以叫滑動視窗
問題
-
如果接收視窗最多能接收4個包 ,但傳送方只發了2個包
-
接收方如何確定後面還有沒有2個包?
- 等待一定時間後沒有第3個包,就會返回確認收到2個包給傳送方
-
SACK - 選擇性確認
-
在TCP通訊過程中,如果傳送序列中間某個資料包丟失(比如1、2、3、4、5中的3丟失了)
-
TCP會通過重傳最後確認的分組後續的分組(最後確認的是2,會重傳3、4、5)
-
這樣原先已經正確傳輸的分組也可能重複傳送(比如4、5),降低了TCP效能
-
為改善上述情況,發展出了SACK(Selective acknowledgment,選擇性確認)技術
- 告訴傳送方哪些資料丟失,哪些資料已經提前收到
- 使TCP只重新發送丟失的包(比如3),不用傳送後續所有的分組(比如4、5)
-
SACK資訊
-
SACK資訊會放在TCP首部的選項部分
- Kind:佔1位元組。值為5代表這是SACK選項
- Length:佔1位元組。表明SACK選項一共佔用多少位元組
- Left Edge:佔4位元組,左邊界
- Right Edge:佔4位元組,右邊界
-
一對邊界資訊需要佔用8位元組,由於TCP首部的選項部分最多40位元組,所以
-
SACK選項最多攜帶4組邊界資訊
-
SACK選項的最大佔用位元組數 = 4 * 8 + 2 = 34
- SACK就是靠 兩個邊界值 來知道,哪些資料收到了,自然就知道哪些資料沒收到
思考一個問題
- 為什麼選擇在傳輸層就將資料“大卸八塊”分成多個段,而不是等到網路層再分片傳遞給資料鏈路層?
-
因為可以提高重傳的效能
-
需要明確的是:可靠傳輸是在傳輸層進行控制的
- 如果在傳輸層不分段,一旦出現數據丟失,整個傳輸層的資料都得重傳
- 如果在傳輸層分了段,一旦出現數據丟失,只需要重傳丟失的那些段即可
-
網路層是沒有可靠性,重傳等機制的,資料要是丟了就丟了,還是需要在傳輸層校驗
-
TCP - 流量控制
- 如果接收方的快取區滿了,傳送方還在瘋狂著傳送資料
- 接收方只能把收到的資料包丟掉,大量的丟包會極大著浪費網路資源
- 所以要進行流量控制
- 什麼是流量控制?
- 讓傳送方的傳送速率不要太快,讓接收方來得及接收處理
- 原理
- 通過確認報文中視窗欄位來控制傳送方的傳送速率
- 傳送方的傳送視窗大小不能超過接收方給出視窗大小
- 當傳送方收到接收視窗的大小為0時,傳送方就會停止傳送資料
- 流量控制是 點對點 通訊的控制
特殊情況
- 有一種特殊情況
- 一開始,接收方給傳送方傳送了0視窗的報文段
- 後面,接收方又有了一些儲存空間,給傳送方傳送的非0視窗的報文段丟失了
- 傳送方的傳送視窗一直為零,雙方陷入僵局
- 解決方案
- 當傳送方收到0視窗通知時,這時傳送方停止傳送報文
- 並且同時開啟一個定時器,隔一段時間就發個測試報文去詢問接收方最新的視窗大小
- 如果接收的視窗大小還是為0,則傳送方再次重新整理啟動定時器
TCP - 擁塞控制
- 擁塞控制
- 防止過多的資料注入到網路中
- 避免網路中的路由器或鏈路過載
- 擁塞控制是一個全域性性的過程
- 涉及到所有的主機、路由器
- 以及與降低網路傳輸效能有關的所有因素
- 是大家共同努力的結果
- 相比而言,流量控制是 點對點 通訊的控制,而擁塞控制則是需要 整個網路 共用控制的
上圖解析
- 最終兩臺路由器中間的頻寬是 1000M,如果從右往右傳送資料到達 1000M或者更高,真的能滿載傳輸嗎?
- 並不會的,當頻寬快滿的時候就會進入 輕度擁塞,擁塞,甚至 死鎖,就和生活中的交通一樣
控制方法
- 慢開始(slow start,慢啟動)
- 擁塞避免(congestion avoidance)
- 快速重傳(fast retransmit)
- 快速恢復(fast recovery)
- 幾個縮寫
- MSS(Maximum Segment Size):每個段最大的資料部分大小 ,在建立連線時確定
- cwnd(congestion window):擁塞視窗
- rwnd(receive window):接收視窗
- swnd(send window):傳送視窗
- swnd = min(cwnd, rwnd) 傳送視窗 = 取擁塞與接收視窗最小值
慢開始
- cwnd的初始值比較小,然後隨著資料包被接收方確認(收到一個ACK)
- cwnd就成倍增長(指數級)
擁塞避免
- ssthresh(slow start threshold):慢開始閾(yu)值,cwnd達到閾值後,以線性方式增加 (加法增大)
- 擁塞避免(加法增大):擁塞視窗緩慢增大,以防止網路過早出現擁塞
- 乘法減小:只要網路出現擁塞(丟包),把ssthresh減為擁塞峰值的一半,同時執行慢開始演算法(cwnd又恢復到初始值)
- 當網路出現頻繁擁塞時,ssthresh值就下降的很快
快重傳
- 接收方
- 每收到一個失序的分組後就立即發出重複確認
- 使傳送方及時知道有分組沒有到達
- 而不要等待自己傳送資料時才進行確認
- 傳送方
- 只要連續收到三個重複確認(總共4個相同的確認),就應當立即重傳對方尚未收到的報文段
- 而不必繼續等待重傳計時器到期後再重傳
如上圖,傳送方,連續傳送了M1到M7的資料給到接收方,當接收方發現M3沒收到的時候,就會立即傳送3次M2的重複確認,傳送方收到3次重複確認後會立即重傳M3
快恢復
-
當傳送方連續收到三個重複確認,說明網路出現擁塞
-
就執行“乘法減小”演算法,把ssthresh減為擁塞峰值的一半
-
與慢開始不同之處是現在不執行慢開始演算法,即cwnd現在不恢復到初始值
- 而是把cwnd值設定為新的ssthresh值(減小後的值)
- 然後開始執行擁塞避免演算法(“加法增大”),使擁塞視窗緩慢地線性增大
快重傳 + 快恢復
傳送視窗的最大值
- 傳送視窗的最大值:swnd = min(cwnd, rwnd)
- 當rwnd < cwnd時,是接收方的接收能力限制傳送視窗的最大值
- 當cwnd < rwnd時,則是網路的擁塞限制傳送視窗的最大值
TCP - 序號、確認號
圖解
-
順序分解
- 前3步為TCP的3次握手
- 第4步才是真正發起HTTP網路請求
- 第5 - 8步 是伺服器返回資料給客戶端
- 第9步是客戶端收到所有的資料,向服務端傳送確認報文
-
引數詳解
-
SYN:同步碼
- 為1時代表建立連線,同步請求,只有第1步和第2步雙方第一次建立請求的時候才會為1,其它為 0
-
ACK:確認碼
- 當對方發訊息給你,你需要回復確認報文,表示你已經收到
-
seq:序號
- 在建立TCP連線時初始化的
- 第1步時會將發起方的序號發給對方
- 第2步對方收到後也會發起同步碼建立連線,也會發它的序號過來
- 序號在每次請求的時候:這一次傳給對方的TCP資料部分的第一個位元組的編號 + 1
-
ack:確認號
-
當對方傳送資料給過來,你要給對方確認回覆
-
確認號:對方的基礎序號 + 資料長度 + 1
-
表示的是:期望對方下一次傳過來的TCP資料部分的第一個位元組的編號
-
-
TCP - 建立連線 - 3次握手
圖解
-
第1步:客戶端 => 伺服器 發起 連線請求,SYN=1,ACK=0,seq=x
- 客戶端預設是 CLOSED(關閉)狀態
- 傳送後,進入 SYN-SENT(同步已傳送)狀態
-
第2步:伺服器收到客戶端的 連線請求 後,向 客戶端 發起 連線請求確認,SYN=1,ACK=1,seq=y,ack=x+1
- 伺服器預設是 LISTEN(監聽)狀態
- 接收到客戶端的SYN報文後,進入 SYN-RCVD(同步已接收)狀態
-
第3步:客戶端收到伺服器的 連線請求確認 報文後,向 伺服器 發起最終 確認 報文,此時連線建立成功
-
客戶端收到伺服器的 連線請求確認 報文後,並再次傳送ACK 確認 報文給伺服器,並將客戶端狀態設定為 ESTABLISHED(連線已建立)狀態
-
伺服器在收到客戶端的第3次ACK確認後,也進入 ESTABLISHED(連線已建立)狀態
-
狀態解讀
-
CLOSED:client處於關閉狀態
-
LISTEN:server處於監聽狀態,等待client連線
-
SYN-RCVD:表示server接受到了SYN報文,當收到client的ACK報文後,它會進入到 ESTABLISHED 狀態
-
SYN-SENT:表示client已傳送SYN報文,等待server的第2次握手
-
ESTABLISHED:表示連線已經建立
前2次握手的特點
-
SYN都設定為1
-
資料部分的長度都為0
-
TCP頭部的長度一般是32位元組
- 固定頭部:20位元組
- 選項部分:12位元組
-
雙方會交換確認一些資訊
-
比如MSS、是否支援SACK、Window scale(視窗縮放係數)等
-
這些資料都放在了TCP頭部的選項部分中(12位元組)
-
一些疑問
1. 為什麼建立連線的時候,要進行3次握手?2次不行麼?
-
主要目的:防止server端一直等待,浪費資源
-
如果建立連線只需要2次握手,可能會出現的情況
- 假設client發出的第一個連線請求報文段,因為網路延遲,在連線釋放以後的某個時間才到達server
- 本來這是一個早已失效的連線請求,但server收到此失效的請求後,誤認為是client再次發出的一個新的連線請求
- 於是server就向client發出確認報文段,同意建立連線
- 如果不採用“3次握手”,那麼只要server發出確認,新的連線就建立了
- 由於現在client並沒有真正想連線伺服器的意願,因此不會理睬server的確認,也不會向server傳送資料
- 但server卻以為新的連線已經建立,並一直等待client發來資料,這樣,server的很多資源就白白浪費掉了
-
採用“三次握手”的辦法可以防止上述現象發生
- 例如上述情況,client沒有向server的確認發出確認,server由於收不到確認,就知道client並沒有要求建立連線
2. 第3次握手失敗了,會怎麼處理?
-
此時server的狀態為SYN-RCVD,若等不到client的ACK,server會重新發送SYN+ACK包
-
如果server多次重發SYN+ACK都等不到client的ACK,就會發送RST包,強制關閉連線
TCP - 釋放連線 - 4次揮手
圖解
-
第1步:客戶端 => 伺服器 發起 FIN(完成本次連線,申請斷開連線)連線釋放 報文請求,ACK=1,seq=u,ack=v
- 預設雙方都是 ESTABLISHED(連線建立)狀態
- 發起 FIN 連線釋放 報文後,進入 FIN-WAIT-1(終止等待1)狀態
-
第2步:伺服器收到客戶端的 FIN 連線釋放 報文後,向 客戶端發起 ACK 確認 報文,ACK=1,seq=v,ack=u+1
- 伺服器收到 FIN 連線釋放 報文後,並回復 ACK 確認 後,進入 CLOSE-WAIT(關閉等待)狀態
- 客戶端收到 ACK 確認 報文後,進入 FIN-WAIT-2(終止等待2)狀態
- 回覆 ACK 確認 報文後,伺服器會判斷是否還有資料要發給客戶端
- 有,就繼續發資料給客戶端,此時客戶端還是能正常接收的
- 沒有,就發起第3步
-
第3步:再次向客戶端發起 FIN 連線釋放 請求,FIN=1,ACK=1,seq=w,ack=u+1
- 向客戶端發起 FIN 連線釋放 報文後,進入 LAST-ACK(最後確認)狀態
- 客戶端收到 FIN 連線釋放 報文後,進入 TIME-WAIT(時間等待)狀態
-
第4步:客戶端收到伺服器的 FIN 連線釋放 報文後,再發送ACK 確認 報文給伺服器
- 伺服器在收到最後一次 ACK 確認 報文後,進入 CLOSED(關閉)狀態
- 客戶端傳送完最後一次 ACK 確認 報文後,開啟一個定時器,在 2 倍的 MSL 時長後 才進入 CLOSED(關閉)狀態
注意點: 第2,3步在有些情況下是會進行合併的,就是當伺服器收到 **FIN 連線釋放** 報文後,能立刻確定也沒有資料傳送給客戶端了,時些就會合並這2步,FIN=1,ACK=1
狀態解讀
-
FIN-WAIT-1:表示想主動關閉連線
- 向對方傳送了FIN報文,此時進入到FIN-WAIT-1狀態
-
CLOSE-WAIT:表示在等待關閉
- 當對方傳送FIN給自己,自己會迴應一個ACK報文給對方,此時則進入到CLOSE-WAIT狀態
- 在此狀態下,需要考慮自己是否還有資料要傳送給對方,如果沒有,傳送FIN報文給對方
-
FIN-WAIT-2:只要對方傳送ACK確認後,主動方就會處於FIN-WAIT-2狀態,然後等待對方傳送FIN報文
-
CLOSING:一種比較罕見的例外狀態
- 表示你傳送FIN報文後,並沒有收到對方的ACK報文,反而卻也收到了對方的FIN報文
- 如果雙方几乎在同時準備關閉連線的話,那麼就出現了雙方同時傳送FIN報文的情況,也即會出現CLOSING狀態
- 表示雙方都正在關閉連線
-
LAST-ACK:被動關閉一方在傳送FIN報文後,最後等待對方的ACK報文
- 當收到ACK報文後,即可進入CLOSED狀態了
-
TIME-WAIT:表示收到了對方的FIN報文,併發送出了ACK報文,就等2MSL後即可進入CLOSED狀態了
- 如果FIN-WAIT-1狀態下,收到了對方同時帶FIN標誌和ACK標誌的報文時
- 可以直接進入到TIME-WAIT狀態,而無須經過FIN-WAIT-2狀態
- 如果FIN-WAIT-1狀態下,收到了對方同時帶FIN標誌和ACK標誌的報文時
-
CLOSED:關閉狀態
-
由於有些狀態的時間比較短暫,所以很難用netstat命令看到,比如SYN-RCVD、FIN-WAIT-1等
釋放連線 - 一些細節
-
TCP/IP協議棧在設計上,允許任何一方先發起斷開請求。這裡演示的是client主動要求斷開
-
client傳送ACK後,需要有個TIME-WAIT階段,等待一段時間後,再真正關閉連線
- 一般是等待2倍的MSL(Maximum Segment Lifetime,最大分段生存期)
- MSL是TCP報文在Internet上的最長生存時間
- 每個具體的TCP實現都必須選擇一個確定的MSL值,RFC 1122建議是2分鐘
- 可以防止本次連線中產生的資料包誤傳到下一次連線中(因為本次連線中的資料包都會在2MSL時間內消失了)
- 一般是等待2倍的MSL(Maximum Segment Lifetime,最大分段生存期)
-
如果client傳送ACK後馬上釋放了,然後又因為網路原因,server沒有收到client的ACK,server就會重發FIN
-
這時可能出現的情況是
-
client沒有任何響應,伺服器那邊會幹等,甚至多次重發FIN,浪費資源
-
client有個新的應用程式剛好分配了同一個埠號,新的應用程式收到FIN後馬上開始執行斷開連線的操作,本來 它可能是想跟server建立連線的
-
-
釋放連線 - 一些疑問
1. 為什麼釋放連線的時候,要進行4次揮手?
-
TCP是全雙工模式,雙方可以同時向發起請求和接收資料
-
如果只有前3次,那伺服器是不知道客戶端有沒有真正收到 FIN 的報文,客戶端也一樣不知道伺服器有沒有收到它的請求連線釋放請求
-
第1次揮手:當主機1發出FIN報文段時
- 表示主機1告訴主機2,主機1已經沒有資料要傳送了,但是,此時主機1還是可以接受來自主機2的資料
-
第2次揮手:當主機2返回ACK報文段時
- 表示主機2已經知道主機1沒有資料傳送了,但是主機2還是可以傳送資料到主機1的
-
第3次揮手:當主機2也傳送了FIN報文段時
- 表示主機2告訴主機1,主機2已經沒有資料要傳送了
-
第4次揮手:當主機1返回ACK報文段時
- 表示主機1已經知道主機2沒有資料傳送了。隨後正式斷開整個TCP連線
2. 為什麼有時候只有 "3次" 揮手?
- 這其實是將第2、3次揮手合併了
- 當server接收到client的FIN時,如果server後面也沒有資料要傳送給client了
- 這時,server就可以將第2、3次揮手合併,同時告訴client兩件事
- 已經知道client沒有資料要發
- server已經沒有資料要發了
- 這時,server就可以將第2、3次揮手合併,同時告訴client兩件事
3次揮手 - 抓包圖
作者:悠悠清風
出處:https://www.ywgao.cn/
本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連線,否則保留追究法律責任的權利。
我的聯絡方式:[email protected]