编码和解码的基本介绍
- 编写网络应用程序时,因为数据在网络中传输的都是二进制字节码数据,在发送数据时就需要编码,接收数据 时就需要解码
- codec(编解码器) 的组成部分有两个:decoder(解码器)和 encoder(编码器)。encoder 负责把业务数据转换成字节 码数据,decoder 负责把字节码数据转换成业务数据
Netty 本身的编码解码的机制和问题分析
-
Netty 自身提供了一些 codec(编解码器)
-
Netty 提供的编码器
-
StringEncoder,对字符串数据进行编码
-
ObjectEncoder,对 Java 对象进行编码
-
…
-
-
Netty 提供的解码器
- StringDecoder, 对字符串数据进行解码
- ObjectDecoder,对 Java 对象进行解码
- …
-
Netty 本身自带的 ObjectDecoder 和 ObjectEncoder 可以用来实现 POJO 对象或各种业务对象的编码和解码,尚硅谷 Netty 核心技术及源码剖析 底层使用的仍是 Java 序列化技术 , 而 Java 序列化技术本身效率就不高,存在如下问题
- 无法跨语言
- 序列化后的体积太大,是二进制编码的 5 倍多
- 序列化性能太低
-
引出 新的解决方案 [Google 的 Protobuf]
Protobuf
- Protobuf 是 Google 发布的开源项目,全称 Google Protocol Buffers,是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或 RPC[远程过程调用 remote procedure call ] 数据交换格式
- 目前很多公司 由http+json 转为了 tcp+protobuf
- Protobuf 是以 message 的方式来管理数据的.
- 支持跨平台、跨语言,即[客户端和服务器端可以是不同的语言编写的] (支持目前绝大多数语言,例如 C++、 C#、Java、python 等)
- 高性能,高可靠性
- 使用 protobuf 编译器能自动生成代码,Protobuf 是将类的定义使用.proto 文件进行描述。说明,在 idea 中编 写 .proto 文件时,会自动提示是否下载 .ptotot 编写插件. 可以让语法高亮。
- 然后通过 protoc.exe 编译器根据.proto 自动生成.java 文件
- protobuf 使用示意图
参考文档 : https://developers.google.com/protocol-buffers/docs/proto
单个pojo
- 客户端可以发送一个 Student PoJo 对象到服务器 (通过 Protobuf 编码)
- 服务端能接收 Student PoJo 对象,并显示信息(通过 Protobuf 解码)
server
public class Server {
public static void main(String[] args) throws IOException {
//创建BossGroup和WorkerGroup
//bossGroup和workerGroup含有的子线程的个数,默认为cpu核数*2
EventLoopGroup bossGroup=new NioEventLoopGroup(5);
EventLoopGroup workerGroup=new NioEventLoopGroup();
//创建服务器端的启动对象,配置参数
ServerBootstrap bootstrap=new ServerBootstrap();
try {
//使用链式编程来进行设置
bootstrap.group(bossGroup,workerGroup) //设置两个线程
.channel(NioServerSocketChannel.class) //使用NioServerSocketChannel,作为服务器的通道实现
.option(ChannelOption.SO_BACKLOG,128) //设置线程队列里连接个数
.childOption(ChannelOption.SO_KEEPALIVE,true)//设置保持连接状态
.childHandler(new ChannelInitializer<SocketChannel>() { //handler对应的是boosGruop,childHandler对应的是workerGroup
//给pipeline设置处理器
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
//加入解码器
//指定对哪种对象进行解码
pipeline.addLast("decoder",new ProtobufDecoder(StudentPOJO.Student.getDefaultInstance()));
pipeline.addLast(new ServerHandler());
}
}); //给我们的workerGroup的EventLoop对应的管道设置处理器
System.out.println("服务器 is ready ...");
//绑定一个端口并且同步,生成一个ChannelFuture对象
ChannelFuture cf = bootstrap.bind(6668).sync();
//给 cf 注册监听器,监控我们关心的事件
cf.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if(future.isSuccess()){
System.out.println("监听端口成功");
}else{
System.out.println("监听端口失败");
}
}
});
//对关闭通道进行监听
cf.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
serverHandler
public class ServerHandler extends SimpleChannelInboundHandler<StudentPOJO.Student> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, StudentPOJO.Student msg) throws Exception {
System.out.println("客户端发送的数据:"+msg.getId()+":"+msg.getName());
}
/**
* @Description 数据读取完毕
* @date 2020/7/23 10:46
* @param ctx
* @return void
*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
//将数据写入到缓冲并刷新
//一般来讲,我们队发送的数据进行编码
ctx.writeAndFlush(Unpooled.copiedBuffer(new Date().toString()+":"+"hello,客户端",CharsetUtil.UTF_8));
}
/**
* @Description 处理异常,一般是需要管理通道
* @date 2020/7/23 11:13
* @param ctx
* @param cause
* @return void
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.channel().close();
}
}
client
public class Client {
public static void main(String[] args) {
//客户端需要一个事件循环组
EventLoopGroup eventExecutors=new NioEventLoopGroup();
try {
//创建客户端启动对象
Bootstrap bootstrap=new Bootstrap();
//设置相关参数
bootstrap.group(eventExecutors) //设置线程组
.channel(NioSocketChannel.class) //设置客户端通道的实现类(反射)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//加入编码器
pipeline.addLast("encoder",new ProtobufEncoder());
pipeline.addLast(new ClientHandler()); //加入自己的处理器
}
});
System.out.println("客户端 ok...");
//启动客户端去连接服务器端
//关于ChannelFuture要分析,涉及到netty的异步模型
ChannelFuture channelFuture = bootstrap.connect(new InetSocketAddress("127.0.0.1", 6668)).sync();
//关闭通道进行监听
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
eventExecutors.shutdownGracefully();
}
}
}
clientHandler
public class ClientHandler extends ChannelInboundHandlerAdapter {
/**
* @Description 当通道就绪时会触发该方法
* @date 2020/7/23 11:27
* @param ctx
* @return void
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
StudentPOJO.Student student = StudentPOJO.Student.newBuilder().setId(4).setName("重庆刘德华,香港徐大虾").build();
ctx.writeAndFlush(student);
}
/**
* @Description 当通道有读取事件时,会触发
* @date 2020/7/23 11:29
* @param ctx
* @param msg
* @return void
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf=(ByteBuf)msg;
System.out.println("服务器回复的消息:"+((ByteBuf) msg).toString(CharsetUtil.UTF_8));
System.out.println("服务器的地址:"+ctx.channel().remoteAddress());
}
}
Student.proto
syntax= "proto3"; //版本
option java_outer_classname="StudentPOJO"; //生成的外部类名
message Student{
int32 id=1;
string name=2;
}
操作
protoc.exe --java_out=. Student.proto
运行
多个pojo
server
public class Server {
public static void main(String[] args) throws IOException {
//创建BossGroup和WorkerGroup
//bossGroup和workerGroup含有的子线程的个数,默认为cpu核数*2
EventLoopGroup bossGroup=new NioEventLoopGroup(5);
EventLoopGroup workerGroup=new NioEventLoopGroup();
//创建服务器端的启动对象,配置参数
ServerBootstrap bootstrap=new ServerBootstrap();
try {
//使用链式编程来进行设置
bootstrap.group(bossGroup,workerGroup) //设置两个线程
.channel(NioServerSocketChannel.class) //使用NioServerSocketChannel,作为服务器的通道实现
.option(ChannelOption.SO_BACKLOG,128) //设置线程队列里连接个数
.childOption(ChannelOption.SO_KEEPALIVE,true)//设置保持连接状态
.childHandler(new ChannelInitializer<SocketChannel>() { //handler对应的是boosGruop,childHandler对应的是workerGroup
//给pipeline设置处理器
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
//加入解码器
//指定对哪种对象进行解码
pipeline.addLast("decoder",new ProtobufDecoder(MyDataInfo.MyMessage.getDefaultInstance()));
pipeline.addLast(new ServerHandler());
}
}); //给我们的workerGroup的EventLoop对应的管道设置处理器
System.out.println("服务器 is ready ...");
//绑定一个端口并且同步,生成一个ChannelFuture对象
ChannelFuture cf = bootstrap.bind(6668).sync();
//给 cf 注册监听器,监控我们关心的事件
cf.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if(future.isSuccess()){
System.out.println("监听端口成功");
}else{
System.out.println("监听端口失败");
}
}
});
//对关闭通道进行监听
cf.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
serverHandler
public class ServerHandler extends SimpleChannelInboundHandler<MyDataInfo.MyMessage> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, MyDataInfo.MyMessage msg) throws Exception {
//根据dataType,显示不同的信息
MyDataInfo.MyMessage.DateType dateType=msg.getDateType();
if(dateType==MyDataInfo.MyMessage.DateType.StudentType){
MyDataInfo.Student student=msg.getStudent();
System.out.println("学生的信息:"+student.getId()+":"+student.getName());
}else if(dateType==MyDataInfo.MyMessage.DateType.WorkerType){
MyDataInfo.Worker worker=msg.getWorker();
System.out.println("工人的信息:"+worker.getName()+":"+worker.getAge());
}else{
System.out.println("传输的类型不正确");
}
}
/**
* @Description 数据读取完毕
* @date 2020/7/23 10:46
* @param ctx
* @return void
*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
//将数据写入到缓冲并刷新
//一般来讲,我们队发送的数据进行编码
ctx.writeAndFlush(Unpooled.copiedBuffer(new Date().toString()+":"+"hello,客户端",CharsetUtil.UTF_8));
}
/**
* @Description 处理异常,一般是需要管理通道
* @date 2020/7/23 11:13
* @param ctx
* @param cause
* @return void
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.channel().close();
}
}
client
public class Client {
public static void main(String[] args) {
//客户端需要一个事件循环组
EventLoopGroup eventExecutors=new NioEventLoopGroup();
try {
//创建客户端启动对象
Bootstrap bootstrap=new Bootstrap();
//设置相关参数
bootstrap.group(eventExecutors) //设置线程组
.channel(NioSocketChannel.class) //设置客户端通道的实现类(反射)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//加入编码器
pipeline.addLast("encoder",new ProtobufEncoder());
pipeline.addLast(new ClientHandler()); //加入自己的处理器
}
});
System.out.println("客户端 ok...");
//启动客户端去连接服务器端
//关于ChannelFuture要分析,涉及到netty的异步模型
ChannelFuture channelFuture = bootstrap.connect(new InetSocketAddress("127.0.0.1", 6668)).sync();
//关闭通道进行监听
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
eventExecutors.shutdownGracefully();
}
}
}
clientHandler
public class ClientHandler extends ChannelInboundHandlerAdapter {
/**
* @Description 当通道就绪时会触发该方法
* @date 2020/7/23 11:27
* @param ctx
* @return void
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
int random = new Random().nextInt(3);
MyDataInfo.MyMessage myMessage = null;
System.out.println(random);
if(random==0){
myMessage=MyDataInfo.MyMessage.newBuilder().setDateType(MyDataInfo.MyMessage.DateType.StudentType)
.setStudent(MyDataInfo.Student.newBuilder().setName("重庆刘德华").setId(2).build()).build();
}else{
myMessage=MyDataInfo.MyMessage.newBuilder().setDateType(MyDataInfo.MyMessage.DateType.WorkerType)
.setWorker(MyDataInfo.Worker.newBuilder().setName("香港徐大虾").setAge(21).build()).build();
}
ctx.writeAndFlush(myMessage);
}
/**
* @Description 当通道有读取事件时,会触发
* @date 2020/7/23 11:29
* @param ctx
* @param msg
* @return void
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf=(ByteBuf)msg;
System.out.println("服务器回复的消息:"+((ByteBuf) msg).toString(CharsetUtil.UTF_8));
System.out.println("服务器的地址:"+ctx.channel().remoteAddress());
}
}
Message.proto
syntax = "proto3";
option optimize_for= SPEED; //快速解析
option java_package="top.codekiller.test.nettty.codec2"; //指定生成到哪个包下
option java_outer_classname="MyDataInfo"; //外部类名称
//protobuf 可以使用message管理其它的message
message MyMessage{
//定义一个枚举
enum DateType{
StudentType=0; //在proto3 要求enum的编号从0开始
WorkerType=1;
}
//用data_type 来标识传的是哪一个枚举类型
DateType date_type=1;
//表示每次枚举类型最多只能出现其中的一个,节省空间
oneof dataBody{
Student student=2;
Worker worker=3;
}
}
message Student{
int32 id=1;
string name=2;
}
message Worker{
string name=1;
int32 age=2;
}
操作
👉 查看操作