一、UDP协议基础定义
UDP是TCP/IP协议簇中的传输层协议。基于UDP的网络通信通过数据报套接字实现,在Java中对应DatagramSocket类(用于创建数据报套接字)和DatagramPacket类(作为发送或接收的UDP数据报载体)。
二、UDP协议格式
UDP协议首部格式简洁,共8字节,具体结构如下:
-
16位源端口号:标识发送端进程的端口
-
16位目的端口号:标识接收端进程的端口
-
16位UDP长度:表示整个数据报(UDP首部+UDP数据)的总长度
-
16位UDP检验和:用于校验数据的完整性,若校验和出错,数据报会被直接丢弃

三、UDP协议核心特点
UDP传输过程类似于寄信,核心特点可概括为以下几点:
-
无连接:无需提前建立连接,只要知道对方的IP地址和端口号,就可以直接发送数据报,通信前不存在"三次握手"的过程。
-
不可靠传输:没有确认应答机制,也没有超时重传机制。如果数据报因网络故障丢失,UDP协议层不会向应用层返回任何错误信息,无法保证数据一定能到达接收端。
-
面向数据报:应用层交给UDP的报文会被原样发送,既不会拆分也不会合并。发送端调用一次发送操作(如Java中的send方法)发送的数据,接收端必须调用一次接收操作(如Java中的receive方法)完整接收,不能分多次接收。例如发送100字节的数据,接收端必须一次接收100字节,无法循环100次每次接收1字节。
-
缓冲区特性:仅存在接收缓冲区,无发送缓冲区。接收缓冲区用于存储收到的数据报,若应用层未及时读取,后续到达的数据报会被丢弃。
-
数据大小受限:一次UDP数据传输的最大长度受限于16位UDP长度字段,最大为64KB。若需传输超过64K的数据,需在应用层手动分包、多次发送,并在接收端手动拼装。
四、Java UDP数据报套接字通信模型
单轮发送端与接收端流程
-
发送端:① 创建DatagramSocket对象;② 构造发送的数据报(DatagramPacket),需指定发送数据(字节数组形式)、接收端IP地址和端口号;③ 调用socket.send(packet)发送数据报(非阻塞,直接发送)。
-
接收端:① 创建DatagramSocket对象;② 构造接收的数据报(DatagramPacket),指定空字节数组作为数据存储载体;③ 调用socket.receive阻塞式等待接收数据报;④ 接收成功后,通过DatagramPacket的方法获取接收的数据、发送端IP地址和端口号。
五、UDP相关API详解
1. DatagramSocket类
用于创建UDP数据报套接字,核心构造方法和方法如下:
构造方法
-
DatagramSocket():创建UDP数据报套接字,绑定到本机任意一个随机端口(一般用于客户端);
-
DatagramSocket(int port):创建UDP数据报套接字,绑定到本机指定的端口(一般用于服务端)。
核心方法
-
void receive(DatagramPacket p):从此套接字接收数据报,若未接收到数据,该方法会阻塞等待;
-
void send(DatagramPacket p):从此套接字发送数据报包,不会阻塞等待,直接发送;
-
void close():关闭此数据报套接字。
2. DatagramPacket类
作为UDP发送和接收的数据报载体,核心构造方法和方法如下:
构造方法
-
DatagramPacket(byte[] buf, int length):构造用于接收数据报的对象,接收的数据保存在字节数组buf中,接收长度为length;
-
DatagramPacket(byte[] buf, int offset, int length, SocketAddress address):构造用于发送数据报的对象,发送的数据为字节数组buf中从offset开始的length个字节,address指定目的主机的IP和端口号(通常使用InetSocketAddress实现)。
核心方法
-
InetAddress getAddress():从接收的数据报中获取发送端主机IP地址,或从发送的数据报中获取接收端主机IP地址;
-
int getPort():从接收的数据报中获取发送端主机端口号,或从发送的数据报中获取接收端主机端口号;
-
byte[] getData():获取数据报中的数据。
3. InetSocketAddress类
SocketAddress的子类,用于创建包含IP地址和端口号的Socket地址,核心构造方法:
-
InetSocketAddress(InetAddress addr, int port):创建一个Socket地址,包含指定的IP地址和端口号。
六、UDP协议使用注意事项
-
数据大小限制处理:由于UDP一次传输最大为64K,若传输数据超过此大小,需在应用层手动分包,多次发送,并在接收端手动拼装。
-
端口号使用规范:避免使用0-1023的知名端口号(如HTTP的80端口、FTP的21端口等),客户端端口号一般由操作系统从1024-65535的范围动态分配。
-
粘包问题说明:UDP不存在粘包问题。因为UDP是面向数据报的,每个数据报都是独立的,接收端要么完整接收一个数据报,要么不接收,不会出现多个数据报合并或拆分的情况。
-
MTU对UDP的影响:以太网的MTU(最大传输单元)为1500字节,UDP数据报若超过MTU(扣除20字节IP首部和8字节UDP首部,实际数据部分超过1472字节),会在网络层被分片。一旦任意一个分片丢失,接收端网络层重组会失败,整个UDP数据报失效,导致数据丢失概率增加。
七. UDP与TCP的区别
UDP和TCP作为传输层的两大核心协议,不存在绝对的优劣,需根据场景选择,核心区别如下:
-
连接性:UDP无连接,TCP有连接;
-
可靠性:UDP不可靠,TCP可靠(具备确认应答、超时重传等机制);
-
数据传输方式:UDP面向数据报,TCP面向字节流;
-
性能:UDP开销小、延迟低,TCP因可靠性机制存在额外开销,延迟相对较高;
-
适用场景:UDP适用于高速传输、实时性要求高的场景(如视频传输、早期QQ),也可用于广播;TCP适用于需要可靠传输的场景(如文件传输、重要状态更新)。
八、用UDP实现可靠传输
UDP本身是不可靠的,但可参考TCP的可靠性机制,在应用层实现可靠传输,核心思路包括:
-
引入序列号:对发送的每个数据报进行编号,确保接收端能按序接收并去重;
-
引入确认应答:接收端收到数据报后,向发送端返回确认信息,告知已收到的数据;
-
引入超时重传:发送端发送数据后启动定时器,若在规定时间内未收到确认应答,则重发该数据报;
-
其他补充机制:可根据需求引入滑动窗口(提高传输效率)、流量控制(根据接收端处理能力调整发送速度)、拥塞控制(根据网络状态调整发送速度)等机制。
九、UDP典型代码示例
1. UDP Echo Server
java
public class UdpEchoServer {
private DatagramSocket socket = null;
// 构造方法:绑定指定端口
public UdpEchoServer(int port) throws SocketException {
socket = new DatagramSocket(port);
}
// 启动服务器
public void start() throws IOException {
System.out.println("服务器启动!");
while (true) {
// 1. 读取请求并解析
DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);
socket.receive(requestPacket); // 阻塞等待接收数据
String request = new String(requestPacket.getData(), 0, requestPacket.getLength());
// 2. 处理请求(回显:响应与请求相同)
String response = process(request);
// 3. 发送响应
DatagramPacket responsePacket = new DatagramPacket(
response.getBytes(), response.getBytes().length,
requestPacket.getSocketAddress()
);
socket.send(responsePacket);
// 记录日志
System.out.printf("[%s:%d] req: %s, resp: %s\n",
requestPacket.getAddress().toString(),
requestPacket.getPort(),
request, response);
}
}
// 处理请求的核心方法
private String process(String request) {
return request; // 回显逻辑
}
public static void main(String[] args) throws IOException {
UdpEchoServer server = new UdpEchoServer(9090);
server.start();
}
}
2. UDP Echo Client
java
public class UdpEchoClient {
private DatagramSocket socket = null;
private String serverIp;
private int serverPort;
// 构造方法:指定服务器IP和端口
public UdpEchoClient(String ip, int port) throws SocketException {
serverIp = ip;
serverPort = port;
socket = new DatagramSocket(); // 客户端绑定随机端口
}
// 启动客户端
public void start() throws IOException {
Scanner scanner = new Scanner(System.in);
System.out.println("客户端启动!");
while (true) {
// 1. 从控制台读取用户输入
System.out.print("-> ");
String request = scanner.next();
// 2. 发送请求到服务器
DatagramPacket requestPacket = new DatagramPacket(
request.getBytes(), request.getBytes().length,
InetAddress.getByName(serverIp), serverPort
);
socket.send(requestPacket);
// 3. 接收服务器响应
DatagramPacket responsePacket = new DatagramPacket(new byte[4096], 4096);
socket.receive(responsePacket);
// 4. 解析并打印响应
String response = new String(responsePacket.getData(), 0, responsePacket.getLength());
System.out.println(response);
}
}
public static void main(String[] args) throws IOException {
// 连接本地服务器,端口9090
UdpEchoClient client = new UdpEchoClient("127.0.0.1", 9090);
client.start();
}
}
3. UDP Dict Server(英译汉服务器)
java
public class UdpDictServer extends UdpEchoServer {
private Map<String, String> dict = new HashMap<>();
// 构造方法:初始化词典数据
public UdpDictServer(int port) throws SocketException {
super(port);
dict.put("cat", "小猫");
dict.put("dog", "小狗");
dict.put("fuck", "卧槽");
// 可添加更多单词映射
}
// 重写process方法,实现翻译逻辑
@Override
public String process(String request) {
return dict.getOrDefault(request, "该词没有查询到!");
}
public static void main(String[] args) throws IOException {
UdpDictServer server = new UdpDictServer(9090);
server.start(); // 复用父类的start方法
}
}