Netty入门(四) --- 编码和解码,Netty的handler调用机制,TCP粘包和拆包原理和解决

本文深入探讨了Netty的编码解码机制,特别是针对TCP粘包和拆包问题,介绍了Google Protobuf作为高效序列化的解决方案。文中详细讲解了Protobuf的基本概念、使用步骤以及在Netty中的应用。此外,还阐述了Netty的handler调用链,包括ChannelInboundHandler和ChannelOutboundHandler的作用,并通过实例展示了如何自定义编码器和解码器来处理粘包和拆包问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


往期

七、Google Protobuf

7.1 编码和解码简介

编写网络应用程序时,因为数据在网络中传输的都是二进制字节码数据,在发送数据时就需要编码,接收数据时就需要解码

codec(编解码器) 的组成部分有两个:decoder(解码器)encoder(编码器)

image-20220124215502699

7.2 Netty本身的编解码机制和问题分析

Netty自身提供了一些编解码器

  • Netty提供的编码器:
    • StringEncoder,对字符串数据进行编码
    • ObjectEncoder,对 Java 对象进行编码
    • 。。。
  • Netty提供的解码器:
    • StringDecoder, 对字符串数据进行解码
    • ObjectDecoder,对 Java 对象进行解码

问题:

Netty本身带的ObjectDecoder 和 ObjectEncoder 虽然可以用来实现 POJO 对象或各种业务对象的编码和解码

但是底层使用的是Java序列化技术

  • Java序列化本身效率不高,
  • 且无法跨语言,
  • 序列化后体积太大,是二进制码的的5倍多
  • 序列化性能太低

所以引出新的解决方案:Google Protobuf

7.3 Protobuf基本介绍

Protobuf 是 Google 发布的开源项目,全称 Google Protocol Buffers,是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或 RPC[远程过程调用 remote procedure call ] 数据交换格式

目前很多公司在从 http+json 向 rpc+protobuf 转型

参考文档: https://developers.google.com/protocol-buffers/docs/proto

优点:

  • 支持跨平台、跨语言,即客户端服务器端可以使用不同语言编写

  • 高性能,高可靠

  • 使用 protobuf 编译器能自动生成代码

  • 通过 protoc.exe 编译器

    image-20220124220859308

7.4 Protobuf发送单对象

客户端可以发送一个Student PoJo对象到服务器 (通过 Protobuf 编码)

服务端能接收Student PoJo对象,并显示信息(通过 Protobuf 解码)

1、idea安装如下插件

image-20220124221035760

2、新建一个Student.proto文件

syntax = "proto3";  //协议版本
option java_outer_classname = "StudentPOJO";    //生成的外部类名,同时也是.java的文件名
//protobuf 使用message 管理数据
message Student{    //在StudentPOJO这个外部类里面生成一个 内部类Student,是真正发送的pojo对象
    int32 id = 1;   //Student类中有一个属性 id 类型为 int32,对应java里的int。  1 不是指,是属性的序号
    string name = 2;
}
  • protobuf 使用message 管理数据
  • message 等号后面是属性的编号,不是值

3、使用 protoc.exe 编译。如图:Student.proto ==》StudentPOJO.java

生成java文件的语句:protoc.exe --java_out=. xxx.proto

image-20220124221304951

4、拷贝回项目

image-20220124221618684

发现StudentPOJO有一个Student内部类

5、编写客户端

NettyClient

public class NettyClient {
   
   
    public static void main(String[] args) {
   
   

        EventLoopGroup eventExecutors = new NioEventLoopGroup();
        try {
   
   
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(eventExecutors)     //设置线程组
                    .channel(NioSocketChannel.class)//设置客户端通道实现类
                    .handler(new ChannelInitializer<SocketChannel>() {
   
   
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
   
   
                            ChannelPipeline pipeline = socketChannel.pipeline();
                            //客户端发送数据,需要加上编码器
                            pipeline.addLast("ProtobufEncoder",new ProtobufEncoder());

                            pipeline.addLast(new NettyClientHandler());
                        }
                    });
            System.out.println("客户端 ok");

            ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 6666).sync();
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
   
   
            e.printStackTrace();
        } finally {
   
   
            eventExecutors.shutdownGracefully();
        }
    }
}

注意:ChannelInitializer里要在pipeline里添加编码器(客户端发送)

NettyClientHandler

public class NettyClientHandler extends ChannelInboundHandlerAdapter {
   
   

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
   
   
        //发送一个 Student 对象到服务器
        StudentPOJO.Student student = StudentPOJO.Student.newBuilder().setId(6).setName("豹子头 零冲").build();
        ctx.writeAndFlush(student);
    }

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

注意:使用StudentPOJO.Student student = StudentPOJO.Student.newBuilder().setId(6).setName("豹子头 零冲").build();获取生成的那个类的实例

6、编写服务器端

NettyServer

public class NettyServer {
   
   
    public static void main(String[] args) {
   
   

            EventLoopGroup bossGroup = new NioEventLoopGroup(1);
            EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
   
   
            //创建服务器启动对象,用来配置参数
            ServerBootstrap bootstrap = new ServerBootstrap();

            bootstrap.group(bossGroup,workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG,128)   //线程队列得到连接的个数
                    .childOption(ChannelOption.SO_KEEPALIVE,true)   //设置保持活动连接状态
                    .childHandler(new ChannelInitializer<SocketChannel>() {
   
   
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
   
   
                            ChannelPipeline pipeline = socketChannel.pipeline();
                            //服务器端接收数据,解码
                            pipeline.addLast("ProtobufDecoder",new ProtobufDecoder(StudentPOJO.Student.getDefaultInstance()));
                            pipeline.addLast(new NettyServerHandler());
                        }
                    });

            System.out.println("... 服务器 ok ...");
            ChannelFuture channelFuture = bootstrap.bind(6666).sync();
            channelFuture.channel().closeFuture().sync();

        } catch (InterruptedException e) {
   
   
            e.printStackTrace();
        } finally {
   
   
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }

    }
}

pipeline.addLast("ProtobufDecoder",new ProtobufDecoder(StudentPOJO.Student.getDefaultInstance())); 配置解码器,需要和Protobuf生成类类型对应

NettyServerHandler

public class NettyServerHandler extends ChannelInboundHandlerAdapter {
   
   


    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
   
   
        StudentPOJO.Student student = (StudentPOJO.Student) msg;
        System.out.println("客户端发来:id="+student.getId()+" 姓名="+student.getName());
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值