一、Protobuf多协议消息支持
1、思路一:netty官方给出“利用netty提供的自定义协议方式”
在传递消息时,会用消息的前两几位(官方例子中是前2位)代表消息的类型,比如AB、CD、EF是三种不同的消息
解码器解析时,解析前两位,并根据结果对应不同的消息类型。这种方案需要自己实现自定义解码器(具体可以参考netty的官网示例中有一个例子:
netty-source-java\src\io\netty\example\portunification、
评价:此种解决方案相对来说比较复杂,没有通过protobuf的特性来解决,而是通过netty对自定义协议进行了很好的支持的方案。
2、思路二:也是老师实际工作中经常使用的,扩展性、可读性更好一些:”通过消息的定义方式(在protobuf的IDL文件中语法特性)“
错误的示例:
因为在Server和Client的handler中的范型只能是一种类型,如果按照下面方式定义的IDL,只能声明其中一种消息类型
正确的示例:
原理:在IDL最外层只定义一个消息类型“包含”(通过oneof)所有消息类型(这里的包含指的是属性上的包含,而不是类定义位置上的包含),这里的所有消息类型包括所有可能出现的消息类(客户端向服务器发送/返回的+服务器向客户端发送/返回的)然后通过 枚举 来判断每次消息传递的类型。
Person2.proto
syntax = "proto2";
package com.shengsiyuan.sixthexample.multyMsgType;
option optimize_for = SPEED;
option java_package = "com.mzj.netty.ssy._06.protobuf.netty.multyMsgType";
option java_outer_classname = "MyDataInfo";
message MyMessage{//MyMessage是最外层的消息
//通过枚举区分不同的消息类型
enum DataType{
PersionType = 1;
DogType = 2;
CatType = 3;
}
//枚举类型是required类型,用来标识oneof中的类型
required DataType data_type = 1;
//oneof相当于一个包裹块
//oneof中成员共享内存
//用oneof包裹的属性用来表示其中的对象只能出现一个,要么Person要么Dog要么Cat
//后设置的会顶替前设置的
oneof dataBody{//其中内部包括其他消息,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;
}
其中,消息每次传递都是最外层的MyMessage类型,并且消息中data_type属性是必须字段,根据其枚举的取值对应不同的消息(内部数据)类型。消息的构造者(使用者)需要保证每次发送的消息中枚举类型与oneof中的对象对应,同时保证Person、Dog、Cat在每个消息中只有一个。
oneof官方文档中说明:
3、编译IDL:protoc --java_out=src/main/java/ src/main/resources/protobuf/netty/Person2.proto
4、编写代码
1)TestServer.java
无变化,略
2)TestServerInitializer.java
package com.mzj.netty.ssy._06.protobuf.netty.multyMsgType;
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 TestServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipline = ch.pipeline();
pipline.addLast(new ProtobufVarint32FrameDecoder());
pipline.addLast(new ProtobufDecoder(MyDataInfo.MyMessage.getDefaultInstance()));//修改了消息实例的类型
pipline.addLast(new ProtobufVarint32LengthFieldPrepender());
pipline.addLast(new ProtobufEncoder());
pipline.addLast(new TestServerHandler());
}
}
注意修改了消息实例的类型
3)TestServerHandler.java
package com.mzj.netty.ssy._06.protobuf.netty.multyMsgType;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
//范型类型:服务端与客户端传递的数据对象类型实例,即编解码类型
public class TestServerHandler extends SimpleChannelInboundHandler<MyDataInfo.MyMessage> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, MyDataInfo.MyMessage msg) throws Exception {
MyDataInfo.MyMessage.DataType dataType = msg.getDataType();
if(dataType == MyDataInfo.MyMessage.DataType.PersionType){
MyDataInfo.Person person = msg.getPerson();
System.out.println("[服务端收到]"+person.getName());
System.out.println("[服务端收到]"+person.getAge());
System.out.println("[服务端收到]"+person.getAddress());
}else if (dataType == MyDataInfo.MyMessage.DataType.DogType){
MyDataInfo.Dog dog = msg.getDog();
System.out.println("[服务端收到]"+dog.getName());
System.out.println("[服务端收到]"+dog.getAge());
}else if (dataType == MyDataInfo.MyMessage.DataType.CatType){
MyDataInfo.Cat cat = msg.getCat();
System.out.println("[服务端收到]"+cat.getName());
System.out.println("[服务端收到]"+cat.getCity());
}
MyDataInfo.MyMessage myMessage = null;
//模拟发送不同消息类型
myMessage = MyDataInfo.MyMessage.newBuilder().
setDataType(MyDataInfo.MyMessage.DataType.PersionType).
setPerson(MyDataInfo.Person.newBuilder().
setName("mazhongjia").
setAge(34).
setAddress("beijing").build()).
build();
ctx.channel().writeAndFlush(myMessage);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.channel().close();
}
}
4)TestClient.java
无变化,略
5)TestClientInitializer.java
package com.mzj.netty.ssy._06.protobuf.netty.multyMsgType;
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;
/**
* Channel初始化器
*/
public class TestClientInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipline = ch.pipeline();
pipline.addLast(new ProtobufVarint32FrameDecoder());
pipline.addLast(new ProtobufDecoder(MyDataInfo.MyMessage.getDefaultInstance()));//注意解码消息类型的变化
pipline.addLast(new ProtobufVarint32LengthFieldPrepender());
pipline.addLast(new ProtobufEncoder());
pipline.addLast(new TestClientrHandler());
}
}
修改内容与服务端相同:消息类型
6)TestClientrHandler.java
package com.mzj.netty.ssy._06.protobuf.netty.multyMsgType;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
public class TestClientrHandler extends SimpleChannelInboundHandler<MyDataInfo.MyMessage> {//范型类型的变化
@Override
protected void channelRead0(ChannelHandlerContext ctx, MyDataInfo.MyMessage msg) throws Exception {
MyDataInfo.MyMessage.DataType dataType = msg.getDataType();
if(dataType == MyDataInfo.MyMessage.DataType.PersionType){
MyDataInfo.Person person = msg.getPerson();
System.out.println("[客户端收到]"+person.getName());
System.out.println("[客户端收到]"+person.getAge());
System.out.println("[客户端收到]"+person.getAddress());
}else if (dataType == MyDataInfo.MyMessage.DataType.DogType){
MyDataInfo.Dog dog = msg.getDog();
System.out.println("[客户端收到]"+dog.getName());
System.out.println("[客户端收到]"+dog.getAge());
}else if (dataType == MyDataInfo.MyMessage.DataType.CatType){
MyDataInfo.Cat cat = msg.getCat();
System.out.println("[客户端收到]"+cat.getName());
System.out.println("[客户端收到]"+cat.getCity());
}
}
/**
* 当client连接成功时,由client发起请求
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
MyDataInfo.MyMessage myMessage = null;
//模拟发送不同消息类型
myMessage = MyDataInfo.MyMessage.newBuilder().
setDataType(MyDataInfo.MyMessage.DataType.PersionType).
setPerson(MyDataInfo.Person.newBuilder().
setName("mazhongjia").
setAge(34).
setAddress("beijing").build()).
build();
ctx.channel().writeAndFlush(myMessage);
}
}
netty多协议的评价:
为什么netty的多协议,与springmvc、springboot的url路由处理多协议相比没有这么优雅呢?后者通过不同url的方式清晰的区分不同协议,而前者需要对不同类型的协议进行if...else...判断。其实url路由也是需要dispatch的(Spring mvc中的DispatcherServlet),只不过被封装了、本质上是相同用的。在启动web框架时,会根据注解也好、配置文件也好,进行解析,生成对应url与处理器的映射关系。
同时,netty相比其他web框架更底层,不会提供url路由这种高级框架功能,如果基于netty开发,类似的URL路由完全可以自己实现、自己封装。(netty开发http服务端时,URL路由也需要自己实现)