在計算機網路中,TCP(傳輸控制協定)和UDP(使用者資料包協定)是兩個常用的傳輸層協定。它們分別提供了可靠的資料傳輸和快速的資料傳送,成為網際網路世界中的雙子星。本文將探討TCP和UDP的特點、優勢和應用場景,以及如何選擇合適的協定來滿足不同的需求。
英文名:Transmission Control Protocol
中文名:傳輸控制協定
協定說明:TCP是一種面向連線的、可靠的、基於位元組流的傳輸層通訊協定。
舉例:打電話,需要雙方都接通,才能進行對話
特點:效率低,資料傳輸比較安全
英文名:User Datagram Protocol
中文名:資料包協定
協定說明:UDP是一種面向無連線的傳輸層通訊協定。
舉例:傳簡訊,不需要雙方建立連線,But,資料包的大小應限制在64k以內
特點:效率高,資料傳輸不安全,容易丟包
TCP是一種面向連線的、可靠的傳輸控制協定,是網際網路通訊中最常用的協定之一。TCP的設計目標是提供可靠的資料傳輸、流量控制和擁塞控制機制,以確保資料在網路中準確無誤地傳輸。
TCP協定總結起來主要有以下幾個特點
面向連線:在TCP通訊中,傳送端和接收端之間需要建立一個可靠的連線,這個連線在資料傳輸結束後再關閉。連線的建立和斷開是通過三次握手和四次揮手來完成的。
可靠性:TCP提供可靠的資料傳輸機制。它通過序列號、確認應答和重傳機制來確保資料的準確傳輸。傳送端將資料劃分為稱為TCP段的小塊,並在每個段上新增一個序列號。接收端通過確認應答來確認已收到的資料,並可以要求傳送端重新傳輸丟失的資料。
流量控制:TCP具有流量控制機制,用於控制資料傳送的速率,以避免接收端無法處理過多的資料而導致的資料丟失。接收端可以通過視窗大小來通知傳送端自己的接收能力,從而實現動態調整資料傳輸的速率。
擁塞控制:TCP通過擁塞控制機制來避免網路擁塞和資料丟失。它使用一種稱為擁塞視窗的機制來控制資料傳送的速率。當網路擁塞時,TCP會降低傳送速率,從而減輕網路負載,保證網路的穩定性。
面向位元組流:TCP是一種面向位元組流的協定,將應用層傳輸的資料視為一連串的位元組流。TCP會將資料劃分為適當大小的資料段,並通過TCP頭部中的序列號來對每個資料段進行標記,以便接收端正確地重組資料。
可靠性校驗:TCP使用校驗和機制來檢測資料在傳輸過程中是否發生了錯誤。接收端會根據校驗和的值判斷資料是否被篡改,如果檢測到錯誤,接收端會要求傳送端重新傳輸資料。
有序性:TCP保證資料的有序傳輸。每個TCP段都有一個序列號,接收端根據序列號對接收到的資料進行排序,以確保資料按正確的順序重新組裝。
全雙工通訊:TCP支援全雙工通訊,即傳送端和接收端可以同時進行傳送和接收操作,實現雙向的資料傳輸。
UDP是一種無連線的傳輸協定,相對於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資料時,會通過緩衝區來進行優化,例如緩衝區為1024個位元組大小。
對於粘包和拆包問題,常見的解決方案有四種:
Netty對解決粘包和拆包的方案做了抽象,提供了一些解碼器(Decoder)來解決粘包和拆包的問題。如:
LineBasedFrameDecoder:以行為單位進行封包的解碼;
DelimiterBasedFrameDecoder:以特殊的符號作為分隔來進行封包的解碼;
FixedLengthFrameDecoder:以固定長度進行封包的解碼;
LenghtFieldBasedFrameDecode:適用於訊息頭包含訊息長度的協定(最常用);
基於Netty進行網路讀寫的程式,可以直接使用這些Decoder來完成封包的解碼。對於高並行、大流量的系統來說,每個封包都不應該傳輸多餘的資料(所以補齊的方式不可取),LenghtFieldBasedFrameDecode更適合這樣的場景。
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中。相比於使用直接記憶體,訊息在傳送過程中多了一次緩衝區的記憶體拷貝。