前言
上一篇关于Netty的学习文章,简单实现基于Client-Server的在线聊天室demo。
此篇文章简单带领读者了解下Google Protocol Buffer协议+ Netty,并实现服务端-客户端通过网络传输对象的demo。
Protobuf简介
Protobuf 全称 Protocol Buffers
(又名protobuf),是谷歌用于序列化(串行化)结构化数据的语言无关、平台无关的可扩展机制。翻译自ProtoBuf Github仓库
它是一种轻量的数据交换格式,相比XML和JSON,有更好的表现(更小体积、速度更快)。使用协议定义各种结构化数据的类型、编码方式,使用Protobuf提供的对应平台编译器生成对应的代码(JAVA、C、Python
等)。由于使用二进制编码,因此在网络传输中表现更好。
只看上述文字应该还不明白个中道理,通过下文的实操和代码更容易了解它的作用。
Protobuf-生成Java代码
因为笔者之前捣鼓Nacos安装了Protobuf
编译器,此处略过安装过程。
安装后运行命令protoc
检查。
新建Student.proto
文件,用于生成一个Student
的java类。
Student字段包含:name、age、address
/**
参考文档:https://protobuf.com.cn/programming-guides/proto2/
https://protobuf.com.cn/programming-guides/proto3/
语法:proto2、proto3
2可在3中使用,3不可在2中使用
*/
syntax = "proto2";
// 生成包名
package org.nott.grpc.model;
// option:选项
// optimize_for(文件选项):可以设置为 SPEED(默认)、CODE_SIZE 或 LITE_RUNTIME
option optimize_for = SPEED;
// 可选项 生成java包名,当与package共存,优先级高于package
option java_package = "org.nott.grpc.model";
// 生成java类名
option java_outer_classname = "DataInfo";
/**
message: 消息类型,Protobuf每个message对应生成一个java类
protobuf支持的类型:double、float、int32、int64、bool、string 等等
*/
message Student{
// optional:字段处于两种可能状态之一,已设置、未设置值
optional string name = 1; // 1: 字段编号,您必须为消息定义中的每个字段指定 1 到 536,870,911 之间的一个数字,每个字段的编号唯一
optional int32 age = 2;
optional string address = 3;
}
使用下列命令,生成Java
代码。正确运行的话会出java_outer_classname
对应名称的Java
类
原来的message
消息类型生成的Java
类属于它的内部类。【不要修改生成的文件】
protoc --java_out=你需要生成的地址(后面会拼接proto文件中的java_package) proto文件所在地址
public static final class Student extends
com.google.protobuf.GeneratedMessage implements
// @@protoc_insertion_point(message_implements:org.nott.grpc.model.Student)
StudentOrBuilder {
...
构建Netty
现在对象已经生成完毕,开始构建Netty的客户端-服务端。
创建Netty客户端的SocketChannel Initializer
、Handler
和具体的客户端
。
public class ProtoBufClientInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pip = socketChannel.pipeline();
// Netty 中处理Protobuf数据流的解码器,用Varint32编码规则
pip.addLast(new ProtobufVarint32FrameDecoder());
// 为Protobuf协议的消息头部加上32位长度的整形字段
pip.addLast(new ProtobufVarint32LengthFieldPrepender());
// Protobuf解码器,为DataInfo.Student对象解码
pip.addLast(new ProtobufDecoder(DataInfo.Student.getDefaultInstance()));
// Protobuf编码器
pip.addLast(new ProtobufEncoder());
// 自定义的Handler
pip.addLast(new ProtobufClientHandler());
}
}
// 客户端ChannelInboundHandlerAdapter
public class ProtobufClientHandler extends ChannelInboundHandlerAdapter {
// 连接时发送一个DataInfo.Student信息
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
DataInfo.Student msg = DataInfo.Student.newBuilder().setName("客户端")
.setAge(1).setAddress("从Localhost来").build();
ctx.channel().writeAndFlush(msg);
}
}
// 客户端
public class ProtoBufClient {
public void run() throws Exception {
NioEventLoopGroup boss = null;
try {
boss = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(boss)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.option(ChannelOption.SO_BACKLOG, 128)
.option(ChannelOption.SO_KEEPALIVE, true)
.handler(new ProtoBufClientInitializer())
.bind(9999)
.sync();
// 连接服务端
ChannelFuture channelFuture = bootstrap.connect("localhost", 8888).sync();
channelFuture.channel().closeFuture().sync();
} finally {
boss.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception{
new ProtoBufClient().run();
}
}
创建Netty服务端的SocketChannel Initializer
、Handler
和具体的服务端
。
//服务端ChannelInitializer
public class ProtobufServiceInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pip = socketChannel.pipeline();
// 此处添加的编码解码器与客户端一致,除了最后的自定义处理器
pip.addLast(new ProtobufVarint32FrameDecoder());
pip.addLast(new ProtobufDecoder(DataInfo.Student.getDefaultInstance()));
pip.addLast(new ProtobufVarint32LengthFieldPrepender());
pip.addLast(new ProtobufEncoder());
pip.addLast(new ProtobufSeverHandler());
}
}
// 服务端SimpleChannelInboundHandler
// 只处理DataInfo.Student类型的信息
@Slf4j
public class ProtobufSeverHandler extends SimpleChannelInboundHandler<DataInfo.Student> {
// 读取信息事件接口
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, DataInfo.Student student) throws Exception {
log.info("接收到Student反序列化对象...");
log.info(student.getName());
log.info(student.getAddress());
log.info(student.getAge() + "");
}
}
// 服务端具体实现
public class ProtoBufServer {
public void run() throws Exception{
NioEventLoopGroup boss = null;
NioEventLoopGroup worker = null;
try {
boss = new NioEventLoopGroup();
worker = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
ChannelFuture future = bootstrap.group(boss, worker)
.channel(NioServerSocketChannel.class)
.childOption(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childOption(ChannelOption.TCP_NODELAY, true)
.childOption(ChannelOption.CONNECT_TIMEOUT_MILLIS, 3000)
.childHandler(new ProtobufServiceInitializer())
// 运行在8888端口
.bind(8888)
.sync();
future.channel().closeFuture().sync();
} finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception{
new ProtoBufServer().run();
}
}
运行
我们在上文构建好了Netty的客户端-服务端
,设置了基于Protobuf
协议的编码解码器,按预想情况在服务端运行时,客户端马上运行并且正常链接服务端时,会发送一条DataInfo.Student
的信息,并且服务端马上接收且正常解码,日志打印Student对象
的各个字段内容。
在服务端运行之后运行客户端
客户端正常连接,同时服务端Handler读取并打印Student对象
信息内容
小结
上文简单使用了Protobuf
定义的数据格式生成了Java代码,可以在Netty
框架使用对应协议的处理器,实现基于网络的客户端与服务端对象传输。
由于Protobuf的无关语言,实际上编写.proto
文件,即可生成不同语言的代码,且由客户端和服务端不需要使用同一个语言都可正常序列化。
加上其无关平台,基于协议的特性。无需局限于某个特定框架,可用于多处平台。
篇幅有限,加上笔者水平还有提升空间,无法在此深入展开。
此篇文章已收录进入本人的学习专栏,其他内容可此同步浏览,感兴趣的朋友可以点下关注,赏给作者一个赞,谢谢。