UDP (User Datagram Protocol,用户数据报协议) 是一种无连接、不可靠、但传输速度快的协议,它不像 TCP 那样需要建立连接、维护连接状态和保证数据顺序到达,而是直接将数据包(Datagram)发送出去,不关心对方是否收到。

UDP 编程的核心就是 发送数据包 和 接收数据包。
在 Java 中,java.net 包提供了 DatagramSocket 和 DatagramPacket 这两个核心类来实现 UDP 通信。
DatagramSocket: 代表一个 socket,用于发送和接收数据报,它就像是邮局,负责将你的信件(数据包)寄出去或接收别人的信件。DatagramPacket: 代表一个数据报,包含了要发送的数据、目标地址(IP 和端口)或接收到的数据以及来源地址(IP 和端口),它就像是信件本身,上面写满了内容、收件人地址和寄件人地址。
核心概念
DatagramSocket 的构造方法
DatagramSocket(): 创建一个未绑定的 socket,系统会自动分配一个可用的端口,通常用于客户端。DatagramSocket(int port): 创建一个绑定到指定端口的 socket,通常用于服务器,因为服务器需要监听一个固定的端口。DatagramSocket(int port, InetAddress laddr): 创建一个绑定到指定 IP 地址和端口的 socket。
DatagramPacket 的构造方法
- 用于发送:
DatagramPacket(byte[] buf, int length, InetAddress address, int port)buf: 要发送的数据字节数组。length: 要发送的数据长度(从buf的第一个字节开始算)。address: 目标主机的 IP 地址。port: 目标主机的端口号。
- 用于接收:
DatagramPacket(byte[] buf, int length)buf: 用于接收数据的字节数组,这个数组的大小决定了能接收的最大数据包大小。length: 数组的长度。
编程模型(Echo 示例)
下面我们通过一个经典的 "Echo"(回声)服务来演示 UDP 编程,一个服务器接收客户端发来的消息,然后将原消息返回给客户端。
服务器端代码
服务器需要:

- 创建一个
DatagramSocket并绑定到一个固定的端口。 - 创建一个
DatagramPacket用于接收数据,并指定一个缓冲区。 - 循环调用
receive()方法来等待并接收客户端的数据包。 - 从接收到的数据包中提取数据、客户端地址和端口。
- 创建一个新的
DatagramPacket,将接收到的数据发回给客户端。 - 调用
send()方法发送回声数据包。 - 关闭
DatagramSocket。
// UDPEchoServer.java
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
public class UDPEchoServer {
// 服务器监听的端口号
private static final int PORT = 9876;
public static void main(String[] args) {
// try-with-resources 语句,确保 DatagramSocket 自动关闭
try (DatagramSocket serverSocket = new DatagramSocket(PORT)) {
System.out.println("服务器已启动,正在监听端口 " + PORT + "...");
// 创建一个缓冲区用于接收数据
byte[] receiveBuffer = new byte[1024];
while (true) {
// 1. 创建一个空的 DatagramPacket 用于接收数据
DatagramPacket receivePacket = new DatagramPacket(receiveBuffer, receiveBuffer.length);
// 2. 接收客户端的数据包(此方法会阻塞,直到收到数据)
serverSocket.receive(receivePacket);
// 3. 从数据包中提取数据
String receivedMessage = new String(receivePacket.getData(), 0, receivePacket.getLength());
System.out.println("收到来自 " + receivePacket.getAddress().getHostAddress() + ":" + receivePacket.getPort() + " 的消息: " + receivedMessage);
// 4. 准备回声数据
byte[] echoMessage = receivedMessage.getBytes();
// 5. 创建一个新的 DatagramPacket 用于发送回声
// 使用客户端的地址和端口
DatagramPacket sendPacket = new DatagramPacket(
echoMessage,
echoMessage.length,
receivePacket.getAddress(),
receivePacket.getPort()
);
// 6. 发送回声数据包
serverSocket.send(sendPacket);
System.out.println("已将回声发送回客户端。");
}
} catch (SocketException e) {
System.err.println("创建或绑定 Socket 时出错: " + e.getMessage());
} catch (IOException e) {
System.err.println("I/O 错误: " + e.getMessage());
}
}
}
客户端代码
客户端需要:
- 创建一个
DatagramSocket,系统会自动分配一个端口。 - 创建一个
DatagramPacket,将要发送的数据、服务器的 IP 地址和端口封装进去。 - 调用
send()方法发送数据包。 - 创建另一个
DatagramPacket用于接收服务器的回声,并指定一个缓冲区。 - 调用
receive()方法等待接收回声数据包。 - 从接收到的数据包中提取并打印数据。
- 关闭
DatagramSocket。
// UDPEchoClient.java
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.Scanner;
public class UDPEchoClient {
// 服务器的 IP 地址和端口号
private static final String SERVER_IP = "127.0.0.1"; // 本地回环地址
private static final int SERVER_PORT = 9876;
public static void main(String[] args) {
// try-with-resources 语句,确保 DatagramSocket 自动关闭
try (DatagramSocket clientSocket = new DatagramSocket()) {
InetAddress serverAddress = InetAddress.getByName(SERVER_IP);
Scanner scanner = new Scanner(System.in);
System.out.println("客户端已启动,连接到服务器 " + SERVER_IP + ":" + SERVER_PORT);
while (true) {
System.out.print("请输入要发送的消息 (输入 'exit' 退出): ");
String message = scanner.nextLine();
if ("exit".equalsIgnoreCase(message)) {
break;
}
// 1. 准备要发送的数据
byte[] sendBuffer = message.getBytes();
// 2. 创建用于发送的 DatagramPacket
DatagramPacket sendPacket = new DatagramPacket(
sendBuffer,
sendBuffer.length,
serverAddress,
SERVER_PORT
);
// 3. 发送数据包
clientSocket.send(sendPacket);
System.out.println("消息已发送: " + message);
// 4. 创建用于接收的 DatagramPacket
byte[] receiveBuffer = new byte[1024];
DatagramPacket receivePacket = new DatagramPacket(receiveBuffer, receiveBuffer.length);
// 5. 接收服务器的回声(此方法会阻塞,直到收到数据)
clientSocket.receive(receivePacket);
// 6. 从数据包中提取并打印回声消息
String echoMessage = new String(receivePacket.getData(), 0, receivePacket.getLength());
System.out.println("收到回声: " + echoMessage);
}
} catch (SocketException e) {
System.err.println("创建 Socket 时出错: " + e.getMessage());
} catch (IOException e) {
System.err.println("I/O 错误: " + e.getMessage());
}
}
}
如何运行
-
编译代码:
javac UDPEchoServer.java UDPEchoClient.java
-
运行服务器:
java UDPEchoServer
你会看到控制台输出:
服务器已启动,正在监听端口 9876...
(图片来源网络,侵删) -
运行客户端: 打开另一个终端窗口,运行客户端:
java UDPEchoClient
-
交互: 在客户端的控制台输入消息,"Hello UDP",然后按回车。
- 客户端会显示:
消息已发送: Hello UDP - 服务器会显示:
收到来自 127.0.0.1:54321 的消息: Hello UDP(端口可能不同) - 客户端会接着显示:
收到回声: Hello UDP
输入
exit可以退出客户端程序,服务器会一直运行,等待下一个客户端连接。 - 客户端会显示:
UDP 的特点与注意事项
- 无连接: 发送数据前无需建立连接,开销小,速度快,但这也意味着发送前必须知道对方的 IP 和端口。
- 不可靠:
- 丢包: 数据包可能在传输过程中丢失。
- 乱序: 数据包可能不按顺序到达。
- 重复: 数据包可能被重复接收。
- 如果你的应用需要可靠性(如文件传输、网页浏览),应该使用 TCP,UDP 适用于对实时性要求高、能容忍少量丢包的场景,如:
- 在线游戏: 玩家的位置更新,偶尔丢一两个包影响不大。
- 视频/音频会议: 偶尔的卡顿可以接受,但不能因为重传而延迟。
- DNS 查询: 请求和响应都很小,速度至关重要。
- 广播/多播: 向网络中的多个主机同时发送消息。
- 面向数据报: 应用程序以数据报的形式读写数据,保留了消息的边界,TCP 是面向字节流的,没有消息边界。
- 大小限制: 单个 UDP 数据报的最大理论大小是 65,507 字节(由 IP 包的 16 位总长度字段限制,IP 头 20 字节,UDP 头 8 字节,剩下 65535 - 28 = 65507),实际应用中,为了网络传输效率,通常建议将数据包大小控制在 512 字节以内。
- 广播和多播: Java UDP 也支持广播和多播。
InetAddress.getByName("255.255.255.255")用于广播,发送到局域网内的所有主机,多播则需要使用特定的 IP 地址范围(D类地址,如 224.0.0.0 到 239.255.255.255)。
