UDP通信需配对使用DatagramSocket和DatagramPacket,前者仅管理端口资源,后者承载数据;发送需构造含目标地址、端口及字节数组的packet,接收需预分配足够buffer并用getLength()获取实际长度。

UDP通信必须配对使用 DatagramSocket 和 DatagramPacket
单独创建 DatagramSocket 没法收发数据,它只是端口资源的持有者;真正承载字节内容的是 DatagramPacket。发送时需把目标地址+端口+字节数组塞进 packet,接收时则用空 packet 等待填充。
常见错误是只 new 一个 socket 就调 send(),结果抛 NullPointerException 或静默失败——因为没构造 packet。
- 发送前:new
DatagramPacket(byte[], offset, length, InetAddress, port) - 接收前:new
DatagramPacket(byte[], length)(buffer 长度必须 >= 预期报文大小) - 接收后务必用
packet.getLength()获取实际读到的字节数,不能直接用 buffer.length
绑定端口失败?检查 bind() 和构造函数冲突
DatagramSocket 构造函数带端口参数(如 new DatagramSocket(8080))会自动 bind;若再显式调 socket.bind(new InetSocketAddress(8080)),会触发 java.net.BindException: Address already in use。
更隐蔽的问题是:不指定端口构造(new DatagramSocket()),然后 bind 到已被占用的端口,同样报错。Windows 下 TIME_WAIT 状态可能让端口几秒内不可重用。
- 开发调试建议:接收端用固定端口(如 9999),发送端用无参构造,由系统自动分配临时端口
- 生产环境若需复用端口,可设
socket.setReuseAddress(true)(但需两端都设才可靠) - Linux 下检查端口占用:
lsof -i :9999或netstat -an | grep 9999
接收卡住?别忘了 receive() 是阻塞调用
socket.receive(packet) 会一直挂起线程,直到收到数据、超时或 socket 关闭。没设超时的话,一旦网络断开或对方没发包,线程就永远等下去。
典型场景:客户端发完包就 exit,服务端还在 receive(),结果进程不退出。
- 加超时:在
receive()前调socket.setSoTimeout(5000)(单位毫秒),超时抛SocketTimeoutException,可捕获后继续循环 - 别在主线程里死等:接收逻辑应放在独立线程,避免阻塞 UI 或主流程
- 关闭 socket 后再调
receive()会抛SocketException: Socket is closed,注意异常类型区分
中文乱码或数据截断?关注 String 编码与 byte[] 长度匹配
UDP 报文不保证顺序和可靠性,但更常踩的坑是编码问题:用 "你好".getBytes() 默认用平台编码(Windows 是 GBK,Linux/macOS 是 UTF-8),而接收端用 new String(packet.getData()) 也会用平台默认编码解码,两边不一致就乱码。
另一个问题是 buffer 太小:如果发送端发 100 字节,接收 packet 只分配了 64 字节 buffer,receive() 会截断,且 getLength() 返回 64 —— 你根本不知道丢了多少。
- 显式指定编码:
"你好".getBytes(StandardCharsets.UTF_8)和new String(data, 0, len, StandardCharsets.UTF_8) - 接收 buffer 至少预留 65536 字节(UDP 最大理论负载 65507 字节),实际按业务预估,但别小于 1024
- 发送前检查
packet.getLength()是否超过 MTU(通常 1500),超了大概率被中间设备丢弃
实际 UDP 通信中,最易被忽略的是“接收方如何知道报文边界”。TCP 有流式边界,UDP 每次 receive() 对应一个完整 datagram,但应用层协议仍需自己定义包头(比如前 4 字节存长度),否则无法处理粘包/半包——虽然 UDP 本身不会粘包,但业务逻辑可能把多条语义消息合并进一个 packet,或拆成多个发送。










