目錄
一、鋪墊知識
1、傳輸層
2、端口號
?2.1、五元組表示 一個進程通信
2.2、端口號范圍劃分
2.3、知名端口
2.4、查看端口號
2.5、問題?
3、pidof & netstat 命令?
①netsate 命令
②pidof命令?
二、UDP協議
1、UDP協議格式
2、UDP報文
1.1、UDP數據封裝的過程
1.2、UDP數據交付的過程
3、UDP特點
3.1、面向數據報
4、UDP緩沖區
5、UDP傳輸最大長度
6、基于UDP的應用層協議
一、鋪墊知識
1、傳輸層
?通過HTTP/HTTPS的學習,對應用層有了一定的認識,接下來就是下一層協議:傳輸層協議。
兩臺計算機通過TCP/IP協議通訊的大致過程如下:
中間的傳輸層和網絡層是在內核實現的,而應用層是在用戶層實現的,這也就注定了在進行學習傳輸層的時候必然就是要學習在Linux內核中的關于網絡的部分,實際上傳輸層在操作系統的內部是提供了一套對應的系統調用,然后進行正常的數據讀取,那對于這部分系統調用也有很多的接口,比如在創建套接字的時候有listen,bind,receive這樣的接口,所以下一步要學習的內容就在傳輸層當中。
2、端口號
在學傳輸層前,先談談端口號,對于端口號的話題在之前已經學習過了,這里只是想說對于端口號的認識,作為一臺主機來說,服務器可能會有很多的應用服務,每一個應用服務都要綁定一個明確的端口號,那這個端口號的意義就是能夠保證讓數據傳輸給上面的那一個應用程序,因為對于進程的區分就可以借助這個端口號來進行區分,借助ip地址和端口號就可以做到在全網找到唯一的一臺主機,而從這個主機上找到唯一的一個進程,這樣就能精確的找到對應的網絡服務了。
?2.1、五元組表示 一個進程通信
在?TCP/IP?協議中,用?“源IP”,“源端口號”,“目的?IP”,“目的端口號”,“協議號”?這樣一個五元組來標識一個通信。?舉例:打開瀏覽器后,添加多個同賬戶頁面訪問 CSDN,雖然源 IP 地址是一樣的,但是源端口號不同,就表示兩個不同的通信。通過這個五元組,服務器能夠準確的區分請求是從哪里來的。
所以現在我們可以大致的清楚通信的流程:
- 先提取出數據當中的目的 IP 地址和目的端口號,確定該數據是發送給當前服務進程的。
- 然后提取出數據當中的協議號,為該數據提供對應類型的服務。
- 最后提取出數據當中的源 IP 地址和源端口號,將其作為響應數據的目的 IP 地址和目的端口號,將響應結果發送給對應的客戶端進程。
2.2、端口號范圍劃分
端口號是一個 16 位的整數,它的取值范圍是 0~65535。
- 0~1023:知名端口號。比如 HTTP,FTP,SSH 等這些廣為使用的應用層協議,它們的端口號都是固定的。(類比 120 或 110)
- 1024~65535:操作系統動態分配的端口號。客戶端程序的端口號就是由操作系統從這個范圍分配的,允許用戶手動綁定。
2.3、知名端口
有些服務器是非常常用的,為了使用方便,人們約定一些常用的服務器,都是用以下這些固定的端口號:
- ssh 服務器使用 22 端口
- ftp 服務器使用 21 端口
- telnet 服務器使用 23 端口
- http 服務器使用 80 端口
- https 服務器使用 443
2.4、查看端口號
可以通過命令 vim /etc/services 查看文件內容,該文件是記錄網絡服務名和它們對應使用的端口號及協議。當自己寫一個程序使用端口號時,要避開這些知名端口號。
2.5、問題?
一個進程可不可以?bind 多個端口號?
可以。假設綁定了兩個端口號 A 和 B,這兩個端口號標識的是同一個進程 ,這與端口號用來標識進程的唯一性并不沖突。
一個端口號是否可以被多個進程 bind?
不行,因為端口號的作用就是標識唯一的一個進程。
如果綁定了多個進程,如何找到對應的進程呢?所以如果綁定一個已經被綁定的端口號就會出現綁定失敗的問題。
3、pidof & netstat 命令?
①netsate 命令
netstat?是一個用來查看網絡狀態的重要工具。
sudo netstat -ntlp? 查看TCP相關網絡信息
sudo netstat -nulp 查看UDP相關網絡信息
- n:拒絕顯示別名,能顯示數字的全部轉化成數字。
- l:僅列出有在 Listen(監聽)的服務狀態。
- p:顯示建立相關鏈接的程序名(進程:process)。
- t(tcp):僅顯示 tcp 相關選項。
- u(udp):僅顯示 udp 相關選項。
- a(all):顯示所有選項,默認不顯示 LISTEN 相關。
②pidof命令?
通過進程名來查看服務器進程的 pid。
ps axj | grep UdpServer
pidof UdpServer
二、UDP協議
學習傳輸層的協議,要從UDP協議入手,其中一個原因是UDP協議還是相對簡單一些。
任何協議進行封裝后都要進行解包,那如何辨別有效載荷和報頭呢?
對于這個問題在自定義協議當中,我們采取的策略是使用了一個\n來進行標記,在前面也有對應的字符串長度來進行解析,而在http協議中采用的方案是用了空行來進行對應的分割工作,那在UDP當中呢?如下:
1、UDP協議格式
- 16 位源端口號:表示數據從哪里來。
- 16 位目的端口號:表示數據要到哪里去。
- 16 位 UDP 長度:表示整個數據報(UDP 首部 + UDP 數據)的最大長度。
- 16 位 UDP 檢驗和:如果UDP報文的檢驗和出錯,就會直接將報文丟棄。
UDP的定義十分簡單粗暴,就是一個定長報頭,規定前面的這些字段就是定長的,規定前8個字節就是報頭,剩下的部分就是有效載荷。
為什么我們前面在應用層編寫代碼的時候,每一次寫端口號都喜歡用?uint16_t 呢?
其根本原因就是因為傳輸層協議中的端口號就是 16 位的。
send 數據并不是直接發送到網絡里,而是發給了傳輸層。然后傳輸層再通過網絡協議棧繼續發送。那接收端如何將報頭和有效載荷進行分離?
UDP 采用的是固定報頭,UDP 的報頭中只包含四個字段,每個字段的長度都是 16 位,總共 8 字節,所以直接提取前 8 個字節就是報頭,其他的就是有效載荷。UDP 具有將報文一個個正確接收的能力,UDP 是面向數據報的。
UDP 如何交付?(有效載荷交給上層的哪一個協議?)
應用層每個進程都綁定有端口號,UDP 就是通過報頭當中的目的端口號來找到對應的應用層進程的,把有效載荷交出去。
2、UDP報文
這里的報頭其實就是一種結構化數據對象(位段):
struct udp_hdr
{uint16_t src_port; // 源端口uint16_t dsc_port; // 目的端口uint16_t udp_len; // UDP長度uint16_t udp_check; // 校驗和
};
這個結構體中的內容其實都與上面的圖中所示的報頭字段一一對應,而實際上對于UDP的報頭理解確實可以理解為是對于UDP的字段填充的過程。?
1.1、UDP數據封裝的過程
那么如果要是使用UDP協議向網絡中發送Hello World這樣的字段,該如何理解這個過程呢?
首先要知道應用層 sendto 數據是發給傳輸層的。
創建一塊內存,計算出有效載荷的起始地址,拷貝有效載荷,強轉填寫報頭部分,最后形成 UDP 報文。
操作系統會提供一個類似于緩沖區的這樣的一段區域,那在這個緩沖區中就會構建對應的UDP請求,未來都是會在這個地方通過操作系統向對方發送UDP對應的報文,那這段UDP的報文會在內核中進行流動,它未來是要向下交互到底層硬件進行發送的,這就必然意味著在操作系統的內部會存在很多的UDP的報文
站在接收方的角度來講,未來它也會受到大量的UDP的報文,所以操作系統必然要對于這么多的UDP報文進行管理,因此在操作系統內部會存在這個叫做sk_buff的結構體
在這個結構體中會有對應的start,end,pos指針,那當未來要構建一個UDP的報文,在內核的層面上就會定義一段緩沖區,之后把前面定義的報頭部分拷貝進來,然后再把用戶空間的Hello World拷貝進來,然后把對應的start,end,pos這樣的指針進行一個描述,這樣對于當前UDP的報文就管理起來了,之后在內核中可以使用鏈表,來把這一個一個的UDP報文進行鏈接管理起來,這樣就能實現對于內核中UDP報文的管理工作
至此,UDP的傳輸原理其實就是這樣,雖然在內核中的實際實現并不是這樣,但是基本思想確實如此,未來有機會再對于源碼中的UDP部分進行分析詳解。
1.2、UDP數據交付的過程
因為是定長報頭,直接取出目的端口號,把有效載荷向上交付給指定協議(進程)。
3、UDP特點
UDP?傳輸的過程類似于寄信。
- 無連接:知道對端的 IP 和端口號就直接進行傳輸,不需要建立連接。
- 不可靠:沒有確認機制,沒有重傳機制。如果因為網絡故障該段無法發到對方,UDP 協議層也不會給應用層返回任何錯誤信息。
- 面向數據報:不能夠靈活的控制讀寫數據的次數和數量。要讀取就必須讀取一個完整的報文。
3.1、面向數據報
舉例理解:別人發了三個快遞,那么我們就一定要收到三個,不會出現只收到一個,一個半這樣的情況。如果只有一個包裹,那我們也不能只拿走一半。結論:發送了一個報文,要么不讀,要么 recvfrom 等到讀取完一整個報文再返回。
應用層交給 UDP 多長的報文,UDP 原樣發送,既不會拆分,也不會合并。
用 UDP 傳輸 100 個字節的數據:如果發送端調用一次 sendto,發送 100 字節,那么接收端也必須調用對應的一次recvfrom,接收100個字節;而不能循環調用10次recvfrom,每次接收10個字節。
4、UDP緩沖區
嚴格意義上來說:
- UDP?沒有真正意義上的發送緩沖區。因為它沒有可靠機制,不需要把數據暫存起來。它直接調用 sendto 會直接交給內核,由內核將數據傳給網絡層協議進行后續的傳輸動作。
- UDP 是只有接收緩沖區。這個緩沖區是用來保存用戶暫時來不及處理的報文,但是這個接收緩沖區不能保證收到的 UDP 報的順序和發送 UDP 報的順序一致,如果緩沖區滿了,再到達的 UDP 數據就會被丟棄。
UDP 的 socket 既能讀,也能寫,所以是全雙工的。
為什么 UDP 要有緩沖區?
如果 UDP 沒有接收緩沖區,那么就要求上層及時將 UDP 獲取到的報文讀取上去,如果一個報文在 UDP 沒有被讀取,那么此時 UDP 從底層獲取上來的報文數據就會被迫丟棄。
5、UDP傳輸最大長度
UDP?協議首部中有一個?16?位的最大長度,因此一個 UDP 能傳輸的數據最大長度是?64K(包含 UDP 報頭的大小)。
然而 64K?在當今的互聯網環境下是一個非常小的數字,如果我們要傳輸的數據大于 64K,就需要在應用層進行手動分包,多次發送并在接收端進行手動拼裝。
6、基于UDP的應用層協議
- NFS:網絡文件系統
- TFTP:簡單文件傳輸協議
- DHCP:動態主機配置協議
- BOOTP:啟動協議(用于無盤設備啟動)
- DNS:域名解析協議
當然也包括我們自己寫?UDP?程序時自定義的應用層協議。