OP_ACCEPT,單 DatagramChannel 收發所有數據SocketAddress同樣的 EchoProtocol 接口,UDP 實現邏輯完全不同:
public class UDPEchoSelectorProtocol implements EchoProtocol {
private static final int ECHOMAX = 255;
// 客戶端上下文:地址 + 緩衝區
static class ClientRecord {
SocketAddress clientAddress;
ByteBuffer buffer = ByteBuffer.allocate(ECHOMAX);
}
@Override public void handleAccept(SelectionKey key) throws IOException {} // UDP 無連接
@Override
public void handleRead(SelectionKey key) throws IOException {
DatagramChannel channel = (DatagramChannel) key.channel();
ClientRecord record = (ClientRecord) key.attachment();
record.buffer.clear(); // 準備讀取
record.clientAddress = channel.receive(record.buffer); // 接收數據報
if (record.clientAddress != null) {
key.interestOps(SelectionKey.OP_WRITE); // 註冊寫就緒
}
}
@Override
public void handleWrite(SelectionKey key) throws IOException {
DatagramChannel channel = (DatagramChannel) key.channel();
ClientRecord record = (ClientRecord) key.attachment();
record.buffer.flip(); // 切換寫模式
int sent = channel.send(record.buffer, record.clientAddress);
if (sent != 0) { // 發送完成
key.interestOps(SelectionKey.OP_READ); // 切回讀就緒
}
}
}
public class UDPEchoClientNonblocking {
private static final int TIMEOUT = 3000, MAXTRIES = 255;
public static void main(String[] args) throws Exception {
DatagramChannel channel = DatagramChannel.open();
channel.configureBlocking(false);
channel.socket().setSoTimeout(TIMEOUT); // 設置超時
ByteBuffer writeBuf = ByteBuffer.wrap("0123456789abcdefghijklmnopqrstuvwxyz".getBytes());
ByteBuffer readBuf = ByteBuffer.allocate(MAXTRIES);
channel.connect(new InetSocketAddress("127.0.0.1", 5500)); // 連接後可直接 read/write
int total = 0;
while (total < writeBuf.capacity()) {
if (writeBuf.hasRemaining()) channel.write(writeBuf);
int r = channel.read(readBuf);
if (r == -1) throw new SocketException("連接關閉");
total += r;
System.out.print(".");
}
System.out.println("收到: " + new String(readBuf.array(), 0, total));
channel.close();
}
}
public class UDPEchoServerSelector {
private static final int TIMEOUT = 3000;
public static void main(String[] args) throws IOException {
Selector selector = Selector.open();
DatagramChannel channel = DatagramChannel.open();
channel.configureBlocking(false);
channel.socket().bind(new InetSocketAddress(5500));
// 註冊讀事件,附帶 ClientRecord 上下文
channel.register(selector, SelectionKey.OP_READ, new UDPEchoSelectorProtocol.ClientRecord());
EchoProtocol protocol = new UDPEchoSelectorProtocol();
while (true) {
if (selector.select(TIMEOUT) == 0) { System.out.print("."); continue; }
Iterator it = selector.selectedKeys().iterator();
while (it.hasNext()) {
SelectionKey key = it.next(); it.remove();
if (key.isReadable()) protocol.handleRead(key);
if (key.isValid() && key.isWritable()) protocol.handleWrite(key);
}
}
}
}
| 特性 | NIO TCP | NIO UDP |
|---|---|---|
| Channel 類型 | SocketChannel / ServerSocketChannel | DatagramChannel |
| 連接建立 | OP_ACCEPT + accept() | 無,直接收發 |
| 數據邊界 | 流式,需自行分幀 | 數據報,天然保邊界 |
| 客戶端狀態 | Channel 本身標識連接 | 需附件記錄 SocketAddress |
| 緩衝區管理 | 單緩衝區雙向 | 每客戶端獨立緩衝區 |
| 可靠性 | 內建重傳、排序 | 應用層自行實現(如示例超時重發) |
receive() 時緩衝區不足會靜默丟包,生產環境需監控 ReceiveBufferSizeconnect() 後才能穿透大多數 NAT,服務端無需 connect