DatagramChannel 是 Java NIO 中支持非阻塞 UDP 通信的核心类,可绑定端口收发数据、配合 Selector 实现单线程高并发管理,适用于游戏、实时音视频等低延迟场景。

DatagramChannel 是 Java NIO 中用于 UDP 通信的核心类,它支持非阻塞模式,适合高并发、低延迟的场景(比如游戏、实时音视频、DNS 查询等)。相比传统的 DatagramSocket,它更轻量、可与 Selector 配合实现单线程管理多个 UDP 连接。
创建并绑定 DatagramChannel
UDP 是无连接协议,所以不需要“连接”,但发送前通常需要绑定本地端口(接收数据必需),也可以不绑定直接发送(系统自动分配临时端口,但无法接收响应)。
示例:
DatagramChannel channel = DatagramChannel.open();
channel.configureBlocking(false); // 非阻塞(如需配合 Selector)
channel.bind(new InetSocketAddress("localhost", 8080)); // 绑定到本地 8080
- 若只发不收,可跳过
bind(),但调用send()时必须指定目标地址 - 绑定
0.0.0.0或null表示监听所有网卡;绑定127.0.0.1仅限本机通信 - Windows 下绑定已占用端口会抛
java.net.BindException
发送 UDP 数据包
使用 send(ByteBuffer, SocketAddress) 方法,注意 ByteBuffer 必须处于 flip() 状态(即写完后调用 flip(),让 position 回 0、limit 指向数据末尾)。
立即学习“Java免费学习笔记(深入)”;
String msg = "Hello UDP";
ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes(StandardCharsets.UTF_8));
SocketAddress target = new InetSocketAddress("127.0.0.1", 9090);
int sent = channel.send(buffer, target);
System.out.println("发送了 " + sent + " 字节");
- UDP 不保证送达、不保证顺序、不保证不重复,应用层需自行处理可靠性(如加序号、重传)
- 单个 UDP 包建议不超过 1400–1500 字节(避开 IP 分片),超大会被静默丢弃或触发 ICMP 错误
- 非阻塞模式下,若网络不可达或缓冲区满,
send()可能返回 0,需重试或忽略
接收 UDP 数据包
用 receive(ByteBuffer) 接收任意来源的数据。调用前确保 buffer 有足够空间(clear()),接收后 buffer 的 position 指向实际读取字节数,可用 flip() 后读取内容。
ByteBuffer buffer = ByteBuffer.allocate(1024);
SocketAddress sender = channel.receive(buffer); // 阻塞等待,返回发送方地址
if (sender != null) {
buffer.flip();
byte[] data = new byte[buffer.remaining()];
buffer.get(data);
System.out.println("收到来自 " + sender + ":" + new String(data, StandardCharsets.UTF_8));
}
- 非阻塞模式下,若无数据到达,
receive()立即返回null - 每次
receive()只读一个完整的 UDP 数据报,不会粘包也不会拆包 - 建议为每个
receive()分配独立 buffer(或 clear 复用),避免残留数据干扰
配合 Selector 实现多路复用(可选进阶)
UDP 的 DatagramChannel 支持注册到 Selector,监听 OP_READ 事件(不能监听 OP_WRITE,因为 UDP 发送几乎总就绪)。
Selector selector = Selector.open();
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_READ);
// 循环轮询
while (true) {
int ready = selector.select();
if (ready == 0) continue;
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> iter = keys.iterator();
while (iter.hasNext()) {
SelectionKey key = iter.next();
iter.remove();
if (key.isReadable()) {
// 调用 receive() 处理数据
}
}
}
- 一个线程即可管理成百上千个 UDP 端口(如做 NAT 穿透、STUN 服务器)
- 注意:UDP 没有连接状态,
SelectionKey不会失效,也不需要OP_CONNECT或OP_ACCEPT - 避免在 selector 循环中执行耗时操作(如解析大包、磁盘 IO),应交由工作线程处理


















