1. 程式人生 > 實用技巧 >Netty入門教程3——Decoder和Encoder

Netty入門教程3——Decoder和Encoder

​ Netty強大的地方,是他能方便的實現自定義協議的網路傳輸。在上一篇文章中,通過使用Netty封裝好的工具類,實現了簡單的http伺服器。在接下來的文章中,我們看看怎麼使用他來搭建自定義協議的伺服器。要做到這點,第一步要做的,就是要自定義編碼器和解碼器,這就是我們這一章主要講的內容。

Netty入門教程——認識Netty
Netty入門教程2——動手搭建HttpServer
什麼是Decoder和Encoder
​ 在學習Decoder和Encoder之前,首先要了解他們在具體是個什麼東西。在Netty裡面,有四個核心概念,這個在第一篇文章提到的,他們的分別是:

Channel,一個客戶端與伺服器通訊的通道

ChannelHandler,業務邏輯處理器,分為ChannelInboundHandler和ChannelOutboundHandler

ChannelInboundHandler,輸入資料處理器
ChannelOutboundHandler,輸出業務處理器
通常情況下,業務邏輯都是存在於ChannelHandler之中

ChannelPipeline,用於存放ChannelHandler的容器

ChannelContext,通訊管道的上下文

他們之間的交流流程如下圖:

Channel關係圖
他們的互動流程是:

事件傳遞給 ChannelPipeline 的第一個 ChannelHandler
ChannelHandler 通過關聯的 ChannelHandlerContext 傳遞事件給 ChannelPipeline 中的 下一個
ChannelHandler 通過關聯的 ChannelHandlerContext 傳遞事件給 ChannelPipeline 中的 下一個
而我們本文所需要詳細講的Decoder和Encoder,他們分別就是ChannelInboundHandler和ChannelOutboundHandler,分別用於在資料流進來的時候將位元組碼轉換為訊息物件和資料流出去的時候將訊息物件轉換為位元組碼。

Encoder
​ Encoder最重要的實現類是MessageToByteEncoder,這個類的作用就是將訊息實體T從物件轉換成byte,寫入到ByteBuf,然後再丟給剩下的ChannelOutboundHandler傳給客戶端,流程圖如下:

Encoder流程圖
Table 7.3 MessageToByteEncoder API

方法名稱 描述
encode The encode method is the only abstract method you need to implement. It is called with the outbound message, which this class will encodes to a ByteBuf. The ByteBuf is then forwarded to the next ChannelOutboundHandler in the ChannelPipeline.
encode方法是繼承MessageToByteEncoder唯一需要重寫的方法,可見其簡單程度。也是因為Encoder相比於Decoder更為簡單,在這裡也不多做贅述,直接上程式碼:

public class ShortToByteEncoder extends
MessageToByteEncoder { //1
@Override
public void encode(ChannelHandlerContext ctx, Short msg, ByteBuf out)
throws Exception {
out.writeShort(msg); //2
}
}
Decoder
​ 和Encoder一樣,decoder就是在服務端收到資料的時候,將位元組流轉換為實體物件Message。但是和Encoder的處理邏輯不一樣,資料傳到服務端有可能不是一次請求就能完成的,中間可能需要經過幾次資料傳輸,並且每一次傳輸傳多少資料也是不確定的,所以它有兩個重要方法:

Table 7.1 ByteToMessageDecoder API

方法名稱 描述
decode This is the only abstract method you need to implement. It is called with a ByteBuf having the incoming bytes and a List into which decoded messages are added. decode() is called repeatedly until the List is empty on return. The contents of the List are then passed to the next handler in the pipeline.
decodeLast The default implementation provided simply calls decode(). This method is called once, when the Channel goes inactive. Override to provide special
​ decode和decodeLast的不同之處,在於他們的呼叫時機不同,正如描述所說,decodeLast只有在Channel的生命週期結束之前會呼叫一次,預設是呼叫decode方法。

​ 同樣是ToInteger的解碼器,他的程式碼如下:

public class ToIntegerDecoder extends ByteToMessageDecoder { //1

@Override
public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out)
        throws Exception {
    if (in.readableBytes() >= 4) {  //2
        out.add(in.readInt());  //3
    }
}

}
​ 從這段程式碼可以看出,因為不知道這次請求發過來多少資料,所以每次都要判斷byte長度夠不夠4,如果你的資料長度更長,且不固定的話,這裡的邏輯會變得非常複雜。所以在這裡介紹另一個我們常用的解碼器 :ReplayingDecoder。

ReplayingDecoder
​ ReplayingDecoder 是 byte-to-message 解碼的一種特殊的抽象基類,讀取緩衝區的資料之前需要檢查緩衝區是否有足夠的位元組,使用ReplayingDecoder就無需自己檢查;若ByteBuf中有足夠的位元組,則會正常讀取;若沒有足夠的位元組則會停止解碼。

​ RelayingDecoder在使用的時候需要搞清楚的兩個方法是checkpoint(S s)和state(),其中checkpoint的引數S,代表的是ReplayingDecoder所處的狀態,一般是列舉型別。RelayingDecoder是一個有狀態的Handler,狀態表示的是它目前讀取到了哪一步,checkpoint(S s)是設定當前的狀態,state()是獲取當前的狀態。

​ 在這裡我們模擬一個簡單的Decoder,假設每個包包含length:int和content:String兩個資料,其中length可以為0,代表一個空包,大於0的時候代表content的長度。程式碼如下:

public class LiveDecoder extends ReplayingDecoder<LiveDecoder.LiveState> { //1

public enum LiveState { //2
    LENGTH,
    CONTENT
}

private LiveMessage message = new LiveMessage();

public LiveDecoder() {
    super(LiveState.LENGTH); // 3
}

@Override
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
    switch (state()) { // 4
        case LENGTH:
            int length = byteBuf.readInt();
            if (length > 0) {
                checkpoint(LiveState.CONTENT); // 5
            } else {
                list.add(message); // 6
            }
            break;
        case CONTENT:
            byte[] bytes = new byte[message.getLength()];
            byteBuf.readBytes(bytes);
            String content = new String(bytes);
            message.setContent(content);
            list.add(message);
            break;
        default:
            throw new IllegalStateException("invalid state:" + state());
    }
}

}
繼承ReplayingDecoder,泛型LiveState,用來表示當前讀取的狀態
描述LiveState,有讀取長度和讀取內容兩個狀態
初始化的時候設定為讀取長度的狀態
讀取的時候通過state()方法來確定當前處於什麼狀態
如果讀取出來的長度大於0,則設定為讀取內容狀態,下一次讀取的時候則從這個位置開始
讀取完成,往結果裡面放解析好的資料
​ 以上就是ReplayingDecoder的使用方法,他比ByteToMessageDecoder更加靈活,能夠通過巧妙的方式來處理複雜的業務邏輯,但是也是因為這個原因,使得ReplayingDecoder帶有一定的侷限性:

不是所有的標準 ByteBuf 操作都被支援,如果呼叫一個不支援的操作會丟擲 UnreplayableOperationException

ReplayingDecoder 略慢於 ByteToMessageDecoder

所以,如果不引入過多的複雜性 使用 ByteToMessageDecoder 。否則,使用ReplayingDecoder。

MessageToMessage
​ Encoder和Decoder除了能完成Byte和Message的相互轉換之外,為了處理複雜的業務邏輯,還能幫助使用者完成Message和Message的相互轉換,我們熟悉的Http協議的處理,其中就用到了很多MessageToMessage的派生類。

​ 因為使用方法和以上的Decoder/Encoder類似,在這裡就不多做贅述了。

​ 以上是我在學習Netty過程中的一些筆記,其中部分內容源自Netty實戰精髓,如有理解不當之處,歡迎指出,一起討論。

作者:追那個小女孩
連結:https://www.jianshu.com/p/fd815bd437cd
來源:簡書
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。