淺談TCP和UDP

2023-06-20 18:00:16

簡介


在計算機網路中,TCP(傳輸控制協定)和UDP(使用者資料包協定)是兩個常用的傳輸層協定。它們分別提供了可靠的資料傳輸和快速的資料傳送,成為網際網路世界中的雙子星。本文將探討TCP和UDP的特點、優勢和應用場景,以及如何選擇合適的協定來滿足不同的需求。

TCP定義

英文名:Transmission Control Protocol
中文名:傳輸控制協定
協定說明:TCP是一種面向連線的、可靠的、基於位元組流的傳輸層通訊協定。
舉例:打電話,需要雙方都接通,才能進行對話
特點:效率低,資料傳輸比較安全

UDP定義

英文名:User Datagram Protocol
中文名:資料包協定
協定說明:UDP是一種面向無連線的傳輸層通訊協定。
舉例:傳簡訊,不需要雙方建立連線,But,資料包的大小應限制在64k以內
特點:效率高,資料傳輸不安全,容易丟包

OSI模型與TCP/IP模型

TCP協定

TCP是一種面向連線的、可靠的傳輸控制協定,是網際網路通訊中最常用的協定之一。TCP的設計目標是提供可靠的資料傳輸、流量控制和擁塞控制機制,以確保資料在網路中準確無誤地傳輸。

TCP協定總結起來主要有以下幾個特點

  1. 面向連線:在TCP通訊中,傳送端和接收端之間需要建立一個可靠的連線,這個連線在資料傳輸結束後再關閉。連線的建立和斷開是通過三次握手和四次揮手來完成的。

  2. 可靠性:TCP提供可靠的資料傳輸機制。它通過序列號、確認應答和重傳機制來確保資料的準確傳輸。傳送端將資料劃分為稱為TCP段的小塊,並在每個段上新增一個序列號。接收端通過確認應答來確認已收到的資料,並可以要求傳送端重新傳輸丟失的資料。

  3. 流量控制:TCP具有流量控制機制,用於控制資料傳送的速率,以避免接收端無法處理過多的資料而導致的資料丟失。接收端可以通過視窗大小來通知傳送端自己的接收能力,從而實現動態調整資料傳輸的速率。

  4. 擁塞控制:TCP通過擁塞控制機制來避免網路擁塞和資料丟失。它使用一種稱為擁塞視窗的機制來控制資料傳送的速率。當網路擁塞時,TCP會降低傳送速率,從而減輕網路負載,保證網路的穩定性。

  5. 面向位元組流:TCP是一種面向位元組流的協定,將應用層傳輸的資料視為一連串的位元組流。TCP會將資料劃分為適當大小的資料段,並通過TCP頭部中的序列號來對每個資料段進行標記,以便接收端正確地重組資料。

  6. 可靠性校驗:TCP使用校驗和機制來檢測資料在傳輸過程中是否發生了錯誤。接收端會根據校驗和的值判斷資料是否被篡改,如果檢測到錯誤,接收端會要求傳送端重新傳輸資料。

  7. 有序性:TCP保證資料的有序傳輸。每個TCP段都有一個序列號,接收端根據序列號對接收到的資料進行排序,以確保資料按正確的順序重新組裝。

  8. 全雙工通訊:TCP支援全雙工通訊,即傳送端和接收端可以同時進行傳送和接收操作,實現雙向的資料傳輸。

UDP協定


UDP是一種無連線的傳輸協定,相對於TCP而言,它具有以下特點:

  1. 無連線性:UDP是無連線的協定,傳送端和接收端之間不需要建立和維護連線。每個UDP封包都是獨立的,包含完整的資料和目標埠資訊。這使得UDP在資料傳輸的開銷上比TCP更小。
  2. 快速傳輸:由於沒有連線建立和維護的開銷,UDP具有更低的延遲。封包不需要等待確認應答或進行重傳,因此可以更快地傳輸資料。這使得UDP適用於實時性要求高的應用場景,如音訊和視訊流傳輸、實時遊戲和語音通訊。
  3. 不可靠性:相對於TCP的可靠性傳輸,UDP是不可靠的。UDP封包在傳送後,不會保證到達接收端,也沒有確認應答機制。如果在傳輸過程中出現丟包或錯誤,UDP協定本身不會進行重傳或糾錯,而是由應用層來處理資料的完整性和順序性。
  4. 簡單性:UDP的協定頭較小,僅包含必要的資訊,使得封包的開銷更小。UDP的設計相對簡單,實現和處理也更加輕量化,使得它在資源有限的環境下更為適用。
  5. 支援廣播和多播:UDP支援廣播和多播功能。廣播是將封包傳送到網路中的所有主機,而多播是將封包傳送到特定組的主機。這使得UDP適用於實現廣播通訊、串流媒體傳輸和組播應用。
  6. 適應性:UDP對資料的處理方式非常靈活,沒有嚴格的順序要求。它適用於傳輸短訊息、查詢應答、簡單請求響應等簡單的通訊模式。UDP也常用於DNS(域名系統)查詢、SNMP(簡單網路管理協定)以及一些輕量級的通訊和傳輸協定。

TCP資料緩衝區

TCP協定是作用是用來進行端對端資料傳送的,那麼就會有傳送端和接收端,在作業系統有兩個空間即user space和kernal space。
每個Tcp socket連線在核心中都有一個傳送緩衝區和接收緩衝區,TCP的全雙工的工作模式以及TCP的流量(擁塞)控制便是依賴於這兩個獨立的buffer以及buffer的填充狀態。
單工:只允許甲方向乙方傳送資訊,而乙方不能向甲方傳送 ,如汽車單行道。
半雙工:半雙工就是指一個時間段內只有一個動作發生,甲方可以向乙方傳送資料,乙方也可以向甲方傳送資料,但不能同時進行,如一條窄馬路同一時間只能允許一個車通行。
全雙工:同時允許資料在兩個方向上同時傳輸,它在能力上相當於兩個單工通訊方式的結合。
一個socket的兩端,都會有send和recv兩個方法,如client傳送資料到server,那麼就是使用者端程序呼叫send傳送資料,而send的作用是將資料拷貝進入socket的核心傳送緩衝區之中,然後send便會在上層返回。也就是說send()方法返回之時,資料不一定會傳送到對端即伺服器上去(和write寫檔案有點類似),send()僅僅是把應用層buffer的資料拷貝進socket的核心傳送buffer中,傳送是TCP的事情,和send其實沒有太大關係。

接收緩衝區把資料快取入核心,等待recv()讀取,recv()所做的工作,就是把核心緩衝區中的資料拷貝到應用層使用者的buffer裡面,並返回。若應用程序一直沒有呼叫recv()進行讀取的話,此資料會一直快取在相應socket的接收緩衝區內。對於TCP,如果應用程序一直沒有讀取,接收緩衝區滿了之後,發生的動作是:收端通知發端,接收視窗關閉(win=0)。這個便是滑動視窗的實現。保證TCP套介面接收緩衝區不會溢位,從而保證了TCP是可靠傳輸。因為對方不允許發出超過所通告視窗大小的資料。 這就是TCP的流量控制,如果對方無視視窗大小而發出了超過視窗大小的資料,則接收方TCP將丟棄它。
檢視socket傳送緩衝區大小,cat /proc/sys/net/ipv4/tcp_wmem

常見的問題

TCP粘包

TCP粘包問題是在TCP協定中常見的一個資料傳輸問題,指的是傳送方連續傳送的多個封包在接收方接收時被粘在一起,導致接收方無法正確解析和處理封包的邊界。這種情況可能會對應用層造成困擾,因為接收方無法準確判斷每個封包的起始和結束位置,從而導致資料解析錯誤或資料丟失。

因為TCP是面向流,沒有邊界,而作業系統在傳送TCP資料時,會通過緩衝區來進行優化,例如緩衝區為1024個位元組大小。

  1. 如果一次請求傳送的資料量比較小,沒達到緩衝區大小,TCP則會將多個請求合併為同一個請求進行傳送,這就形成了粘包問題。
  2. 如果一次請求傳送的資料量比較大,超過了緩衝區大小,TCP就會將其拆分為多次傳送,這就是拆包。

粘包常見解決辦法

對於粘包和拆包問題,常見的解決方案有四種:

  1. 傳送端將每個包都封裝成固定的長度,比如100位元組大小。如果不足100位元組可通過補0或空等進行填充到指定長度;
  2. 傳送端在每個包的末尾使用固定的分隔符,例如\r\n。如果發生拆包需等待多個包傳送過來之後再找到其中的\r\n進行合併;例如,FTP協定;
  3. 將訊息分為頭部和訊息體,頭部中儲存整個訊息的長度,只有讀取到足夠長度的訊息之後才算是讀到了一個完整的訊息;
  4. 通過自定義協定進行粘包和拆包的處理。

Netty對TCP粘包和拆包問題的處理

Netty對解決粘包和拆包的方案做了抽象,提供了一些解碼器(Decoder)來解決粘包和拆包的問題。如:
LineBasedFrameDecoder:以行為單位進行封包的解碼;
DelimiterBasedFrameDecoder:以特殊的符號作為分隔來進行封包的解碼;
FixedLengthFrameDecoder:以固定長度進行封包的解碼;
LenghtFieldBasedFrameDecode:適用於訊息頭包含訊息長度的協定(最常用);
基於Netty進行網路讀寫的程式,可以直接使用這些Decoder來完成封包的解碼。對於高並行、大流量的系統來說,每個封包都不應該傳輸多餘的資料(所以補齊的方式不可取),LenghtFieldBasedFrameDecode更適合這樣的場景。

為什麼UDP沒有粘包?

UDP協定不會發生粘包問題的主要原因是UDP是面向資料包的協定,每個UDP封包都是獨立的、自包含的。在UDP中,每個封包被視為一個獨立的實體,沒有像TCP那樣的資料流概念,因此不會發生粘包現象。
UDP協定在傳輸資料時,每個封包都有自己的報文頭,其中包含了目標埠、源埠、資料長度等資訊。在傳送端,每個UDP封包獨立傳送,並且每個封包都有固定的大小。在接收端,每次接收到一個UDP封包時,就會被視為一個完整的資料包,而不會與其他封包發生合併或拆分的情況。
因此,UDP協定的特性決定了它不會發生粘包問題。每個UDP封包都具有獨立性和完整性,接收方可以準確地處理每個封包,而不需要擔心封包的邊界和順序。
然而,儘管UDP不會發生粘包問題,但它也存在其他的問題,例如資料丟失、資料無序等。由於UDP是無連線、不可靠的協定,它不提供重傳機制和確認應答機制,因此在網路不穩定或擁塞的情況下,UDP封包可能會丟失或無序到達。因此,在使用UDP進行資料傳輸時,應用層需要自行處理這些問題,如使用序列號、確認應答等手段來確保資料的可靠性和順序性。

什麼是零拷貝?

即所謂的 Zero-copy, 就是在運算元據時, 不需要將資料 buffer 從一個記憶體區域拷貝到另一個記憶體區域. 因為少了一次記憶體的拷貝, 因此 CPU 的效率就得到的提升.
在 OS 層面上的 Zero-copy 通常指避免在 使用者態(User-space) 與 核心態(Kernel-space) 之間來回拷貝資料. 例如 Linux 提供的 mmap 系統呼叫, 它可以將一段使用者空間記憶體對映到核心空間, 當對映成功後, 使用者對這段記憶體區域的修改可以直接反映到核心空間; 同樣地, 核心空間對這段區域的修改也直接反映使用者空間. 正因為有這樣的對映關係, 我們就不需要在 使用者態(User-space) 與 核心態(Kernel-space) 之間拷貝資料, 提高了資料傳輸的效率.

而需要注意的是, Netty 中的 Zero-copy 與上面我們所提到到 OS 層面上的 Zero-copy 不太一樣, Netty的 Zero-coyp完全是在使用者態(Java 層面)的, 它的 Zero-copy 的更多的是偏向於 優化資料操作 這樣的概念.
Netty 的 Zero-copy 體現在如下幾個個方面:
Netty 提供了 CompositeByteBuf 類, 它可以將多個 ByteBuf 合併為一個邏輯上的 ByteBuf, 避免了各個 ByteBuf 之間的拷貝.
通過 wrap 操作, 我們可以將 byte[] 陣列、ByteBuf、ByteBuffer等包裝成一個 Netty ByteBuf 物件, 進而避免了拷貝操作.
ByteBuf 支援 slice 操作, 因此可以將 ByteBuf 分解為多個共用同一個儲存區域的 ByteBuf, 避免了記憶體的拷貝.
通過 FileRegion 包裝的FileChannel.tranferTo 實現檔案傳輸, 可以直接將檔案緩衝區的資料傳送到目標 Channel, 避免了傳統通過迴圈 write 方式導致的記憶體拷貝問題.
Netty的接收和傳送ByteBuffer使用直接DirectBuffer記憶體進行Socket讀寫,不需要進行位元組緩衝區的二次拷貝。如果使用JVM的堆記憶體進行Socket讀寫,JVM會將堆記憶體Buffer拷貝一份到直接記憶體中,然後才寫入Socket中。相比於使用直接記憶體,訊息在傳送過程中多了一次緩衝區的記憶體拷貝。