Netty自定义序列化编解码器的解决粘包/半包问题的编解码方案方案一:使用LengthFieldPrepender与LengthFieldBasedFrameDecoder

通过扩展MessageToByteEncoder和ByteToMessageDecoder来自定义编解码器

  • 原理

使用int数据类型来记录整个消息的字节数组长度,然后将该int数据作为消息的消息头一起传输

服务端接收消息数据时,先接收4个字节的int数据类型数据,此数据即为整个消息字节数组的长度,再接收剩余字节,直到接收的字节数组长度等于最先接收的int数据类型数据大小,即字节数组的长度

类似protobuf

  • 消息格式

length | Message Data

  • LengthFieldPrepender与LengthFieldBasedFrameDecoder原理
    在这里插入图片描述
  • 代码

继续使用Netty对Protobuf的解决粘包/半包问题的编解码方案中的UserInfo

1.CustomServer

/**
 * @author pdc
 */
public class CustomServer {

    public void bind(int port) throws Exception {
        //配置服务端的NIO线程组
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 100)
                    .handler(new LoggingHandler(LogLevel.INFO))
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            //解决粘包/半包,根据消息长度自动拆包
                            ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(65535, 0, 2, 0, 2));
                            //配置自定义序列化解码工具
                            ch.pipeline().addLast(new CustomV1Decoder());
                            //解决粘包/半包问题附加消息长度在消息头部
                            ch.pipeline().addLast(new LengthFieldPrepender(2));
                            //配置自定义序列化编码工具
                            ch.pipeline().addLast(new CustomV1Encoder());
                            ch.pipeline().addLast(new CustomServerHandler());
                        }
                    });
            //绑定端口,同步等待成功
            ChannelFuture f = b.bind(port).sync();
            //等待服务端监听端口关闭
            f.channel().closeFuture().sync();
        } finally {
            //优雅退出,释放线程池资源
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws Exception {
        int port = 8080;
        new CustomServer().bind(port);
    }
}

2.CustomServerHandler

/**
 * @author pdc
 */
public class CustomServerHandler extends ChannelInboundHandlerAdapter {

    private static AtomicInteger counter = new AtomicInteger(0);

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        //接收客户端发送过来的消息
        // 其中经过前面解码工具的处理
        // 将字节码消息自动转换成了UserInfo对象
        UserInfo req = (UserInfo) msg;
        System.out.println("received from client:" + req.toString() + " counter :" + counter.incrementAndGet());
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }
}

3.CustomClient

/**
 * @author pdc
 */
public class CustomClient {

    public void connect(int port, String host) throws Exception {
        // 配置客户端NIO线程组
        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 LengthFieldBasedFrameDecoder(65535, 0, 2, 0, 2));
                            //配置自定义序列化解码工具
                            ch.pipeline().addLast(new CustomV1Decoder());
                            //解决粘包/半包问题附加消息长度在消息头部
                            ch.pipeline().addLast(new LengthFieldPrepender(2));
                            //配置自定义序列化编码工具
                            ch.pipeline().addLast(new CustomV1Encoder());

                            ch.pipeline().addLast(new CustomClientHandler());
                        }
                    });
            ChannelFuture f = b.connect(host, port).sync();
            f.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws Exception {
        int port = 8080;
        new CustomClient().connect(port, "127.0.0.1");
    }
}

4.CustomClientHandler

/**
 * @author pdc
 */
public class CustomClientHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        for (int i = 0; i < 1000; i++) {
            //channel建立之后,向服务端发送消息
            // 需要注意的是这里写入的消息是完整的UserInfo对象
            UserInfo user = UserInfo.newBuilder()
                    .name("pdc")
                    .userId(10000)
                    .email("[email protected]")
                    .mobile("155****8925")
                    .remark("remark info").build();

            ctx.writeAndFlush(user);
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }
}

5.CustomV1Encoder

/**
 * @author pdc
 */
public class CustomV1Encoder extends MessageToByteEncoder {

    @Override
    public void encode(ChannelHandlerContext ctx, Object in, ByteBuf out) throws Exception {
        //使用hessian序列化对象
        byte[] data = HessianSerializer.serialize(in);
        out.writeBytes(data);
    }
}

6.CustomV1Decoder

/**
 * @author pdc
 */
public class CustomV1Decoder extends ByteToMessageDecoder {

    @Override
    public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out){
        //读取字节数组
        int dataLength = in.readableBytes();
        if (dataLength <= 0) return;
        byte[] data = new byte[dataLength];
        in.readBytes(data);
        //将字节数组使用Hession反序列化为对象
        Object obj = HessianSerializer.deserialize(data);
        out.add(obj);
    }
}

7.HessianSerializer

/**
 * @author pdc
 */
public class HessianSerializer {

    private HessianSerializer() {
    }

    public static byte[] serialize(Object obj) {
        if (obj == null)
            throw new NullPointerException();

        try {
            ByteArrayOutputStream os = new ByteArrayOutputStream();
            HessianOutput ho = new HessianOutput(os);
            ho.writeObject(obj);
            return os.toByteArray();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @SuppressWarnings("unchecked")
    public static <T> T deserialize(byte[] data) {
        if (data == null) throw new NullPointerException();
        
        try {
            ByteArrayInputStream is = new ByteArrayInputStream(data);
            HessianInput hi = new HessianInput(is);
            return (T) hi.readObject();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
  • 运行

先运行CustomServer的main,再运行CustomClient的main即可

猜你喜欢

转载自blog.csdn.net/qq_41594698/article/details/94714986