Netty如何解碼分隔符和基于長(zhǎng)度的協(xié)議

2018-08-08 10:43 更新

使用 Netty 時(shí)會(huì)遇到需要解碼以分隔符和長(zhǎng)度為基礎(chǔ)的協(xié)議,本節(jié)講解Netty 如何解碼這些協(xié)議。

分隔符協(xié)議

經(jīng)常需要處理分隔符協(xié)議或創(chuàng)建基于它們的協(xié)議,例如SMTP、POP3IMAP、Telnet等等。Netty 附帶的解碼器可以很容易的提取一些序列分隔:

Table 8.5 Decoders for handling delimited and length-based protocols

名稱描述
DelimiterBasedFrameDecoder接收ByteBuf由一個(gè)或多個(gè)分隔符拆分,如NUL或換行符
LineBasedFrameDecoder接收ByteBuf以分割線結(jié)束,如"\n"和"\r\n"

下圖顯示了使用"\r\n"分隔符的處理:

Figure%208

  1. 字節(jié)流
  2. 第一幀
  3. 第二幀

Figure 8.5 Handling delimited frames

下面展示了如何用 LineBasedFrameDecoder 處理

Listing 8.8 Handling line-delimited frames

public class LineBasedHandlerInitializer extends ChannelInitializer<Channel> {

    @Override
    protected void initChannel(Channel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        pipeline.addLast(new LineBasedFrameDecoder(65 * 1024));   //1
        pipeline.addLast(new FrameHandler());  //2
    }

    public static final class FrameHandler extends SimpleChannelInboundHandler<ByteBuf> {
        @Override
        public void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {  //3
            // Do something with the frame
        }
    }
}
  1. 添加一個(gè) LineBasedFrameDecoder 用于提取幀并把數(shù)據(jù)包轉(zhuǎn)發(fā)到下一個(gè)管道中的處理程序,在這種情況下就是 FrameHandler
  2. 添加 FrameHandler 用于接收幀
  3. 每次調(diào)用都需要傳遞一個(gè)單幀的內(nèi)容

使用 DelimiterBasedFrameDecoder 可以方便處理特定分隔符作為數(shù)據(jù)結(jié)構(gòu)體的這類情況。如下:

  • 傳入的數(shù)據(jù)流是一系列的幀,每個(gè)由換行(“\n”)分隔
  • 每幀包括一系列項(xiàng)目,每個(gè)由單個(gè)空格字符分隔
  • 一幀的內(nèi)容代表一個(gè)“命令”:一個(gè)名字后跟一些變量參數(shù)

清單8.9中顯示了的實(shí)現(xiàn)的方式。定義以下類:

  • 類 Cmd - 存儲(chǔ)幀的內(nèi)容,其中一個(gè) ByteBuf 用于存名字,另外一個(gè)存參數(shù)
  • 類 CmdDecoder - 從重寫方法 decode() 中檢索一行,并從其內(nèi)容中構(gòu)建一個(gè) Cmd 的實(shí)例
  • 類 CmdHandler - 從 CmdDecoder 接收解碼 Cmd 對(duì)象和對(duì)它的一些處理。

所以關(guān)鍵的解碼器是擴(kuò)展了 LineBasedFrameDecoder

Listing 8.9 Decoder for the command and the handler

public class CmdHandlerInitializer extends ChannelInitializer<Channel> {

    @Override
    protected void initChannel(Channel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        pipeline.addLast(new CmdDecoder(65 * 1024));//1
        pipeline.addLast(new CmdHandler()); //2
    }

    public static final class Cmd { //3
        private final ByteBuf name;
        private final ByteBuf args;

        public Cmd(ByteBuf name, ByteBuf args) {
            this.name = name;
            this.args = args;
        }

        public ByteBuf name() {
            return name;
        }

        public ByteBuf args() {
            return args;
        }
    }

    public static final class CmdDecoder extends LineBasedFrameDecoder {
        public CmdDecoder(int maxLength) {
            super(maxLength);
        }

        @Override
        protected Object decode(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
            ByteBuf frame =  (ByteBuf) super.decode(ctx, buffer); //4
            if (frame == null) {
                return null; //5
            }
            int index = frame.indexOf(frame.readerIndex(), frame.writerIndex(), (byte) ' ');  //6
            return new Cmd(frame.slice(frame.readerIndex(), index), frame.slice(index +1, frame.writerIndex())); //7
        }
    }

    public static final class CmdHandler extends SimpleChannelInboundHandler<Cmd> {
        @Override
        public void channelRead0(ChannelHandlerContext ctx, Cmd msg) throws Exception {
            // Do something with the command  //8
        }
    }
}
  1. 添加一個(gè) CmdDecoder 到管道;將提取 Cmd 對(duì)象和轉(zhuǎn)發(fā)到在管道中的下一個(gè)處理器
  2. 添加 CmdHandler 將接收和處理 Cmd 對(duì)象
  3. 命令也是 POJO
  4. super.decode() 通過(guò)結(jié)束分隔從 ByteBuf 提取幀
  5. frame 是空時(shí),則返回 null
  6. 找到第一個(gè)空字符的索引。首先是它的命令名;接下來(lái)是參數(shù)的順序
  7. 從幀先于索引以及它之后的片段中實(shí)例化一個(gè)新的 Cmd 對(duì)象
  8. 處理通過(guò)管道的 Cmd 對(duì)象

基于長(zhǎng)度的協(xié)議

基于長(zhǎng)度的協(xié)議協(xié)議在幀頭文件里定義了一個(gè)幀編碼的長(zhǎng)度,而不是結(jié)束位置用一個(gè)特殊的分隔符來(lái)標(biāo)記。表8.6列出了 Netty 提供的兩個(gè)解碼器,用于處理這種類型的協(xié)議。

Table 8.6 Decoders for length-based protocols

名稱描述
FixedLengthFrameDecoder提取固定長(zhǎng)度
LengthFieldBasedFrameDecoder讀取頭部長(zhǎng)度并提取幀的長(zhǎng)度

如下圖所示,F(xiàn)ixedLengthFrameDecoder 的操作是提取固定長(zhǎng)度每幀8字節(jié)

Figure%208

  1. 字節(jié)流 stream
  2. 4個(gè)幀,每個(gè)幀8個(gè)字節(jié)

大部分時(shí)候幀的大小被編碼在頭部,這種情況可以使用LengthFieldBasedFrameDecoder,它會(huì)讀取頭部長(zhǎng)度并提取幀的長(zhǎng)度。下圖顯示了它是如何工作的:

Figure%208

  1. 長(zhǎng)度 "0x000C" (12) 被編碼在幀的前兩個(gè)字節(jié)
  2. 后面的12個(gè)字節(jié)就是內(nèi)容
  3. 提取沒(méi)有頭文件的幀內(nèi)容

Figure 8.7 Message that has frame size encoded in the header

LengthFieldBasedFrameDecoder 提供了幾個(gè)構(gòu)造函數(shù)覆蓋各種各樣的頭長(zhǎng)字段配置情況。清單8.10顯示了使用三個(gè)參數(shù)的構(gòu)造函數(shù)是maxFrameLength,lengthFieldOffset lengthFieldLength。在這 情況下,幀的長(zhǎng)度被編碼在幀的前8個(gè)字節(jié)。

Listing 8.10 Decoder for the command and the handler

public class LengthBasedInitializer extends ChannelInitializer<Channel> {
    @Override
    protected void initChannel(Channel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        pipeline.addLast(
        new LengthFieldBasedFrameDecoder(65 * 1024, 0, 8)); //1
        pipeline.addLast(new FrameHandler()); //2
    }

    public static final class FrameHandler
        extends SimpleChannelInboundHandler<ByteBuf> {
        @Override
        public void channelRead0(ChannelHandlerContext ctx,
        ByteBuf msg) throws Exception {
        // Do something with the frame //3
        }
    }
}
  1. 添加一個(gè) LengthFieldBasedFrameDecoder ,用于提取基于幀編碼長(zhǎng)度8個(gè)字節(jié)的幀。
  2. 添加一個(gè) FrameHandler 用來(lái)處理每幀
  3. 處理幀數(shù)據(jù)

總而言之,本部分探討了 Netty 提供的編解碼器支持協(xié)議,包括定義特定的分隔符的字節(jié)流的結(jié)構(gòu)或協(xié)議幀的長(zhǎng)度。這些編解碼器非常有用。


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)