bigywater.tech
Java · 195

DatagramChannel:UDP 和 TCP 的实现边界

UDP 和 TCP 的行为边界到底在哪?这篇用 DatagramChannel 在回环上把数据报边界、大小上限、丢包与 connect 的语义逐项打印出来(TCP 的字节流与粘包拆包见前面的篇目)。

边界保留
一次 send 对应一次 receive:[10, 100, 1000] 字节
4099 / 100000
接收方来不及读时,默认缓冲区收到的数据报数
PortUnreachableException
已 connect 的 channel 向没人监听的端口写的结果

数据报边界

实测 · 本机执行输出
  发送 3 个数据报(10、100、1000 字节),接收方用 4096 字节的缓冲区各 receive 一次:
    每次收到的字节数 = [10, 100, 1000](一次 send 对应一次 receive,边界保留,不像 TCP 会合并)
  发 1000 字节、接收缓冲区只有 100 字节:这次 receive 读到 100 字节,剩下 900 字节被丢弃——再 receive 一次会阻塞(用非阻塞检查):
    再次 receive 的结果:没有数据(null),被截断的 900 字节没有留下
  发送 0 字节的数据报:接收方收到 position = 0(空数据报是合法的)

大小上限

实测 · 本机执行输出
  发送缓冲区(SO_SNDBUF)= 65527,接收缓冲区(SO_RCVBUF)= 786896
  send   1400 字节 → 成功,返回 1400
  send   8192 字节 → 成功,返回 8192
  send   9200 字节 → 成功,返回 9200
  send   9216 字节 → 成功,返回 9216
  send   9217 字节 → 成功,返回 9217
  send  20000 字节 → 成功,返回 20000
  send  65000 字节 → 成功,返回 65000
  send  65507 字节 → 成功,返回 65507
  send  65508 字节 → SocketException: Message too long

丢包

实测 · 本机执行输出
  SO_RCVBUF 设为 默认(实际 786896):发送方一口气发 100000 个 100 字节的数据报,接收方之后才开始读,收到 4099 个(丢了 95901 个,95.9%);最早的序号 0,最晚的 4098
  SO_RCVBUF 设为 1 MB(实际 1048576):发送方一口气发 100000 个 100 字节的数据报,接收方之后才开始读,收到 5462 个(丢了 94538 个,94.5%);最早的序号 0,最晚的 5461
  SO_RCVBUF 设为 8 MB(实际 6710884):发送方一口气发 100000 个 100 字节的数据报,接收方之后才开始读,收到 34953 个(丢了 65047 个,65.0%);最早的序号 0,最晚的 34952

connect

实测 · 本机执行输出
  未连接的 channel 向没人监听的端口 send:没有任何异常(UDP 不知道对方在不在)
  connect 到没人监听的端口之后连续 write:第 2 次 write 抛 PortUnreachableException(对端的 ICMP 端口不可达被转成了异常;只有已连接的 channel 才会)
  rx 已 connect 到 good:另一个陌生的发送者先发 {9},good 后发 {1},rx.read 读到的是 1(已连接的 channel 只接收指定对端的数据报,别的被丢弃)

分步演示

DatagramChannel
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12,macOS 回环地址
    • 没有验证:真实网络上的丢包、乱序与重复、IP 分片对大数据报的影响、多播与广播、IPv6、非阻塞 Selector 配合 UDP 的吞吐;丢包数量与机器、系统缓冲区大小有关,每次运行不同
    京ICP备2026067502号-1京公网安备11010502063487号