計算機網絡 : 傳輸層協議UDP與TCP
目錄
- 計算機網絡 : 傳輸層協議UDP與TCP
- 引言
- 1. 傳輸層協議UDP
- 1.2 UDP協議段格式
- 1.3 UDP的特點
- 1.4 面向數據報
- 1.5 UDP的緩沖區
- 1.6 基于UDP的應用層協議及使用注意事項
- 2. 傳輸層協議TCP
- 2.1 再談端口號
- 2.2 TCP協議段格式
- 2.3 確認應答(ACK)機制
- 2.4 超時重傳機制
- 2.5 連接管理機制
- 2.6 理解TIME_WAIT狀態
- 2.7 滑動窗口
- 2.8 流量控制
- 2.9 擁塞控制
- 2.10 延遲應答
- 2.11 捎帶應答
- 2.12 面向字節流
- 2.13 粘包問題
- 2.14 TCP異常情況
- 2.15 基于TCP應用層協議與小結
- 3. TCP/UDP對比
引言
在計算機網絡中,傳輸層是實現端到端通信的關鍵,而UDP和TCP則是傳輸層最核心的兩種協議。它們各自具有獨特的特點和適用場景,為不同的網絡需求提供了多樣化的解決方案。
- UDP(用戶數據報協議):以無連接、不可靠但高效的方式傳輸數據,適用于實時性要求高、允許少量丟包的應用,如視頻流和DNS查詢。
- TCP(傳輸控制協議):通過連接管理、確認應答、流量控制等機制確保數據的可靠傳輸,廣泛應用于文件傳輸、網頁瀏覽等對數據完整性要求嚴格的場景。
本文將深入解析UDP與TCP的協議格式、工作機制、優缺點以及典型應用場景,幫助你理解如何根據實際需求選擇合適的傳輸層協議。
1. 傳輸層協議UDP
1.2 UDP協議段格式
-
16位UDP長度表示整個數據報(UDP首部(8字節定長報頭)+UDP數據)的最大長度
-
如果校驗和出錯,就會直接丟棄
-
16位端口號是因為內核協議是16位;協議本質就是結構體;OS之間直接傳遞結構體對象。
-
對報文的理解
-
在操作系統內部,一定會存在大量的報文,操作系統將這些報文結構體鏈接在隊列當中,其結構體中的
*head
,*data
,*tail
,*end
指針指向內存中存儲報頭和有效載荷的空間。(可以類比進程的PCB結構體和數據) -
所謂的封裝和解包,本質就是移動
data
指針在緩沖區中的指向,而指針+-
的是對應層報文的長度。 -
data -= sizeof(struct udphdr) (struct udphdr*)data->...
-
-
1.3 UDP的特點
- UDP傳輸的過程類似于寄信
- 無連接(知道對端的IP和端口號就直接傳輸,不需要建立連接)
- 不可靠(沒有確認和重傳機制,若因網絡故障無法送達,UDP協議層不會給應用層返回錯誤信息)
- 面向數據報(不能靈活控制讀寫數據的次數和數量)
1.4 面向數據報
-
應用層交給UDP多長的報文,UDP就原樣發送,既不會拆分也不會合并。
-
例如用UDP傳輸100個字節的數據時:
如果發送端調用一次
sendto
發送100個字節,那么接收端也必須調用對應的一次recvfrom
接收100個字節,而不能循環調用10次recvfrom
每次接收10個字節。
1.5 UDP的緩沖區
- UDP沒有真正意義上的發送緩沖區,調用
sendto
會直接交給內核,由內核將數據傳給網絡層協議進行后續傳輸; - 但UDP具有接收緩沖區,不過這個緩沖區不能保證收到的UDP報文順序與發送順序一致,且緩沖區滿時新到達的UDP數據會被丟棄。
- 全雙工特性:UDP的
socket
既能讀也能寫,這種特性稱為全雙工。 - UDP的接收緩沖區中,是存放一整個報文,通過隊列進行管理;而TCP的接收緩沖區中只有有效載荷,報頭已經被去掉了。
1.6 基于UDP的應用層協議及使用注意事項
-
基于UDP的應用層協議
- NFS:網絡文件系統;
- TFTP:簡單文件傳輸協議;
- DHCP:動態主機配置協議;
- BOOTP:啟動協議(用于無盤設備啟動);
- DNS:域名解析協議。
- 包括你自己寫 UDP 程序時自定義的應用層協議。
-
UDP使用注意事項
- 我們注意到,UDP 協議首部中有一個 16 位的最大長度,也就是說一個 UDP 能傳輸的數據最大長度是 64K(包含 UDP 首部)。
- 然而 64K 在當今的互聯網環境下是一個非常小的數字。如果我們需要傳輸的數據超過 64K,就需要在應用層手動分包,多次發送,并在接收端手動拼裝。
2. 傳輸層協議TCP
2.1 再談端口號
端口號(Port)標識了一個主機上進行通信的不同的應用程序;
在 TCP/IP 協議中,用"源 IP"、“源端口號”、“目的 IP”、“目的端口號”、"協議號"這樣一個五元組來標識一個通信(可以通過 netstat -n
查看)。
-
端口號劃分
- 0-1023為知名端口號,HTTP、FTP、SSH等廣為使用的應用層協議均使用固定的知名端口號;
- 1024-65535為操作系統動態分配的端口號范圍,客戶端程序的端口號通常由操作系統從該范圍內動態分配。
-
知名端口號
- SSH服務器使用22端口,
- FTP服務器使用21端口,
- Telnet服務器使用23端口,
- HTTP服務器使用80端口,
- HTTPS服務器使用443端口。
-
查看知名端口號命令
cat /etc/services
2.2 TCP協議段格式
-
源/目的端口號表示數據是從哪個進程來,到哪個進程去。
-
32位序號/32位確認號:在確認應答機制中會詳細講解。
-
4位TCP報頭長度的單位是
4字節
,表示該TCP頭部有多少個32位bit(有多少個4字節),所以TCP頭部最大長度是15*4=60字節,根據規定頭部最短應為20字節,所以tcp
報頭范圍為[5,15]
。 -
6位標志位包括:
- URG(緊急指針是否有效),有效說明16位緊急指針會在當前報文的有效載荷中的特定偏移量處,有緊急數據。
- ACK(確認號是否有效),此標志幾乎被常設為1。
- PSH(提示接收端應用程序立刻從TCP緩沖區把數據讀走)
- RST(通信過程中連接出現任何問題,對方可以要求重新建立連接,攜帶RST標識的稱為復位報文段)
- SYN(請求建立連接,攜帶SYN標識的稱為同步報文段)
- FIN(通知對方本端要關閉了,攜帶FIN標識的稱為結束報文段)
-
16位窗口大小:會在流量控制進行詳細講解。
-
16位校驗和由發送端填充(CRC校驗),接收端校驗不通過則認為數據有問題(此處的檢驗和包含TCP首部和數據部分);
-
16位緊急指針標識哪部分數據是緊急數據;
-
40字節頭部選項暫時忽略。
2.3 確認應答(ACK)機制
-
TCP將每個字節的數據都進行了編號,即為序列號(一段數據的最大序列號為序號)。
-
每一個ACK都帶有對應的確認序列號(確認序列號 = 序號+1),其意義是告訴發送者,我已經收到了這個確認序號之前的數據了,下一次你可以從我這個確認序號開始發送數據。
-
總結:TCP報頭中有2個序號(攜帶應答),確認序號是對對方報文做確認,同時序號是自己的報文要發送給對方。
-
可靠性的本質:
- 應答可以保證對歷史信息的可靠性。
- 通信中,最新的報文永遠沒有應答,所以最新報文的可靠性無法保證。
2.4 超時重傳機制
- 主機 A 發送數據給 B 之后,可能因為網絡擁堵等原因,數據無法到達主機 B;
- 如果主機 A 在一個特定時間間隔內沒有收到 B 發來的確認應答,就會進行重發。
-
但是,主機 A 未收到 B 發來的確認應答,也可能是因為 ACK 丟失了,因此主機 B 會收到很多重復數據。
那么 TCP 協議需要能夠識別出那些包是重復的包,并且把重復的丟棄掉,這時候我們可以利用前面提到的序列號,就可以很容易做到去重的效果。
-
那么,如果超時的時間如何確定?
- 最理想的情況下,找到一個最小的時間,保證“確認應答一定能在這個時間內返回”,但是這個時間的長短,隨著網絡環境的不同,是有差異的。
- 如果超時時間設的太長,會影響整體的重傳效率;
- 如果超時時間設的太短,有可能會頻繁發送重復的包。
-
TCP 為了保證無論在任何環境下都能比較高性能的通信,因此會動態計算這個最大超時時間。
Linux 中(BSD Unix 和 Windows 也是如此),**超時以 500ms 為一個單位進行控制,每次判定超時重發的超時時間都是 500ms 的整數倍。**如果重發一次之后,仍然得不到應答,等待 2×500ms 后再進行重傳;如果仍然得不到應答,等待 4×500ms 進行重傳,依次類推,以指數形式遞增。累計到一定的重傳次數,TCP 認為網絡或者對端主機出現異常,強制關閉連接。
-
發送方沒有收到應答只能意味著數據可能丟失了,對方沒有收到。因此我無法100%保證對方是否收到信息(無法保證可靠性);所以TCP會通過收不到應答&&超時(超過了特定的時間間隔)判斷丟包了;而因為有序號的存在所以不用擔心報文重復問題。
2.5 連接管理機制
- 在正常情況下,TCP要經過三次握手建立連接,四次揮手斷開連接。
- 不同的標志位在這里就代表著不同的報文類型,也就代表著不同的事項。可能是申請建立連接,發送數據,應答,斷開連接……
- 三次握手和四次揮手本質都是四次,只不過三次握手的第二次使用了攜帶應答,因為面對客戶端的連接請求服務器都要接受。
- 三次握手的前兩次不能攜帶數據,因為還沒有建立起連接。
- 三次握手與四次揮手都是由操作系統自動完成的。
- 因為服務器和多個客戶端都要建立連接,所以連接也是要被管理起來的,這也就證明了連接是有成本的,要耗費時間+空間。因此,三次握手的原因是以最小成本,100%確認對方的通信意愿;以及以最短的方式驗證兩端所處的網絡是否通暢,能夠支持全雙工。
- 四次揮手的本質也是建立雙方斷開連接的共識,但是服務器不一定愿意斷開連接,因為有一定可能服務器的數據還沒有發送完畢,所以可以進行延遲應答,等發送完再同意斷開連接。
connect
發起三次握手,而accept
不參與三次握手。
-
服務端狀態轉化
- [CLOSED -> LISTEN] 服務器端調用 listen 后進入 LISTEN 狀態,等待客戶端連接;
- [LISTEN -> SYN_RCVD] 一旦監聽到連接請求(同步報文段),就將該連接放入內核等待隊列中,并向客戶端發送 SYN 確認報文;
- [SYN_RCVD -> ESTABLISHED] 服務端一旦收到客戶端的確認報文,就進入 ESTABLISHED 狀態,可以進行讀寫數據;
- [ESTABLISHED -> CLOSE_WAIT] 當客戶端主動關閉連接(調用 close),服務器會收到結束報文段,服務器返回確認報文段并進入 CLOSE_WAIT;
- [CLOSE_WAIT -> LAST_ACK] 進入 CLOSE_WAIT 后說明服務器準備關閉連接(需要處理完之前的數據);當服務器真正調用 close 關閉連接時,會向客戶端發送 FIN,此時服務器進入 LAST_ACK 狀態,等待最后一個 ACK 到來(這個 ACK 是客戶端確認收到了 FIN);
- [LAST_ACK -> CLOSED] 服務器收到了對 FIN 的 ACK,徹底關閉連接。
-
客戶端狀態轉化
- [CLOSED -> SYN_SENT] 客戶端調用 connect,發送同步報文段;
- [SYN_SENT -> ESTABLISHED] connect 調用成功,則進入 ESTABLISHED 狀態,開始讀寫數據;
- [ESTABLISHED -> FIN_WAIT_1] 客戶端主動調用 close 時,向服務器發送結束報文段,同時進入 FIN_WAIT_1;
- [FIN_WAIT_1 -> FIN_WAIT_2] 客戶端收到服務器對結束報文段的確認,則進入 FIN_WAIT_2,開始等待服務器的結束報文段;
- [FIN_WAIT_2 -> TIME_WAIT] 客戶端收到服務器發來的結束報文段,進入 TIME_WAIT,并發出 LAST_ACK;
- [TIME_WAIT -> CLOSED] 客戶端要等待一個 2MSL(Max Segment Life,報文最大生存時間)的時間,才會進入 CLOSED 狀態。
下圖是TCP狀態轉換圖(了解即可)
- 較粗的虛線表示服務端的狀態變化情況;較粗的實線表示客戶端的狀態變化情況;CLOSED是一個假想的起始點,不是真實狀態。
2.6 理解TIME_WAIT狀態
-
現在做一個測試,首先啟動 server,然后啟動 client,接著用 Ctrl-C 終止 server,這時馬上再運行 server,結果:
-
這是因為雖然 server 的應用程序終止了,但 TCP 協議層的連接并沒有完全斷開,因此不能再次監聽同樣的 server 端口。
-
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 的值。 -
TIME_WAIT的時間是2MSL的原因:
- MSL 是 TCP 報文的最大生存時間,因此 TIME_WAIT 持續存在 2MSL 的話,就能保證在兩個傳輸方向上的尚未被接收或遲到的報文段都已經消失(否則服務器立刻重啟,可能會收到來自上一個進程的遲到的數據,但是這種數據很可能是錯誤的);
- TIME_WAIT可以強迫主動退出的一端短時間內不會復用原先的端口號進行通信。
- 同時也是在理論上保證最后一個報文可靠到達(假設最后一個 ACK 丟失,那么服務器會再重發一個 FIN,這時雖然客戶端的進程不在了,但是 TCP 連接還在,仍然可以重發 LAST_ACK)。
-
解決TIME_WAIT狀態引起的
bind
失敗的方法:- 在服務器的TCP連接沒有完全斷開之前不允許重新監聽,某些情況下可能是不合理的。
- 服務器需要處理非常大量的客戶端連接(每個連接的生存時間可能很短,但每秒都有大量客戶端請求)。
- 此時如果由服務器端主動關閉連接(比如某些不活躍的客戶端需要被清理),就會產生大量TIME_WAIT狀態的連接。
- 由于請求量很大,可能導致TIME_WAIT連接數很多,每個連接都會占用一個通信五元組(源IP、源端口、目的IP、目的端口、協議)。其中服務器的IP、端口和協議是固定的,如果新客戶端連接的IP和端口號與TIME_WAIT占用的鏈接重復了,就會出現問題。
- 可以通過使用
setsockopt()
設置socket描述符的選項SO_REUSEADDR為1來解決,表示允許創建端口號相同但IP地址不同的多個socket描述符。
2.7 滑動窗口
剛才我們討論了確認應答策略,對每一個發送的數據段都要給一個ACK確認應答,收到ACK后再發送下一個數據段。這樣做有一個比較大的缺點,就是性能較差,尤其是數據往返時間較長的時候。
既然這樣一發一收的方式性能較低,那么一次發送多條數據就可以大大提高性能(其實是將多個段的等待時間重疊在一起了)。
- 窗口大小指的是無需等待確認應答而可以繼續發送數據的最大值,比如上圖的窗口大小就是4000個字節(四個段)。
- 發送前四個段的時候不需要等待任何ACK,直接發送;
- 收到第一個ACK后滑動窗口向后移動,繼續發送第五個段的數據,依次類推;
- 操作系統內核為了維護這個滑動窗口需要開辟發送緩沖區來記錄當前還有哪些數據沒有應答,只有確認應答過的數據才能從緩沖區刪掉;
- 窗口越大則網絡的吞吐率就越高。
- 滑動窗口移動的本質是:指向發送緩沖區中的滑動窗口的
start && end
兩指針的下標增加。 start
指向報文確認序號
的位置,end
指向start + 窗口大小
的位置。- 滑動窗口必須在確認應答之后才會移動,因為
start
執行的位置。 - 滑動窗口的本質:流量控制的具體實現方案。
-
出現丟包情況,如何進行重傳:
-
滑動窗口不會向右滑動跳過報文進行應答,而是等待觸發重傳,由確認序號決定。由此保證確認信息一定是連續的。
-
最左側報文的數據丟了,滑動窗口左側不變;最左側報文的應答丟了,滑動窗口正常工作。
-
數據包已經到達,ACK丟了:
這種情況下,部分ACK丟了并不要緊,因為可以通過后續的ACK進行確認。
-
數據包直接丟了:
- 當某一段報文段丟失之后,發送端會一直收到 1001 這樣的 ACK,就像是在提醒發送端“我想要的是 1001”一樣;
- 如果發送端主機連續三次收到了同樣一個“1001”這樣的應答,就會將對應的數據 1001 - 2000 重新發送。
- 這個時候接收端收到了 1001 之后,再次返回的 ACK 就是 7001 了(因為 2001 - 7000 接收端其實之前就已經收到了,被放到了接收端操作系統內核的接收緩沖區中)。
- 這種機制被稱為“高速重發控制”(也叫“快重傳”)。
- 如果發送的總數據不足三條,就不會觸發快重傳,此時由超時重傳來進行兜底。
- 超時重傳和快重傳的底層支持是滑動窗口。
-
2.8 流量控制
-
接收端處理數據的速度是有限的。如果發送端發送過快,導致接收端的緩沖區被打滿,此時若發送端繼續發送,就會造成丟包,繼而引發丟包重傳等一系列連鎖反應。
-
因此,TCP支持根據接收端的處理能力來決定發送端的發送速度,這一機制稱為流量控制(Flow Control)。
- 接收端通過將自身緩沖區可接收的大小放入TCP首部中的"窗口大小"字段,并通過ACK報文通知發送端,這個過程從三次握手的時候就已經開始進行了,這樣發送端第一次也知道發送多少數據。
- 窗口大小字段越大,說明網絡的吞吐量越高。
- 一旦接收端發現緩沖區即將滿,就會將窗口大小設置為更小的值并通知發送端;
- 發送端收到窗口更新后,會降低發送速度。
- 若接收端緩沖區完全滿,則會將窗口置為0,此時發送方暫停發送數據,但需定期發送窗口探測數據段以獲取最新的窗口大小。
- 接收端通過TCP首部中的16位窗口字段傳遞窗口大小信息。然而,16位數字最大僅能表示65535,這是否意味著TCP窗口最大只能是65535字節?實際上,TCP首部40字節選項中還包含一個窗口擴大因子M,實際窗口大小為窗口字段值左移M位后的結果。
2.9 擁塞控制
- 雖然 TCP 有滑動窗口來高效可靠地發送大量的數據,但是如果在剛開始階段就發送大量的數據,仍然可能引發問題。
- 因為網絡上有很多計算機,可能當前網絡狀態就已經比較擁堵,在不清楚當前網絡狀態下貿然發送大量數據,很有可能雪上加霜。
- 因此,TCP 引入了慢啟動機制:先發少量數據探路,摸清當前網絡擁堵狀態,再決定按照多大的速度傳輸數據。
-
此處引入一個概念稱為擁塞窗口:
- 發送開始時定義擁塞窗口大小為 1;
- 每次收到一個 ACK 應答,擁塞窗口加 1;
- 每次發送數據包時,將擁塞窗口和接收端主機反饋的窗口大小比較,取較小值作為實際發送窗口。
-
像這樣的擁塞窗口增長速度是指數級別的,“慢啟動” 只是初始時慢,但增長速度非常快。
- 為了控制增長速度,不能使擁塞窗口單純加倍,
- 因此引入慢啟動閾值:當擁塞窗口超過該閾值時,不再按指數增長,而是按線性增長。
- 指數增長階段:解決擁塞,恢復網絡通信;線性增長階段:不斷探測網絡的新的擁塞窗口的值。
- 當 TCP 啟動時,慢啟動閾值等于窗口最大值;
- 每次超時重傳時,慢啟動閾值變為原來的一半,同時擁塞窗口重置為 1。
- 少量丟包僅觸發超時重傳,大量丟包則認為網絡擁塞。
- 當擁塞時新的
ssthresh
值會變為上次擁塞窗口最大值的一半。 - TCP 通信開始后,網絡吞吐量逐漸上升;隨著網絡擁堵,吞吐量會立刻下降。
- 擁塞控制本質上是 TCP 協議在盡可能快速傳輸數據和避免給網絡造成過大壓力之間的折中方案。
2.10 延遲應答
-
如果接收數據的主機立刻返回 ACK 應答,這時候返回的窗口可能比較小。
- 假設接收端緩沖區為 1M,一次收到了 500K 的數據;如果立刻應答,返回的窗口就是 500K;但實際上可能處理端處理的速度很快,10ms 之內就把 500K 數據從緩沖區消費掉了;
- 在這種情況下,接收端處理還遠沒有達到自己的極限,即使窗口再放大一些,也能處理過來;如果接收端稍微等一會再應答,比如等待 200ms 再應答,那么這個時候返回的窗口大小就是 1M。
-
一定要記得,窗口越大,網絡吞吐量就越大,傳輸效率就越高,我們的目標是在保證網絡不擁塞的情況下盡量提高傳輸效率。
-
那么所有的包都可以延遲應答么?肯定也不是。
- 數量限制:每隔 N 個包就應答一次;
- 時間限制:超過最大延遲時間就應答一次;
- 具體的數量和超時時間,依操作系統不同也有差異,一般 N 取 2,超時時間取 200ms。
2.11 捎帶應答
捎帶應答(Piggybacking)
-
這是一種在雙向通信中優化數據傳輸的技術,通過將確認信息(ACK)附加在數據幀中一起發送,減少單獨傳輸ACK的開銷。
-
核心原理:
當通信的一方(如B)需要發送數據給另一方(A)時,如果正好有對A之前發送數據的ACK需要回復,就將這個ACK"捎帶"在B的數據幀中,而不是單獨發送一個ACK幀。
-
適用場景:
主要用于需要雙向交互的協議(如TCP),特別是在頻繁交換數據的場景(如遠程登錄、實時聊天)。
-
優點:
- 減少網絡中的報文數量,提高帶寬利用率。
- 降低延遲,因為ACK無需等待單獨傳輸。
-
限制:
- 如果接收方暫時沒有數據要發送,仍需單獨發送ACK,否則發送方會超時重傳。
-
示例對比:
傳統方式:A → 數據 → B → ACK → B → 數據 → A → ACK
捎帶應答:A → 數據 → B → (數據 + ACK)→ A → (數據 + ACK)
簡單來說,捎帶應答就是**"搭便車"發確認**,讓通信更高效。
2.12 面向字節流
-
創建一個 TCP 的 socket 時,會在內核中創建一個發送緩沖區和一個接收緩沖區。
- 調用 write 時,數據會先寫入發送緩沖區;
- 如果發送的字節數太長,會被拆分成多個 TCP 數據包發出;
- 如果發送的字節數太短,則會先在緩沖區等待,直到緩沖區長度合適或其他時機再發送。
- 接收數據時,數據從網卡驅動程序到達內核的接收緩沖區,然后應用程序通過 read 從接收緩沖區讀取數據。
- TCP 的一個連接同時具備發送緩沖區和接收緩沖區,因此可以同時進行讀寫操作,這種特性稱為全雙工。
-
由于緩沖區的存在,TCP 程序的讀寫操作不需要一一匹配:
- 例如,寫入 100 字節數據時,可以調用一次 write 寫入全部數據,也可以分 100 次每次寫入 1 字節;
- 同樣,讀取 100 字節數據時,可以一次 read 全部數據,也可以分多次讀取。
2.13 粘包問題
-
首先要明確,粘包問題中的"包"是指應用層的數據包。
- 在TCP的協議頭中,沒有如同UDP一樣的"報文長度"這樣的字段,但是有一個序號這樣的字段。
- 站在傳輸層的角度,TCP是一個一個報文過來的,按照序號排好序放在緩沖區中;
- 站在應用層的角度,看到的只是一串連續的字節數據。
- 那么應用程序看到了這么一連串的字節數據,就不知道從哪個部分開始到哪個部分是一個完整的應用層數據包。
-
那么如何避免粘包問題呢?
- 歸根結底就是一句話,明確兩個包之間的邊界。
- 對于定長的包,保證每次都按固定大小讀取即可,例如上面的Request結構是固定大小的,那么就從緩沖區從頭開始按
sizeof(Request)
依次讀取即可; - 對于變長的包,可以在包頭的位置約定一個包總長度的字段,從而就知道了包的結束位置;
- 對于變長的包,還可以在包和包之間使用明確的分隔符(應用層協議是程序猿自己來定的,只要保證分隔符不和正文沖突即可)。
-
對于UDP協議來說,是否也存在"粘包問題"呢?
- 對于UDP,如果還沒有上層交付數據,UDP的報文長度仍然在,同時UDP是一個一個把數據交付給應用層,就有很明確的數據邊界。
- 站在應用層的角度,使用UDP的時候,要么收到完整的UDP報文,要么不收,不會出現"半個"的情況。
2.14 TCP異常情況
- 進程終止:進程終止會釋放文件描述符,仍然可以發送FIN,和正常關閉沒有什么區別。
- 機器重啟:和進程終止的情況相同。
- 機器掉電/網線斷開:接收端認為連接還在,一旦接收端有寫入操作,接收端發現連接已經不在了,就會進行reset。即使沒有寫入操作,TCP自己也內置了一個保活定時器,會定期詢問對方是否還在。如果對方不在,也會把連接釋放。
- 另外,應用層的某些協議也有一些這樣的檢測機制,例如HTTP長連接中會定期檢測對方的狀態。例如QQ,在QQ斷線之后也會定期嘗試重新連接。
2.15 基于TCP應用層協議與小結
-
基于TCP應用層的協議:
- HTTP
- HTTPS
- SSH
- Telnet
- FTP
- SMTP
- 也包括自定義的TCP應用層協議
-
TCP復雜的原因:因為既要保證可靠性,又要盡可能的提高性能。
可靠性機制:校驗和、序列號(按序到達)、確認應答、超時重發、連接管理、流量控制、擁塞控制。
性能優化:滑動窗口、快速重傳、延遲應答、捎帶應答。
其他功能:定時器(超時重傳定時器、保活定時器、TIME_WAIT定時器等)。
3. TCP/UDP對比
我們說了 TCP 是可靠連接,但這是否意味著 TCP 一定優于 UDP 呢?實際上,TCP 和 UDP 的優缺點不能簡單、絕對地比較。TCP 適用于需要可靠傳輸的場景,如文件傳輸或重要狀態更新;而 UDP 則更適合對高速傳輸和實時性要求較高的領域,例如早期的 QQ、視頻傳輸等,此外 UDP 還支持廣播。歸根結底,TCP 和 UDP 都是程序員的工具,具體使用哪種協議需根據實際需求場景來判斷。