UDP非阻塞通信中地址即上下文:未connect时每次send需指定目标地址、receive返回源地址;connect仅设单向过滤器,广播必须不connect并启用setBroadcast;接收用大缓冲+flip/limit精确读取,发送前需flip且处理send返回0;bind前须setReuseAddress支持端口复用,且必须先bind才能receive;Selector事件循环中需register OP_READ、select后处理isReadable并remove key。

UDP 在非阻塞模式下用 DatagramChannel 收发数据,核心在于“地址即上下文”——每次 receive() 返回发送方地址,每次 send() 必须显式指定目标地址;没有连接状态,也就没有默认通信端点,所有地址信息都得靠 SocketAddress 承载。
非阻塞收发中地址如何动态绑定与分离
UDP 本身无连接,DatagramChannel 的 connect() 并非建立连接,而是设置地址过滤器:调用后,receive() 只接收该地址发来的包,send() 也不再需要传入 SocketAddress。但这个“伪连接”是单向且排他的——一旦 connect 到 127.0.0.1:9000,就无法再收发其他地址的数据,除非调用 disconnect()。
- 不 connect:每次
send(buffer, addr)都要带目标地址;receive(buffer)返回的SocketAddress是实际来源,必须保存用于后续回复 - 已 connect:可直接
send(buffer)(隐式发往已连地址),receive(buffer)仍返回地址,但仅当该地址匹配时才成功读取 - 广播场景必须不 connect,并手动设置
channel.socket().setBroadcast(true),目标地址用255.255.255.255或子网定向广播地址
变量报文长度处理的关键缓冲策略
UDP 数据报长度不固定,接收时不能预设 buffer 大小——太小会截断,太大浪费内存。实践中建议:
- 分配足够容纳最大预期报文的 buffer(如 65536 字节),但接收后必须用
buffer.flip()+buffer.limit()精确提取有效字节数,不能直接array()全取 -
receive()在非阻塞模式下可能返回null(无数据到达),需配合Selector使用OP_READ事件驱动,避免空轮询 - 发送前确保
ByteBuffer处于flip()状态,且remaining()> 0;若send()返回 0,说明内核发送队列满,需等待重试(非阻塞下不会阻塞线程)
bind() 与端口复用的实际约束
bind(new InetSocketAddress(port)) 是接收数据的前提,但端口是否能重复绑定取决于系统配置:
- 默认情况下,同一端口只能被一个进程 bind;若需多实例监听(如多个 worker 线程),必须提前开启
SO_REUSEADDR(channel.socket().setReuseAddress(true)) - bind 时指定
0.0.0.0表示监听本机所有 IPv4 地址;指定127.0.0.1则仅响应本地回环请求 - 未 bind 就调用 receive 会抛
NotYetBoundException;bind 后不可再 bind 其他地址,除非先 close
Selector 驱动下的典型事件循环结构
纯非阻塞 UDP 服务必须依赖 Selector 实现高效轮询,否则容易陷入忙等:
- 创建
Selector后,datagramChannel.register(selector, SelectionKey.OP_READ) - 循环调用
selector.select(),返回值 > 0 才处理就绪 key - 对每个
isReadable()的 key,调用channel.receive(buffer),检查返回地址并处理数据 - 务必在处理完每个 key 后调用
it.remove(),否则下次 select 会重复触发










