《Netty学习打卡--从小白到放弃》----- 11 - netty 之protobuf多消息协议传递

打卡日期(2019-07-10)

学习要点

-   1.oneof
-   2.解决protobuf 多协议的解决方案

1.protobuf oneof

    如果消息有很多可选字段或者对象,而同一时刻只能是一个字段被设值,就可以使用oneof来强化这个特性并且节约存储空间

message SampleMessage {
  oneof test_oneof {
     string name = 4;
     SubMessage sub_message = 9;
  }
}

2.解决protobuf 多协议的解决方案

    在使用netty的过程中,有时候为了高效的传输数据,经常使用protobuf进行传输,但是默认情况下我们实现protobuf编码的时候只能对单个对象进行编码,如果想对多个对象进行编码的话比较笨的方法是写多个init初始化类,但是这样操作的话只会增加一些重复的工作。
    如何解决这个问题呢?着就用到了oneof字段类型,被oneof声明的字段类似于required字段,在同一时刻只能有一个值,并且他们会共享内存。
    接下来我们来实现一个netty基于protobuf的多消息协议传递。

netty基于protobuf实现多消息协议传递

1.编写proto文件
// proto2 版本
syntax ="proto2";

package protobuf;

option optimize_for = SPEED;
option java_package = "com.dragon.protobuf";

option java_outer_classname = "MyDataInfo";
// 三种消息 Person / Animal / Computer
message DataInfo{
    
    

    // 定义一个枚举类用来表示三种消息的类型
    enum DataInfoType{
    
    
        PersonType = 1;
        AnimalType = 2;
        ComputerType = 3;
    }

    required DataInfoType data_info_type = 1;

    oneof info_type{
    
    
        Person person = 2;
        Animal animal = 3;
        Computer computer = 4;
    }

    message Person{
    
    
        optional string name = 1;
        optional int32 age = 2;
        optional string adress = 3;
        optional string sex = 4;
    }

    message Animal{
    
    
        optional string name = 1;
        optional int32 age = 2;
        optional string type = 3;
    }

    message Computer{
    
    
        optional string name = 1;
        optional string type = 2;
        optional double price = 3;
    }
}
2.利用protoc生成对应的java类文件
运行命令:
E:\tools\gradle\netty01>protoc --java_out=src/main/java src/main/protobuf/DataInfo.proto

3.编写客户端代码(Client)

    由于Client端启动代码与前几章的代码一样,这里就不重复写了,可以参考前几章的代码来写。
《Netty学习打卡–从小白到放弃》----- 10 - netty 之protobuf单消息协议传递

package com.dragon.protobuf.more.client;

import com.dragon.protobuf.MyDataInfo;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.protobuf.ProtobufDecoder;
import io.netty.handler.codec.protobuf.ProtobufEncoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender;

public class MyProtobufClientInit extends ChannelInitializer<SocketChannel> {
    
    
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
    
    
        ChannelPipeline pipeline = ch.pipeline();
        //用于decode(解码)前解决半包和粘包问题(利用包头中包含数组长度来识别半包沾包)
        pipeline.addLast(new ProtobufVarint32FrameDecoder());
        //这个解析器实际上就是告诉ProtobufDecoder要处理的目标类是什么,否则仅仅从字节数组中是无法判断出要解析的目标类型信息
        pipeline.addLast(new ProtobufDecoder(MyDataInfo.DataInfo.getDefaultInstance()));
        //protobuf编码器 对protobuf协议的的消息头上加上一个长度为32的整形字段,用于标志这个消息的长度
        pipeline.addLast(new ProtobufVarint32LengthFieldPrepender());
        //protobuf编码器
        pipeline.addLast(new ProtobufEncoder());
        //自定义处理器
        pipeline.addLast(new MyProtobufClientHandler());
    }
}

    这里需要注意这一行,与上一章不同的是,这个参数变成了一个类名,且这个类下面有好几个内部子类,将这个类作为一个参数传递

pipeline.addLast(new ProtobufDecoder(MyDataInfo.DataInfo.getDefaultInstance()));
package com.dragon.protobuf.more.client;

import com.dragon.protobuf.MyDataInfo;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

import java.util.Random;

public class MyProtobufClientHandler extends SimpleChannelInboundHandler<MyDataInfo.DataInfo> {
    
    
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, MyDataInfo.DataInfo msg) throws Exception {
    
    

    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
    
    
        int random = new Random().nextInt(3);
        MyDataInfo.DataInfo dataInfo = null;
        switch (random){
    
    
            case 0:
                MyDataInfo.DataInfo.Person person = MyDataInfo.DataInfo.Person
                        .newBuilder()
                        .setName("隔壁老王")
                        .setAdress("绿色草原小区 A栋楼 3单元 666号")
                        .setAge(66)
                        .setSex("男")
                        .build();
                dataInfo = MyDataInfo.DataInfo.newBuilder()
                        .setDataInfoType(MyDataInfo.DataInfo.DataInfoType.PersonType)
                        .setPerson(person)
                        .build();
                break;
            case 1:
                MyDataInfo.DataInfo.Animal animal = MyDataInfo.DataInfo.Animal
                        .newBuilder()
                        .setName("深林之王 Tiger")
                        .setAge(12)
                        .setType("哺乳类")
                        .build();
                dataInfo = MyDataInfo.DataInfo.newBuilder()
                        .setDataInfoType(MyDataInfo.DataInfo.DataInfoType.AnimalType)
                        .setAnimal(animal)
                        .build();
                break;
            case 2:
                MyDataInfo.DataInfo.Computer computer = MyDataInfo.DataInfo.Computer
                        .newBuilder()
                        .setName("华为Pro")
                        .setPrice(9999.99)
                        .setType("笔记本")
                        .build();
                dataInfo = MyDataInfo.DataInfo.newBuilder()
                        .setDataInfoType(MyDataInfo.DataInfo.DataInfoType.ComputerType)
                        .setComputer(computer)
                        .build();
                break;
        }

        ctx.writeAndFlush(dataInfo);

    }
}

4.编写服务端代码(Server)

    由于Server端启动代码与前几章的代码一样,这里就不重复写了,可以参考前几章的代码来写。
《Netty学习打卡–从小白到放弃》----- 10 - netty 之protobuf单消息协议传递

package com.dragon.protobuf.more.server;

import com.dragon.protobuf.MyDataInfo;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.protobuf.ProtobufDecoder;
import io.netty.handler.codec.protobuf.ProtobufEncoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender;

public class MyProtobufServerInit extends ChannelInitializer<SocketChannel> {
    
    
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
    
    
        ChannelPipeline pipeline = ch.pipeline();
        //用于decode(解码)前解决半包和粘包问题(利用包头中包含数组长度来识别半包沾包)
        pipeline.addLast(new ProtobufVarint32FrameDecoder());
        //这个解析器实际上就是告诉ProtobufDecoder要处理的目标类是什么,否则仅仅从字节数组中是无法判断出要解析的目标类型信息
        pipeline.addLast(new ProtobufDecoder(MyDataInfo.DataInfo.getDefaultInstance()));
        //protobuf编码器 对protobuf协议的的消息头上加上一个长度为32的整形字段,用于标志这个消息的长度
        pipeline.addLast(new ProtobufVarint32LengthFieldPrepender());
        //protobuf编码器
        pipeline.addLast(new ProtobufEncoder());
        //自定义处理器
        pipeline.addLast(new MyProtobufServerHandler());
    }
}

    这里需要注意这一行,与上一章不同的是,这个参数变成了一个类名,且这个类下面有好几个内部子类,将这个类作为一个参数传递

pipeline.addLast(new ProtobufDecoder(MyDataInfo.DataInfo.getDefaultInstance()));
package com.dragon.protobuf.more.server;
import com.dragon.protobuf.MyDataInfo;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

public class MyProtobufServerHandler extends SimpleChannelInboundHandler<MyDataInfo.DataInfo> {
    
    
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, MyDataInfo.DataInfo msg) throws Exception {
    
    
        MyDataInfo.DataInfo.DataInfoType dataInfoType = msg.getDataInfoType();
        switch (dataInfoType){
    
    
            case PersonType:
                System.out.println("=======================**  Person **============================");
                MyDataInfo.DataInfo.Person person = msg.getPerson();
                System.out.println(person.getName());
                System.out.println(person.getAdress());
                System.out.println(person.getAge());
                System.out.println(person.getSex());
                break;
            case AnimalType:
                System.out.println("=======================**  Animal **============================");
                MyDataInfo.DataInfo.Animal animal = msg.getAnimal();
                System.out.println(animal.getName());
                System.out.println(animal.getAge());
                System.out.println(animal.getType());
                break;
            case ComputerType:
                System.out.println("=======================**  Computer **============================");
                MyDataInfo.DataInfo.Computer computer = msg.getComputer();
                System.out.println(computer.getName());
                System.out.println(computer.getPrice());
                System.out.println(computer.getType());
                break;
        }
        System.out.println("==================================================================");
    }
}

分别运行Server端和Client端的启动类,运行结果如下(可以多运行几次)

七月 10, 2019 4:52:20 下午 io.netty.handler.logging.LoggingHandler channelRead
信息: [id: 0x2ba733ff, L:/0:0:0:0:0:0:0:0:8080] READ: [id: 0x4703852a, L:/127.0.0.1:8080 - R:/127.0.0.1:4282]
七月 10, 2019 4:52:20 下午 io.netty.handler.logging.LoggingHandler channelReadComplete
信息: [id: 0x2ba733ff, L:/0:0:0:0:0:0:0:0:8080] READ COMPLETE
=======================**  Animal **============================
深林之王 Tiger
12
哺乳类
==================================================================
=======================**  Person **============================
隔壁老王
绿色草原小区 A栋楼 3单元 666号
66
男
==================================================================

从运行结果来看,我们的确是实现了protobuf多消息协议传递的过程

猜你喜欢

转载自blog.csdn.net/u011291990/article/details/95352288