解决多类型传输限制

解决方案一:第一种方式利用了自定义协议,传递消息的时候,对消息的前几位(比如2位)进行自定义的位置(比如AB)解码器解析的时候前二位为AB表示一种协议类型,CD一种协议类型。这种方式没有利用protobuf,而是直接使用Netty自定义协议来解决的方案。

解决方案二:在proto文件外层定义一个消息。然后通过一种枚举的方式来定义消息传递的类型。写多个消息类型,通过一个最外层的消息对象包装一下。

解决方案二

.proto文件

syntax = "proto2";

package com.sakura.protobuf; //包路径,自定义

// 文件选项
// SPEED加快解析速度,默认就是SPEED
option optimize_for = SPEED;
//包路径,自定义,比package优先级高
//如果java_package存在则默认使用java_package的配置,如果使用java_package就必须要指定package
//以免在生成java以外的语言时,不存在java_package参数,造成命名秘密空间问题
option java_package = "com.sakura.protobuf";
//生成的外部类的名称,自定义 如果不定义则默认使用文件名的驼峰命名法转换的名称作为外部类名称
option java_outer_classname = "DataInfo";

/*
message 表示消息,如同Java的类

格式:
修饰符 数据类型 属性名 = 唯一标记数;
修饰符:
required 必须的,必须存在,必须赋值
optional 可选的,可以不使用
repeated 重复的,标识一个list/链表
数据类型:
包括 bool,int32,float,double,和string
唯一标记数:
标签编号1-15与较高的编号相比,编码所需的字节减少了一个字节,因此,
为了进行优化,您可以决定将这些标签用于常用或重复的元素
*/
//包含了所有可能出现的消息类型,最外层的消息类型
message Message{
enum DataType{
PersonType = 1;
DogType = 2;
CatType = 3;
}
//用于表示当前传递的类型
required DataType data_type = 1;

/*
如果消息中包含许多可选字段,并且最多同时设置一个字段,
则可以使用oneof功能强制执行此行为并节省内存。

oneof中的所有字段会共享内存,oneof字段类似于可选字段,
并且最多可以同时设置一个字段。设置oneof中的任何成员会自动清除所有其他成员。
您可以根据所选择的语言,使用特殊case()或WhichOneof()方法来检查其中一个设置的值(如果有)。
*/
//oneof 里面的值最多出现一个,确定一个后其他的都会被删除掉,不会浪费资源
oneof dataBody{
Person person = 2;
Dog dog = 3;
Cat cat = 4;
}

}

//-------------具体消息-------------
message Person{
optional string name = 1;
optional int32 age = 2;
optional string address = 3;
}

message Dog{
optional string name = 1;
optional int32 age = 2;
}
message Cat{
optional string name = 1;
optional string city = 2;
}

使用编译器编译proto文件,生成MyDataInfo对象

服务端代码

服务端主启动类

public class TestServer {
public static void main(String[] args) {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup,workerGroup).channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new TestServerInitializer());

ChannelFuture channelFuture = serverBootstrap.bind(8899).sync();
channelFuture.channel().closeFuture().sync();
}catch (Exception e){
System.out.println(e.getMessage());
}finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}

初始化器 (Initializer)

public class TestServerInitializer extends ChannelInitializer<SocketChannel> {

@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();

//用于decode前解决半包和粘包问题(利用包头中的包含数组长度来识别半包粘包)
pipeline.addLast(new ProtobufVarint32FrameDecoder());
/*
ProtobufDecoder(MessageLite m) 是解码器,将protobuf构造出来的字节数组,转化成真正的对象
MessageLite(参数) 表示要转换/传输的类的实例

反序列化指定的Probuf字节数组为protobuf类型。
*/
//这里解码器解析的对象是DataInfo.Message 最外层的消息体/类
pipeline.addLast(new ProtobufDecoder(DataInfo.Message.getDefaultInstance()));
//用于在序列化的字节数组前加上一个简单的包头,只包含序列化的字节长度。
pipeline.addLast(new ProtobufVarint32LengthFieldPrepender());
//用于对Probuf类型序列化。
pipeline.addLast(new ProtobufEncoder());
pipeline.addLast(new TestServerHandler());
}
}

自定义处理器 (Handler)

public class TestServerHandler extends SimpleChannelInboundHandler<DataInfo.Message> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, DataInfo.Message msg) throws Exception {
DataInfo.Message.DataType dataType = msg.getDataType();
//根据不同的类型获取到不同的对象
switch (dataType){
case PersonType:
DataInfo.Person person = msg.getPerson();
System.out.println(person.getName());
System.out.println(person.getAge());
System.out.println(person.getAddress());
break;
case DogType:
DataInfo.Dog dog = msg.getDog();
System.out.println(dog.getName());
System.out.println(dog.getAge());
break;
case CatType:
DataInfo.Cat cat = msg.getCat();
System.out.println(cat.getName());
System.out.println(cat.getCity());
break;
default:
break;
}
}
}

客户端代码

客户端主启动类

public class TestClient {
public static void main(String[] args) throws Exception {
EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class)
.handler(new TestClientInitializer());
//与对应的url建立连接通道
ChannelFuture channelFuture = bootstrap.connect("localhost",8899).sync();
channelFuture.channel().closeFuture().sync();
}finally {
eventLoopGroup.shutdownGracefully();
}
}
}

初始化器 (Initializer)

public class TestClientInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();

pipeline.addLast(new ProtobufVarint32FrameDecoder());
/*
ProtobufDecoder 是解码器,将protobuf构造出来的字节数组,转化成真正的对象
MessageLite(参数)表示要转换的类的实例
*/
pipeline.addLast(new ProtobufDecoder(DataInfo.Message.getDefaultInstance()));
pipeline.addLast(new ProtobufVarint32LengthFieldPrepender());
pipeline.addLast(new ProtobufEncoder());
pipeline.addLast(new TestClientHandler());
}
}

自定义处理器 (Handler)

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

}

@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//这里用一个随机数类,0 1 2
int randomInt = new Random().nextInt(3);
DataInfo.Message message = null;
//根据不同的数字生成不同的proto消息类型
if (randomInt == 0){
DataInfo.Person person = DataInfo.Person.newBuilder()
.setName("星空").setAge(18).setAddress("宇宙").build();
message = DataInfo.Message.newBuilder()
.setDataType(DataInfo.Message.DataType.PersonType).setPerson(person).build();
}else if(randomInt == 1){
message = DataInfo.Message.newBuilder().setDataType(DataInfo.Message.DataType.DogType)
.setDog(DataInfo.Dog.newBuilder().setName("阿拉斯加").setAge(2).build()).build();
}else {
message = DataInfo.Message.newBuilder().setDataType(DataInfo.Message.DataType.CatType)
.setCat(DataInfo.Cat.newBuilder().setName("大花猫").setCity("北京").build()).build();
}
ctx.channel().writeAndFlush(message);
}
}