Netty讀書筆記5: 使用LengthFieldBasedFrameDecoder解碼器自定義協(xié)議

在之前的文章中,介紹了DelimiterBasedFrameDecoder(基于特殊符號的編碼器)和FixedLengthFrameDecoder定長編碼器)的用法.這篇文章主要是介紹使用LengthFieldBasedFrameDecoder解碼器自定義協(xié)議.通常,協(xié)議的格式如下:

image.png

通常來說,使用ByteToMessageDocoder這個編碼器,我們要分別解析出Header,length,body這幾個字段.而使用LengthFieldBasedFrameDecoder,我們就可以直接接收想要的一部分,相當于在原來的基礎上包上了一層,有了這層之后,我們可以控制我們每次只要讀想讀的字段,這對于自定義協(xié)議來說十分方便.

1. LengthFieldBasedFrameDecoder的定義
public class MyProtocolDecoder extends LengthFieldBasedFrameDecoder {

    private static final int HEADER_SIZE = 6;

    /**
     *
     * @param maxFrameLength  幀的最大長度
     * @param lengthFieldOffset length字段偏移的地址
     * @param lengthFieldLength length字段所占的字節(jié)長
     * @param lengthAdjustment 修改幀數(shù)據(jù)長度字段中定義的值串结,可以為負數(shù) 因為有時候我們習慣把頭部記入長度,若為負數(shù),則說明要推后多少個字段
     * @param initialBytesToStrip 解析時候跳過多少個長度
     * @param failFast 為true驰贷,當frame長度超過maxFrameLength時立即報TooLongFrameException異常,為false血巍,讀取完整個幀再報異
     */

    public MyProtocolDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength, int lengthAdjustment, int initialBytesToStrip, boolean failFast) {

        super(maxFrameLength, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip, failFast);

    }

    @Override
    protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
        //在這里調(diào)用父類的方法,實現(xiàn)指得到想要的部分,我在這里全部都要,也可以只要body部分
        in = (ByteBuf) super.decode(ctx,in);  

        if(in == null){
            return null;
        }
        if(in.readableBytes()<HEADER_SIZE){
            throw new Exception("字節(jié)數(shù)不足");
        }
        //讀取type字段
        byte type = in.readByte();
        //讀取flag字段
        byte flag = in.readByte();
        //讀取length字段
        int length = in.readInt();
        
        if(in.readableBytes()!=length){
            throw new Exception("標記的長度不符合實際長度");
        }
        //讀取body
        byte []bytes = new byte[in.readableBytes()];
        in.readBytes(bytes);

        return new MyProtocolBean(type,flag,length,new String(bytes,"UTF-8"));

    }
}
  • 在上述的代碼中,調(diào)用父類的方法,實現(xiàn)截取到自己想要的字段.
2. 協(xié)議實體的定義
public class MyProtocolBean {
    //類型  系統(tǒng)編號 0xA 表示A系統(tǒng),0xB 表示B系統(tǒng)
    private byte type;

    //信息標志  0xA 表示心跳包    0xC 表示超時包  0xC 業(yè)務信息包
    private byte flag;

    //內(nèi)容長度
    private int length;

    //內(nèi)容
    private String content;

    public byte getType() {
        return type;
    }

    public void setType(byte type) {
        this.type = type;
    }

    public byte getFlag() {
        return flag;
    }

    public void setFlag(byte flag) {
        this.flag = flag;
    }

    public int getLength() {
        return length;
    }

    public void setLength(int length) {
        this.length = length;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public MyProtocolBean(byte flag, byte type, int length, String content) {
        this.flag = flag;
        this.type = type;
        this.length = length;
        this.content = content;
    }

    @Override
    public String toString() {
        return "MyProtocolBean{" +
                "type=" + type +
                ", flag=" + flag +
                ", length=" + length +
                ", content='" + content + '\'' +
                '}';
    }
}
3.服務端的實現(xiàn)
public class Server {

    private static final int MAX_FRAME_LENGTH = 1024 * 1024;  //最大長度
    private static final int LENGTH_FIELD_LENGTH = 4;  //長度字段所占的字節(jié)數(shù)
    private static final int LENGTH_FIELD_OFFSET = 2;  //長度偏移
    private static final int LENGTH_ADJUSTMENT = 0;
    private static final int INITIAL_BYTES_TO_STRIP = 0;

    private int port;

    public Server(int port) {
        this.port = port;
    }

    public void start(){
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap sbs = new ServerBootstrap().group(bossGroup,workerGroup).channel(NioServerSocketChannel.class).localAddress(new InetSocketAddress(port))
                    .childHandler(new ChannelInitializer<SocketChannel>() {

                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new MyProtocolDecoder(MAX_FRAME_LENGTH,LENGTH_FIELD_OFFSET,LENGTH_FIELD_LENGTH,LENGTH_ADJUSTMENT,INITIAL_BYTES_TO_STRIP,false));
                            ch.pipeline().addLast(new ServerHandler());
                        };

                    }).option(ChannelOption.SO_BACKLOG, 128)
                    .childOption(ChannelOption.SO_KEEPALIVE, true);
            // 綁定端口殃饿,開始接收進來的連接
            ChannelFuture future = sbs.bind(port).sync();

            System.out.println("Server start listen at " + port );
            future.channel().closeFuture().sync();
        } catch (Exception e) {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws Exception {
        int port;
        if (args.length > 0) {
            port = Integer.parseInt(args[0]);
        } else {
            port = 8080;
        }
        new Server(port).start();
    }
}

maxFrameLength: 幀的最大長度
lengthFieldOffset length: 字段偏移的地址
lengthFieldLength length;字段所占的字節(jié)長
lengthAdjustment: 修改幀數(shù)據(jù)長度字段中定義的值茄唐,可以為負數(shù) 因為有時候我們習慣把頭部記入長度,若為負數(shù),則說明要推后多少個字段
initialBytesToStrip: 解析時候跳過多少個長度
failFast; 為true,當frame長度超過maxFrameLength時立即報TooLongFrameException異常舀锨,為false岭洲,讀取完整個幀再報異

  • 只需要把MyProtocolDecoder加入pipeline中就可以.但是得在Handler之前.
  1. 服務端Hanlder
public class ServerHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        MyProtocolBean myProtocolBean = (MyProtocolBean)msg;  //直接轉(zhuǎn)化成協(xié)議消息實體
        System.out.println(myProtocolBean.getContent());
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        super.channelActive(ctx);
    }
}
* 服務端Handler沒什么特別的地方,只是輸出接收到的消息
5. 客戶端和客戶端Handler
public class Client {
    static final String HOST = System.getProperty("host", "127.0.0.1");
    static final int PORT = Integer.parseInt(System.getProperty("port", "8080"));
    static final int SIZE = Integer.parseInt(System.getProperty("size", "256"));

    public static void main(String[] args) throws Exception {

        // Configure the client.
        EventLoopGroup group = new NioEventLoopGroup();

        try {
            Bootstrap b = new Bootstrap();
            b.group(group)
                    .channel(NioSocketChannel.class)
                    .option(ChannelOption.TCP_NODELAY, true)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new MyProtocolEncoder());
                            ch.pipeline().addLast(new ClientHandler());
                        }
                    });

            ChannelFuture future = b.connect(HOST, PORT).sync();
            future.channel().writeAndFlush("Hello Netty Server ,I am a common client");
            future.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully();
        }
    }

}
客戶端Handler
public class ClientHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        super.channelRead(ctx, msg);
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {

        MyProtocolBean myProtocolBean = new MyProtocolBean((byte)0xA, (byte)0xC, "Hello,Netty".length(), "Hello,Netty");
        ctx.writeAndFlush(myProtocolBean);

    }
}
  • 客戶端Handler實現(xiàn)發(fā)送消息.
客戶端編碼器
public class MyProtocolEncoder extends MessageToByteEncoder<MyProtocolBean> {

    @Override
    protected void encode(ChannelHandlerContext ctx, MyProtocolBean msg, ByteBuf out) throws Exception {
        if(msg == null){
            throw new Exception("msg is null");
        }
        out.writeByte(msg.getType());
        out.writeByte(msg.getFlag());
        out.writeInt(msg.getLength());
        out.writeBytes(msg.getContent().getBytes(Charset.forName("UTF-8")));
    }
}
  • 編碼的時候,只需要按照定義的順序依次寫入到ByteBuf中.
6. 總結(jié)

通過以上的消息,我們能夠在客戶端和服務端實現(xiàn)自定義協(xié)議的交互.其實,在以上的過程中,如果我們把

ch.pipeline().addLast(new MyProtocolDecoder(MAX_FRAME_LENGTH,LENGTH_FIELD_OFFSET,LENGTH_FIELD_LENGTH,LENGTH_ADJUSTMENT,INITIAL_BYTES_TO_STRIP,false));

改成

ch.pipeline().addLast(new MyProtocolDecoder(MAX_FRAME_LENGTH,LENGTH_FIELD_OFFSET,LENGTH_FIELD_LENGTH,LENGTH_ADJUSTMENT,6,false));

那么我們就可以直接跳過Header和length字段,從而解碼的時候,就只需要對body字段解碼就行.其他字段跳過就忽略了.這也就是調(diào)用父類的方法的原因(使最后拿到的ByteBuf只有body部分,而沒有其他部分).

參考文章
一起學Netty(九)之LengthFieldBasedFrameDecoder
Netty的LengthFieldBasedFrameDecoder使用

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市坎匿,隨后出現(xiàn)的幾起案子盾剩,更是在濱河造成了極大的恐慌,老刑警劉巖替蔬,帶你破解...
    沈念sama閱讀 222,252評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件告私,死亡現(xiàn)場離奇詭異,居然都是意外死亡承桥,警方通過查閱死者的電腦和手機驻粟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來快毛,“玉大人格嗅,你說我怎么就攤上這事番挺。” “怎么了屯掖?”我有些...
    開封第一講書人閱讀 168,814評論 0 361
  • 文/不壞的土叔 我叫張陵玄柏,是天一觀的道長。 經(jīng)常有香客問我贴铜,道長粪摘,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,869評論 1 299
  • 正文 為了忘掉前任绍坝,我火速辦了婚禮徘意,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘轩褐。我一直安慰自己椎咧,他們只是感情好,可當我...
    茶點故事閱讀 68,888評論 6 398
  • 文/花漫 我一把揭開白布把介。 她就那樣靜靜地躺著勤讽,像睡著了一般。 火紅的嫁衣襯著肌膚如雪拗踢。 梳的紋絲不亂的頭發(fā)上脚牍,一...
    開封第一講書人閱讀 52,475評論 1 312
  • 那天,我揣著相機與錄音巢墅,去河邊找鬼诸狭。 笑死,一個胖子當著我的面吹牛君纫,可吹牛的內(nèi)容都是我干的驯遇。 我是一名探鬼主播,決...
    沈念sama閱讀 41,010評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼庵芭,長吁一口氣:“原來是場噩夢啊……” “哼妹懒!你這毒婦竟也來了雀监?” 一聲冷哼從身側(cè)響起双吆,我...
    開封第一講書人閱讀 39,924評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎会前,沒想到半個月后好乐,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,469評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡瓦宜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,552評論 3 342
  • 正文 我和宋清朗相戀三年蔚万,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片临庇。...
    茶點故事閱讀 40,680評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡反璃,死狀恐怖昵慌,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情淮蜈,我是刑警寧澤斋攀,帶...
    沈念sama閱讀 36,362評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站梧田,受9級特大地震影響淳蔼,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜裁眯,卻給世界環(huán)境...
    茶點故事閱讀 42,037評論 3 335
  • 文/蒙蒙 一鹉梨、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧穿稳,春花似錦存皂、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至埋虹,卻和暖如春猜憎,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背搔课。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評論 1 274
  • 我被黑心中介騙來泰國打工胰柑, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人爬泥。 一個月前我還...
    沈念sama閱讀 49,099評論 3 378
  • 正文 我出身青樓柬讨,卻偏偏與公主長得像,于是被迫代替她去往敵國和親袍啡。 傳聞我的和親對象是個殘疾皇子踩官,可洞房花燭夜當晚...
    茶點故事閱讀 45,691評論 2 361

推薦閱讀更多精彩內(nèi)容

  • 前奏 https://tech.meituan.com/2016/11/04/nio.html 綜述 netty通...
    jiangmo閱讀 5,868評論 0 13
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn)境输,斷路器蔗牡,智...
    卡卡羅2017閱讀 134,711評論 18 139
  • 編解碼處理器作為Netty編程時必備的ChannelHandler,每個應用都必不可少嗅剖。Netty作為網(wǎng)絡應用框架...
    Hypercube閱讀 3,654評論 7 12
  • 工作許多年辩越,期間聽聞大事小事倒也頗多,讓人受益匪淺信粮,心腸變得冷硬黔攒,唯有那么一件小事叫人感動。幾年前一個冬天的早...
    混思亂寫hs閱讀 270評論 0 0
  • 這兩天莫名低靡,非常低 過去無助督惰、迷惑不傅、孤單、焦慮的場景赏胚,一一出來報到 我什么都不想做蛤签,卻不能接受自己什么都不做 ...
    TheTrueSelf花精之鏡閱讀 316評論 0 1