DatagramChannel 实现 UDP 通信需配置非阻塞、绑定端口、仅注册 OP_READ;接收用 clear/allocateDirect/receive 获取地址;发送前 flip 并指定目标地址;配合 Selector 高效轮询就绪事件。

Java NIO 中用 DatagramChannel 实现 UDP 通信,关键在于理解它“无连接、面向数据报、非阻塞优先”的特性。它不维护会话状态,每次收发都独立指定地址,适合高并发、低延迟场景(如心跳检测、DNS 查询、实时音视频分片)。
创建并配置通道
必须显式设为非阻塞才能配合 Selector 使用,否则无法注册事件:
- 调用
DatagramChannel.open()获取实例 - 立即执行
configureBlocking(false) - 用
bind(new InetSocketAddress(port))绑定本地端口;端口可设为 0 让系统自动分配 - 注册到 Selector 时只关注
OP_READ——UDP 的send()总是立即返回,无需监听可写事件
接收数据报的正确姿势
每次 receive() 只读一个完整 UDP 包,缓冲区管理和来源识别不能出错:
- 推荐用直接缓冲区:
ByteBuffer.allocateDirect(64 * 1024),减少 GC 和内存拷贝 - 每次调用前必须
buffer.clear(),重置 position 和 limit -
receive(buffer)返回SocketAddress,即发送方 IP 和端口——这是回包的唯一依据,务必保存 - 若数据超长,缓冲区装不下,内容会被截断;应用层需检查
buffer.position()是否等于buffer.capacity(),超出则丢弃整包
发送数据要注意缓冲区状态和目标地址
UDP 发送不依赖连接,目标地址在每次 send() 时单独传入:
立即学习“Java免费学习笔记(深入)”;
-
send()前必须对 buffer 调用flip(),确保 limit 指向有效数据末尾 - 目标地址是参数,与
bind()的本地地址完全无关,例如:channel.send(buf, new InetSocketAddress("192.168.1.100", 8080)) - 失败通常抛
IOException或子类(如PortUnreachableException),多因 ICMP 不可达响应或路由问题,不是业务逻辑错误 - 高并发下建议将待发数据暂存队列,由专用 I/O 线程批量调用
send(),避免主线程频繁陷入系统调用
配合 Selector 进行事件轮询
单个 Selector 可高效管理成千上万个 DatagramChannel,核心是只处理就绪事件:
- 典型循环:调用
selector.select(timeout)→ 遍历selectedKeys()→ 对每个isReadable()的 key 执行receive() - 处理完后可调用
key.cancel()或保留复用;若需关联上下文(如会话 ID、解析器),可用key.attach(obj) - 用
select(timeout)控制轮询间隔,平衡响应速度与 CPU 占用;对延迟敏感场景可用selectNow()


















