Socket(套接字)是网络编程的核心工具,也是操作系统提供给应用程序的网络通信接口。简单来说,Socket 文件是操作系统对网卡的抽象封装,读写 Socket 文件,本质上就是借助网卡完成数据的收发操作。
在网络协议中,UDP 是一种无连接、不可靠的传输层协议,而 Java 提供了 DatagramSocket 和 DatagramPacket 两个类来支持 UDP 编程。接下来,我们就详细拆解 UDP 编程的核心知识点,并通过完整的示例代码落地实践。
一、UDP 数据报
UDP 编程的核心是两个类:DatagramSocket(用于收发数据报的套接字)和 DatagramPacket(用于封装数据报的载体)。
DatagramSocket
DatagramSocket 是 UDP 对应的 Socket 实现,专门用于发送和接收 UDP 数据报,它就像一个「数据收发中转站」,负责建立应用程序与网络层的连接。
(1)核心构造方法
| 方法签名 | 方法说明 |
|---|---|
DatagramSocket() | 创建一个 UDP 数据报套接字,绑定到本机任意随机端口(适用于客户端,无需固定端口) |
DatagramSocket(int port) | 创建一个 UDP 数据报套接字,绑定到本机指定端口(适用于服务端,需要固定端口供客户端访问) |
(2)常用核心方法
| 方法签名 | 方法说明 |
|---|---|
void receive(DatagramPacket p) | 从此套接字接收数据报(无数据时会阻塞等待,直到收到数据) |
void send(DatagramPacket p) | 从此套接字发送数据报(直接发送,不阻塞等待对方确认) |
void close() | 关闭此数据报套接字,释放相关资源 |
DatagramPacket
DatagramPacket 是 UDP 数据报的「载体」,负责封装 UDP 通信中的数据内容、目标地址、端口号等信息。所有通过 DatagramSocket 收发的数据,都必须封装在这个对象中。
(1)核心构造方法
UDP 的收发数据报构造方法有所区分,需注意区分使用场景:
| 方法签名 | 方法说明 |
|---|---|
DatagramPacket(byte[] buf, int length) | 用于接收数据报:接收到的数据会存入字节数组 buf,仅保留前 length 长度的内容 |
DatagramPacket(byte[] buf, int offset, int length, SocketAddress address) | 用于发送数据报:从字节数组 buf 的 offset 下标开始,取 length 长度的数据作为发送内容;address 指定接收端的 IP 和端口 |
(2)常用核心方法
| 方法签名 | 方法说明 |
|---|---|
InetAddress getAddress() | 接收场景:获取发送端的 IP 地址;发送场景:获取接收端的 IP 地址 |
int getPort() | 接收场景:获取发送端的端口号;发送场景:获取接收端的端口号 |
byte[] getData() | 获取数据报中封装的原始字节数据 |
创建 SocketAddress
构造发送用的 DatagramPacket 时,需要传入 SocketAddress 对象,通常我们使用 InetSocketAddress 来快速创建(指定 IP 和端口):
// 示例:创建指向 本地回环地址 127.0.0.1:8888 的 Socket 地址
SocketAddress address = new InetSocketAddress("127.0.0.1", 8888);
UDP 实战:实现回声服务器
回声服务器(Echo Server)的核心功能是:接收客户端发送的消息,不做任何处理,直接原封不动地返回给客户端。下面我们分别实现服务端和客户端代码,一步一步完成 UDP 通信。
UDP 服务端代码
服务端需要固定端口,循环接收客户端请求,并返回响应结果,核心步骤是「接收请求 → 构造响应 → 发送响应」。
import java.io.*;
import java.net.*;
public class UdpEchoServer {
// 声明 DatagramSocket 实例,负责收发数据
private DatagramSocket socket = null;
/**
* 构造方法:绑定指定端口,创建 UDP 套接字
* @param port 服务端监听端口
* @throws SocketException 端口被占用时抛出异常
*/
public UdpEchoServer(int port) throws SocketException {
socket = new DatagramSocket(port);
}
/**
* 启动服务端,循环处理客户端请求
* @throws IOException 数据收发异常时抛出
*/
public void start() throws IOException {
System.out.println("服务器启动...");
// 无限循环,持续处理请求(服务端通常不主动退出)
while (true) {
// 1. 创建空白的 UDP 数据包,接收客户端请求
DatagramPacket requestPacket = new DatagramPacket(new byte[1024],
1024);
socket.receive(requestPacket);
// 2. 解析请求数据,转为字符串(方便阅读和处理)
String request = new String(requestPacket.getData(), 0,
requestPacket.getLength());
// 3. 根据请求构造响应
String response = process(request);
// 4. 封装响应数据包,发送给客户端
DatagramPacket responsePacket = new DatagramPacket(
response.getBytes(), // 响应数据字节数组
response.getBytes().length, // 响应数据长度
requestPacket.getSocketAddress()
);
socket.send(responsePacket); // 发送响应
// 5. 打印日志
System.out.printf("[客户端 %s:%d] 请求:%s | 响应:%s\n",
requestPacket.getAddress().getHostAddress(),
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();
}
}
UDP 客户端代码
客户端需要指定服务端的 IP 和端口,读取用户输入并发送给服务端,然后接收服务端的响应并打印。
import java.io.*;
import java.net.*;
import java.util.Scanner;
public class UdpEchoClient {
// 声明 DatagramSocket 实例,负责收发数据
private DatagramSocket socket = null;
// 服务端 IP 地址
private String serverIP;
// 服务端监听端口
private int serverPort;
/**
* 构造方法:初始化客户端,指定服务端地址和端口
* @param serverIP 服务端 IP 地址
* @param serverPort 服务端监听端口
* @throws SocketException 创建套接字异常时抛出
*/
public UdpEchoClient(String serverIP, int serverPort) throws SocketException {
// 客户端无需绑定固定端口,由操作系统自动分配随机端口(避免端口冲突)
socket = new DatagramSocket();
this.serverIP = serverIP;
this.serverPort = serverPort;
}
/**
* 启动客户端,循环与服务端通信
* @throws IOException 数据收发异常时抛出
*/
public void start() throws IOException {
System.out.println("客户端启动...");
Scanner scanner = new Scanner(System.in);
// 无限循环,持续发送用户输入的消息
while (true) {
// 1. 读取用户控制台输入
System.out.println("-> ");
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[1024],
1024);
socket.receive(responsePacket);
// 4.打印结果
String response = new String(responsePacket.getData(), 0,
responsePacket.getLength());
System.out.println(response);
// 关闭资源
scanner.close();
socket.close();
}
}
public static void main(String[] args) throws IOException {
UdpEchoClient client = new UdpEchoClient("127.0.0.1", 9090);
client.start();
}
}
总结
- UDP 是无连接、不可靠的传输协议,Java 中通过
DatagramSocket(收发)和DatagramPacket(封装)实现 UDP 编程。 DatagramSocket服务端需绑定固定端口,客户端由操作系统分配随机端口,避免冲突。- UDP 通信中,服务端无法主动保存客户端地址,需从接收的
DatagramPacket中获取客户端的 IP 和端口,才能返回响应。 DatagramPacket的收发构造方法需区分使用,接收时仅需传入缓冲区,发送时需指定目标地址和端口。