
本文详解java udp客户端仅接收首字符的根本原因——datagrampacket未正确处理实际数据长度,导致服务端读取缓冲区溢出与截断,并提供带长度前缀的健壮通信实现方案。
本文详解java udp客户端仅接收首字符的根本原因——datagrampacket未正确处理实际数据长度,导致服务端读取缓冲区溢出与截断,并提供带长度前缀的健壮通信实现方案。
在使用 DatagramSocket 进行 Java UDP 通信时,一个常见却隐蔽的陷阱是:发送端与接收端对数据长度的认知不一致。您遇到的客户端仅打印 "T"(实为 "Result 123" 的首字母),正是这一问题的典型表现。
根本原因在于:
- 客户端调用
String.valueOf(add1).getBytes()生成字节数组(如"42"→byte[2]),但随后创建DatagramPacket(buf, buf.length, ...)时虽指定了真实长度,服务端却始终以固定 256 字节缓冲区接收:byte[] buf = new byte[256]; DatagramPacket packet = new DatagramPacket(buf, buf.length); // ← 始终等待填满256字节! socket.receive(packet);
- UDP 协议本身不携带长度元信息,
packet.getLength()才是实际收到的字节数。而new String(packet.getData())会将整个 256 字节数组(含大量\0)转为字符串,导致后续解析(如trim())仍可能受干扰,且Integer.parseInt()在遇到非数字字符时易抛异常。
更严重的是,您的服务端代码存在逻辑缺陷:
- 第一次
socket.receive(packet)后,add1解析成功; - 但第二次
socket.receive(packet)复用了同一packet对象,其内部buf仍是原 256 字节数组,而packet.getLength()未重置 —— 实际上,第二次接收的数据被写入buf[0..len-1],但new String(packet.getData())仍返回全部 256 字节,trim()无法清除buf[len..255]中残留的旧数据(如前次"Result 42"的'T'),最终Integer.parseInt("T\0\0...")失败或产生不可预测行为。
✅ 正确做法:显式传递有效载荷长度,并严格按需解析
立即学习“Java免费学习笔记(深入)”;
Java Maven 项目审查工作流的通用输入处理。当 Java Maven 项目以 ZIP压缩包或 GitLab 仓库 URL 形式提供且需要...时使用。
以下是修复后的完整实现(含长度前缀协议):
客户端(Client.java)
public class Client {
public static void main(String[] args) throws IOException {
Scanner scan = new Scanner(System.in);
InetAddress address = InetAddress.getByName("localhost");
try (DatagramSocket socket = new DatagramSocket()) {
// 发送第一个加数
System.out.print("First adder: ");
int add1 = scan.nextInt();
sendInt(socket, add1, address, 8189);
// 发送第二个加数
System.out.print("Second adder: ");
int add2 = scan.nextInt();
sendInt(socket, add2, address, 8189);
// 接收结果(带长度前缀)
String response = receiveString(socket);
System.out.println("Server response: " + response);
}
}
private static void sendInt(DatagramSocket socket, int value, InetAddress addr, int port)
throws IOException {
String data = String.valueOf(value);
byte[] payload = data.getBytes(StandardCharsets.UTF_8);
// 构造:[2字节长度][payload]
ByteBuffer bb = ByteBuffer.allocate(2 + payload.length);
bb.putShort((short) payload.length);
bb.put(payload);
DatagramPacket packet = new DatagramPacket(
bb.array(), bb.position(), addr, port);
socket.send(packet);
}
private static String receiveString(DatagramSocket socket) throws IOException {
// 先读2字节长度
byte[] lenBuf = new byte[2];
DatagramPacket lenPacket = new DatagramPacket(lenBuf, lenBuf.length);
socket.receive(lenPacket);
short len = ByteBuffer.wrap(lenBuf).getShort();
// 再读指定长度数据
byte[] dataBuf = new byte[len];
DatagramPacket dataPacket = new DatagramPacket(dataBuf, dataBuf.length);
socket.receive(dataPacket);
return new String(dataBuf, 0, dataPacket.getLength(), StandardCharsets.UTF_8);
}
}服务端(Server.java)
public class Server {
public static void main(String[] args) {
try (DatagramSocket socket = new DatagramSocket(8189)) {
System.out.println("Server started on port 8189...");
while (true) {
// 接收第一个加数
int add1 = receiveInt(socket);
// 接收第二个加数
int add2 = receiveInt(socket);
int sum = add1 + add2;
String result = "Result " + sum;
sendString(socket, result);
}
} catch (IOException e) {
e.printStackTrace();
}
}
private static int receiveInt(DatagramSocket socket) throws IOException {
byte[] lenBuf = new byte[2];
DatagramPacket lenPacket = new DatagramPacket(lenBuf, lenBuf.length);
socket.receive(lenPacket);
short len = ByteBuffer.wrap(lenBuf).getShort();
byte[] dataBuf = new byte[len];
DatagramPacket dataPacket = new DatagramPacket(dataBuf, dataBuf.length);
socket.receive(dataPacket);
String str = new String(dataBuf, 0, dataPacket.getLength(), StandardCharsets.UTF_8);
return Integer.parseInt(str.trim());
}
private static void sendString(DatagramSocket socket, String msg) throws IOException {
byte[] payload = msg.getBytes(StandardCharsets.UTF_8);
ByteBuffer bb = ByteBuffer.allocate(2 + payload.length);
bb.putShort((short) payload.length);
bb.put(payload);
// 注意:需从receiveInt中获取客户端地址和端口
// 此处简化,实际应保存上一次packet的SourceAddress/port
// 为演示清晰,假设客户端地址已知(生产环境必须动态获取)
DatagramPacket packet = new DatagramPacket(
bb.array(), bb.position(),
InetAddress.getByName("localhost"), 8189);
socket.send(packet);
}
}⚠️ 关键注意事项:
-
永远使用
packet.getLength()获取真实接收字节数,而非packet.getData().length; -
避免复用
DatagramPacket对象,每次接收都应新建或显式重置(packet.setLength(256)); - 推荐添加长度前缀(如 2 字节 short),这是 UDP 应用层协议设计的黄金实践,可彻底规避截断与粘包风险;
-
显式指定字符集(如
StandardCharsets.UTF_8),避免平台默认编码差异; - 生产环境中,服务端必须通过
packet.getAddress()和packet.getPort()动态获取客户端地址,不可硬编码。
通过以上重构,您的 UDP 通信将稳定、可预测,再也不会只看到一个孤零零的 "T"。

















