1.Decoder原理
1.1什麼叫作Netty的解碼器呢?
首先,它是一個InBound入站處理器,解碼器負責處理“入站數據”。其次,它能將上一站Inbound入站處理器傳過來的輸入(Input)數據,進行數據的解碼或者格式轉換,然後輸出(Output)到下一站Inbound入站處理器。一個標準的解碼器將輸入類型爲ByteBuf緩衝區的數據進行解碼,輸出一個一個的Java POJO對象。Netty內置了這個解碼器,叫作ByteToMessageDecoder,位在Netty的io.netty.handler.codec包中。
- ByteToMessageDecoder僅僅提供了一個流程性質的框架:它僅僅將子類的decode方法解碼之後的Object結果,放入自己內部的結果列表List<Object>中,最終,父類會負責將List<Object>中的元素,一個一個地傳遞給下一個站
1.2代碼示例
//解碼
public class Byte2IntegerDecoder extends ByteToMessageDecoder {
@Override
public void decode(ChannelHandlerContext ctx, ByteBuf in,
List<Object> out) {
while (in.readableBytes() >= 4) {
int i = in.readInt();
Logger.info("解碼出一個整數: " + i);
out.add(i);
}
}
}
//處理程序
public class IntegerProcessHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
Integer integer = (Integer) msg;
Logger.info("打印出一個整數: " + integer);
}
}
//測試類
public class Byte2IntegerDecoderTester {
/**
* 整數解碼器的使用實例
*/
@Test
public void testByteToIntegerDecoder() {
ChannelInitializer i = new ChannelInitializer<EmbeddedChannel>() {
protected void initChannel(EmbeddedChannel ch) {
ch.pipeline().addLast(new Byte2IntegerDecoder());
ch.pipeline().addLast(new IntegerProcessHandler());
}
};
EmbeddedChannel channel = new EmbeddedChannel(i);
for (int j = 0; j < 100; j++) {
ByteBuf buf = Unpooled.buffer();
buf.writeInt(j);
channel.writeInbound(buf);
}
try {
Thread.sleep(Integer.MAX_VALUE);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- ByteBuf緩衝區由誰負責進行引用計數和釋放管理
基類ByteToMessageDecoder負責解碼器的ByteBuf緩衝區的釋放工作,它會調用ReferenceCountUtil.release(in)方法,將之前的ByteBuf緩衝區的引用數減1。
1.3 ReplayingDecoder解碼器
ReplayingDecoder類是ByteToMessageDecoder的子類。其作用是:
· 在讀取ByteBuf緩衝區的數據之前,需要檢查緩衝區是否有足夠的字節。
· 若ByteBuf中有足夠的字節,則會正常讀取;反之,如果沒有足夠的字節,則會停止解碼。
- ReplayingDecoder進行長度判斷的原理,其實很簡單:它的內部定義了一個新的二進制緩衝區類,對ByteBuf緩衝區進行了裝飾,這個類名爲ReplayingDecoderBuffer。該裝飾器的特點是:在緩衝區真正讀數據之前,首先進行長度的判斷:如果長度合格,則讀取數據;否則,拋出ReplayError。ReplayingDecoder捕獲到ReplayError後,會留着數據,等待下一次IO事件到來時再讀取。
- ReplayingDecoder的作用,遠遠不止於進行長度判斷,它更重要的作用是用於分包傳輸的應用場景
1.4整數分包解碼器
-
底層通信協議是分包傳輸的,一份數據可能分幾次達到對端。發送端出去的包在傳輸過程中會進行多次的拆分和組裝。接收端所收到的包和發送端所發送的包不是一模一樣的
在Java OIO流式傳輸中,不會出現這樣的問題,因爲它的策略是:不讀到完整的信息,就一直阻塞程序,不向後執行。但是,在Java的NIO中,由於NIO的非阻塞性,就會出現上述情況
可以使用ReplayingDecoder來解決
要完成以上的例子,需要用到ReplayingDecoder一個很重要的屬性——state成員屬性。該成員屬性的作用就是保存當前解碼器在解碼過程中的當前階段
- ReplayingDecoder源碼
protected ReplayingDecoder() {
this((Object)null);
}
protected ReplayingDecoder(S initialState) {
this.replayable = new ReplayingDecoderByteBuf();
this.checkpoint = -1;
this.state = initialState;
}
protected void checkpoint() {
this.checkpoint = this.internalBuffer().readerIndex();
}
protected void checkpoint(S state) {
this.checkpoint();
this.state(state);
}
- checkpoint(Status)方法有兩個作用
(1)設置state屬性的值,更新一下當前的狀態。
(2)還有一個非常大的作用,就是設置“讀斷點指針”。
(3)“讀斷點指針”是ReplayingDecoder類的另一個重要的成員,它保存着裝飾器內部ReplayingDecoderBuffer成員的起始讀指針,有點兒類似於mark標記。當讀數據時,一旦可讀數據不夠,ReplayingDecoderBuffer在拋出ReplayError異常之前,ReplayingDecoder會把讀指針的值還原到之前的checkpoint(IntegerAddDecoder.Status)方法設置的“讀斷點指針”(checkpoint)。於是乎,在ReplayingDecoder下一次讀取時,還會從之前設置的斷點位置開始。
1.5分包解碼器
在原理上,字符串分包解碼和整數分包解碼是一樣的。有所不同的是:整數的長度是固定的,目前在Java中是4個字節;而字符串的長度不是固定的,是可變長度的,這就是一個小小的難題
- 如何獲取字符串的長度信息呢?
(1)在協議的Head部分放置字符串的字節長度。Head部分可以用一個整型int來描述即可。
(2)在協議的Content部分,放置的則是字符串的字節數組。
public class StringReplayDecoder
extends ReplayingDecoder<StringReplayDecoder.Status> {
enum Status {
PARSE_1, PARSE_2
}
private int length;
private byte[] inBytes;
public StringReplayDecoder() {
//構造函數中,需要初始化父類的state 屬性,表示當前階段
super(Status.PARSE_1);
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in,
List<Object> out) throws Exception {
switch (state()) {
case PARSE_1:
//第一步,從裝飾器ByteBuf 中讀取長度
length = in.readInt();
inBytes = new byte[length];
// 進入第二步,讀取內容
// 並且設置“讀指針斷點”爲當前的讀取位置
checkpoint(Status.PARSE_2);
break;
case PARSE_2:
//第二步,從裝飾器ByteBuf 中讀取內容數組
in.readBytes(inBytes, 0, length);
out.add(new String(inBytes, "UTF-8"));
// 第二步解析成功,
// 進入第一步,讀取下一個字符串的長度
// 並且設置“讀指針斷點”爲當前的讀取位置
checkpoint(Status.PARSE_1);
break;
default:
break;
}
}
public class StringProcessHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
String s = (String) msg;
System.out.println("打印: " + s);
}
}
public class StringReplayDecoderTester {
static String content = "smallmartial:Netty知識學習";
/**
* 字符串解碼器的使用實例
*/
@Test
public void testStringReplayDecoder() {
ChannelInitializer i = new ChannelInitializer<EmbeddedChannel>() {
protected void initChannel(EmbeddedChannel ch) {
ch.pipeline().addLast(new StringReplayDecoder());
ch.pipeline().addLast(new StringProcessHandler());
}
};
EmbeddedChannel channel = new EmbeddedChannel(i);
byte[] bytes = content.getBytes(Charset.forName("utf-8"));
for (int j = 0; j < 100; j++) {
//1-3之間的隨機數
int random = RandomUtil.randInMod(3);
ByteBuf buf = Unpooled.buffer();
buf.writeInt(bytes.length * random);
for (int k = 0; k < random; k++) {
buf.writeBytes(bytes);
}
channel.writeInbound(buf);
}
try {
Thread.sleep(Integer.MAX_VALUE);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- ReplayingDecoder解碼器不足
(1)不是所有的ByteBuf操作都被ReplayingDecoderBuffer裝飾類所支持,可能有些ByteBuf操作在ReplayingDecoder子類的decode實現方法中被使用時就會拋出ReplayError異常。
(2)在數據解析邏輯複雜的應用場景,ReplayingDecoder在解析速度上相對較差。
1.6MessageToMessageDecoder解碼器
MessageToMessageDecoder<I>。在繼承它的時候,需要明確的泛型實參<I>。這個實參的作用就是指定入站消息JavaPOJO類型。