一、Java UDP核心API解析
1.1DatagramSocket------数据报套接字
|--------------------------------------------|-----------------|------|
| 构造方法 | 说明 | 典型场景 |
| DatagramSocket() | 绑定到本机任意一个随机空闲端口 | 客户端 |
| DatagramSocket(int port) | 绑定到本机指定端口 | 服务端 |
| DatagramSocket(int port, InetAddress addr) | 绑定到指定IP和端口 | 很少用 |
|--------------------------------|----------------------------------|
| 核心方法 | 说明 |
| void send(DatagramPacket p) | 发送一个数据报,该方法不会阻塞,直接发送 若网卡队列满,可能丢包 |
| void receive(DatagramPacket p) | 阻塞等待接收一个数据报,直到有数据来或超时 |
| void close() | 关闭套接字,释放底层资源 |
1.2DatagramPacket------数据包
1.用于接收数据
java
DatagramPacket(byte[] buf, int length)
创建一个空的包,准备接收数据,接收到的数据存入buf,最多length字节。
2.用于发送数据(客户端发送请求/服务器发送响应):
java
DatagramPacket(byte[] buf, int offset, int length, SocketAddress address)
buf:待发送的数据字节数组
offset:从数组哪个位置开始发送
length:发送多少字节
address:目标主机的IP+端口
核心方法:
|---------------------------------|-----------------------------|
| 方法 | 说明 |
| byte\[\] getData() | 获取数据字节数组(接收时获取内容,发送时获取待发数据) |
| int getLength() | 获取数据长度 |
| inetAddress getAddress() | 获取发送方或接收方的IP |
| int getPort() | 获取发送方或接收方的端口 |
| SocketAddress getSocketAddree() | 获取完整的Socket地址,常用于直接回传响应 |
二、简单实现UDP回显服务器
java
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class UdpEchoServer {
// 先创建 socket 对象
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[1024], 1024);
socket.receive(requestPacket);
// 为了处理请求方便, 把里面的载荷数据获取出来, 构造成一个 String
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);
// 4. 打印日志
System.out.printf("[%s:%d] req: %s; resp: %s\n", requestPacket.getAddress().toString(), responsePacket.getPort() , request, response);
}
}
// 此处是回显服务器. 响应就和请求完全一样.
public String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
// 端口号是一个整数, 我们可以随意指定.
// 端口范围是 0-65535, < 1024 的端口一般也不使用
UdpEchoServer server = new UdpEchoServer(9090);
server.start();
}
}
socket.receive()会阻塞,直到收到数据,使用while循环一直保持服务器运行。
每次接收都创建新的DatagramPcaket,避免前一次数据残留
三、UDP回显客户端
java
import java.io.IOException;
import java.net.*;
import java.util.Scanner;
public class UdpEchoClient {
private DatagramSocket socket = null;
private String serverIp;
private int serverPort;
public UdpEchoClient(String serverIp, int serverPort) throws SocketException {
socket = new DatagramSocket();
this.serverIp = serverIp;
this.serverPort = serverPort;
}
public void start() throws IOException {
System.out.println("客户端启动...");
// 客户端我们也想要让它持续运行.
// 每循环一次, 就从控制台读取到一个输入的数据, 把这个数据发送到服务器, 读取服务器的响应.
Scanner scanner = new Scanner(System.in);
while (true) {
// 1. 从控制台读取用户输入
System.out.print("-> ");
String request = scanner.next();
// 2. 构造请求, 发送给服务器
DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.getBytes().length,
InetAddress.getByName(serverIp), this.serverPort);
socket.send(requestPacket);
// 3. 读取服务器的响应
DatagramPacket responsePacket = new DatagramPacket(new byte[1024], 1024);
socket.receive(responsePacket);
String response = new String(responsePacket.getData(), 0, responsePacket.getLength());
// 4. 打印出 response
System.out.println(response);
}
}
public static void main(String[] args) throws IOException {
// UdpEchoClient client = new UdpEchoClient("8.137.149.9", 9090);
UdpEchoClient client = new UdpEchoClient("127.0.0.1", 9090);
client.start();
}
}
客户端IP为127.0.0.1表示本机回环
每次发送请求,receive()会阻塞直到服务器响应。
四、调试的常见问题
4.1端口被占用(BindException)
现象:启动服务器时抛出java.net.BingException:Address already in use.
原因:端口9090已经被其他进程占用
解决:netstat -ano|findstr 9090查找到PID,任务管理器结束该进程
4.2接收缓冲区不足
如果发送的数据大于接收端DatagramPacket的缓冲区大小,多余的数据会被截断。
安全做法:缓冲区设置为最大64kb,或使用getlength()检查实际接收长度
4.3同一主机VS跨主机测试
同一主机:IP用127.0.0.1或localhost
跨主机:需要修改客户端serverIp,为服务端实际公网/内网IP,并确保防火墙允许该端口通信