使用 LengthFieldBasedFrameDecoder 自定义解码

3,339 阅读4分钟

前言:

本文只是一个简单的小随笔,讲述了LengthFieldBasedFrameDecoder如何使用。
关于NIO和 netty,要弄懂(还不敢说是熟悉)还是要花一些时间的。近期会更新一系列的文章,专门讲述NIO和Netty,应该会比网上其他的文章讲解的更透彻明白一些。以前写过一本书叫做Java并发编程系统与模型 ,对于初学者是绝对有用的,对整个JAVA 并发可以起到一个提纲挈领的作用,个人觉得"先整体,后局部”的学习方式要好一些(可以参考《金字塔原理》。

正所谓:“善删者字去而意留,善敷者辞殊而意显”,我写的文章都比较简单简洁。基本上都是为说明某个问题而来的,其思考也是按照先有骨架,后有肉的思考方式去思考的。

 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;
    }


    public byte[] getBytes() {
        byte[] bodyBytes = this.body.getBytes();
        int bodyLength = bodyBytes.length;
        byte[] bytes = new byte[bodyLength + 6];
        bytes[0] = this.type;
        bytes[1] = this.flag;
        bytes[2] = (byte)((bodyLength >> 24) & 0xFF);
        bytes[3] = (byte)((bodyLength >> 16) & 0xFF);
        bytes[4] = (byte)((bodyLength >> 8) & 0xFF); 
        bytes[5] = (byte)(bodyLength & 0xFF);
        for (int i = 0; i < bodyBytes.length; i++) {
            bytes[i + 6] = bodyBytes[i];
        }
        return bytes;
    }
}


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 {
        new CustomServer(10000).start();
    }
}

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());
        }

    }

}
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;
    }

}

Client端如下:

public class Client {
    public static void main(String[] args) throws InterruptedException, UnknownHostException, IOException {
        final int port = 10000;
        final String clientname = "clientThread";
        Socket socket = new Socket("127.0.0.1", port);
        final OutputStream out = socket.getOutputStream();
        new Thread(clientname + "_write") {
            @Override
            public void run() {
                int a = 0;
                for(int i=0;i<100;i++) {
                     CustomMsg customMsg = new CustomMsg((byte)0xAB, (byte)0xCD, "Hello,Netty".length(), "Hello,Netty"); 
                    try {
                        out.write(customMsg.getBytes());
                        out.flush();
                        a++;
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();

    }
}

这样就自定义一个解码器。
观察如下代码:

    public byte[] getBytes() {
        byte[] bodyBytes = this.body.getBytes();
        int bodyLength = bodyBytes.length;
        byte[] bytes = new byte[bodyLength + 6];
        bytes[0] = this.type;
        bytes[1] = this.flag;
        bytes[2] = (byte)((bodyLength >> 24) & 0xFF);
        bytes[3] = (byte)((bodyLength >> 16) & 0xFF);
        bytes[4] = (byte)((bodyLength >> 8) & 0xFF); 
        bytes[5] = (byte)(bodyLength & 0xFF);
        for (int i = 0; i < bodyBytes.length; i++) {
            bytes[i + 6] = bodyBytes[i];
        }
        return bytes;
    }
}

实际上与如下代码对应,相当于自己将一个对象变成byte数组:

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);