傳輸層( 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次揮手 - 抓包圖

