编码和解码
- 数据在网络中的传输都是以二进制字节码数据,在发送时需要编码,接收时需要解码。
- codec由两部分组成,decoder(解码器)和 encoder(编码器)
Netty的编码和解码
Netty提供的编码器:
- StringEncoder
- ObjectEncoder
Netty提供的解码器:
- StringDecoder
- ObjectDecoder
Netty自带的编码解码器,底层使用的时Java的序列化技术,Java的序列化技术效率不高,而且存在以下问题;
- 不能跨语言,Server和Client都必须是Java
- 序列化后体积太大,是二进制编码的5倍多
- 性能太低
为解决这些问题,Google提供了ProtoBuf
ProtoBuf
Google发布的开源项目,全程Goole Protocol Buffers。
是一种高效的结构化数据存储方式,可以用来序列化。很适合做数据存储和RPC的数据交换格式。
使用protoBuf的实例:
- 下载ProtoBuf
- 项目导入protoBuf
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.6.1</version>
</dependency>
- 编写proto文件
Student.proto
syntax="proto3"; // 声明版本
option java_outer_classname="StudentPOJO"; //指定外部类
message Student{
int32 id=1;
string name=2;
}
- 使用protoc.exe生成Java文件
protoc.exe --java_out=. Student.proto
- 编写客户端处理器和服务端处理器
ClientHandler
// 在管道激活时发送一个Student对象
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
// 发送一个student对象到服务器
// Student时StudentPOJO的内部类
StudentPOJO.Student student = StudentPOJO.Student.newBuilder().setId(10).setName("张三").build();
ctx.writeAndFlush(student);
}
ServerHandler
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// 强转即可使用
StudentPOJO.Student student = (StudentPOJO.Student) msg;
System.out.println(student.getId() + student.getName());
}
服务端和客户端分别添加解码器和编码器:
Server:
// 指定解码器,并指定对哪种对象解码
pipeline.addLast(new ProtobufDecoder(StudentPOJO.Student.getDefaultInstance()));
//添加自定义Handler
pipeline.addLast(new ServerHandler());
Client:
// 添加编码器
pipeline.addLast(new ProtobufEncoder());
//添加自定义的Handler
pipeline.addLast(new ClientHandler());
- 先后启动Server、Client
服务器控制台打印:
10张三
成功接收到客户端发送的对象。
虽然成功接收到Student对象,但如果想要发送其他对象则又需要编写proto文件,着实麻烦。
解决办法:
编写proto文件,使用一个message来管理其他的message。在管理的message中,添加一个标识,用于标识发送过来的是哪一个的类的对象。
实例如下:
syntax = "proto3"; // 指定版本
option optimize_for = SPEED; // 加快解析
option java_package = "com.xing.netty.codec2"; //指定包位置
option java_outer_classname = "MyDataInfo"; //外部类名
// protoBuf可以使用message管理其他的message
message MyMessage{
// MyMessage的第一个属性,用data_type来标传的是哪一个枚举类型
DataType data_type = 1;
// 每次只能有一个枚举类型,节省空间
oneof dataBody{
Student student = 2;
Worker worker = 3;
}
}
// 枚举类型
enum DataType{
// enum中编号从0开始
StudentType = 0;
WorkType = 1;
}
message Student{
// Student类的属性
int32 id = 1;
string name = 2;
}
message Worker{
// Worker类的属性
int32 age = 1;
string name = 2;
}
生成MyDataInfo.java文件。
Server添加MyDataInfo的解码器:
// 指定解码器,并指定对哪种对象解码
pipeline.addLast(new ProtobufDecoder(MyDataInfo.MyMessage.getDefaultInstance()));
pipeline.addLast(new ServerHandler());
经过测试可以发送Student和Worker对象。