打卡日期(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多消息协议传递的过程