本系列旨在提供可直接運行、編碼規範的 NIO 框架入門 Demo,作為 MINA/Netty 學習的拋磚引玉。本文為第 2 篇,對標第 1 篇 Netty 4 實現,便於對比評估。
客戶端每 3 秒發送消息 → 服務端收到立即回復 → 實現雙向通信。稍加改造即可成簡易聊天程序。
EchoServerpublic class EchoServer {
private static final Logger logger = LoggerFactory.getLogger(EchoServer.class);
public static void main(String[] args) throws Exception {
NioDatagramAcceptor acceptor = new NioDatagramAcceptor();
// 關鍵:線程池隔離業務處理,性能提升 10 倍
acceptor.getFilterChain()
.addLast("threadPool", new ExecutorFilter(Executors.newCachedThreadPool()));
acceptor.setHandler(new EchoServerHandler());
acceptor.setSessionRecycler(new ExpiringSessionRecycler(15_000)); // 15s 會話超時
// UDP 參數調優
DatagramSessionConfig dcfg = acceptor.getSessionConfig();
dcfg.setReuseAddress(true);
dcfg.setReceiveBufferSize(1024); // 壓測證明 2048 反而降性能
dcfg.setSendBufferSize(1024);
acceptor.bind(new InetSocketAddress(9999));
logger.info("UDP 服務端監聽 9999 端口...");
}
}
EchoServerHandlerpublic class EchoServerHandler extends IoHandlerAdapter {
private static final Logger logger = LoggerFactory.getLogger(EchoServerHandler.class);
private static final CharsetDecoder DECODER = Charset.forName("UTF-8").newDecoder();
@Override
public void exceptionCaught(IoSession session, Throwable cause) {
logger.error("異常捕獲", cause);
session.close(true);
}
@Override
public void messageReceived(IoSession session, Object message) {
// 接收:關鍵解碼方式,避免標準 Java 客戶端數據首部亂碼
IoBuffer buffer = (IoBuffer) message;
String body = buffer.getString(DECODER);
logger.debug("收到客戶端數據: {}", body);
// 回復:關鍵構包方式,避免客戶端收到多餘字節
String reply = "Hello,我是Server,時間戳" + System.currentTimeMillis();
byte[] res = reply.getBytes(StandardCharsets.UTF_8);
IoBuffer buf = IoBuffer.wrap(res); // 核心:wrap 而非 allocate+putInt+flip
WriteFuture future = session.write(buf);
future.awaitUninterruptibly(100);
if (!future.isWritten()) {
logger.warn("回復發送失敗");
}
}
}
buffer.getString(decoder) 替代官方 message.toString(),解決非 MINA 客戶端首部亂碼IoBuffer.wrap(bytes) 替代官方 allocate+putInt(length)+put+flip+shrink,解決客戶端收到多餘長度字段EchoClientpublic class EchoClient {
public static void main(String[] args) throws Exception {
LocalUDPSocketProvider.getInstance().initSocket();
LocalUDPDataReceiver.getInstance().startup();
while (true) {
String msg = "Hi,我是客戶端,時間戳" + System.currentTimeMillis();
boolean ok = UDPUtils.send(msg.getBytes("UTF-8"));
Log.d(ok ? "發送成功" : "發送失敗");
Thread.sleep(3000);
}
}
}
LocalUDPSocketProviderpublic class LocalUDPSocketProvider {
private DatagramSocket socket;
public void initSocket() {
socket = new DatagramSocket(Config.LOCAL_UDP_PORT); // 0=系統分配
socket.connect(InetAddress.getByName(Config.SERVER_IP), Config.SERVER_UDP_PORT);
// 關鍵:connect 必須在 receive() 前調用,否則 send 會錯誤阻塞(疑似 JDK bug)
socket.setReuseAddress(true);
}
}
LocalUDPDataReceiverpublic class LocalUDPDataReceiver {
public void startup() {
new Thread(() -> {
byte[] data = new byte[1024];
DatagramPacket packet = new DatagramPacket(data, data.length);
while (true) {
socket.receive(packet); // 阻塞等待
String msg = new String(packet.getData(), 0, packet.getLength(), "UTF-8");
Log.w("收到服務端: " + msg);
}
}).start();
}
}

服務端控制台每次多出 4 行 MINA 內部 DEBUG 日誌屬正常現象,不可關閉除非關閉整體日誌。