1、UDP協議基礎
1. UDP是什么?
UDP(User Datagram Protocol,用戶數據報協議)是傳輸層的核心協議之一,與TCP并列。它的主要特點是:????
-
無連接:通信前不需要建立連接(知道對端的IP和端口號就直接進行傳輸,不需要建立連接)
-
不可靠:不保證數據包的順序、完整性或可達性(沒有確認機制,沒有重傳機制;如果因為網絡故障該段無法發到對方,UDP協議層也不會給應用層返回任何錯誤信息)
-
大小受限:?次最多傳輸64k(UDP協議首部中有?個16位的最大長度.也就是說?個UDP能傳輸的數據最大長度是 64K(包含UDP首部))
-
輕量級:頭部開銷小(僅8字節)
-
高效:沒有TCP的握手、確認和重傳機制
2. UDP報文結構
UDP數據包(稱為數據報)由頭部和數據部分組成:
-
源端口號(2字節):發送方端口
-
目標端口號(2字節):接收方端口
-
數據報長度(2字節):頭部+數據的長度
-
校驗和(2字節):錯誤檢測(可選)如果校驗和出錯則直接丟棄
2、UDP的核心特性
1. 無連接通信:UDP不需要預先建立連接,直接發送數據報。這類似于寄信,不需要確認收件人是否在家。
2. 不可靠傳輸:UDP不提供:數據包確認機制、丟失重傳機制、數據包排序功能
3.面向數據報:每個UDP數據報都是獨立的,這與TCP的字節流模式不同
4.支持廣播和多播:UDP可以向多個主機同時發送數據:
-
單播?:一對一
-
廣播?:一對所有
-
多播?:一對一組
3、基于UDP的應用層協議
- NFS:網絡文件系統
- TFTP:簡單文件傳輸協議
- DHCP:動態主機配置協議
- BOOTP:啟動協議(用于無盤設備啟動)
- DNS:域名解析協議
當然,也包括我們自己寫UDP程序時自定義的應用層協議。
4、Java中的UDP編程
主要使用了兩個類 :
1、DatagramSocket :用于發送和接收數據報的套接字
// 創建UDP套接字(綁定隨機端口)
DatagramSocket socket = new DatagramSocket();// 創建綁定特定端口的套接字
DatagramSocket serverSocket = new DatagramSocket(8080);
2、DatagramPacket :表示UDP數據報的容器
// 創建接收數據包
byte[] buffer = new byte[1024];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);// 創建發送數據包
String message = "Hello UDP";
byte[] data = message.getBytes();
DatagramPacket sendPacket = new DatagramPacket(
? ? data,?
? ? data.length,?
? ? InetAddress.getByName("localhost"),?
? ? 8080
);
5、完整Java UDP示例
1.Echoserver:UDP服務器,接收客戶端消息并原樣返回
package Network.UDP;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;public class Echoserver {//創建一個socket對象private DatagramSocket socket;//構造方法,初始化socket對象public Echoserver(int port) throws SocketException {socket = new DatagramSocket(port);}//啟動服務器,完成主要的業務邏輯public void start() throws IOException {System.out.println("服務器啟動!");while(true) {//1.接收客戶端的請求并解析//1)創建一個字節數組(DatagramPacket 對象),用于存儲接收到的數據DatagramPacket reqPacket = new DatagramPacket(new byte[4096],4096);//2)通過receive讀取網卡的數據,如果網卡沒有收到數據,就會阻塞等待socket.receive(reqPacket);//3)把DATagramPacket中的數據解析成字符串,只需要從DatagramPacket取到有效的樹即可String request = new String(reqPacket.getData(),0,reqPacket.getLength());//2.根據請求計算響應String response = process(request);//3.把響應寫回客戶端//1)把響應字符串轉成字節數組,并封裝成DatagramPacket對象DatagramPacket resPacket = new DatagramPacket(response.getBytes(),response.getBytes().length,reqPacket.getSocketAddress());//2)通過socket把DatagramPacket對象發送出去 (把DatagramPacket寫回到客戶端)socket.send(resPacket);//4.打印日志System.out.printf("[%s:%d] req: %s,resp: %s\n",reqPacket.getAddress(),reqPacket.getPort(),request,response);}}//由于是“回顯服務器”,所以響應和請求是一樣的,直接返回請求即可public String process(String request) {return request;}public static void main(String[] args) throws IOException {Echoserver server = new Echoserver(9090);server.start(); //回顯服務器完成}
}
代碼詳細解析:
①創建 EchoServer 服務端的類,定義成員變量
-
DatagramSocket:UDP通信的核心類,用于發送和接收數據報
-
封裝在類中作為成員變量,整個生命周期內有效
②構造方法
-
創建綁定到指定端口的 DatagramSocket(可能會拋出異常,如:該端口已被占用)
-
port 是服務器自身綁定的監聽端口號,等待客戶端發送數據,客戶端需要知道服務器的這個端口號才能向其發送消息。
客戶端通過該端口找到服務器,而客戶端的端口由系統動態分配。
這種設計體現了?UDP 無連接的特性,服務器只需關注自身綁定端口即可接收所有客戶端消息。
③核心業務邏輯(start方法)
- while 無限循環接收來自客戶端的數據,這里可以根據自己的需求更改接收次數
- 創建緩沖區:分配一個4096字節(字節大小自行定義不溢出就行)的空數組,存儲接收到的數據
- 阻塞等待數據報到達(數據此時已存入?reqPacket?的字節數組中)如果 reqPacket 沒有接收到客戶端發來的數據這里會阻塞等待
- 聲明 String 類型的 request 變量,將 reqPacket?中的數據解析為字符串
- getData() :獲取字節數組(可能包含多余的空字節)
- getLength() :獲取實際有效數據長度(避免解析無效字節)
在網絡編程中,將響應(
response
)轉成字節數組(byte[]
)再發送是必要的步驟,這與計算機底層的數據傳輸機制和網絡協議的特性密切相關。
- process() :返回響應后的數據,由于整段代碼實現的是 Echo(回顯) 服務器代碼,響應內容=請求內容,所以方法中直接返回請求即可
- soclet.send() :將數據發送回客戶端
附上發送回客戶端的終端代碼
- 打印日志:打印客戶端信息和通信內容
④主方法
-
new Echoserver(9090) :調用構造函數,創建綁定到?
9090
?端口的?DatagramSocket
(UDP套接字) -
server.start() :啟動服務器循環,等待客戶端數據報
2.EchoClient:UDP客戶端,發送消息并顯示服務器返回的響應
package Network.UDP;import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;public class EchoClient {private DatagramSocket socket = null;private String serverIp;private int serverPort;public EchoClient(String serverIp, int serverPort) throws IOException { socket = new DatagramSocket();this.serverIp = serverIp;this.serverPort = serverPort;} //啟動客戶端,發送數據,接收數據,關閉連接public void start() throws IOException {Scanner scanner = new Scanner(System.in);System.out.println("客戶端啟動!");while(true) {//1.從控制臺讀取要發送的數據內容(用字符串表示)System.out.print(">");String request = scanner.nextLine(); //2.構造成UDP請求,并發送.不光要填內容,還要填服務器的地址和端口號DatagramPacket reqPacket = new DatagramPacket(request.getBytes(),request.getBytes().length,InetAddress.getByName(serverIp),serverPort); socket.send(reqPacket);//3.接收服務器響應的數據DatagramPacket resPacket = new DatagramPacket(new byte[4096], 4096);socket.receive(resPacket);String response = new String(resPacket.getData(),0,resPacket.getLength());//4.把響應的數據解析并打印System.out.println(response);}}public static void main(String[] args) throws IOException {EchoClient echoclient = new EchoClient("127.0.0.1",9090);echoclient.start();}
}
詳細代碼解析:
①創建 EchoClient 客戶端的類,定義成員變量
-
socket :客戶端UDP套接字
-
serverIp/serverPort :服務器地址信息
?
②構造方法
- DatagramSocket ( )? 為客戶端隨機分配一個端口號
- 保存服務器地址信息
③核心業務邏輯(start方法)
- request 用戶輸入要發送的數據
- reqPacket 封裝要發送的數據
- request.getBytes() :將用戶輸入的字符串轉換為字節數組
- request.getBytes().length :獲取字節數組的長度,表示要發送的數據大小
- InetAddress.getByName(serverIp) :通過服務器的IP地址獲取對應的 InetAddress 對象,表示數據要發送到的目標地址
- serverPort :服務器的端口號,表示數據要發送到的端口
- socket.send() :調用 send 方法將數據報發送到指定的目標地址和端口
- resPacket() :接收服務器發回的響應數據,socket.receive() 是一個阻塞操作,直到接收到數據才會繼續執行
- response :提取響應內容,將接收到的字節數據轉換為字符串
④主方法
- 創建客戶端實例,連接本地服務器的9090端口
- start 啟動客戶端
6、UDP編程注意事項
1.數據報大小限制
- UDP數據報最大長度理論為65535字節
- 實際受MTU限制(通常1500字節)
- 建議保持數據報在1472字節以內(IPv4)
2.數據邊界問題
- 每個 receive() 調用接收一個完整的數據報
- 不會出現TCP的“粘包”問題
3.錯誤處理
- 網絡不可達
- 端口未監聽
- 數據包丟失
4.安全性考慮
- UDP易受DDoS攻擊
- 應考慮實現應用層的驗證機制
?