UDP,你要耗子喂汁呀!


作者 | cxuan

來源 | 程序員cxuan(ID:cxuangoodjob)

運輸層位於應用層和網絡層之間,是OSI分層體系中的第四層,同時也是網絡體系結構的重要部分。運輸層主要負責網絡上的端到端通信。

運輸層爲運行在不同主機上的應用程序之間的通信起着至關重要的作用。下面我們就來一起探討一下關於運輸層的協議部分。

運輸層概述


計算機網絡的運輸層非常類似於高速公路,高速公路負責把人或者物品從一端運送到另一端,而計算機網絡的運輸層則負責把報文從一端運輸到另一端,這個端指的就是端系統。在計算機網絡中,任意一個可以交換信息的介質都可以稱爲端系統,比如手機、網絡媒體、電腦、運營商等。

在運輸層運輸報文的過程中,會遵守一定的協議規範,比如一次傳輸的數據限制、選擇什麼樣的運輸協議等。運輸層實現了讓兩個互不相關的主機進行邏輯通信的功能,看起來像是讓兩個主機相連一樣。

運輸層協議是在端系統中實現的,而不是在路由器中實現的。路由只是做識別地址並轉發的功能。這就比如快遞員送快遞一樣,當然是要由地址的接受人也就是 xxx 號樓 xxx 單元 xxx 室的這個人來判斷了!

數據包經過每層後,該層協議都會在數據包附上包首部,一個完整的包首部圖如上所示。

在數據傳輸到運輸層後,會爲其附上TCP首部,首部包含着源端口號和目的端口號。

在發送端,運輸層將從發送應用程序進程接收到的報文轉化成運輸層分組,分組在計算機網絡中也稱爲 報文段(segment)。運輸層一般會將報文段進行分割,分割成爲較小的塊,爲每一塊加上運輸層首部並將其向目的地發送。

在發送過程中,可選的運輸層協議(也就是交通工具) 主要有TCP和UDP,關於這兩種運輸協議的選擇及其特性也是我們着重探討的重點。


TCP 和 UDP 前置知識


在TCP/IP協議中能夠實現傳輸層功能的,最具代表性的就是TCP和UDP。提起 TCP和UDP,就得先從這兩個協議的定義說起。

TCP叫做傳輸控制協議(TCP,Transmission Control Protocol),通過名稱可以大致知道TCP協議有控制傳輸的功能,主要體現在其可控,可控就表示着可靠,確實是這樣的,TCP爲應用層提供了一種可靠的、面向連接的服務,它能夠將分組可靠的傳輸到服務端。

UDP叫做用戶數據報協議(UDP,User Datagram Protocol),通過名稱可以知道UDP把重點放在了數據報上,它爲應用層提供了一種無需建立連接就可以直接發送數據報的方法。

怎麼計算機網絡中的術語對一個數據的描述這麼多啊?

在計算機網絡中,在不同層之間會有不同的描述。我們上面提到會將運輸層的分組稱爲報文段,除此之外,還會將TCP中的分組也稱爲報文段,然而將 UDP的分組稱爲數據報,同時也將網絡層的分組稱爲數據報。

但是爲了統一,一般在計算機網絡中我們統一稱TCP和UDP的報文爲 “報文段”。

套接字


在TCP或者UDP發送具體的報文信息前,需要先經過一扇 門,這個門就是套接字(socket),套接字向上連接着應用層,向下連接着網絡層。在操作系統中,操作系統分別爲應用和硬件提供了接口(Application Programming Interface)。而在計算機網絡中,套接字同樣是一種接口,它也是有接口API 的。

使用TCP或UDP通信時,會廣泛用到套接字的API,使用這套API設置IP地址、端口號,實現數據的發送和接收。

現在我們知道了, Socket 和TCP/IP沒有必然聯繫,Socket的出現只是方便了 TCP/IP的使用,如何方便使用呢?你可以直接使用下面Socket API的這些方法。

套接字類型

套接字的主要類型有三種,下面我們分別介紹一下

  • 數據報套接字(Datagram sockets):數據報套接字提供一種無連接的服務,而且並不能保證數據傳輸的可靠性。數據有可能在傳輸過程中丟失或出現數據重複,且無法保證順序地接收到數據。數據報套接字使用UDP(User DatagramProtocol)協議進行數據的傳輸。由於數據報套接字不能保證數據傳輸的可靠性,對於有可能出現的數據丟失情況,需要在程序中做相應的處理。

  • 流套接字(Stream sockets):流套接字用於提供面向連接、可靠的數據傳輸服務。能夠保證數據的可靠性、順序性。流套接字之所以能夠實現可靠的數據服務,原因在於其使用了傳輸控制協議,即TCP(The Transmission Control Protocol)協議

  • 原始套接字(Raw sockets): 原始套接字允許直接發送和接收IP數據包,而無需任何特定於協議的傳輸層格式,原始套接字可以讀寫內核沒有處理過的IP數據包。

套接字處理過程

在計算機網絡中,要想實現通信,必須至少需要兩個端系統,至少需要一對兩個套接字纔行。下面是套接字的通信過程。

  1. socket中的API用於創建通信鏈路中的端點,創建完成後,會返回描述該套接字的套接字描述符。

就像使用文件描述符來訪問文件一樣,套接字描述符用來訪問套接字。

  1. 當應用程序具有套接字描述符後,它可以將唯一的名稱綁定在套接字上,服務器必須綁定一個名稱才能在網絡中訪問

  2. 在爲服務端分配了socket並且將名稱使用bind綁定到套接字上後,將會調用listen api。listen 表示客戶端願意等待連接的意願,listen 必須在accept api之前調用。

  3. 客戶端應用程序在流套接字(基於TCP)上調用connect發起與服務器的連接請求。

  4. 服務器應用程序使用acceptAPI接受客戶端連接請求,服務器必須先成功調用bind和listen後,再調用accept api。

  5. 在流套接字之間建立連接後,客戶端和服務器就可以發起read/write api調用了。

  6. 當服務器或客戶端要停止操作時,就會調用 close API 釋放套接字獲取的所有系統資源。

雖然套接字API位於應用程序層和傳輸層之間的通信模型中,但是套接字API不屬於通信模型。套接字API允許應用程序與傳輸層和網絡層進行交互。

在往下繼續聊之前,我們先播放一個小插曲,簡單聊一聊IP。

聊聊 IP

IP是Internet Protocol(網際互連協議)的縮寫,是TCP/IP體系中的網絡層協議。設計IP的初衷主要想解決兩類問題

  • 提高網絡擴展性:實現大規模網絡互聯

  • 對應用層和鏈路層進行解藕,讓二者獨立發展。

IP是整個TCP/IP協議族的核心,也是構成互聯網的基礎。爲了實現大規模網絡的互通互聯,IP更加註重適應性、簡潔性和可操作性,並在可靠性做了一定的犧牲。IP不保證分組的交付時限和可靠性,所傳送分組有可能出現丟失、重複、延遲或亂序等問題。

我們知道,TCP協議的下一層就是IP協議層,既然IP不可靠,那麼如何保證數據能夠準確無誤地到達呢?

這就涉及到TCP傳輸機制的問題了,我們後面聊到TCP的時候再說。

端口號

在聊端口號前,先來聊一聊文件描述以及socket和端口號的關係

爲了方便資源的使用,提高機器的性能、利用率和穩定性等等原因,我們的計算機都有一層軟件叫做操作系統,它用於幫我們管理計算機可以使用的資源,當我們的程序要使用一個資源的時候,可以向操作系統申請,再由操作系統爲我們的程序分配和管理資源。

通常當我們要訪問一個內核設備或文件時,程序可以調用系統函數,系統就會爲我們打開設備或文件,然後返回一個文件描述符fd(或稱爲ID,是一個整數),我們要訪問該設備或文件,只能通過該文件描述符。可以認爲該編號對應着打開的文件或設備。

而當我們的程序要使用網絡時,要使用到對應的操作系統內核的操作和網卡設備,所以我們可以向操作系統申請,然後系統會爲我們創建一個套接字 Socket,並返回這個Socket的ID,以後我們的程序要使用網絡資源,只要向這個Socket的編號ID操作即可。而我們的每一個網絡通信的進程至少對應着一個Socket。向Socket的ID中寫數據,相當於向網絡發送數據,向Socket中讀數據,相當於接收數據。而且這些套接字都有唯一標識符——文件描述符fd。

端口號是16位的非負整數,它的範圍是0-65535之間,這個範圍會分爲三種不同的端口號段,由Internet號碼分配機構IANA進行分配

  • 周知/標準端口號,它的範圍是0-1023

  • 註冊端口號,範圍是1024-49151

  • 私有端口號,範圍是49152-6553

一臺計算機上可以運行多個應用程序,當一個報文段到達主機後,應該傳輸給哪個應用程序呢?你怎麼知道這個報文段就是傳遞給HTTP服務器而不是SSH服務器的呢?

是憑藉端口號嗎?當報文到達服務器時,是端口號來區分不同應用程序的,所以應該藉助端口號來區分。

舉個例子反駁一下cxuan,假如到達服務器的兩條數據都是由80端口發出的你該如何區分呢?或者說到達服務器的兩條數據端口一樣,協議不同,該如何區分呢?

所以僅憑端口號來確定某一條報文顯然是不夠的。

互聯網上一般使用源IP地址、目標IP地址、源端口號、目標端口號來進行區分。如果其中的某一項不同,就被認爲是不同的報文段。這些也是多路分解和多路複用的基礎。

確定端口號

在實際通信之前,需要先確定一下端口號,確定端口號的方法分爲兩種:

  • 標準既定的端口號

標準既定的端口號是靜態分配的,每個程序都會有自己的端口號,每個端口號都有不同的用途。端口號是一個16比特的數,其大小在0-65535 之間,0-1023 範圍內的端口號都是動態分配的既定端口號,例如HTTP使用80端口來標識,FTP使用21端口來標識,SSH使用22來標識。這類端口號有一個特殊的名字,叫做周知端口號(Well-Known Port Number)。

  • 時序分配的端口號

第二種分配端口號的方式是一種動態分配法,在這種方法下,客戶端應用程序可以完全不用自己設置端口號,憑藉操作系統進行分配,操作系統可以爲每個應用程序分配互不衝突的端口號。這種動態分配端口號的機制即使是同一個客戶端發起的TCP連接,也能識別不同的連接。

多路複用和多路分解

我們上面聊到了在主機上的每個套接字都會分配一個端口號,當報文段到達主機時,運輸層會檢查報文段中的目的端口號,並將其定向到相應的套接字,然後報文段中的數據通過套接字進入其所連接的進程。下面我們來聊一下什麼是多路複用和多路分解的概念。

多路複用和多路分解分爲兩種,即無連接的多路複用(多路分解)和麪向連接的多路複用(多路分解)

無連接的多路複用和多路分解

開發人員會編寫代碼確定端口號是周知端口號還是時序分配的端口號。假如主機 A中的一個10637端口要向主機B中的45438端口發送數據,運輸層採用的是 UDP協議,數據在應用層產生後,會在運輸層中加工處理,然後在網絡層將數據封裝得到IP數據報,IP數據包通過鏈路層盡力而爲的交付給主機B,然後主機 B會檢查報文段中的端口號判斷是哪個套接字的。

這一系列的過程如下所示:

UDP套接字就是一個二元組,二元組包含目的IP地址和目的端口號。

所以,如果兩個UDP報文段有不同的源IP地址和/或相同的源端口號,但是具有相同的目的IP地址和目的端口號,那麼這兩個報文會通過套接字定位到相同的目的進程。

這裏思考一個問題,主機A給主機B發送一個消息,爲什麼還需要知道源端口號呢?比如我給妹子表達出我對你有點意思的信息,妹子還需要知道這個信息是從我的哪個器官發出的嗎?知道是我這個人對你有點意思不就完了?實際上是需要的,因爲妹子如果要表達出她對你也有點意思,她是不是可能會親你一口,那她得知道往哪親吧?

這就是,在A到B的報文段中,源端口號會作爲 返回地址 的一部分,即當B需要回發一個報文段給A時,B需要從A到B中的源端口號取值,如下圖所示

面向連接的多路複用與多路分解

如果說無連接的多路複用和多路分解指的是UDP的話,那麼面向連接的多路複用與多路分解指的是TCP了,TCP和UDP在報文結構上的差別是,UDP是一個二元組而TCP是一個四元組,即源IP地址、目標IP地址、源端口號、目標端口號 ,這個我們上面也提到了。當一個TCP報文段從網絡到達一臺主機時,這個主機會根據這四個值拆解到對應的套接字上。

上圖顯示了面向連接的多路複用和多路分解的過程,圖中主機C向主機B發起了兩個HTTP請求,主機A向主機C發起了一個HTTP請求,主機A、B、C都有自己唯一的P地址,當主機C發出HTTP請求後,主機B能夠分解這兩個HTTP連接,因爲主機C發出請求的兩個源端口號不同,所以對於主機B來說,這是兩條請求,主機B能夠進行分解。對於主機A和主機C來說,這兩個主機有不同的IP地址,所以對於主機B來說,也能夠進行分解。


UDP


終於,我們開始了對UDP協議的探討,淦起!

UDP的全稱是用戶數據報協議(UDP,User Datagram Protocol),UDP爲應用程序提供了一種無需建立連接就可以發送封裝的 IP 數據包的方法。如果應用程序開發人員選擇的是UDP而不是TCP的話,那麼該應用程序相當於就是和IP 直接打交道的。

從應用程序傳遞過來的數據,會附加上多路複用/多路分解的源和目的端口號字段,以及其他字段,然後將形成的報文傳遞給網絡層,網絡層將運輸層報文段封裝到IP數據報中,然後盡力而爲的交付給目標主機。最關鍵的一點就是,使用 UDP協議在將數據報傳遞給目標主機時,發送方和接收方的運輸層實體間是沒有握手的。正因爲如此,UDP被稱爲是無連接的協議。

UDP特點

UDP協議一般作爲流媒體應用、語音交流、視頻會議所使用的傳輸層協議,我們大家都知道的DNS協議底層也使用了UDP協議,這些應用或協議之所以選擇 UDP主要是因爲以下這幾點

  • 速度快,採用UDP協議時,只要應用進程將數據傳給UDP,UDP就會將此數據打包進UDP報文段並立刻傳遞給網絡層,然後TCP有擁塞控制的功能,它會在發送前判斷互聯網的擁堵情況,如果互聯網極度阻塞,那麼就會抑制TCP的發送方。使用UDP的目的就是希望實時性。

  • 無須建立連接,TCP在數據傳輸之前需要經過三次握手的操作,而UDP 則無須任何準備即可進行數據傳輸。因此UDP沒有建立連接的時延。如果使用TCP和UDP來比喻開發人員:TCP就是那種凡事都要設計好,沒設計不會進行開發的工程師,需要把一切因素考慮在內後再開幹!所以非常靠譜;而UDP就是那種上來直接乾乾幹,接到項目需求馬上就開幹,也不管設計,也不管技術選型,就是幹,這種開發人員非常不靠譜,但是適合快速迭代開發,因爲可以馬上上手!

  • 無連接狀態,TCP需要在端系統中維護連接狀態,連接狀態包括接收和發送緩存、擁塞控制參數以及序號和確認號的參數,在UDP中沒有這些參數,也沒有發送緩存和接受緩存。因此,某些專門用於某種特定應用的服務器當應用程序運行在 UDP上,一般能支持更多的活躍用戶

  • 分組首部開銷小,每個TCP報文段都有20字節的首部開銷,而UDP僅僅只有8字節的開銷。

這裏需要注意一點,並不是所有使用UDP協議的應用層都是不可靠的,應用程序可以自己實現可靠的數據傳輸,通過增加確認和重傳機制。所以使用 UDP協議最大的特點就是速度快。

UDP報文結構

下面來一起看一下UDP的報文結構,每個UDP報文分爲UDP報頭和UDP數據區兩部分。報頭由4個16位長(2字節)字段組成,分別說明該報文的源端口、目的端口、報文長度和校驗值。

  • 源端口號(Source Port) :這個字段佔據UDP報文頭的前16位,通常包含發送數據報的應用程序所使用的UDP端口。接收端的應用程序利用這個字段的值作爲發送響應的目的地址。這個字段是可選項,有時不會設置源端口號。沒有源端口號就默認爲0 ,通常用於不需要返回消息的通信中。

  • 目標端口號(Destination Port): 表示接收端端口,字段長爲16位

  • 長度(Length): 該字段佔據16位,表示UDP數據報長度,包含UDP報文頭和UDP數據長度。因爲UDP報文頭長度是8個字節,所以這個值最小爲8,最大長度爲65535字節。

  • 校驗和(Checksum):UDP使用校驗和來保證數據安全性,UDP的校驗和也提供了差錯檢測功能,差錯檢測用於校驗報文段從源到目標主機的過程中,數據的完整性是否發生了改變。發送方的UDP對報文段中的16比特字的和進行反碼運算,求和時遇到的位溢出都會被忽略,比如下面這個例子,三個16比特的數字進行相加

這些16比特的前兩個和是

然後再將上面的結果和第三個16比特的數進行相加

最後一次相加的位會進行溢出,溢出位1要被捨棄,然後進行反碼運算,反碼運算就是將所有的1變爲0,0變爲1。因此1000 0100 1001 0101的反碼就是0111 1011 0110 1010,這就是校驗和,如果在接收方,數據沒有出現差錯,那麼全部的4個16比特的數值進行運算,同時也包括校驗和,如果最後結果的值不是1111 1111 1111 1111的話,那麼就表示傳輸過程中的數據出現了差錯。

下面來想一個問題,爲什麼UDP會提供差錯檢測的功能?

這其實是一種 “端到端” 的設計原則,這個原則是要讓傳輸中各種錯誤發生的概率降低到一個可以接受的水平。

文件從主機A傳到主機B,也就是說AB主機要通信,需要經過三個環節:首先是主機A從磁盤上讀取文件並將數據分組成一個個數據包packet,,然後數據包通過連接主機A和主機B的網絡傳輸到主機B,最後是主機B收到數據包並將數據包寫入磁盤。在這個看似簡單其實很複雜的過程中可能會由於某些原因而影響正常通信。比如:磁盤上文件讀寫錯誤、緩衝溢出、內存出錯、網絡擁擠等等這些因素都有可能導致數據包的出錯或者丟失,由此可見用於通信的網絡是不可靠的。

由於實現通信只要經過上述三個環節,那麼我們就想是否在其中某個環節上增加一個檢錯糾錯機制來用於對信息進行把關呢?

網絡層肯定不能做這件事,因爲網絡層的最主要目的是增大數據傳輸的速率,網絡層不需要考慮數據的完整性,數據的完整性和正確性交給端系統去檢測就行了,因此在數據傳輸中,對於網絡層只能要求其提供儘可能好的數據傳輸服務,而不可能寄希望於網絡層提供數據完整性的服務。

UDP不可靠的原因是它雖然提供差錯檢測的功能,但是對於差錯沒有恢復能力更不會有重傳機制。

更多精彩推薦
☞挑戰 TensorFlow、PyTorch,誰纔是中國 AI 開源框架之星?
☞買房必看!又一程序員自編“購房寶典”火爆 GitHub
☞Google迴應全球宕機:磁盤滿了;摩拜App昨晚正式停止服務;Docker Desktop 3.0.0發佈|極客頭條☞爲什麼蘋果M1芯片這麼快?

☞我是Redis,MySQL大哥被我害慘了!
☞“剛畢業1年,做數據分析憑什麼漲薪三連跳!”網友:吹的不多..
點分享點點贊點在看
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章