Java NIO (New I/O) 自 J2SE 1.4 引入 Buffer、Channel、Selector 等核心概念,為高效能網路程式設計提供新範式。本文整理開發自訂網路協定的實戰經驗,提出一套可重用的協定開發框架,核心解決訊息邊界問題與Buffer 操作封裝。
Socket 傳輸的是位元組流,單次 send() 的資料可能分多次 recv() 接收,或一次 recv() 收到多個封包。應用層協定必須自行解決邊界問題,主流兩種方案:
\n 結束訊息public interface MessageHeader {
int getMessageType(); // 訊息型別識別碼
int getMessageLength(); // 訊息總長度 (含 Header)
void buildFromBuffer(ByteBuffer buffer); // 從 Buffer 解析 Header
void appendToBuffer(ByteBuffer buffer); // 將 Header 序列化至 Buffer
}
public interface Message {
void setHeader(MessageHeader header); // 設定 Header (Channel 接收時呼叫)
MessageHeader getHeader(); // 取得 Header
void buildBodyFromBuffer(ByteBuffer buffer); // 從 Buffer 解析 Body
void appendBodyToBuffer(ByteBuffer buffer); // 將 Body 序列化至 Buffer
}
public interface MessageFactory {
int getMessageHeaderLength(); // 固定 Header 位元組長度
MessageHeader createMessageHeader(); // 建立 Header 實例
Message createMessage(int type); // 依型別建立 Message 實例
}
class MessageChannel {
public MessageChannel(int receiveBufferSize, int sendBufferSize,
SocketChannel sc, MessageFactory mf);
public Message receive() throws IOException, MessageFormatException;
public void send(Message message) throws IOException;
}
receive() 流程:
MessageFactory.getMessageHeaderLength() 讀取 Header 位元組createMessageHeader() 建立物件,buildFromBuffer() 解析createMessage(type) 建立物件,buildBodyFromBuffer() 解析setHeader() 關聯,回傳完整 MessageMessageFormatExceptionclass BufferUtil {
// 固定長度字串
static String getString(ByteBuffer buffer, int length);
static void putString(ByteBuffer buffer, String str, int length);
// C 風格字串 (以 \0 結束,不足補 0)
static String getCString(ByteBuffer buffer, int maxLength);
static void putCString(ByteBuffer buffer, String str, int maxLength);
// 變長字串:長度前綴 (1/2/4 位元組)
static String getVarStringByteLength(ByteBuffer buffer);
static String getVarStringShortLength(ByteBuffer buffer);
static String getVarStringIntLength(ByteBuffer buffer);
static void putVarStringByteLength(ByteBuffer buffer, String str);
static void putVarStringShortLength(ByteBuffer buffer, String str);
static void putVarStringIntLength(ByteBuffer buffer, String str);
}
註:目前未處理字元編碼 (Charset),實務應擴充支援 UTF-8/Big5 等。
封裝協定解析錯誤:Header 長度不符、未知訊息型別、Body 長度超過緩衝區等。
// 伺服器端啟動
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.socket().bind(new InetSocketAddress(8888));
ssc.configureBlocking(false);
Selector selector = Selector.open();
ssc.register(selector, SelectionKey.OP_ACCEPT);
MyMessageFactory factory = new MyMessageFactory(); // 實作 MessageFactory
while (selector.select() > 0) {
for (SelectionKey key : selector.selectedKeys()) {
if (key.isAcceptable()) {
SocketChannel sc = ssc.accept();
sc.configureBlocking(false);
MessageChannel mc = new MessageChannel(8192, 8192, sc, factory);
key.attach(mc);
key.interestOps(SelectionKey.OP_READ);
} else if (key.isReadable()) {
MessageChannel mc = (MessageChannel) key.attachment();
try {
Message msg = mc.receive();
handleMessage(msg); // 業務邏輯處理
} catch (MessageFormatException e) {
// 協定錯誤處理:關閉連線、記錄日誌
}
}
}
selector.selectedKeys().clear();
}
| 特性 | 說明 |
|---|---|
| 關注點分離 | 協定定義 (Header/Message/Factory) 與傳輸邏輯 (Channel) 完全解耦 |
| Buffer 操作封裝 | 應用層無需直接操作 ByteBuffer position/limit/flip,降低出錯機率 |
| 擴充性 | 新增協定只需實作 MessageFactory、MessageHeader、Message 三介面 |
| 非阻塞支援 | MessageChannel 可配合 Selector 實作高併發伺服器 |
建議擴充方向:加入 TLS/SSL 支援 (SSLEngine)、訊息壓縮、心跳機制、協定版本協商、序列化框架整合 (Protobuf/MessagePack)。