??
目錄
1. 應用層:用戶與網絡的 “交互窗口”
1.1?應用層協議:規范交互的 “通用語言”? ??
1.2?自定義協議:適配特殊需求的 “專屬規則”
1.3?應用層數據格式:讓數據 “說得明白”?
1.3.1 XML:結構化但繁瑣的 “老大哥”
1.3.2?JSON:輕量高效的 “新主流”
1.3.3?Protobuffer:極致高效的 “二進制選手”
2. 傳輸層:數據傳輸的 “交通指揮官”
2.1 再談端口號:應用程序的 “門牌號”
2.1.1 端口號的核心作用
2.1.2 唯一標識一次通信的 “五元組”?
2.1.3 端口號的范圍劃分?
2.1.4 關于端口號的兩個常見問題
2.2 UDP 協議:簡單直接的 “快遞服務”
2.2.1 UDP 協議的格式
2.22?UDP 的特點
2.2.3 UDP 的使用注意事項?
2.2.4 基于 UDP 的應用層協議
2.3?TCP 協議:可靠傳輸的 “幕后管家”?
2.3.1 TCP 協議段格式:“快遞面單” 的復雜設計
2.3.2 確認應答(ACK):“收到請回復” 的保障機制
2.3.3 超時重傳:“沒收到回復就重試” 的保險
2.3.4 連接管理:“三次握手建連接,四次揮手斷連接”
2.3.5 滑動窗口:“批量發貨,提升效率” 的優化?
2.3.6 流量控制:“別發太猛,我處理不過來”
2.3.7 擁塞控制:“別把網絡搞堵了” 的全局優化
2.3.8 延遲應答:“攢一波回復,減少交互”
2.3.9 捎帶應答:“順手回復,一石二鳥”?
2.3.10 面向字節流:“數據無邊界,但有序”?
2.3.11 粘包問題:“數據粘在一起,怎么拆分?”
2.3.12 異常情況:“遇到問題,如何處理?”
2.3.13基于 TCP 的應用層協議
2.4 TCP/UDP對比
????????在我們每天刷手機、逛網頁、發消息的背后,隱藏著一套復雜而精妙的網絡規則 ——TCP/IP 協議棧。從今天開始,我們就一步步揭開它的面紗,先從最貼近我們的應用層說起,再深入傳輸層的核心知識:端口號和 UDP / TCP 協議。
1. 應用層:用戶與網絡的 “交互窗口”
????????應用層是網絡體系里最貼近我們的 “用戶接口”,像日常用的瀏覽器、聊天軟件、辦公工具,它們實現核心功能的邏輯都扎根于此。這一層不用操心數據在網絡里咋傳輸,專注處理用戶業務需求?—— 你想翻譯文字,它負責完成翻譯邏輯;你要在線協作,它就保障文檔編輯、同步,是用戶和網絡交互的直接 “橋梁”。
1.1?應用層協議:規范交互的 “通用語言”? ??
????????應用層靠?協議?讓不同應用有序通信,這些協議是應用間交流的 “共同規則”。簡單說,協議就是一套 “約定”:既可以是像?HTTP(網頁通信)、DNS(域名解析)?這樣的通用協議(相當于行業通用 “規則手冊” ),也能是程序員根據場景自定義的 “專屬規則”。后續我們會專門講解應用層的具體協議(比如 HTTP、DNS 等),這里先記住:所有你能直接感知到的網絡功能,都離不開應用層的支撐。
????????你可以把協議理解成 “對話腳本”—— 瀏覽器和服務器用 HTTP 交流,就知道啥時候說 “要資源”、啥時候回 “給你內容”;要是通用協議滿足不了需求(比如企業內部系統要特殊數據交互 ),就該?自定義協議?登場了。
1.2?自定義協議:適配特殊需求的 “專屬規則”
????????碰到通用協議覆蓋不到的場景(比如企業內部生產管理、定制化系統 ),就得自己設計?自定義協議。核心要約定兩件事:
- 交互什么信息:客戶端(像 APP、網頁 )和服務器之間,要傳哪些數據?比如工廠監控系統,可能要傳 “設備編號、運行參數、故障代碼”;
- 數據格式咋組織:這些信息用啥格式傳?是用簡單的 “文本分隔符”(比如用?
\n
?分割不同字段 ),還是用更規范的 XML、JSON?
舉個例子:做外賣 APP 時,打開 “附近商家” 頁面,APP(客戶端 )得告訴服務器 “用戶位置、想找啥類型商家”,服務器再返回 “商家列表、評分、地址”—— 這些 “傳什么、怎么傳”,要么是產品邏輯定的,要么是程序員結合場景設計的,這就是自定義協議的過程。
1.3?應用層數據格式:讓數據 “說得明白”?
不管是通用協議還是自定義協議,傳遞數據時都得有統一格式。
常見的有這幾種:
1.3.1 XML:結構化但繁瑣的 “老大哥”
????????XML(可擴展標記語言 )用?標簽嵌套?組織數據,結構清晰、可讀性強,適合描述復雜信息。
比如一份系統配置:
<system-config><server><ip>192.168.1.100</ip><port>8080</port></server><database><name>myDB</name><username>admin</username></database>
</system-config>
????????標簽層層包裹,數據關系一目了然,但缺點也明顯 ——?語法繁瑣、冗余大(標簽占空間 ),傳輸大文件時效率低,適合對可讀性要求高的場景(比如配置文件 )。?
1.3.2?JSON:輕量高效的 “新主流”
????????JSON(JavaScript 對象表示法 )靠?鍵值對?存數據,語法簡潔、解析方便,成了 Web 開發的 “首選格式”。
?比如前端給后端傳用戶登錄信息:
{"username": "csdn_user","password": "123456","remember_me": true
}
????????不管是網頁和服務器交互,還是手機 App 傳數據,JSON 因為 “輕量、好解析”,幾乎成了標配。和 XML 比,它少了冗余標簽,傳輸效率更高。?
1.3.3?Protobuffer:極致高效的 “二進制選手”
????????Protobuffer 是 Google 搞的?二進制數據格式,特點是?體積小、解析快,但需要先定義 “數據模板”(寫?.proto
?文件 )。
比如定義用戶信息:
syntax = "proto3";
message User {string username = 1; // 字段1:用戶名int32 age = 2; // 字段2:年齡
}
????????它傳輸的是二進制數據,比 JSON、XML 更省帶寬,適合對性能要求極高的場景(比如游戲實時同步、分布式系統通信 )。不過它有學習成本(得先定義模板 ),所以常用在 “追求極致效率” 的場景里。?
- 通用場景(網頁、App 常規交互 ):優先用?JSON,輕量、易解析,開發成本低;
- 需要強結構化、可讀性(配置文件、舊系統兼容 ):選?XML,標簽嵌套清晰,但別在大文件傳輸里用;
- 極致性能場景(游戲、高頻通信 ):上?Protobuffer,二進制傳輸省帶寬,但要接受 “先定義模板” 的額外工作;
- 特殊業務需求(企業內部系統、定制化邏輯 ):搞?自定義協議,靈活適配,但要做好 “規則設計”,別讓格式太混亂。
2. 傳輸層:數據傳輸的 “交通指揮官”
????????如果說應用層是直接服務用戶的 “窗口”,那傳輸層就是負責把數據從一個應用準確送到另一個應用的 “交通樞紐”。它的核心任務是確保數據能夠可靠或高效地在發送端和接收端的應用程序之間傳輸。而要完成這個任務,就離不開兩個重要的 “工具”:端口號和傳輸協議。
2.1 再談端口號:應用程序的 “門牌號”
????????想象一下,你要給朋友寄一個包裹,只知道他家的地址(就像網絡中的 IP 地址)還不夠,因為一棟樓里可能有很多住戶,你需要知道具體的門牌號才能確保包裹準確送達。在網絡中,端口號就相當于應用程序的 “門牌號”。
2.1.1 端口號的核心作用
????????一臺電腦上可能同時運行著多個網絡應用,比如瀏覽器、聊天軟件、下載工具等。當數據通過網絡到達這臺電腦時,操作系統需要知道該把這些數據交給哪個應用程序處理,這時候端口號就派上了用場。
例如,你的電腦(IP 地址:172.23.12.14)同時打開了瀏覽器和 FTP 客戶端:
- 瀏覽器要和網頁服務器的 80 端口通信(因為 HTTP 協議默認使用 80 端口);
- FTP 客戶端要和 FTP 服務器的 21 端口通信(FTP 協議默認使用 21 端口);
- 操作系統通過不同的端口號,就能準確地把來自服務器 80 端口的數據交給瀏覽器,把來自 21 端口的數據交給 FTP 客戶端。
2.1.2 唯一標識一次通信的 “五元組”?
????????在 TCP/IP 協議中,一次完整的通信(比如你用瀏覽器訪問某個網站)是通過 “五元組” 來唯一確定的,它就像通信的 “身份證”,包括:
- 源 IP 地址:發送數據的主機 IP(比如你的電腦 IP:192.168.1.100);
- 源端口號:發送數據的應用程序端口(比如瀏覽器臨時使用的 56789 端口);
- 目的 IP 地址:接收數據的主機 IP(比如網站服務器 IP:123.123.123.123);
- 目的端口號:接收數據的應用程序端口(比如網站服務器的 80 端口);
- 協議號:使用的傳輸協議(比如 TCP 協議的協議號是 6)。
????????通過這五個信息,網絡中的設備就能精準地識別每一次通信,不會出現數據錯亂的情況。在電腦上,我們可以通過netstat -n
命令查看當前的通信五元組信息。
2.1.3 端口號的范圍劃分?
端口號是一個 16 位的數字,范圍從 0 到 65535,根據用途可以分為兩類:
-
知名端口號(0-1023):這些端口號是 “標準化” 的,被一些廣泛使用的應用層協議固定占用。比如:
- SSH(遠程登錄)協議使用 22 端口;
- FTP(文件傳輸)協議使用 21 端口;
- Telnet(遠程終端)協議使用 23 端口;
- HTTP(超文本傳輸)協議使用 80 端口;
- HTTPS(加密的超文本傳輸)協議使用 443 端口。
????????我們自己開發程序時,要避開這些端口號,否則會和系統中的標準服務發生沖突。
-
動態分配端口號(1024-65535):這些端口號主要供客戶端應用程序臨時使用。當你打開一個客戶端程序(比如瀏覽器),操作系統會從這個范圍中隨機分配一個端口號作為 “源端口”,當程序關閉后,這個端口號會被釋放,以便下次分配給其他程序。
2.1.4 關于端口號的兩個常見問題
-
一個進程可以綁定多個端口號嗎?
可以。比如一臺服務器上的監控程序,可能需要同時監聽多個端口,以便監控不同的服務,它可以通過綁定多個端口來實現這一功能。 -
一個端口號可以被多個進程綁定嗎?
通常情況下不可以。端口號就像門牌號,一個門牌號不能同時屬于多個住戶。如果兩個進程試圖綁定同一個端口號,操作系統會提示 “端口已被占用” 的錯誤。不過,在一些特殊的網絡架構(如負載均衡)中,通過特殊的配置可以實現多個進程 “共享” 一個端口號,但這并不是默認情況。
2.2 UDP 協議:簡單直接的 “快遞服務”
????????傳輸層有兩大核心協議:UDP 和 TCP。UDP(用戶數據報協議)是其中相對簡單的一個,它就像一種 “普通快遞服務”,簡單直接,但不保證數據一定能送達。
2.2.1 UDP 協議的格式
UDP 的協議首部非常簡潔,只有 8 個字節,包含四個部分:
- 16 位源端口號:發送數據的應用程序端口號;
- 16 位目的端口號:接收數據的應用程序端口號;
- 16 位 UDP 長度:整個 UDP 數據報(包括首部和數據)的總長度,最大為 65535 字節(近似值:64KB)一旦數據報的長度超出64KB,此時可能導致數據出現截斷;
- 16 位 UDP 檢驗和:用于檢查數據在傳輸過程中是否損壞,如果檢驗失敗,數據會被直接丟棄。
16位源端口號 | 16位目的端口號 | 16位UDP長度 | 16位UDP檢驗和 | 數據 |
UDP報頭 | UDP載荷 |
這種簡潔的格式讓 UDP 的處理效率很高,開銷也很小。
2.22?UDP 的特點
-
無連接:使用 UDP 發送數據時,不需要像打電話那樣先 “撥號建立連接”,只要知道對方的 IP 地址和端口號,就可以直接發送數據。就像寄普通快遞,你不需要提前通知收件人,填好地址直接投遞即可。
-
不可靠:UDP 沒有確認機制和重傳機制。如果數據在傳輸過程中因為網絡擁堵、故障等原因丟失了,UDP 不會通知發送方,也不會重新發送數據。這就像普通快遞如果丟失了,快遞公司可能不會主動告訴你,除非你自己查詢。
-
面向數據報:應用層交給 UDP 多長的報文,UDP 就原樣發送,既不會拆分,也不會合并。比如,應用層一次給 UDP 100 字節的數據,UDP 就封裝成一個 100 字節的數據包發送;接收端必須一次接收完整的 100 字節,不能分 10 次每次接收 10 字節。這就像你寄一個 10 斤的包裹,快遞不會把它拆成 10 個 1 斤的小包裹,收件人也必須一次把整個包裹取走。
2.2.3 UDP 的使用注意事項?
????????由于 UDP 協議首部中 “UDP 長度” 字段是 16 位,這意味著整個 UDP 數據報(包括首部和數據)的最大長度是 65535 字節,去掉 8 字節的首部,UDP 能傳輸的最大數據量約為 64KB。
????????在當今的網絡環境中,64KB 的數據量往往滿足不了需求(比如傳輸一個大文件)。這時候,就需要在應用層手動處理:把大文件拆分成多個不超過 64KB 的小數據包,分別發送,接收端收到后再重新拼接成完整的文件。
2.2.4 基于 UDP 的應用層協議
雖然 UDP 不保證可靠性,但它憑借高效、低延遲的特點,被很多重要的應用層協議采用:
- DNS(域名系統):負責把域名(如www.baidu.com)轉換為 IP 地址,每次查詢的數據量小,使用 UDP 能快速完成查詢。
- DHCP(動態主機配置協議):新設備接入網絡時,通過 DHCP 自動獲取 IP 地址等網絡配置信息,需要快速響應,UDP 是合適的選擇。
- TFTP(簡單文件傳輸協議):在局域網內傳輸小文件,追求簡單高效,UDP 能滿足需求。
- NFS(網絡文件系統):用于在局域網內共享文件,對傳輸速度有一定要求,UDP 能提供較好的性能。
????????當然,我們自己開發 UDP 程序時,也可以根據需求自定義應用層協議。
2.3?TCP 協議:可靠傳輸的 “幕后管家”?
????????TCP全稱為 "傳輸控制協議(Transmission Control Protocol"). 人如其名, 要對數據的傳輸進行一個詳細的控制;
2.3.1 TCP 協議段格式:“快遞面單” 的復雜設計
????????TCP 協議段(也叫 “TCP 報文段”)的格式比 UDP 復雜得多,這是因為它要實現 “可靠傳輸”,需要更多控制信息。我們把 TCP 報文段想象成 “帶復雜條款的快遞面單”,每個字段都有特殊作用:
16 位源端口號:標識發送數據的應用進程,告知接收方 “數據從哪個進程來”,用于區分同一主機上不同應用的網絡數據。
16 位目的端口號:標識接收數據的應用進程,告知接收方 “數據要到哪個進程去”,讓接收端準確將數據交付對應應用。
32 位序號:TCP 是面向字節流傳輸,序號對發送的字節流編號,確保數據按序重組,解決網絡傳輸亂序問題,比如發送數據段 “1 - 1000 字節”,序號字段填起始編號,接收方依序號拼接。
32 位確認序號:期望收到對方下一個報文段的第一個數據字節的序號,用于確認已收到此前數據,實現可靠傳輸的 “確認機制”,若收到序號 1-1000??數據,確認序號填 1001 ,告知對方期待從該序號繼續發。
4 位首部長度:表示 TCP 首部占多少個 32 位(4 字節)塊,用于接收方解析報文時,區分首部和數據邊界,最大為 15,即首部最大 15×4 = 60 字節(含選項時變長)。
保留(6 位):為未來擴展預留,當前無實際功能,默認置?0
,避免新功能與現有實現沖突。
6 位標志位(URG、ACK、PSH、RST、SYN、FIN)
- URG:緊急指針是否有效,為?
1
?時,緊急指針字段標識緊急數據在報文段中的位置,優先處理。 - ACK:確認序號是否有效,連接建立后一般恒為 1,用于確認收到數據。
- PSH:提示接收端應用程序 “立刻從 TCP 緩沖區讀走數據”,無需等緩沖區滿,加快交互。
- RST:要求重新建立連接,即 “復位報文段”,網絡異常(如連接超時、端口未監聽)時,發送帶?
RST
?的報文重置連接。 - SYN:請求建立連接,“同步報文段” 標志,三次握手時,發起方發 SYN = 1 的報文,協商初始序號。
- FIN:通知對方 “本端要關閉連接”,“結束報文段” 標志,四次揮手時,主動關閉方發 FIN=1 報文,告知對方不再發數據。
16 位窗口大小:用于 TCP 流量控制,接收方通過此字段告知發送方 “自己的接收緩沖區剩余容量”,發送方據此調整發送速率,避免接收方緩沖區溢出。
16 位檢驗和:發送端用 CRC 校驗算法,對 TCP 首部、數據部分計算校驗值;接收端重新計算校驗,若不一致則認為數據傳輸出錯,丟棄或重傳,保障數據完整性。
16 位緊急指針:僅當 URG=1 時有效,標識緊急數據在報文段中的偏移量,配合 URG 優先處理緊急內容(如遠程登錄的中斷指令)。
選項(如果有):可選字段,用于實現額外功能(如協商最大報文段長度 MSS、時間戳等),非必選,無選項時首部長度由 4位首部長度 體現(不超 60 字節)。
數據(如果有):承載應用層數據(如 HTTP 報文、文件內容等),TCP 作為傳輸層協議,最終目的是可靠交付這部分數據到應用進程 。
2.3.2 確認應答(ACK):“收到請回復” 的保障機制
????????TCP 的可靠傳輸基礎就是 “確認應答”—— 發送方發數據后,必須收到接收方的 “確認回復(ACK)”,才認為數據成功送達。如果沒收到,就重新發送。
流程拆解:
- 發送方:發數據(帶 Seq=X)→ 啟動 “定時器”→ 等待 ACK
- 接收方:收到數據→ 回復 ACK(Ack=X+1)
- 發送方:收到 ACK→ 關閉定時器→ 繼續發下一段數據
????????如果發送方定時器超時(比如網絡擁堵,ACK 丟了),就重新發送數據。這就像你給朋友發消息:“幫我取個快遞”(Seq=100),然后盯著手機等回復。如果朋友回復 “收到”(Ack=101),你就放心了;如果半天沒回復(超時),你會再發一遍 “幫我取個快遞”。
關鍵點:
- ACK 是 “累積確認”:接收方不需要對每個字節確認,而是確認 “到某個序列號為止的所有數據都收到了”。比如收到 Seq=100、200、300 的數據,直接回復 Ack=301,代表 “100-300 都收到,下次發 301”。
- 超時時間動態調整:網絡情況復雜,超時時間不能固定(比如 Wi-Fi 很快,4G 可能慢)。TCP 會根據網絡延遲動態計算超時時間(類似 “堵車時多等一會兒,不堵車少等”)。
2.3.3 超時重傳:“沒收到回復就重試” 的保險
????????雖然有確認應答,但網絡是不可靠的 —— 數據可能丟包,ACK 也可能丟包。這時候,超時重傳就是 TCP 的 “保險措施”:
兩種丟包場景及處理:
- 數據丟包:發送方發 seq=1000?的數據→ 超時沒收到 ACK→ 重傳 Seq=1000
- ACK 丟包:發送方發 Seq=1000→ 接收方收到并回復 Ack=1001→ 但 Ack 丟包→ 發送方超時→ 重傳 Seq=1000→ 接收方發現 “數據已收到”,但還是會回復 Ack=1001→ 發送方收到后,就知道之前的 ACK 丟了,但數據其實已送達。
問題:重傳可能導致 “重復數據”,但 TCP 靠序列號解決 —— 接收方會自動丟棄重復的 Seq 數據(比如收到兩個 Seq=1000,只保留一個,回復 Ack=1001)。
那么, 如果超時的時間如何確定?
- 最理想的情況下, 找到一個最小的時間, 保證 "確認應答一定能在這個時間內返回"。
- 但是這個時間的長短, 隨著網絡環境的不同, 是有差異的.
- 如果超時時間設的太長, 會影響整體的重傳效率;
- 如果超時時間設的太短, 有可能會頻繁發送重復的包;
TCP為了保證無論在任何環境下都能比較高性能的通信, 因此會動態計算這個最大超時時間:
- Linux中(BSD Unix和Windows也是如此), 超時以500ms為一個單位進行控制, 每次判定超時重發的。
- 超時時間都是500ms的整數倍。
- 如果重發一次之后, 仍然得不到應答, 等待 2*500ms 后再進行重傳。
- 如果仍然得不到應答, 等待 4*500ms 進行重傳. 依次類推, 以指數形式遞增。
- 累計到一定的重傳次數, TCP認為網絡或者對端主機出現異常, 強制關閉連接。
總結:
????????重傳的超時時間的閾值不是固定不變的,隨著重傳次數的增加而增大(重傳頻率越來越低)。如果多次重傳之后還丟包,說明當前丟包的概率太大了,這個時候意味著網絡已經出現非常嚴重的故障了,再傳也意義不大,就只能放棄重傳。
2.3.4 連接管理:“三次握手建連接,四次揮手斷連接”
????????TCP 通信前必須先建立連接(類似 “打電話前先撥號接通”),通信后要斷開連接(類似 “掛電話”)。這就是著名的三次握手(建立連接)和四次揮手(斷開連接)。
三次握手(建立連接:SYN、SYN+ACK、ACK)
- 客戶端→服務器:發SYN 包(控制位 SYN=1,Seq = 隨機數 X)→ 請求建立連接。
- 服務器→客戶端:回復SYN+ACK 包(SYN=1,ACK=1,Seq = 隨機數 Y,Ack=X+1)→ 同意建立連接。
- 客戶端→服務器:發ACK 包(ACK=1,Seq=X+1,Ack=Y+1)→ 確認連接建立。
?
為什么是三次??兩次不行嗎?
- 兩次握手可能導致 “失效的連接請求”。比如客戶端發的 SYN 包因網絡延遲,20 秒后才到服務器,服務器以為是新請求,回復 SYN+ACK。但客戶端早已超時放棄,不會回復 ACK。服務器會一直等 ACK,浪費資源。
- 三次握手能確保雙方都能收發數據:客戶端確認 “能發能收”(收到服務器的 SYN+ACK,說明服務器能收且能發);服務器確認 “能收能發”(收到客戶端的 ACK,說明客戶端能收)。
?為什么要進行握手??意義何在?
- 三次握手,可以先針對通信路徑,進行投石問路,初步的確認一下通信鏈路是否通暢(可靠性的前提條件)。
- 三次握手,是在驗證通信雙方,發送能力和接收能力是否正常。
- 三次握手的過程中也會協商一些必要的參數。通信是客戶端、服務器雙方的事情,要配合,其中有些內容要保持一致。參數往往是在選項中體現。
? ? ? ? 建立連接本質上就是讓通信雙方保存對方的信息在相應的數據結構中,?斷開連接的本質目的就是為了把對端的信息,從數據結構中給刪除掉、釋放掉。
四次揮手(斷開連接:FIN、ACK、FIN+ACK、ACK)
- 客戶端→服務器:發FIN 包(FIN=1,Seq=U)→ 告訴服務器 “我要斷開,沒數據發了”。
- 服務器→客戶端:回復ACK 包(ACK=1,Ack=U+1,Seq=V)→ 告訴客戶端 “收到斷開請求,我處理一下剩余數據”。
- 服務器→客戶端:發FIN+ACK 包(FIN=1,ACK=1,Seq=W,Ack=U+1)→ 告訴客戶端 “我也沒數據了,同意斷開”。
- 客戶端→服務器:回復ACK 包(ACK=1,Seq=U+1,Ack=W+1)→ 確認斷開。
?
為什么是四次??因為 “斷開請求” 和 “剩余數據處理” 要分開:
- 客戶端發 FIN 后,服務器可能還有數據沒發完(比如緩存的消息),所以先回復 ACK “收到斷開請求”,等數據發完再發 FIN “同意斷開”。
- 客戶端最后發 ACK,是為了讓服務器確認 “斷開請求已收到”,避免服務器重發 FIN。
LISTEN(監聽狀態):
- 角色:僅服務端會進入這個狀態。
- 作用:服務端調用 listen() 后,socket 進入 LISTEN 狀態,開始被動等待客戶端的連接請求。可以理解為 “開門營業,等客戶上門”,此時服務端會維護一個「半連接隊列」(處理 SYN 包)和「全連接隊列」(處理已完成三次握手的連接)。
ESTABLISHED(已建立連接狀態):
- 角色:客戶端和服務端成功完成三次握手后,雙發都會進入這個狀態。
- 作用:表示 TCP 連接已 “就緒”,雙方可以正常收發數據(應用層的 read/write 都會基于這個狀態的連接)。是 TCP 數據傳輸的 “黃金階段”,大部分業務邏輯(如 HTTP 請求 / 響應)都在這個狀態完成。
CLOSE_WAIT(關閉等待狀態):
- 角色:被動關閉方(一般是服務端,也可能是客戶端)會進入這個狀態。
- 作用:當主動關閉方(比如客戶端)發 FIN 包請求斷開連接時,被動關閉方收到 FIN 后,會先回 ACK,然后進入 CLOSE_WAIT。這個狀態的含義是:“我(被動方)已經知道對方要斷開,但我可能還有未發完的數據,需要先處理完自己的『收尾工作』(比如應用層繼續讀數據、發數據)”。
- 注意:如果代碼里沒及時關閉 socket,CLOSE_WAIT 會持續存在,可能導致資源泄漏(比如服務端大量連接 stuck 在 CLOSE_WAIT,最終無法接受新連接 )。
TIME_WAIT(時間等待狀態):
- 角色:主動關閉方(發完最后一個 ACK 的一端,一般是客戶端)會進入這個狀態。
- 核心作用:
- 防止遲到的數據包干擾新連接:TCP 是基于 IP 的,網絡里可能有延遲 / 重傳的包。TIME_WAIT 會等待 2MSL(MSL 是報文最大生存時間,一般是 1 - 2 分鐘 ),確保舊連接的殘留報文都消失,新連接不會被舊包 “搞混”。
- 可靠關閉連接:如果最后一個 ACK 丟了,被動關閉方會重發 FIN,TIME_WAIT 狀態讓主動關閉方有機會重傳 ACK,保證雙方都能 “干凈斷開”。
- 小坑:高并發場景下,大量 TIME_WAIT 可能占用端口(默認端口范圍有限),導致新連接失敗。可以通過調整內核參數(如 tcp_tw_reuse)優化,但需謹慎。
簡單總結:
LISTEN
?是服務端 “等連接” 的狀態;ESTABLISHED
?是 “正常傳數據” 的狀態;CLOSE_WAIT
?是被動關閉方 “處理收尾” 的狀態;TIME_WAIT
?是主動關閉方 “兜底防殘留” 的狀態。????????這些狀態共同保障了 TCP 連接 “建立 - 傳輸 - 斷開” 的可靠性,但也會帶來一些運維 / 開發需要關注的細節(比如 CLOSE_WAIT 泄漏、TIME_WAIT 端口占用 )。
2.3.5 滑動窗口:“批量發貨,提升效率” 的優化?
????????剛才我們討論了確認應答策略, 對每一個發送的數據段, 都要給一個ACK確認應答。收到ACK后再發送下一個數據段。?如果每次發數據都要等 ACK,效率太低(尤其網絡快時,等待時間占比大)。
????????TCP 用滑動窗口(Sliding Window)?優化:允許發送方一次性發多個數據段,不用等一個 ACK 再發下一個。?
核心邏輯:
- 窗口大小指的是無需等待確認應答而可以繼續發送數據的最大值。上圖的窗口大小就是4000個字節(四個段)。
- 發送前四個段的時候, 不需要等待任何ACK, 直接發送;
- 收到第一個ACK后, 滑動窗口向后移動,繼續發送第五個段的數據; 依次類推;
- 操作系統內核為了維護這個滑動窗口,需要開辟發送緩沖區來記錄當前還有哪些數據沒有應答;只有確認應答過的數據, 才能從緩沖區刪掉;
- 窗口越大,則網絡的吞吐率就越高;
舉個例子:
????????窗口大小 = 3,發送方發 Seq=1000、2000、3000→ 收到 Ack=1001(確認第一個)→ 窗口滑動,可發 Seq=4000。相當于 “流水線作業”,不用等全部 ACK,提升了效率。
那么如果出現了丟包,如何進行重傳?這里分兩種情況討論:
情況一:數據包已經抵達,ACK被丟了。
這種情況下,部分ACK丟了并不要緊,因為可以通過后續的ACK進行確認;
情況二:數據包就直接丟了。必然需要重傳數據了~~
丟失報文的 “提醒”:
????????接收端未收到 1001 - 000 字節,會持續返回 ACK = 1001 表示 “期望接收從1001開始的數據” )。這相當于接收端在向發送端 “喊話”:“我要的是 1001 開頭的數據,之前丟了,快補發!”?
觸發快速重傳條件:
????????發送端連續 3 次收到相同的 ACK = 1001 時,判斷 “對應數據(1001 - 2000字節)極有可能丟失”,直接啟動快速重傳 ,主動補發該段數據。
接收端的 “緩沖兜底”:
????????其實接收端可能早已收到 2001 - 7000 字節(網絡亂序等原因),但因 1001 - 2000 缺失,這些數據被暫存于接收端內核的接收緩沖區 。
????????當發送端補發 1001 - 2000 后,接收端確認完整收到 1 - 7000 字節,會返回 ACK = 7001(表示 “已接收 1 - 7000 字節,接下來期望 7001 開頭的數據” ),讓傳輸恢復正常。
2.3.6 流量控制:“別發太猛,我處理不過來”
????????接收端處理數據的速度是有限的。如果發送端發的太快,導致接收端的緩沖區被打滿,這個時候如果發送端繼續發送,就會造成丟包,繼而引起丟包重傳等等一系列連鎖反應。
????????因此TCP支持根據接收端的處理能力, 來決定發送端的發送速度。這個機制就叫做流量控制(FlowControl);
流量控制是接收方限制發送方速度的機制,靠 “窗口大小” 實現:
- 接收方:根據自己的處理能力(比如緩存大小、CPU 負載),在 ACK 里設置 “窗口大小”(比如窗口 = 1000 字節)。
- 發送方:根據接收方的窗口大小,調整發送速度(最多發 1000 字節的數據,等收到 ACK 再調整)。
????????如果接收方處理不過來(比如緩存滿了),可以把窗口設為 0→ 發送方停止發數據,直到接收方窗口 > 0。
類比:
????????你給朋友傳文件,朋友說 “我硬盤只剩 1G 空間了(窗口 = 1G)”,你就只傳 1G 的數據;等朋友清理出空間,告訴你 “現在有 2G 空間(窗口 = 2G)”,你再傳更多。
2.3.7 擁塞控制:“別把網絡搞堵了” 的全局優化
????????TCP 依靠滑動窗口實現了高效可靠的大數據量傳輸,但這一機制并非 “萬能解藥”—— 若連接建立初期就盲目發送大量數據,仍會引發網絡問題。
????????網絡環境中存在多臺計算機,可能本身已處于擁塞狀態(如鏈路帶寬不足、路由轉發壓力大)。此時若不了解網絡實際狀況,直接發送大量數據,會進一步加劇擁塞,導致數據延遲、丟包甚至傳輸中斷,形成 “雪上加霜” 的惡性循環。
????????為解決上述問題,TCP 引入慢啟動(Slow Start) 機制:連接初期不直接發送大量數據,而是先以小量數據試探,通過觀察數據傳輸反饋(如 ACK 確認、丟包情況),動態感知網絡擁塞程度,再據此逐步調整后續數據發送速率,實現 “既避免加劇擁塞,又能高效利用網絡資源” 的平衡。
????????簡單說,慢啟動是 TCP 應對復雜網絡的 “謹慎探路者”—— 用 “先小步試探,再按需加速” 的策略,讓數據傳輸適配網絡狀態,保障整體傳輸的穩定性與效率 。
核心算法(簡化版):
- 慢啟動(Slow Start):剛建立連接時,窗口從小到大指數增長(比如初始窗口 = 1→ 發 1 個數據段→ 收到 ACK→ 窗口 = 2→ 發 2 個→ 收到 ACK→ 窗口 = 4……),快速探測網絡狀態。
- 擁塞避免(Congestion Avoidance):窗口增長到 “慢啟動閾值” 后,改為線性增長(窗口每次 + 1),避免增長過快搞堵網絡。
- 擁塞發生(Congestion Detection):如果發生超時重傳或快速重傳,說明網絡擁堵→ 降低慢啟動閾值,重新慢啟動。
- 此處引入一個概念程為擁塞窗口;
- 發送開始的時候, 定義擁塞窗口大小為1;
- 每次收到一個ACK應答, 擁塞窗口加1;
- 每次發送數據包的時候, 將擁塞窗口和接收端主機反饋的窗口大小做比較, 取較小的值作為實際發送的窗口;
????????像上面這樣的擁塞窗口增長速度, 是指數級別的. "慢啟動" 只是指初使時慢, 但是增長速度非常快。
- 為了不增長的那么快, 因此不能使擁塞窗口單純的加倍;
- 此處引入一個叫做慢啟動的閾值;
- 當擁塞窗口超過這個閾值的時候, 不再按照指數方式增長, 而是按照線性方式增長;
- 當TCP開始啟動的時候, 慢啟動閾值等于窗口最大值;
- 在每次超時重發的時候, 慢啟動閾值會變成原來的一半, 同時擁塞窗口置回1;
????????少量的丟包, 我們僅僅是觸發超時重傳; 大量的丟包, 我們就認為網絡擁塞;當TCP通信開始后,網絡吞吐量會逐漸上升;隨著網絡發生擁堵,吞吐量會立刻下降;
????????擁塞控制,歸根結底是TCP協議想盡可能快的把數據傳輸給對方,但是又要避免給網絡造成太大壓力的折中方案。?
2.3.8 延遲應答:“攢一波回復,減少交互”
????????接收方收到數據后,不是立刻回復 ACK,而是延遲一會兒(比如幾十毫秒),等應用層取走數據,或者 “攢幾個 ACK 一起回復”。這樣做的好處是:
- 優化網絡交互:減少 ACK 的數量(比如攢 3 個 ACK 一起發),降低網絡開銷。
- 輔助流量控制:延遲期間,接收方的窗口可能變大(應用層取走數據,緩存空了),可以告訴發送方 “窗口更大了,多發包”。
注意:
????????延遲不能太久(否則發送方超時重傳),一般由操作系統調優(比如 Linux 默認延遲 40ms 左右)。
2.3.9 捎帶應答:“順手回復,一石二鳥”?
????????捎帶應答是基于延時應答引入的機制,TCP 允許在回復數據時,順手把 ACK 帶過去,不用單獨發 ACK 包。
例子:
????????客戶端給服務器發請求(Seq=100),服務器處理完要給客戶端發響應數據(Seq=200)。這時候,服務器可以在響應數據的 TCP 頭里,把 Ack=101(確認客戶端的請求)捎帶過去。相當于 “回消息時順便說‘收到你的消息了’”,減少一次單獨的 ACK 交互。
2.3.10 面向字節流:“數據無邊界,但有序”?
- 創建一個TCP的socket,同時在內核中創建一個 發送緩沖區 和一個 接收緩沖區;
- 調用write時,數據會先寫入發送緩沖區中;
- 如果發送的字節數太長,會被拆分成多個TCP的數據包發出;
- 如果發送的字節數太短,就會先在緩沖區里等待, 等到緩沖區長度差不多了,或者其他合適的時機發送出去;
- 接收數據的時候,數據也是從網卡驅動程序到達內核的接收緩沖區;
- 然后應用程序可以調用read從接收緩沖區拿數據;
- 另一方面,TCP的一個連接,既有發送緩沖區,也有接收緩沖區,那么對于這一個連接,既可以讀數據,也可以寫數據。這個概念叫做 全雙工;
由于緩沖區的存在,TCP程序的讀和寫不需要一一匹配,例如:
- 寫100個字節數據時, 可以調用一次write寫100個字節, 也可以調用100次write, 每次寫一個字節;
- 讀100個字節數據時,也完全不需要考慮寫的時候是怎么寫的, 既可以一次read 100個字節,也可以一次read一個字節, 重復100次;
2.3.11 粘包問題:“數據粘在一起,怎么拆分?”
????????因為 TCP 是面向字節流的,發送方連續發多個數據段,接收方可能 “一次性收到多個段的數據”,導致粘包(比如本該是兩條消息 “你好”“吃飯了嗎”,接收方收到 “你好吃飯了嗎”)。
解決方法(應用層實現):
- 定長消息:每個消息固定長度(比如每條消息 100 字節),接收方按長度拆分。
- 分隔符:用特殊字符分隔消息(比如換行符 “\n”),接收方按分隔符拆分。
- 長度字段:每個消息前加 “長度字段”(比如前 4 字節表示消息長度),接收方先讀長度字段,再讀對應長度的數據。
2.3.12 異常情況:“遇到問題,如何處理?”
如果發送丟包更嚴重的情況,甚至說網絡直接出現故障的情況,如何處理??
進程崩潰場景:
????????不管進程是正常結束,還是因故障異常崩潰,系統都會自動回收文件資源、關閉相關文件?。這一過程會觸發 TCP 連接的四次揮手流程?,目的是讓通信雙方有序關閉連接,確認彼此刪除保存的連接信息 。
????????TCP 連接的生命周期和進程生命周期不完全綁定,即便進程已退出,只要 TCP 連接還在,就能依靠系統中留存的連接信息,完成后續揮手操作,和正常流程的四次揮手,在核心邏輯(確認雙方刪除連接信息)上是一致的 。
正常關機場景:
????????當主機執行正常關機操作,系統會強制終止所有進程?,進而觸發TCP連接的四次揮手流程 。不過實際中,若關機動作很快,四次揮手可能還沒完整執行,系統就關閉了 。
- 理想情況:本端和對端能順利配合,正確刪除各自保存的連接信息,平穩關閉連接 。
- 非理想情況:至少本端能發送第一個 FIN 報文,告知對端 “我方要結束連接” 。對端收到 FIN 后,會進入連接釋放流程,回復 ACK 并發送自己的 FIN(此 FIN 無需 ACK 回應 );若對端發的 FIN 沒收到 ACK,會啟動超時重傳機制 。多次重傳仍無回應時,對端會單方面釋放連接信息 ,類似協商離婚不成,通過 “法律手段” 強制終止關系 。
突發斷電(直接拔電源)場景:
????????直接拔電源會讓機器瞬間關機,大概率來不及發送 FIN 報文。
- 接收方斷電:
????????發送方發現長時間沒收到 ACK 確認,會觸發報文重傳。重傳多次仍失敗后,TCP 會執行 “復位(RST)” 操作,清除原連接的臨時數據,重新初始化連接 。這個過程靠 “復位報文段(RST)” 實現,RST 報文不帶 ACK 確認 ;若重傳、復位都沒用,發送方會單方面放棄連接 。
-
發送方斷電
????????接收方本就在等待發送方的消息,結果遲遲沒收到,需判斷發送方是 “徹底斷開” 還是 “暫時沒發” 。TCP 協議里,接收方若一段時間沒收到數據,會主動發?“心跳包”?探測對端狀態 。若對端沒回應心跳,接收方會嘗試復位連接,單方面釋放連接資源 。
????????這里的心跳包是 TCP 層不帶應用層數據的特殊報文?,按固定周期發送(類似 “定期打電話問平安” );若收不到心跳回應,就判定對端 “故障失聯” 。
網線斷開場景:
????????本質是 “接收方斷電 + 發送方斷電” 兩種情況的組合 。發送方會因收不到?ACK
?重傳報文,接收方會因收不到數據發心跳探測,雙方多次嘗試無果后,觸發連接復位、單方面釋放等操作,處理邏輯融合了前兩種場景的機制 。
2.3.13基于 TCP 的應用層協議
TCP 的可靠性讓它成為很多應用層協議的基礎:
- HTTP/HTTPS:網頁傳輸,必須確保數據完整(否則頁面顯示錯亂)。
- FTP:文件傳輸,大文件不能丟包。
- SMTP/POP3/IMAP:郵件收發,郵件內容不能丟失。?
2.4 TCP/UDP對比
TCP:有連接、可靠傳輸、面向字節流、全雙工。
UDP:無連接、不可靠傳輸、面向數據報、全雙工。
????????我們說了TCP是可靠連接,那么是不是TCP一定就優于UDP呢??TCP和UDP之間的優點和缺點, 不能簡單,絕對的進行比較。
- TCP用于可靠傳輸的情況, 應用于文件傳輸, 重要狀態更新等場景;
- UDP用于對高速傳輸和實時性要求較高的通信領域, 例如, 早期的QQ, 視頻傳輸等. 另外UDP可以用于廣播;
????????歸根結底,TCP和UDP都是程序員的工具,什么時機用,具體怎么用, 還是要根據具體的需求場景去判定。?