十一.Netty入门到超神系列-Netty使用Protobuf编码解码

Posted 墨家巨子@俏如来

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了十一.Netty入门到超神系列-Netty使用Protobuf编码解码相关的知识,希望对你有一定的参考价值。

前言

数据在网络传输的过程中需要序列化或和反序列化,也就需要用到编码器和解码器,本篇文章主要是探讨Netty中的编码解码器以及Protobuf的使用。

Netty中的编码解码

当我们的Netty客户端和服务端进行通信时数据在传输的过程中需要进行序列化,比如以二进制数据进行传输,那么我们的业务数据就需要有相应的编码器进行编码为二进制数据,当服务端拿到二进制数据后需要有相应的解码器进行解码得到真实的业务数据。如下图:

当Netty发送或者接受一个消息的时候,就将会发生一次数据转换。入站消息会被解码:从字节转换为另一种格式(比如java对象);如果是出站消息,它会被编码成字节。

Netty提供一系列实用的编解码器,他们都实现了ChannelInboundHadnler或者ChannelOutboundHandler接口。在这些类中,channelRead方法已经被重写了。以入站为例,对于每个从入站Channel读取的消息,这个方法会被调用。随后,它将调用由解码器所提供的decode()方法进行解码,并将已经解码的字节转发给ChannelPipeline中的下一个ChannelInboundHandler。

在Netty提供了一些编码解码器如下:

  • StringEncoder : 针对于字符串的编码器
  • StringDecoder : 针对于字符串的解码器
  • ObjectEncoder : 针对于Java对象的编码器
  • ObjectDecoder : 针对于Java对象的解码器
  • MessageToByteEncoder:把message转换成byte的编码器 。
  • ByteToMessageDecoder :把byte转换成message的解码器,会有粘包拆包问题。
  • ReplayingDecoder:对ByteToMessageDecoder的扩展

对于 ObjectEncoder 和 ObjectDecoder而言它只是针对Java对象的编码解码, 换句话说底层使用的是Java的序列化技术,而Java序列化存在如下问题

  • 效率低下
  • 无法夸语言,只能支持java
  • 序列化后体积庞大,远远大于二进制编码

所以我们引入了 google 的 protobuf来解决这一问题

Protobuf认识

Protobuf 是 google 谷歌开源的项目,它是一种灵活,高效,自动化机制的结构数据序列化方法;它是一种跨语言、可扩展的序列化结构数据的方法,它可用于(数据)通信协议(RPC)、数据存储等。其具有以下特点:

  • 语言无关、平台无关。即 ProtoBuf 支持 Java、C++、Python 等多种语言,支持多个平台
  • 高效。类比XML,比 XML 更小(3 ~ 10倍)、更快(20 ~ 100倍)、更为简单
  • 扩展性、兼容性好。你可以更新数据结构,而不影响和破坏原有的旧程序

你可以定义数据的结构,然后使用特殊生成的源代码轻松的在各种数据流中使用各种语言进行编写和读取结构数据。你甚至可以更新数据结构,而不破坏由旧数据结构编译的已部署程序

在Netty中提供了针对于protobuf的编码器 ProtobufEncoder 和 解码器 ProtobufDecoder

使用Protobuf生成POJO

官方文档:http://developers.google.com/protocol-buffers/docs/proto

使用Protobuf需要使用protobuf 编译器编译器生成Java代码,步骤如下

  1. 定义以.proto结尾的文件
  2. 使用protoc.exe编译.proto文件生成java代码
  3. 编写Netty案例

第一步:编写.proto文件 , 可以使用IDEA编写,文件名我这里是User.proto

//定义版本
syntax = "proto3";
//生成的代码的类名和文件名
option java_outer_classname = "UserPOJO";
//使用messae管理数据,注意名字不要和java_outer_classname冲突
message User
  //定义ID属性,int32对应java的,注意这里的1指的是需要而不是值
  int64 id = 1;
  //定义一个name属性,类型为字符串String,注意这里的2指的是需要而不是值
  string name = 2;
  //定义一个数组,爱好
  repeated string   favorite = 3;

这个表格显示了在.proto文件内可以指定的类型, 与自动生成的相对类型!

第二步:安装proto.exe,下载地址:https://github.com/protocolbuffers/protobuf/releases,解压后进入bin目录

第三步:拷贝User.proto文件到bin目录,使用protoc.exe生产java文件 ,执行命令protoc.exe --java_out=. User.proto

生成的文件如下

Netty使用Protobuf编解码

还是以Netty的 Server/Client 模式为例来演示,只是这里的数据使用protobuf的编解码。

第一步:搭建工程,把UserPOJO.java文件拷贝到项目中,此时该类会报错,这需要导入一个依赖

<dependency>
  	<groupId>com.google.protobuf</groupId>
    <artifactId>protobuf-java</artifactId>
    <version>3.4.0</version>
</dependency>
<dependency>
   <groupId>io.netty</groupId>
   <artifactId>netty-all</artifactId>
   <version>4.1.42.Final</version>
</dependency>

第二步:创建Netty服务端:NettyServer

public class NettyServer 
    public static void main(String[] args) 
        NioEventLoopGroup bossGroup = new NioEventLoopGroup();
        NioEventLoopGroup workGroup = new NioEventLoopGroup();
        ServerBootstrap bootstrap = new ServerBootstrap();

        try 
            bootstrap.group(bossGroup,workGroup)
                .channel(NioserverSocketChannel.class)
                .childHandler(new ChannelInitializer<SocketChannel>() 
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception 
                        ChannelPipeline pipeline = ch.pipeline();
                        //服务端:使用protobuf解码器,需要指定解码器解码哪种类型
                        pipeline.addLast("decoder",new ProtobufDecoder(UserPOJO.User.getDefaultInstance()));
                        pipeline.addLast(new ServerHandler());
                    
                );

            //启动服务器
            ChannelFuture sync = bootstrap.bind(2000).sync();
            sync.channel().closeFuture().sync();
         catch (InterruptedException e) 
            e.printStackTrace();
        finally 
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        
    

这里我们通过pipeline添加了protobuf的解码器

第三步:编写服务端的handler

public class ServerHandler extends ChannelInboundHandlerAdapter 
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception 
    	//把消息强转成UserPOJO.User
        UserPOJO.User user = (UserPOJO.User) msg;
        System.out.println("拿到数据:"+user.getName());
    

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


第四步:编写客户端:NettyClient

public class NettyClient 
    public static void main(String[] args) 
        NioEventLoopGroup eventLoopGroup = new NioEventLoopGroup();
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(eventLoopGroup)
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<SocketChannel>() 
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception 
                        ChannelPipeline pipeline = ch.pipeline();
                        //给客户端增加编码器,使用ProtobufEncoder编码器
                        pipeline.addLast("encoder",new ProtobufEncoder());
                        pipeline.addLast(new ClientHandler());
                    
                );
        try 
            ChannelFuture sync = bootstrap.connect("127.0.0.1", 2000).sync();
            sync.channel().closeFuture().sync();
         catch (InterruptedException e) 
            e.printStackTrace();
        finally 
            eventLoopGroup.shutdownGracefully();
        
    


客户端添加了ProtobufEncoder编码器

第五步:编写客户端的handler

public class ClientHandler extends ChannelInboundHandlerAdapter 
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception 
    

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception 
        //当建立连接,就发送一个对象给服务端
        UserPOJO.User user = UserPOJO.User.newBuilder().setId(1).setName("zs").build();
        ctx.writeAndFlush(user);
        System.out.println("客户端发送 user="+user.getName());
    

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


代码编写完成,最终项目结构如下

第六步:测试,依次启动NettyServer,NettyClient ,效果如下

客户端:

服务端:

protobuf的内容就介绍到这里把,喜欢的话给个好评把。

以上是关于十一.Netty入门到超神系列-Netty使用Protobuf编码解码的主要内容,如果未能解决你的问题,请参考以下文章

十.Netty入门到超神系列-基于Netty群聊系统

九.Netty入门到超神系列-Netty开发Http服务器

一.Netty入门到超神系列-BIONIOAIO的认识

七.Netty入门到超神系列-Netty介绍和线程模型

十.Netty入门到超神系列-基于WebSocket开发聊天室

十三.Netty入门到超神系列-手撸简单版RPC框架(仿Dubbo)