17-跨语言调用 Google ProtoBuf
编码和解码的基本介绍
- 编写网络应用程序时, 因为数据在网络中传输的都是二进制字节码数据, 在发送数据时就需要编码, 接收数据时就需要解码
- codec(编解码器) 的组成部分有两个 : decoder(解码器)和encoder(编码器). encoder负责把业务数据转换成字节码数据, decoder负责把字节码数据转换成业务数据
Netty 本身的编解码的机制和问题分析
- Netty自身提供了一些codec(编解码器)
- Netty提供的编码器
- StringEncoder, 对字符串数据进行编码
- ObjectEncoder, 对Java对象进行编码
- ......
- Netty提供的解码器
- StringDecoder: 对字符串数据进行解码
- ObjectDecoder: 对Java对象进行解码
- ......
- Netty本身自带的ObjectDecoder和ObjectEncoder可以用来实现Pojo对象或各种业务对象的编码和解码,底层使用的依然是Java序列化技术, 而Java序列化技术本身效率就不高, 存在如下问题
- 无法跨语言
- 序列化后的体积太大, 是二进制编码的5倍多
- 序列化性能太低
- => 引出新的解决方案[Google 的 ProtoBuf]
Protobuf
- Protobuf基本介绍和使用示意图
- Protobuf是Google发布的开源项目, 全称 Google Protocol Buffers ,是一种 轻便高效的结构化数据存储格式,可以用于结构化数据串行化, 或者说序列化, 它很适合做数据存储或者RPC[远程过程调用]数据交换格式
- 目前很多公司 http + JSON -> tcp + protobuf
- 参考文档: https://developers.google.com/protocol-buffers/docs/proto 语言指南
- Protobuf是以message的方式来管理数据的
- 支持跨平台, 跨语言, 即[客户端和服务器端可以是不同的语言编写的] (支持目前绝大多数语言, 例如C++, C#, Java, Python等)
- 高性能, 高可靠性
- 使用Protobuf编译器能自动生成代码, Protobuf是将类的定义使用.proto文件进行描述, 说明, 在IDEA中编写.proto文件时, 会自动提示是否下载 .proto编写插件, 可以让语法高亮
- 然后通过protoc.exe编译器根据.proto自动生成.java文件
- Protobuf使用示意图
Protobuf快速入门案例
编写程序, 使用Protobuf完成如下功能
- 客户端可以发送一个Student POJO对象到服务器(通过Protobuf编码)
- 服务器能接受Student POJO对象, 并显示信息(通过Protobuf解码)
引入Protobuf的依赖
<!-- https://mvnrepository.com/artifact/com.google.protobuf/protobuf-java --> <dependency> <groupId>com.google.protobuf</groupId> <artifactId>protobuf-java</artifactId> <version>3.6.1</version> </dependency>
数据类型
特殊字段
定义其他复杂类型参考:https://blog.csdn.net/lijingjingchn/article/details/89466437
软件
新建Student.proto
syntax = "proto3"; // 版本 option java_outer_classname = "StudentPOJO"; // Java的外部类名 // protobuf 使用message 管理数据 message Student { // 会在 StudentPOJO外部类中生成一个内部类 Student, 它是真正发送的POJO对象 /** 表示 在Student类中有一个属性名字为 id , 类型为int32(Protobuf类型) 1表示序号, 不是值 */ int32 id = 1; string name = 2; }
根据.proto文件生成Java文件
将写好的文件放入bin文件夹中
在当前位置启动cmd
执行编译
protoc.exe --java_out=. Student.proto
将生成的文件拷贝到项目中
生成后的文件
不粘贴了[太大了],自己看一下吧
新建NettyServer
package com.dance.netty.netty.protobuf; import com.dance.netty.netty.protobuf.pojo.StudentPOJO; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.protobuf.ProtobufDecoder; public class NettyServer { public static void main(String[] args) throws InterruptedException { EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.group(bossGroup, workerGroup) //设置两个线程组 .channel(NioServerSocketChannel.class) // 使用NioServerSocketChannel作为服务器的通道实现 .option(ChannelOption.SO_BACKLOG, 128) // 设置线程队列等待连接个数 .childOption(ChannelOption.SO_KEEPALIVE, true) // 设置保持活动连接状态 .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel socketChannel) throws Exception { ChannelPipeline pipeline = socketChannel.pipeline(); // 加入protobuf的解码器 指定解码默认实例 pipeline.addLast(new ProtobufDecoder(StudentPOJO.Student.getDefaultInstance())); pipeline.addLast(new NettyServerHandler()); } }); System.out.println("server is ready......"); ChannelFuture channelFuture = serverBootstrap.bind(6668).sync(); channelFuture.channel().closeFuture().sync(); } catch (InterruptedException e) { e.printStackTrace(); } finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } }
新建NettyServerhandler
package com.dance.netty.netty.protobuf; import com.dance.netty.netty.protobuf.pojo.StudentPOJO; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import java.nio.charset.StandardCharsets; public class NettyServerHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { // 读取从客户端发送的额StudentPOJO.student StudentPOJO.Student student = (StudentPOJO.Student) msg; System.out.println("客户端发送的数据:" + student.getId() + " " + student.getName()); } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { ctx.writeAndFlush(Unpooled.copiedBuffer("Hello 客户端", StandardCharsets.UTF_8)); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { ctx.close(); cause.printStackTrace(); } }
新建NettyClient
package com.dance.netty.netty.protobuf; import io.netty.bootstrap.Bootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.protobuf.ProtobufDecoder; import io.netty.handler.codec.protobuf.ProtobufEncoder; public class NettyClient { public static void main(String[] args) throws InterruptedException { EventLoopGroup eventExecutors = new NioEventLoopGroup(); try { Bootstrap bootstrap = new Bootstrap(); bootstrap.group(eventExecutors) .channel(NioSocketChannel.class) .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel socketChannel) throws Exception { ChannelPipeline pipeline = socketChannel.pipeline(); // 加入处理Protobuf的编码器 pipeline.addLast(new ProtobufEncoder()); pipeline.addLast(new NettyClientHandler()); } }); System.out.println("客户端 ok !"); ChannelFuture sync = bootstrap.connect("127.0.0.1", 6668).sync(); sync.channel().closeFuture().sync(); } catch (InterruptedException e) { e.printStackTrace(); } finally { eventExecutors.shutdownGracefully(); } } }
新建NettyClientHandler
package com.dance.netty.netty.protobuf; import com.dance.netty.netty.protobuf.pojo.StudentPOJO; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import java.nio.charset.StandardCharsets; public class NettyClientHandler extends ChannelInboundHandlerAdapter { @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { // 发送一个Student对象到服务器 StudentPOJO.Student dance = StudentPOJO.Student.newBuilder().setId(4).setName("dance").build(); ctx.writeAndFlush(dance); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ByteBuf byteBuf = (ByteBuf) msg; System.out.println("服务器回复的消息: " + byteBuf.toString(StandardCharsets.UTF_8)); System.out.println("服务器地址: " + ctx.channel().remoteAddress()); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { ctx.close(); cause.printStackTrace(); } }
测试
启动Server
启动Client
server is ready...... 客户端发送的数据:4 dance 客户端 ok ! 服务器回复的消息: Hello 客户端 服务器地址: /127.0.0.1:6668
使用SimpleChannelInBoundHandler定义泛型
package com.dance.netty.netty.protobuf; import com.dance.netty.netty.protobuf.pojo.StudentPOJO; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.SimpleChannelInboundHandler; import java.nio.charset.StandardCharsets; //public class NettyServerHandler extends ChannelInboundHandlerAdapter { public class NettyServerHandler extends SimpleChannelInboundHandler<StudentPOJO.Student> { @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { ctx.writeAndFlush(Unpooled.copiedBuffer("Hello 客户端", StandardCharsets.UTF_8)); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { ctx.close(); cause.printStackTrace(); } @Override protected void channelRead0(ChannelHandlerContext ctx, StudentPOJO.Student msg) throws Exception { System.out.println("客户端发送的数据:" + msg.getId() + " " + msg.getName()); } }
Protobuf快速入门实例2
需求
- 编写程序, 使用Protobuf完成如下功能
- 客户端可以随机发送studentPOJO/workerPOJO对象到服务器(通过Protobuf编码)
- 服务器接收StudentPOJO/WorkerPOJO对象, (需要判断是那种类型), 并显示信息(通过Protobuf解码)
- 具体 看老师演示(show time)
编写proto文件
syntax = "proto3"; // 版本 option optimize_for = SPEED; // 加快解析 option java_package = "com.dance.netty.netty.protobuf2.pojo"; // 指定包 option java_outer_classname = "MyDataInfo"; // Java的外部类名 message MyMessage{ // 定义一个枚举类型 enum DataType { StudentType = 0; WorkerType = 1; } // 用DataType来标识传的是哪个枚举类型 DataType dataType = 1; // 表示每次枚举类型最多只能出现其中的一个, 节省空间 oneof dataBody{ Student student = 2; Worker worker = 3; } } message Student { int32 id = 1; string name = 2; } message Worker { int32 id = 1; string name = 2; }
生成java类型并拷贝到项目
好大~
根据之前的案例1修改
修改NettyClientHandler
@Override public void channelActive(ChannelHandlerContext ctx) throws Exception { // 随机发送Student或者Worker int i = new Random().nextInt(3); MyDataInfo.MyMessage myMessage = null; if(0 == i){ myMessage = MyDataInfo.MyMessage.newBuilder() .setDataType(MyDataInfo.MyMessage.DataType.StudentType) .setStudent(MyDataInfo.Student.newBuilder().setId(1).setName("flower").build()).build(); }else{ myMessage = MyDataInfo.MyMessage.newBuilder() .setDataType(MyDataInfo.MyMessage.DataType.StudentType) .setWorker(MyDataInfo.Worker.newBuilder().setId(2).setName("dance").build()).build(); } ctx.writeAndFlush(myMessage); }
修改NettyServer
pipeline.addLast(new ProtobufDecoder(MyDataInfo.MyMessage.getDefaultInstance()));
修改NettyServerHandler
public class NettyServerHandler extends SimpleChannelInboundHandler<MyDataInfo.MyMessage> { @Override protected void channelRead0(ChannelHandlerContext ctx, MyDataInfo.MyMessage msg) throws Exception { if(msg.getDataType() == MyDataInfo.MyMessage.DataType.StudentType){ System.out.println("客户端发送的数据:" + msg.getStudent().getId() + " " + msg.getStudent().getName()); }else if(msg.getDataType() == MyDataInfo.MyMessage.DataType.WorkerType){ System.out.println("客户端发送的数据:" + msg.getWorker().getId() + " " + msg.getWorker().getName()); } }
测试
client
客户端 ok !
服务器回复的消息: Hello 客户端
服务器地址: /127.0.0.1:6668
Server
server is ready...... 客户端发送的数据:2 dance 客户端发送的数据:2 dance 客户端发送的数据:1 flower
若有收获,就点个赞吧