mica-net 内置标准 NIO UDP,基于
DatagramChannel+UdpChannel/UdpHandler,不依赖 TCP 的ChannelContext重量级状态机,更轻量。
UDP 与 TCP 共用 mica-net-core,无需额外模块:
<dependency>
<groupId>net.dreamlu</groupId>
<artifactId>mica-net-core</artifactId>
<version>${mica-net.version}</version>
</dependency>| 名称 | 作用 |
|---|---|
UdpChannel |
UDP 网络通道:暴露 send(Packet)、remoteAddress()、getConfig();是 UDP 中唯一的「上下文」 |
UdpHandler |
UDP 业务接口:decode / encode / handler 三段式 |
UdpConfig / UdpServerConfig / UdpClientConfig |
I/O 配置:readBufferSize、可选 workerPool,以及 server/client 专有项 |
UdpServer / UdpClient |
启动器:start() 启动后台 I/O 线程后立即返回;close() 释放资源 |
UDP 本身无连接。服务端会按对端
InetSocketAddress复用UdpServerChannel(便于维护 per-peer 状态),并按peerIdleTimeoutMs/maxPeers约束会话;close()或UdpChannel#close(remark)会移除该会话。客户端始终对应配置中的单一目标地址。同一对端的
handler串行执行(per-peer 队列);不同对端仍可并行。回包经有界发送队列由单线程写出。
- 以 单个 datagram 为界循环调用
decode,一包可含多帧。 - 不会跨 datagram 自动拼接半包;
decode返回null即丢弃本 datagram 剩余数据。 - 返回
Packet后应推进ByteBuffer#position;若不推进,框架为防死循环只处理一帧。 readBufferSize小于实际 datagram 时可能被截断(框架会打 warn)。
任何返回字节数组的协议都可以,下面用"UTF-8 文本无分隔"做演示,业务中可以替换为长度前缀、protobuf 等等。
import net.dreamlu.mica.net.core.intf.*;
import net.dreamlu.mica.net.core.udp.UdpConfig;
import net.dreamlu.mica.net.server.udp.*;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
public class UdpEchoServer {
public static void main(String[] args) throws Exception {
UdpHandler handler = new UdpHandler() {
@Override
public Packet decode(ByteBuffer buffer, int limit, int position,
int readableLength, UdpChannel ctx) {
// 数据不足一帧返回 null;这里直接以"缓冲区剩余全部作为一包"为例
if (readableLength <= 0) return null;
byte[] body = new byte[readableLength];
buffer.position(position);
buffer.get(body);
return new TextPacket(new String(body, StandardCharsets.UTF_8));
}
@Override
public ByteBuffer encode(Packet packet, UdpConfig config, UdpChannel ctx) {
String text = ((TextPacket) packet).getBody();
return ByteBuffer.wrap(text.getBytes(StandardCharsets.UTF_8));
}
@Override
public void handler(Packet packet, UdpChannel channel) {
String msg = ((TextPacket) packet).getBody();
channel.send(new TextPacket("echo:" + msg));
}
};
UdpServer server = new UdpServer(
UdpServerConfig.builder().port(9999).build(),
handler
);
server.start();
Runtime.getRuntime().addShutdownHook(new Thread(server::close));
}
/** 自定义业务包:只需实现 Packet 接口 */
static class TextPacket extends Packet {
private static final long serialVersionUID = 1L;
private final String body;
TextPacket(String body) { this.body = body; }
String getBody() { return body; }
@Override public String logstr() { return "TextPacket(" + body + ")"; }
}
}启动后用 nc -u 127.0.0.1 9999 或者 UdpClient 都可以打过来。
import net.dreamlu.mica.net.client.udp.*;
import net.dreamlu.mica.net.core.udp.UdpConfig;
import net.dreamlu.mica.net.core.intf.*;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
public class UdpEchoClient {
public static void main(String[] args) throws Exception {
UdpHandler handler = new UdpHandler() {
@Override
public Packet decode(ByteBuffer buffer, int limit, int position,
int readableLength, UdpChannel ctx) {
if (readableLength <= 0) return null;
byte[] body = new byte[readableLength];
buffer.position(position);
buffer.get(body);
return new UdpEchoServer.TextPacket(new String(body, StandardCharsets.UTF_8));
}
@Override public ByteBuffer encode(Packet p, UdpConfig c, UdpChannel ch) {
return ByteBuffer.wrap(((UdpEchoServer.TextPacket) p).getBody()
.getBytes(StandardCharsets.UTF_8));
}
@Override public void handler(Packet p, UdpChannel ch) {
System.out.println("recv: " + ((UdpEchoServer.TextPacket) p).getBody());
}
};
UdpClient client = new UdpClient(
UdpClientConfig.builder().host("127.0.0.1").port(9999).build(),
handler
);
client.start();
client.send(new UdpEchoServer.TextPacket("hello"));
Thread.sleep(1000);
client.close();
}
}
client.send(packet)/UdpChannel#send(packet)均发往客户端配置中的host:port。服务端回包则发往该会话的remoteAddress()。当前不提供「同一 channel 改发其他地址」的 API。
UdpServerConfig cfg = UdpServerConfig.builder()
.port(9999)
.readBufferSize(4096) // 单次 UDP 读取缓冲
.workerThreads(8) // 默认池线程数(未注入 workerPool 时)
.peerIdleTimeoutMs(300_000) // 对端空闲淘汰;0 表示不按空闲淘汰
.maxPeers(10_000) // 对端会话上限,满则丢弃新对端报文
.sendQueueCapacity(4096) // 回包发送队列;满则 send 返回 false
.workerPool(ThreadUtils.getGroupExecutor()) // 可选:复用业务线程池
.build();
UdpClientConfig clientCfg = UdpClientConfig.builder()
.host("127.0.0.1")
.port(9999)
.sendQueueCapacity(1024) // 发送队列满时 send 返回 false
.build();| 字段 | 默认 | 含义 |
|---|---|---|
port |
必填(server) | 监听端口 |
host |
127.0.0.1(client) |
对端地址 |
readBufferSize |
2048 |
UDP 读取缓冲;过小会导致 datagram 截断 |
workerPool |
null |
注入业务线程池;为 null 时由 mica 自管理 |
workerThreads |
max(2, CPUs*2)(server) |
默认业务池线程数 |
peerIdleTimeoutMs |
300000(server) |
对端会话空闲超时;0 关闭空闲淘汰 |
maxPeers |
10000(server) |
对端会话上限 |
sendQueueCapacity |
4096(server)/ 1024(client) |
发送队列容量,满则 send 返回 false |
- IoT 上报:设备 → 网关 UDP 接收,无需维护长连接。
- 服务发现:节点之间周期性 UDP 心跳/广播。
- 查询/应答:DNS 风格短报文。
- 日志/指标通道:高并发、低开销、按需丢弃数据。
重要业务(订单、消息)请走 TCP;UDP 应用层需自行实现 ACK/重传与幂等。 非阻塞发送在内核缓冲区满时可能丢包(
send返回false/ 客户端累计getDroppedSendCount())。
仓库自带的 mica-net-core/src/test/java/net/dreamlu/mica/net/core/udp/UdpDemo.java 演示了"长度前缀 + UTF-8 文本"的完整协议(含 runBoth 模式:同一 JVM 启 server + client 互相收发后退出),可作为更复杂协议的起点。
# 编译测试代码
mvn -pl mica-net-core test-compile -DskipTests
# 同进程启 server 和 client
java -cp mica-net-core/target/test-classes:mica-net-core/target/classes:... \
net.dreamlu.mica.net.core.udp.UdpDemo
# 分别启
java ... net.dreamlu.mica.net.core.udp.UdpDemo server
java ... net.dreamlu.mica.net.core.udp.UdpDemo client