Netty对Protobuf的解决粘包/半包问题的编解码方案

protobuf编解码内置方案

解码工具类:ProtobufDecoder+ProtobufVarint32FrameDecoder

编码工具类:ProtobufEncoder+ProtobufVarint32LengthFieldPrepender

  • 原理
    在这里插入图片描述
  • 代码:

1.user.proto

syntax="proto2";

package netty.protobuf;

option java_package="netty.protobuf";
option java_outer_classname="UserInfo";

message User{
  required string name =1;
  required int64 userId =2;
  optional string email=3;
  optional string mobile=4;
  optional string remark=5;
}

编译生成UserInfo,复制进工程项目即可

2.ProtobufServer

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

    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 {
                            //配置Protobuf解码工具ProtobufVarint32FrameDecoder与ProtobufDecoder
                            ch.pipeline().addLast(new ProtobufVarint32FrameDecoder());
                            ch.pipeline().addLast(new ProtobufDecoder(UserInfo.User.getDefaultInstance()));
                            ch.pipeline().addLast(new ProtoServerHandler());
                        }
                    });
            //绑定端口,同步等待成功
            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 ProtobufServer().bind(port);
    }
}

3.ProtoServerHandler

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

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

4.ProtobufClient

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

    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 {
                            //配置Protobuf编码工具ProtobufVarint32LengthFieldPrepender与ProtobufEncoder
                            ch.pipeline().addLast(new ProtobufVarint32LengthFieldPrepender());
                            ch.pipeline().addLast(new ProtobufEncoder());
                            ch.pipeline().addLast(new ProtobufClientHandler());
                        }
                    });
            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 ProtobufClient().connect(port, "127.0.0.1");
    }
}

5.ProtobufClientHandler

/**
 * 客户端向服务端发送消息
 * @author pdc
 */
public class ProtobufClientHandler extends ChannelInboundHandlerAdapter {
    /**
     * channel建立之后,向服务端发送消息
     * 需要注意的是这里写入的消息是完整的UserInfo.User对象
     * 因为后续会被工具ProtobufVarint32LengthFieldPrepender与ProtobufEncoder进行编码处理
     * @param ctx
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        //channel建立之后,向服务端发送消息,需要注意的是这里写入的消息是完整的UserInfo.User对象
        //因为后续会被工具ProtobufVarint32LengthFieldPrepender与ProtobufEncoder进行编码处理
        UserInfo.User user = UserInfo.User.newBuilder()
                .setName("pdc")
                .setUserId(10000)
                .setEmail("[email protected]")
                .setMobile("155****8925")
                .setRemark("remark info").build();

        ctx.writeAndFlush(user);
    }

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

先运行ProtobufServer的main,再运行ProtobufClient的main即可

猜你喜欢

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