前言
一般我们在开始使用netty的时候,都习惯性的会用json/fastjson等来进行序列化,这个并没有什么问题,但是如果对性能有非常高的要求,那就需要用到其他的序列化协议了,目前用的比较广泛和性能比较高的就是Protobuf。
下载安装
官方下载链接
下载编译器:protoc-3.20.0-win64.zip,配置编译器到环境变量
官方文档地址
数据类型对比
- repeated:指定字段为集合,对应到java文件里,生成的是List。
- optional:指定字段为可选字段,可以为空,对于optional字段还可以使用 [default] 指定默认值,如果没有指定,则会使用字段类型的默认值。
编辑proto文件
基本使用
syntax="proto3";
option optimize_for=SPEED;//加快解析
//定义protobuf的包名称空间
option java_package = "com.hwdz.compute.entity";
// 消息体名称
option java_outer_classname = "ProtoMsg";
//.....
/*聊天消息*/
message MessageRequest{
uint64 msg_id = 1;
string from = 2;
string to = 3;
uint64 time = 4;
uint32 msg_type = 5;
string content = 6;
string url = 7;
string property = 8;
string from_nick = 9;
optional string json = 10;
}
进阶版-如果根据传递过来数据进行对应的解析,可以这样做
syntax="proto3";
option optimize_for=SPEED;//加快解析
option java_package="com.hwdz.compute.entity"; //指定生成到那个包下
option java_outer_classname="MyDataInfo"; //指定外部类名称
//protobuf 可以使用message 管理其他message
message MyMessage{
//定义一个枚举
enum DataType{
StudentType=0; //在proto3 要求enum编号从0开始
WorkerType=1;
}
DataType data_type=1; //用data_type标识传的是哪一个枚举类型
//oneof表示每次枚举类型最多只能出现定义的message(Student、Worker)的其中一个
oneof dataBody{
Student student =2;
Worker worker =3;
}
}
message Student{
int32 id=1; //Student类的属性
string name =2;
}
message Worker{
string name=1; //Student类的属性
int32 age =2;
}
maven依赖
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.20.0</version>
</dependency>
编写服务端代码:
这里只展示核心代码,其他的可以参考专栏下netty搭建代码
首先是针对数据的编解码器和防止拆包粘包的编解码器,protobuf在高并发下是存在粘包拆包的现象
//防止粘包拆包
pipeline.addLast(new ProtobufVarint32FrameDecoder());
pipeline.addLast("decoder", new ProtobufDecoder(MyDataInfo.MyMessage.getDefaultInstance()));
//防止粘包拆包
pipeline.addLast(new ProtobufVarint32LengthFieldPrepender());
pipeline.addLast("encoder", new ProtobufEncoder());
handler处理器
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
//根据dataType显示不同信息
MyDataInfo.MyMessage myMessage = (MyDataInfo.MyMessage) msg;
MyDataInfo.MyMessage.DataType dataType = myMessage.getDataType();
if (dataType == MyDataInfo.MyMessage.DataType.StudentType) {
MyDataInfo.Student student = myMessage.getStudent();
System.out.println("学生id="+student.getId()+"学生姓名="+student.getName());
} else if (dataType == MyDataInfo.MyMessage.DataType.WorkerType) {
MyDataInfo.Worker worker = myMessage.getWorker();
System.out.println("工人年龄="+worker.getAge()+"工人姓名="+worker.getName());
} else {
System.out.println("传输的类型不正确");
}
}
编写客户端代码:
MyDataInfo.MyMessage myMessage = MyDataInfo.MyMessage.newBuilder().setDataType(MyDataInfo.MyMessage.DataType.StudentType)
.setStudent(MyDataInfo.Student.newBuilder().setId(5).setName("玉麒麟").build()).build();
tx.writeAndFlush(myMessage);
注意一点:记得改泛型,如果你用的是SimpleChannelInboundHandler
public class NettyClientHandler extends SimpleChannelInboundHandler<MyDataInfo.MyMessage> {
当然还有心跳消息,之前用的是字符串ping,现在换编解码器了之后会报错,可以改一下这个实体类,加一个心跳的消息类型。