netty 详解(五)netty 使用 protobuf 序列化
1、编码和解码
2、Google Protobuf 介绍
3、案例--netty 使用 protobuf 序列化
3.1、编写 .proto 文件
3.2、自动生成代码
3.3、netty 通过 Protobuf 传递消息
4、netty 使用 protobuf 传输多种类型对象
1、编码和解码 <--返回目录
编写网络应用程序时,因为数据在网络中传输的都是二进制字节码数据,在发送数据时就需要编码,接收数据时就需要解码。
codec(编解码器) 的组成部分有两个:decoder(解码器) 和 encoder(编码器)。encoder 负责把业务数据转换成字节码数据,decoder 负责把字节码数据转换成业务数据。
netty 提供的 StringEncoder/StringDecoder 是对字符串数据进行编解码;ObjectEncoder/ObjectDecoder 是对 Java 对象进行编解码。
ObjectEncoder/ObjectDecoder 可以用来实现 POJO 对象或各种业务对象的编解码,底层使用的是 Java 序列化技术,而 Java 序列化技术本身效率不高,并存在如下问题:
- 无法跨语言
- 序列化后的体积太大,是二进制编码的 5 倍多
- 序列化性能低
所以,引出新的解决方案:Google 的 Protobuf。
2、Google Protobuf 介绍 <--返回目录
参考文档:https://developers.google.com/protocol-buffers/docs/proto
Protobuf 是 Google 发布的开源项目,全称 Google Protocol Buffers,是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化或者说序列化。它很适合做数据存储或 RPC 数据交换格式。
支持跨平台/跨语言,支持绝大数语言,例如 c++,c#, Java, python 等。‘
Protobuf 自动生成代码:
- 使用 Protobuf 编译器自动生成代码,Protobuf 是将类的定义使用 .proto 文件进行描述。在 IDEA 中编写 .proto 文件时,会自动提示是否下载 .protot 编写插件(protobuf support 插件),可以让语法高亮。
- 然后通过 protoc.exe 编译器根据 .proto 自动生成 .java 文件
自动生成 .java 文件 参考:(注意 .proto 文件放在 src/main/proto 目录下)
3、案例--netty 使用 protobuf 序列化 <--返回目录
需求:
1)客户端可以发送一个 User POJO 对象到服务器(通过 protobuf 编码);
2)服务端能接收 User POJO 对象,并显示信息(通过 protobuf 解码);
3.1、编写 .proto 文件 <--返回目录
src/main/proto/User.proto
syntax = "proto3"; option java_package = "com.oy.protobuf"; option java_outer_classname = "UserModel";// 生成的外部类名,同时也是文件名 // protobuf 使用 message 管理数据 message User { // 会在 UserModel 里面生成一个内部类 User,即是真正发送的 POJO 对象 int32 id = 1; string name = 2; }
3.2、自动生成代码 <--返回目录
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>netty-helloworld</artifactId> <version>1.0-SNAPSHOT</version> <properties> <java.version>1.8</java.version> <grpc.version>1.14.0</grpc.version> <protobuf.version>3.3.0</protobuf.version> </properties> <dependencies> <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.22.Final</version> </dependency> <dependency> <groupId>io.grpc</groupId> <artifactId>grpc-netty</artifactId> <version>${grpc.version}</version> </dependency> <dependency> <groupId>io.grpc</groupId> <artifactId>grpc-protobuf</artifactId> <version>${grpc.version}</version> </dependency> <dependency> <groupId>io.grpc</groupId> <artifactId>grpc-stub</artifactId> <version>${grpc.version}</version> </dependency> <!--<dependency> <groupId>com.google.protobuf</groupId> <artifactId>protobuf-java</artifactId> <version>${protobuf.version}</version> </dependency>--> </dependencies> <build> <extensions> <extension> <groupId>kr.motd.maven</groupId> <artifactId>os-maven-plugin</artifactId> <version>1.5.0.Final</version> </extension> </extensions> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>8</source> <target>8</target> </configuration> </plugin> <plugin> <groupId>org.xolstice.maven.plugins</groupId> <artifactId>protobuf-maven-plugin</artifactId> <version>0.5.1</version> <configuration> <protocArtifact>com.google.protobuf:protoc:3.5.1:exe:${os.detected.classifier}</protocArtifact> <pluginId>grpc-java</pluginId> <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.14.0:exe:${os.detected.classifier}</pluginArtifact> </configuration> <executions> <execution> <goals> <goal>compile</goal> <goal>compile-custom</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project>
执行 mvn clean compile 命令
3.3、netty 通过 Protobuf 传递消息 <--返回目录
Server
package com.oy.protobuf; import io.netty.bootstrap.ServerBootstrap; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; 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; import io.netty.handler.codec.protobuf.ProtobufEncoder; import io.netty.util.CharsetUtil; import java.net.InetSocketAddress; import java.util.Date; public class Server { private int port; public static void main(String[] args) { new Server(8003).start(); } public Server(int port) { this.port = port; } public void start() { EventLoopGroup boss = new NioEventLoopGroup(1); EventLoopGroup work = new NioEventLoopGroup(); try { ServerBootstrap server = new ServerBootstrap() .group(boss, work) .channel(NioServerSocketChannel.class) //.localAddress(new InetSocketAddress(port)) //.option(ChannelOption.SO_BACKLOG, 128) //.childOption(ChannelOption.SO_KEEPALIVE, true) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast("encoder", new ProtobufEncoder()); // protobuf 编码器 // 需要指定要对哪种对象进行解码 pipeline.addLast("decoder", new ProtobufDecoder(UserModel.User.getDefaultInstance())); pipeline.addLast(new NettyServerHandler()); } }); // 绑定端口 ChannelFuture future = server.bind(port).sync(); System.out.println("server started and listen " + port); future.channel().closeFuture().sync(); } catch (Exception e) { e.printStackTrace(); } finally { boss.shutdownGracefully(); work.shutdownGracefully(); } } public static class NettyServerHandler extends ChannelInboundHandlerAdapter { @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { System.out.println("HelloWorldServerHandler active"); } @Override public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exception { System.out.println("server channelRead..."); // 读取客户端发送的数据 UserMOdel.User UserModel.User user = (UserModel.User) msg; System.out.println("客户端发送的数据: " + user.getId() + "--" + user.getName()); } /** * 数据读取完毕 */ @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { UserModel.User user = UserModel.User.newBuilder().setId(20).setName("服务器").build(); ctx.writeAndFlush(user); } /** * 处理异常,关闭通道 */ @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { ctx.channel().close(); } } }
Client
package com.oy.protobuf; import com.oy.helloworld.NettyClient; import io.netty.bootstrap.Bootstrap; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.*; 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; import io.netty.util.CharsetUtil; public class Client { private static final String HOST = "127.0.0.1"; private static final int PORT = 8003; public static void main(String[] args) { new Client().start(HOST, PORT); } public void start(String host, int port) { EventLoopGroup group = new NioEventLoopGroup(); try { Bootstrap client = new Bootstrap() .group(group) .channel(NioSocketChannel.class) .option(ChannelOption.TCP_NODELAY, true) .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast("encoder", new ProtobufEncoder()); // protobuf 编码器 // 需要指定要对哪种对象进行解码 pipeline.addLast("decoder", new ProtobufDecoder(UserModel.User.getDefaultInstance())); pipeline.addLast(); pipeline.addLast(new NettyClientHandler()); } }); ChannelFuture future = client.connect(host, port).sync(); future.channel().closeFuture().sync(); } catch (Exception e) { e.printStackTrace(); } finally { group.shutdownGracefully(); } } public static class NettyClientHandler extends ChannelInboundHandlerAdapter { /** * 通道就绪触发该方法 */ @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { System.out.println("HelloWorldClientHandler Active"); // 发送 User POJO 对象到服务器 UserModel.User user = UserModel.User.newBuilder().setId(10).setName("客户端张三").build(); ctx.writeAndFlush(user); } /** * 当通道有读取事件时触发该方法 */ @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { // 读取服务器发送的数据 UserMOdel.User UserModel.User user = (UserModel.User) msg; System.out.println("收到服务器响应: " + user.getId() + "--" + user.getName()); } } }
启动服务端和客户端程序:
4、netty 使用 protobuf 传输多种类型对象 <--返回目录
MyDataInfo.proto
- 使用 message 管理其他的 message
- 定义的 MyMessage 的第一个参数是枚举类型,标识传递的是 Student 还是 Teacher;第二个参数是 Student 或 Teacher 中的一个;
syntax = "proto3"; option optimize_for = SPEED; // 加快解析 option java_package = "com.oy.protobuf2"; option java_outer_classname = "MyDataInfo";// 生成的外部类名,同时也是文件名 // protobuf 可以使用 message 管理其他的 message message MyMessage { // 定义一个枚举类型 enum DataType { teacherType = 0; studentType = 1; } // 用 data_type 来标识传的是哪一个枚举类型 DataType data_type = 1; // 表示每次枚举类型最多只能出现其中一个,节省空间 oneof dataBody { Teacher teacher = 2; Student student = 3; } } // protobuf 使用 message 管理数据 message Teacher { int32 id = 1; string name = 2; } message Student { int32 id = 1; string name = 2; }
Server
package com.oy.protobuf2; 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; import io.netty.handler.codec.protobuf.ProtobufEncoder; public class Server { private int port; public static void main(String[] args) { new Server(8004).start(); } public Server(int port) { this.port = port; } public void start() { EventLoopGroup boss = new NioEventLoopGroup(1); EventLoopGroup work = new NioEventLoopGroup(); try { ServerBootstrap server = new ServerBootstrap() .group(boss, work) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast("encoder", new ProtobufEncoder()); // protobuf 编码器 // 需要指定要对哪种对象进行解码 pipeline.addLast("decoder", new ProtobufDecoder(MyDataInfo.MyMessage.getDefaultInstance())); pipeline.addLast(new NettyServerHandler()); } }); // 绑定端口 ChannelFuture future = server.bind(port).sync(); System.out.println("server started and listen " + port); future.channel().closeFuture().sync(); } catch (Exception e) { e.printStackTrace(); } finally { boss.shutdownGracefully(); work.shutdownGracefully(); } } public static class NettyServerHandler extends ChannelInboundHandlerAdapter { @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { System.out.println("HelloWorldServerHandler active"); } @Override public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exception { System.out.println("server channelRead..."); // 读取客户端发送的数据 Student POJO MyDataInfo.MyMessage myMessage = (MyDataInfo.MyMessage) msg; System.out.println("客户端发送的数据: " + myMessage.getDataType() + "--" + myMessage.getStudent().getId() + "--" + myMessage.getStudent().getName()); } /** * 数据读取完毕 */ @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { // 服务端返回 Teacher POJO MyDataInfo.Teacher teacher = MyDataInfo.Teacher.newBuilder().setId(222).setName("老师").build(); MyDataInfo.MyMessage myMessage = MyDataInfo.MyMessage.newBuilder() .setDataType(MyDataInfo.MyMessage.DataType.teacherType).setTeacher(teacher) .build(); ctx.writeAndFlush(myMessage); } /** * 处理异常,关闭通道 */ @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { ctx.channel().close(); } } }
Client
package com.oy.protobuf2; import io.netty.bootstrap.Bootstrap; import io.netty.channel.*; 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 Client { private static final String HOST = "127.0.0.1"; private static final int PORT = 8004; public static void main(String[] args) { new Client().start(HOST, PORT); } public void start(String host, int port) { EventLoopGroup group = new NioEventLoopGroup(); try { Bootstrap client = new Bootstrap() .group(group) .channel(NioSocketChannel.class) .option(ChannelOption.TCP_NODELAY, true) .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast("encoder", new ProtobufEncoder()); // protobuf 编码器 // 需要指定要对哪种对象进行解码 pipeline.addLast("decoder", new ProtobufDecoder(MyDataInfo.MyMessage.getDefaultInstance())); pipeline.addLast(); pipeline.addLast(new NettyClientHandler()); } }); ChannelFuture future = client.connect(host, port).sync(); future.channel().closeFuture().sync(); } catch (Exception e) { e.printStackTrace(); } finally { group.shutdownGracefully(); } } public static class NettyClientHandler extends ChannelInboundHandlerAdapter { /** * 通道就绪触发该方法 */ @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { System.out.println("HelloWorldClientHandler Active"); // 发送 Student POJO 对象到服务器 MyDataInfo.Student student = MyDataInfo.Student.newBuilder().setId(111).setName("学生").build(); MyDataInfo.MyMessage myMessage = MyDataInfo.MyMessage.newBuilder() .setDataType(MyDataInfo.MyMessage.DataType.studentType).setStudent(student) .build(); ctx.writeAndFlush(myMessage); } /** * 当通道有读取事件时触发该方法 */ @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { // 读取服务器发送的数据,服务器发送的是 Teacher POJO MyDataInfo.MyMessage myMessage = (MyDataInfo.MyMessage) msg; System.out.println("服务器返回的数据: " + myMessage.getDataType() + "--" + myMessage.getTeacher().getId() + "--" + myMessage.getTeacher().getName()); } } }
测试:客户端给服务器发送的是 Student POJO,服务器返回的是 Teacher POJO
---
posted on 2020-05-04 04:35 wenbin_ouyang 阅读(7236) 评论(0) 编辑 收藏 举报