一起學Netty(九)之LengthFieldBasedFrameDecoder

來源:互聯網
上載者:User

標籤:

之前介紹了Netty天然的幾種解析器,也稍微介紹了一下ByteToMessageDecoder類,我們對Netty的解碼器還是有了一定的瞭解~


今天要介紹的是Netty中一個很重要的解碼器,因為相比於其他的普通的解碼器,這個解碼器用的情境更多,並不是說其他解碼器不重要,只是因為我們業務情境所致


在當今比較流行的水平分割的架構之下,RPC協議很是流行,這樣可以使各個項目解耦,使得更加靈活,每個項目之間通過遠程調用互動,相互之間定義一個通訊私人協議,然後解析,這樣就可以進行資料介面互動


例如我們定義一個這樣的協議類:

package com.lyncc.netty.codec.lengthFieldBasedFrame;public class CustomMsg {        //類型  系統編號 0xAB 表示A系統,0xBC 表示B系統    private byte type;        //資訊標誌  0xAB 表示心跳包    0xBC 表示逾時包  0xCD 商務資訊包    private byte flag;        //主題資訊的長度    private int length;        //主題資訊    private String body;        public CustomMsg() {            }        public CustomMsg(byte type, byte flag, int length, String body) {        this.type = type;        this.flag = flag;        this.length = length;        this.body = body;    }    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 getBody() {        return body;    }    public void setBody(String body) {        this.body = body;    }}
我們規定兩個系統通過Netty去發送這樣的一個格式的資訊,CustomMsg中包含這樣的幾類資訊:

1)type表示發送端的系統類別型

2)flag表示發送資訊的類型,是業務資料,還是心跳包資料

3)length表示主題body的長度

4)body表示主題資訊


有了這樣的相互規定,發送端與接收端按照這種格式去編碼和解碼資料,這樣就很容易的進行資料互動,當然如果netty不提供任何的類,我們也能進行編碼解碼,但是Netty還是提供了一個現有的類,這樣可以避免我們重複造車,並且即使我們願意重複造車,我們造的車也不一定比Netty好,所以我們還是直接使用吧


Netty提供的類叫做LengthFieldBasedFrameDecoder,與其他的解碼器不一致的地方是它需要幾個參數作為它的建構函式參數:


這幾個參數的詳細解析可以見如下文檔:

http://blog.163.com/[email protected]/blog/static/127857195201210821145721/


我們也仔細說明一下這些參數,加入我們需要解析,加入我們需要解析我們剛才定義的CustomMsg,我們需要自訂一個decoder,這個類繼承Netty提供的LengthFieldBasedFrameDecoder:

package com.lyncc.netty.codec.lengthFieldBasedFrame;import io.netty.buffer.ByteBuf;import io.netty.channel.ChannelHandlerContext;import io.netty.handler.codec.LengthFieldBasedFrameDecoder;public class CustomDecoder extends LengthFieldBasedFrameDecoder {        //判斷傳送用戶端傳送過來的資料是否按照協議傳輸,頭部資訊的大小應該是 byte+byte+int = 1+1+4 = 6    private static final int HEADER_SIZE = 6;        private byte type;        private byte flag;        private int length;        private String body;    /**     *      * @param maxFrameLength 解碼時,處理每個幀資料的最大長度     * @param lengthFieldOffset 該幀資料中,存放該幀資料的長度的資料的起始位置     * @param lengthFieldLength 記錄該幀資料長度的欄位本身的長度     * @param lengthAdjustment 修改幀資料長度欄位中定義的值,可以為負數     * @param initialBytesToStrip 解析的時候需要跳過的位元組數     * @param failFast 為true,當frame長度超過maxFrameLength時立即報TooLongFrameException異常,為false,讀取完整個幀再報異常     */    public CustomDecoder(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 {        if (in == null) {            return null;        }        if (in.readableBytes() < HEADER_SIZE) {            throw new Exception("可讀資訊段比頭部資訊都小,你在逗我?");        }                //注意在讀的過程中,readIndex的指標也在移動        type = in.readByte();                flag = in.readByte();                length = in.readInt();                if (in.readableBytes() < length) {            throw new Exception("body欄位你告訴我長度是"+length+",但是真實情況是沒有這麼多,你又逗我?");        }        ByteBuf buf = in.readBytes(length);        byte[] req = new byte[buf.readableBytes()];        buf.readBytes(req);        body = new String(req, "UTF-8");                CustomMsg customMsg = new CustomMsg(type,flag,length,body);        return customMsg;    }}
頭部資訊的大小我們這邊寫的是6,原因在代碼裡面也解釋了,byte是一個位元組,int是四個位元組,那麼頭部大小就是6個位元組,接下來就是要定義建構函式了,建構函式的入參的解釋代碼裡已經標註了,我們真實的入參是:

稍微解釋一下:

1)LENGTH_FIELD_LENGTH指的就是我們這邊CustomMsg中length這個屬性的大小,我們這邊是int型,所以是4

2)LENGTH_FIELD_OFFSET指的就是我們這邊length欄位的起始位置,因為前面有type和flag兩個屬性,且這兩個屬性都是byte,兩個就是2位元組,所以位移量是2

3)LENGTH_ADJUSTMENT指的是length這個屬性的值,假如我們的body長度是40,有時候,有些人喜歡將length寫成44,因為length本身還佔有4個位元組,這樣就需要調整一下,那麼就需要-4,我們這邊沒有這樣做,所以寫0就可以了


好了,以下給出完整的代碼:

CustomServer.java

package com.lyncc.netty.codec.lengthFieldBasedFrame;import io.netty.bootstrap.ServerBootstrap;import io.netty.channel.ChannelFuture;import io.netty.channel.ChannelInitializer;import io.netty.channel.ChannelOption;import io.netty.channel.EventLoopGroup;import io.netty.channel.nio.NioEventLoopGroup;import io.netty.channel.socket.SocketChannel;import io.netty.channel.socket.nio.NioServerSocketChannel;import java.net.InetSocketAddress;public class CustomServer {        private static final int MAX_FRAME_LENGTH = 1024 * 1024;    private static final int LENGTH_FIELD_LENGTH = 4;    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 CustomServer(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 CustomDecoder(MAX_FRAME_LENGTH,LENGTH_FIELD_LENGTH,LENGTH_FIELD_OFFSET,LENGTH_ADJUSTMENT,INITIAL_BYTES_TO_STRIP,false));                             ch.pipeline().addLast(new CustomServerHandler());                        };                                            }).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 CustomServer(port).start();    }}
CustomServerHandler.java

package com.lyncc.netty.codec.lengthFieldBasedFrame;import io.netty.channel.ChannelHandlerContext;import io.netty.channel.SimpleChannelInboundHandler;public class CustomServerHandler extends SimpleChannelInboundHandler<Object> {    @Override    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {        if(msg instanceof CustomMsg) {            CustomMsg customMsg = (CustomMsg)msg;            System.out.println("Client->Server:"+ctx.channel().remoteAddress()+" send "+customMsg.getBody());        }            }}
CustomClient.java

package com.lyncc.netty.codec.lengthFieldBasedFrame;import io.netty.bootstrap.Bootstrap;import io.netty.channel.ChannelFuture;import io.netty.channel.ChannelInitializer;import io.netty.channel.ChannelOption;import io.netty.channel.EventLoopGroup;import io.netty.channel.nio.NioEventLoopGroup;import io.netty.channel.socket.SocketChannel;import io.netty.channel.socket.nio.NioSocketChannel;public class CustomClient {        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 CustomEncoder());                     ch.pipeline().addLast(new CustomClientHandler());                 }             });            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();        }    }}
CustomClientHandler.java

package com.lyncc.netty.codec.lengthFieldBasedFrame;import io.netty.channel.ChannelHandlerContext;import io.netty.channel.ChannelInboundHandlerAdapter;public class CustomClientHandler extends ChannelInboundHandlerAdapter {        @Override    public void channelActive(ChannelHandlerContext ctx) throws Exception {        CustomMsg customMsg = new CustomMsg((byte)0xAB, (byte)0xCD, "Hello,Netty".length(), "Hello,Netty");        ctx.writeAndFlush(customMsg);    }}
最最重要的就是兩個解碼器:

CustomDecoder.java

package com.lyncc.netty.codec.lengthFieldBasedFrame;import io.netty.buffer.ByteBuf;import io.netty.channel.ChannelHandlerContext;import io.netty.handler.codec.LengthFieldBasedFrameDecoder;public class CustomDecoder extends LengthFieldBasedFrameDecoder {        //判斷傳送用戶端傳送過來的資料是否按照協議傳輸,頭部資訊的大小應該是 byte+byte+int = 1+1+4 = 6    private static final int HEADER_SIZE = 6;        private byte type;        private byte flag;        private int length;        private String body;    /**     *      * @param maxFrameLength 解碼時,處理每個幀資料的最大長度     * @param lengthFieldOffset 該幀資料中,存放該幀資料的長度的資料的起始位置     * @param lengthFieldLength 記錄該幀資料長度的欄位本身的長度     * @param lengthAdjustment 修改幀資料長度欄位中定義的值,可以為負數     * @param initialBytesToStrip 解析的時候需要跳過的位元組數     * @param failFast 為true,當frame長度超過maxFrameLength時立即報TooLongFrameException異常,為false,讀取完整個幀再報異常     */    public CustomDecoder(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 {        if (in == null) {            return null;        }        if (in.readableBytes() < HEADER_SIZE) {            throw new Exception("可讀資訊段比頭部資訊都小,你在逗我?");        }                //注意在讀的過程中,readIndex的指標也在移動        type = in.readByte();                flag = in.readByte();                length = in.readInt();                if (in.readableBytes() < length) {            throw new Exception("body欄位你告訴我長度是"+length+",但是真實情況是沒有這麼多,你又逗我?");        }        ByteBuf buf = in.readBytes(length);        byte[] req = new byte[buf.readableBytes()];        buf.readBytes(req);        body = new String(req, "UTF-8");                CustomMsg customMsg = new CustomMsg(type,flag,length,body);        return customMsg;    }}
CustomEncoder.java

package com.lyncc.netty.codec.lengthFieldBasedFrame;import java.nio.charset.Charset;import io.netty.buffer.ByteBuf;import io.netty.channel.ChannelHandlerContext;import io.netty.handler.codec.MessageToByteEncoder;public class CustomEncoder extends MessageToByteEncoder<CustomMsg> {    @Override    protected void encode(ChannelHandlerContext ctx, CustomMsg msg, ByteBuf out) throws Exception {        if(null == msg){            throw new Exception("msg is null");        }                String body = msg.getBody();        byte[] bodyBytes = body.getBytes(Charset.forName("utf-8"));        out.writeByte(msg.getType());        out.writeByte(msg.getFlag());        out.writeInt(bodyBytes.length);        out.writeBytes(bodyBytes);            }}
好了,到此為止代碼就全部寫完了,運行測試一下:

啟動伺服器端:


運行用戶端後,再回到伺服器端的控制台:



一起學Netty(九)之LengthFieldBasedFrameDecoder

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.