7.1 编码和解码的基本介绍
-
编写网络应用程序时,因为数据在网络中传输的都是二进制字节码数据,在发送数据时就需要编码,接收数据 时就需要解码
-
codec(编解码器) 的组成部分有两个:decoder(解码器)和 encoder(编码器)。encoder 负责把业务数据转换成字节 码数据,decoder 负责把字节码数据转换成业务数据
7.2 Netty 本身的编码解码的机制和问题分析
-
Netty 自身提供了一些 codec(编解码器)
-
Netty 提供的编码器 StringEncoder,对字符串数据进行编码 ObjectEncoder,对 Java 对象进行编码 ...
-
Netty 提供的解码器 StringDecoder, 对字符串数据进行解码 ObjectDecoder,对 Java 对象进行解码 ...
-
Netty 本身自带的 ObjectDecoder 和 ObjectEncoder 可以用来实现 POJO 对象或各种业务对象的编码和解码 底层使用的仍是 Java 序列化技术 , 而 Java 序列化技术本身效率就不高,存在如下问题 无法跨语言 序列化后的体积太大,是二进制编码的 5 倍多。 序列化性能太低
-
=> 引出 新的解决方案 [Google 的 Protobuf]
7.3 Protobuf
-
Protobuf 基本介绍和使用示意图
-
Protobuf 是 Google 发布的开源项目,全称 Google Protocol Buffers,是一种轻便高效的结构化数据存储格式, 可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或 RPC[远程过程调用 remote procedure call ] 数据交换格式 。 目前很多公司 http+json tcp+protobuf
-
参考文档 : developers.google.com/protocol-bu… 语言指南
-
Protobuf 是以 message 的方式来管理数据的.
-
支持跨平台、跨语言,即[客户端和服务器端可以是不同的语言编写的] (支持目前绝大多数语言,例如 C++、 C#、Java、python 等)
-
高性能,高可靠性
-
使用 protobuf 编译器能自动生成代码,Protobuf 是将类的定义使用.proto 文件进行描述。说明,在 idea 中编 写 .proto 文件时,会自动提示是否下载 .ptotot 编写插件. 可以让语法高亮。
-
然后通过 protoc.exe 编译器根据.proto 自动生成.java 文件
7.4 Protobuf 快速入门实例 编写程序,使用 Protobuf 完成如下功能
-
客户端可以发送一个 Student PoJo 对象到服务器 (通过 Protobuf 编码)
-
服务端能接收 Student PoJo 对象,并显示信息(通过 Protobuf 解码)
7.4.1 相关环境搭建
- 下载编译器 github.com/protocolbuf…
我们下载的是3.6.1的版本
github.com/protocolbuf…
-
解压目录,加入到环境变量中
-
测试是否配置成功
-
编写文件 student.proto
syntax = "proto3"; //版本
option java_outer_classname = "StudentPOJO";//生成的外部类名,同时也是文件名
//protobuf 使用 message 管理数据
//会在 StudentPOJO 外部类生成一个内部类 Student, 他是真正发送的 POJO 对象
message Student {
// Student 类中有 一个属性 名字为 id 类型为 int32(protobuf 类型) 1 表示属性序号,不是值
int32 id = 1;
string name = 2;
}
- 定位到文件夹目录,编译文件
E:\develop\protoc-3.6.1-win32\bin\protoc.exe --java_out=. Student.proto
- pom文件引入 protobuf坐标
<dependencies>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.6.1</version>
</dependency>
</dependencies>
7.4.2 使用potoBuf进行数据传输
NettyServer
import com.atguigu.netty.groupChat.StudentPOJO;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
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 {
NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
NioEventLoopGroup workGroup = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workGroup) //Netty服务端有两个工作组,这里得设置上
.channel(NioServerSocketChannel.class) //当前的通道用 NioServerSocketChanel作为服务器的实现
.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(new ProtobufDecoder(StudentPOJO.Student.getDefaultInstance()));
pipeline.addLast(new NettyServerHandler());
}
});
System.out.println("服务器 is ready...");
ChannelFuture cf = bootstrap.bind(6668).sync();
cf.channel().closeFuture().sync();
}
}
NettyServerHandler
import com.atguigu.netty.groupChat.StudentPOJO;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelPipeline;
import io.netty.util.CharsetUtil;
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("服务端接收了消息");
StudentPOJO.Student student = (StudentPOJO.Student) msg;
System.out.println(student.getId()+" "+student.getName());
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("客户端已连接:"+ctx.channel().remoteAddress());
}
}
NettyClient
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
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.ProtobufEncoder;
public class NettyClient {
public static void main(String[] args) throws InterruptedException {
NioEventLoopGroup group = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap
.group(group)//第一步就设置一个事件循环组
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() { //第二步,就是设置处理器
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new ProtobufEncoder());
pipeline.addLast(new NettyClientHandler());
}
});
ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 6668).sync();
channelFuture.channel().closeFuture().sync();
}
}
NettyClientHandler
import com.atguigu.netty.groupChat.StudentPOJO;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
public class NettyClientHandler extends ChannelInboundHandlerAdapter {
//当通道就绪,就会触发该方法
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
StudentPOJO.Student student = StudentPOJO.Student.newBuilder().setId(1).setName("rexli").build();
System.out.println("已就绪,发送消息给服务端");
ctx.writeAndFlush(student);
}
}
使用protobuf遇到的坑
7.5 Protobuf 快速入门实例 2
-
客户端可以随机发送 Student PoJo/ Worker PoJo 对象到服务器 (通过 Protobuf 编码)
-
服务端能接收 Student PoJo/ Worker PoJo 对象(需要判断是哪种类型),并显示信息(通过 Protobuf 解码)
Student.proto
syntax = "proto3";
option optimize_for = SPEED; // 加快解析
option java_outer_classname="MyDataInfo"; // 外部类名, 文件名
//protobuf 可以使用 message 管理其他的 message
message MyMessage {
//定义一个枚举类型
enum DataType {
StudentType = 0; //在 proto3 要求 enum 的编号从 0 开始
WorkerType = 1;
}
//用 data_type 来标识传的是哪一个枚举类型
DataType data_type = 1; //表示每次枚举类型最多只能出现其中的一个, 节省空间
oneof dataBody {
Student student = 2;
Worker worker = 3;
}
}
message Student {
int32 id = 1;//Student 类的属性
string name = 2; //
}
message Worker {
string name=1;
int32 age=2;
}
下面进行相关代码展示
NettyClient
这个类不需要做改变
public class NettyClient {
public static void main(String[] args) throws InterruptedException {
NioEventLoopGroup group = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap
.group(group)//第一步就设置一个事件循环组
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() { //第二步,就是设置处理器
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new ProtobufEncoder());
pipeline.addLast(new NettyClientHandler());
}
});
ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 6668).sync();
channelFuture.channel().closeFuture().sync();
}
}
NettyClientHandler
在进行发送消息的时候,随机的发送Student对象,或者Worker对象
import com.atguigu.netty.groupChat.StudentPOJO;
import com.sun.nio.sctp.MessageInfo;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.util.Random;
public class NettyClientHandler extends ChannelInboundHandlerAdapter {
//当通道就绪,就会触发该方法
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
int random = new Random().nextInt(3);
MyDataInfo.MyMessage myMessage=null;
if(random==0){
myMessage= MyDataInfo.MyMessage.newBuilder()
.setDataType(MyDataInfo.MyMessage.DataType.StudentType)
.setStudent(MyDataInfo.Student.newBuilder().setId(5).setName("ABC"))
.build();
}else{
myMessage= MyDataInfo.MyMessage.newBuilder()
.setDataType(MyDataInfo.MyMessage.DataType.WorkerType)
.setWorker(MyDataInfo.Worker.newBuilder().setAge(55).setName("ABC"))
.build();
}
ctx.writeAndFlush(myMessage);
}
}
NettyServer
在设置解码器的时候,要换一下类型
MyDataInfo.MyMessage.getDefaultInstance()
package com.atguigu.netty.protobuf02;
import com.atguigu.netty.groupChat.StudentPOJO;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
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 {
NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
NioEventLoopGroup workGroup = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workGroup) //Netty服务端有两个工作组,这里得设置上
.channel(NioServerSocketChannel.class) //当前的通道用 NioServerSocketChanel作为服务器的实现
.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(new ProtobufDecoder(StudentPOJO.Student.getDefaultInstance()));
pipeline.addLast(new ProtobufDecoder(MyDataInfo.MyMessage.getDefaultInstance()));
pipeline.addLast(new NettyServerHandler());
}
});
System.out.println("服务器 is ready...");
ChannelFuture cf = bootstrap.bind(6668).sync();
cf.channel().closeFuture().sync();
}
}
NettyServerHandler
客户端在进行接收时,也要根据消息的类型进行判断, 然后用来展示分别发送什么消息
package com.atguigu.netty.protobuf02;
import com.atguigu.netty.groupChat.StudentPOJO;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("服务端接收了消息");
MyDataInfo.MyMessage myMessage = (MyDataInfo.MyMessage) msg;
if(myMessage.getDataType()==MyDataInfo.MyMessage.DataType.StudentType){
MyDataInfo.Student student = myMessage.getStudent();
System.out.println(student);
}else{
MyDataInfo.Worker worker = myMessage.getWorker();
System.out.println(worker);
}
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("客户端已连接:"+ctx.channel().remoteAddress());
}
}