UDP (User Datagram Protocol,用户数据报协议) 是一种无连接、不可靠但高效的传输层协议。
无连接:发送数据前不需要像 TCP 那样进行“三次握手”建立连接,直接发送即可,因此延迟极低。
不可靠:不保证数据一定到达,不保证顺序,丢失后不会自动重传。
数据包机制:数据被封装成独立的数据报 (Datagram)。每个数据报包含源 IP、目的 IP、端口号以及数据内容。
大小限制:单个 UDP 数据包的有效载荷通常限制在 64KB 以内(受限于 IP 协议的最大包大小)。
适用场景:视频会议、直播流、在线游戏、语音通话 (VoIP)、DNS 查询等对实时性要求高、能容忍少量丢包的场景。
Java 在 java.net 包中提供了两个核心类来实现 UDP 通信:DatagramPacket (数据包) 和 DatagramSocket (套接字)。
数据的载体
DatagramPacket 对象代表一个 UDP 数据包,它既用于发送(封装要发出的数据),也用于接收(作为容器装载收到的数据)。
通信的门户
DatagramSocket 是发送和接收数据包的端点。无论是客户端还是服务端,都需要创建这个对象。
本案例演示最简单的“一发一收”模式。
发送端:构造数据,指定目标 IP 和端口,发送。
接收端:绑定固定端口,等待并接收数据。
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class UDPSender {
public static void main(String[] args) {
try {
// 1. 创建 DatagramSocket (客户端通常不需要指定端口,由系统分配)
DatagramSocket socket = new DatagramSocket();
// 2. 准备要发送的数据
String message = "Hello, UDP! 这是一条测试消息。";
byte[] data = message.getBytes("UTF-8"); // 建议指定编码
// 3. 指定接收方的 IP 和端口
// 127.0.0.1 表示本机,实际开发中替换为服务器真实 IP
InetAddress serverAddress = InetAddress.getByName("127.0.0.1");
int serverPort = 9999;
// 4. 创建数据包 (填入数据、长度、目标 IP、目标端口)
DatagramPacket packet = new DatagramPacket(data, data.length, serverAddress, serverPort);
// 5. 发送数据包
socket.send(packet);
System.out.println("消息已发送: " + message);
// 6. 关闭资源
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class UDPReceiver {
public static void main(String[] args) {
try {
// 1. 创建 DatagramSocket 并绑定固定端口 (必须与发送端的目标端口一致)
int localPort = 9999;
DatagramSocket socket = new DatagramSocket(localPort);
System.out.println("接收端已启动,监听端口: " + localPort);
// 2. 准备接收数据的缓冲区 (大小需 >= 发送端数据长度,通常设为 1024 或更大)
byte[] buffer = new byte[1024];
// 3. 创建用于接收的数据包 (只需传入缓冲区和长度)
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
// 4. 接收数据 (阻塞方法,直到收到数据)
socket.receive(packet);
// 5. 解析数据
// 注意:必须使用 getLength() 获取实际接收到的字节数,否则会有多余的空字符
String receivedMessage = new String(packet.getData(), 0, packet.getLength(), "UTF-8");
// 可选:打印发送方信息
InetAddress senderIp = packet.getAddress();
int senderPort = packet.getPort();
System.out.println("接收到来自 [" + senderIp.getHostAddress() + ":" + senderPort + "] 的消息: " + receivedMessage);
// 6. 关闭资源
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}运行步骤:
先运行
UDPReceiver(服务端),程序会阻塞在receive()处等待。再运行
UDPSender(客户端)。观察控制台输出,接收端应打印出消息内容。
模拟聊天
在实际应用中,通信往往是持续的。为了模拟“一边发一边收”,我们需要使用多线程。
主线程:负责启动发送和接收逻辑。
发送线程:循环发送消息。
接收线程:循环接收消息。
注意:同一个
DatagramSocket实例可以在多个线程中安全地进行send和receive操作。
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;
public class UDPChatSimulator {
// 定义通信端口
private static final int PORT = 8888;
public static void main(String[] args) {
try {
// 创建一个 Socket,绑定端口 (既做发送也做接收)
DatagramSocket socket = new DatagramSocket(PORT);
System.out.println("=== UDP 聊天模拟启动 (端口: " + PORT + ") ===");
// 1. 启动接收线程
Thread receiveThread = new Thread(() -> {
try {
while (!Thread.currentThread().isInterrupted()) {
byte[] buffer = new byte[1024];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
// 阻塞接收
socket.receive(packet);
String msg = new String(packet.getData(), 0, packet.getLength(), "UTF-8");
String sender = packet.getAddress().getHostAddress() + ":" + packet.getPort();
System.out.println("\n[收到] 来自 " + sender + ": " + msg);
}
} catch (Exception e) {
if (!socket.isClosed()) {
System.err.println("接收线程异常: " + e.getMessage());
}
}
});
receiveThread.start();
// 2. 启动发送线程 (模拟持续发送或用户输入)
Thread sendThread = new Thread(() -> {
try {
Scanner scanner = new Scanner(System.in);
InetAddress targetAddress = InetAddress.getByName("127.0.0.1");
while (!Thread.currentThread().isInterrupted()) {
System.out.print("请输入消息 (输入 'exit' 退出): ");
String input = scanner.nextLine();
if ("exit".equalsIgnoreCase(input)) {
break;
}
byte[] data = input.getBytes("UTF-8");
// 构造发送包:目标是本机同端口 (模拟自发自收或与同一机器另一程序通信)
// 实际场景中 targetAddress 和 PORT 应是对方的地址
DatagramPacket packet = new DatagramPacket(data, data.length, targetAddress, PORT);
socket.send(packet);
System.out.println("[发送] 消息已发出");
}
scanner.close();
} catch (Exception e) {
System.err.println("发送线程异常: " + e.getMessage());
} finally {
// 退出时关闭 Socket
socket.close();
System.out.println("=== 连接已关闭 ===");
}
});
sendThread.start();
} catch (Exception e) {
e.printStackTrace();
}
}
}