Protobuf入门
下载及安装Protobuf
官网地址 :需要去 git 下载你所需要的编译器 我在这里使用的是win32的
下载下来之后配置一下环境变量即可
官网给出的关于Protobuf的定义: 协议缓冲区是Google的与语言无关,与平台无关,可扩展的机制,用于对结构化数据进行序列化(例如XML),但更小,更快,更简单。您定义要一次构造数据的方式,然后可以使用生成的特殊源代码轻松地使用各种语言在各种数据流中写入和读取结构化数据。
用来进行RPC的数据传输
自定义的一种协议,以体积更小的方式对数据进行编码解码(序列化与反序列化)
RMI:全称 remote method invocation (远程方法调用) 只能用JAVA语言实现
RPC:客户端和服务器端 底层通过socket 进行数据的双向传递 ,很多RPC框架都是跨语言的
- 定义一个接口说明文件: 描述了对象(结构体)、对象成员、接口方法等一系列消息。
- 通过RPC框架所提供的编译器,将接口说明文件编译成具体语言文件,
- 在客户端与服务器端分别引入RPC编译器生成的文件,即可像调用本地方法一样调用远程方法
什么是Protobuf?
protobuf是Google提供的一个具有高效的协议数据交互格式工具库。
一、protobuf有什么?
Protobuf 提供了C++、java、python语言的支持,提供了windows和linux平台动态编译生成proto文件对应的源文件。proto文件定义了协议数据中的实体结构(message ,field)
关键字message: 代表了实体结构,由多个消息字段(field)组成。
消息字段(field): 包括数据类型、字段名、字段规则、字段唯一标识、默认值
数据类型:常见的原子类型都支持(在FieldDescriptor::kTypeToName中有定义)
字段规则:
required:必须初始化字段,如果没有赋值,在数据序列化时会抛出异常
optional:可选字段,可以不必初始化。
repeated:数据可以重复(相当于java 中的Array或List)
字段唯一标识:序列化和反序列化将会使用到。
默认值:在定义消息字段时可以给出默认值。
Protobuf在Java中的使用
- 创建proto文件,定义消息的实体结构
- 编译proto文件生成对应的源代码(java文件)
- 在java中实现对消息结构的序列化/反序列化
protobuf的缺点:
数据结构可读性差
在IDEA中使用protobuf
编写.proto文件,生成对应的源代码
这里我们使用IDEA 在IDEA在使用.proto文件你需要下载一下相关的插件
步骤: File–>Settings–>Plugins–>
安装完成之后会提醒你重启IDEA
注意在安装插件之前需要先安装golang-protobuf 不然可能会下载失败,报缺失文件
使用protoc 生成对应.proto的源代码
指定要生成的文件地址【命令】
生成出来的文件:注意千万不要对文件进行修改
我第一次生成出来文件的时候 UnusedPrivateParameter 报错
问题出现原因 :protobuf版本过低
解决办法:引入高版本的依赖 compile group: ‘com.google.protobuf’, name: ‘protobuf-java’, version: ‘3.8.0’
Protobuf与Netty的整合
客户端构造一个对象 发送给服务端 服务端获取并打印出数据 再将另外一个对象返回给客户端
proto文件
syntax = "proto2";
package com.mjw.protobuf;
option optimize_for = SPEED;
option java_package = "com.mjw.netty.sixthexample";
option java_outer_classname = "MyDataInfo";
message MyMessage{
enum DataType{
StudentType = 1;
PersonType = 2;
TomcatType = 3;
}
required DataType data_type = 1;
oneof dataBody{
Student student = 2;
Person person = 3;
Tomcat tomcat = 4;
}
}
message Student{
optional string name = 1;
optional int32 age = 2;
optional string address = 3;
}
message Person{
optional string name = 1;
optional int32 age = 2;
}
message Tomcat{
optional string name = 1;
optional int32 age = 2;
}
服务器端
public class TestServer {
public static void main(String[] args) throws Exception {
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();
}finally {
//优雅关闭
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
//initializer
public class TestServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new ProtobufVarint32FrameDecoder());
pipeline.addLast(new ProtobufDecoder(MyDataInfo.MyMessage.getDefaultInstance())); //解码 把protobuf构造出来的字节数组转换成对象
pipeline.addLast(new ProtobufVarint32LengthFieldPrepender());
pipeline.addLast(new ProtobufEncoder());
pipeline.addLast(new TestServerHandler()); //自己的
}
}
//Handler
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.StudentType){
MyDataInfo.Student student = msg.getStudent();
System.out.println("name: "+student.getName());
System.out.println("age: "+student.getAge());
System.out.println("address:" +student.getAddress());
}else if (dataType == MyDataInfo.MyMessage.DataType.PersonType){
MyDataInfo.Person person = msg.getPerson();
System.out.println("name: "+person.getName());
System.out.println("age: "+ person.getAge());
}else {
MyDataInfo.Tomcat tomcat = msg.getTomcat();
System.out.println("name: "+tomcat.getName());
System.out.println("age: "+tomcat.getAge());
}
}
}
客户端
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());
ChannelFuture channelFuture = bootstrap.connect("localhost", 8899).sync();
channelFuture.channel().closeFuture().sync();
}finally {
eventLoopGroup.shutdownGracefully();
}
}
}
public class TestClientInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new ProtobufVarint32FrameDecoder());
pipeline.addLast(new ProtobufDecoder(MyDataInfo.MyMessage.getDefaultInstance())); //解码 把protobuf构造出来的字节数组转换成对象
pipeline.addLast(new ProtobufVarint32LengthFieldPrepender());
pipeline.addLast(new ProtobufEncoder());
pipeline.addLast(new TestClientHandler());
}
}
//客户端构造不同的数据类型发送给服务器端
public class TestClientHandler extends SimpleChannelInboundHandler<MyDataInfo.MyMessage> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, MyDataInfo.MyMessage msg) throws Exception {
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
MyDataInfo.MyMessage myMessage = null;
int nextInt = new Random().nextInt(3);
if (nextInt == 0){
myMessage = MyDataInfo.MyMessage.newBuilder().setDataType(MyDataInfo.MyMessage.DataType.StudentType).
setStudent(MyDataInfo.Student.newBuilder().
setName("张三").setAge(22).setAddress("北京天安门")).build();
}else if (nextInt == 1){
myMessage = MyDataInfo.MyMessage.newBuilder().setDataType(MyDataInfo.MyMessage.DataType.PersonType).
setPerson(MyDataInfo.Person.newBuilder().
setName("李四").setAge(26)).build();
}else {
myMessage = MyDataInfo.MyMessage.newBuilder().setDataType(MyDataInfo.MyMessage.DataType.TomcatType).
setTomcat(MyDataInfo.Tomcat.newBuilder().
setName("王五").setAge(35)).build();
}
ctx.channel().writeAndFlush(myMessage);
}
}
好程序现在完成了 这时候外面就要思考了 :
我们这里针对客户端传过来的不同的数据类型 使用了else if来进行判断,如果有几十个上百个的请求呢?都是这样处理吗?
其实这也是没办法的 因为Netty是一个底层的框架,不像是SpringMVC或者Struts2等框架,有控制器
Spring MVC 控制器 DispatcherServlet
Struts2 控制器 StrutsPrepareAndExecuteFilter
@RequestMapping(value = "/login/{123}")
@Action(value="/hello",results={@Result(name="success",location="/index.jsp")})
其实在我们编写的服务器代码中
`````````````````````````````````````````````````````````````````````````````````````
protected void channelRead0(ChannelHandlerContext ctx, MyDataInfo.MyMessage msg)` throws Exception { `
MyDataInfo.MyMessage.DataType dataType = msg.getDataType(); `
if (dataType == MyDataInfo.MyMessage.DataType.StudentType){ `
MyDataInfo.Student student = msg.getStudent(); `
System.out.println("name: "+student.getName()); `
}else if (dataType == MyDataInfo.MyMessage.DataType.PersonType){ `
MyDataInfo.Person person = msg.getPerson(); `
System.out.println("name: "+person.getName()); `
}else { `
MyDataInfo.Tomcat tomcat = msg.getTomcat(); `
System.out.println("name: "+tomcat.getName()); `
} `
} `
`````````````````````````````````````````````````````````````````````````````````````
这段代码就相当于是SpringMVC中的DispatcherServlet或者Struts2在的StrutsPrepareAndExecuteFilter
让我们来看下SpringMVC处理请求的流程
客户端向服务器发送的所有请求都会先经过这个控制器,然后再分发给不同的Controller
在你启动程序的时候 他会检测每个方法上的注解 判断上面URL映射和每个方法是否对应上,
把对应关系以map映射保存起来,客户端发过来一个请求的时候,到服务器端在缓存里保存的URL进行匹配,
匹配了之后去执行特定的方法,
这种处理模式本质上都是一样的 只不过这些都是框架帮做好了 我们在框架的基础上编写一个个Controller
而Netty是底层的框架,并没有给我们提供路由(就是url)相关的东西,路由的判断都需要我们自己去写
如果你想要和SpringMVC中@RequestMapping有相同实现, 你可以自己在Netty中去实现注解