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 是 Google 发布的开源项目,全称Google Protocol Buffers,是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或RPC[远程过程调用remote procedurecall ]数据交换格式。
目前很多公司http+jsontcp+protobuf -
参考文档 : https:/ldevelopers.google.com/protocol-buffers/docs/proto语言指南
-
Protobuf是以message 的方式来管理数据的.
-
支持跨平台、跨语言,即[客户端和服务器端可以是不同的语言编写的](支持目前绝大多数语言。例如c++,Python,Go等)
-
高性能,高可靠
-
使用Protobuf编译器能自动生成代码,ProtoBuf是将类的定义使用.proto文件进行描述,(ps:idea在编写proto文件时,会自动提示是否下载.proto编写插件,可以让语法高亮显示,真是太方便了)
-
然后通过proto.exe编译器根据.proto自动生成java文件
-
示意图
7.4、ProtoBuf快速入门实例
-
客户端可以发送一个Student Pojo对象到服务器(通过ProtoBuf编码)
-
服务端能接收Student PoJo对象,并显示信息(通过ProtoBuf解码)
-
Proto文件编写和POJO类自动生成
//ProtoBuf文件 syntax = "proto3";//版本 option java_outer_classname = "StudentPOJO";//生成的外部类名,同时也是文件名 //protobuf 使用message管理数据 message Student{//会在 StudentPOJO外部类生成一个内部类Student,他是真正发送的POJO对象 int32 id = 1;//Student 类中有一个属性 名字为id的类型为int32(Proto类型)1表示属性序号,不是值 string name =2; } //编译 // protoc.exe --java_out=. Student.proto // 将生成的StudentPOJO放入到项目使用
-
服务端
package com.feng.netty.codec; 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 { //创建BossGroup和WorkerGroup //说明 //1.创建两个线程组bossGroup 和 workerGroup //2.bossGroup 只是处理连接请求,真正的客户端业务处理,会交给workerGroup完成 //3.两个都是无限循环 EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup =new NioEventLoopGroup(); try { //创建服务器端的启动对象,配置参数 ServerBootstrap bootstrap = new ServerBootstrap(); //使用链式编程来进行设置 bootstrap.group(bossGroup,workerGroup)//设置两个线程组 .channel(NioServerSocketChannel.class)//使用NioSocketChannel作为服务器的通道实现 .option(ChannelOption.SO_BACKLOG,128)//设置线程队列得到连接个数 .childOption(ChannelOption.SO_KEEPALIVE,true)//设置保持活动连接状态 .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { //在pipeline中加入ProtoBufEncoder //指定哪种对象进行解码 ch.pipeline().addLast("decoder",new ProtobufDecoder(StudentPOJO.Student.getDefaultInstance())); ch.pipeline().addLast(new NettyServerHandler()); } });// System.out.println("...服务器 is ready..."); //绑定一个端口并且同步,生成了一个chanelFuture ChannelFuture cf = bootstrap.bind(6668).sync(); cf.addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture channelFuture) throws Exception { if (cf.isSuccess()){ System.out.println("监听端口6668成功"); }else { System.out.println("监听端口6668失败"); } } }); //对关闭通道进行监听 cf.channel().closeFuture().sync(); } finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } }
package com.feng.netty.codec; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.util.CharsetUtil; /** * 继承SimpleChannelInboundHandler,不用强转类型,更加方便 */ public class NettyServerHandler extends SimpleChannelInboundHandler<StudentPOJO.Student> { @Override protected void channelRead0(ChannelHandlerContext channelHandlerContext, StudentPOJO.Student student) throws Exception { System.out.println("客户端发送的数据 id="+student.getId()+"名字="+student.getName()); } /** * 数据读取完毕 * @param ctx 上下文对象,含有管道pipeline,管道channel,地址 */ @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { //将数据写人到缓存,并刷新 //一般讲,我们对这个发送的数据进行编码 ctx.writeAndFlush(Unpooled.copiedBuffer("hello,客户端(>^ω^<)喵", CharsetUtil.UTF_8)); } /** * 处理异常,一般是需要关闭通道 */ @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { ctx.close(); } }
-
客户端
package com.feng.netty.codec; 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.ProtobufEncoder; public class NettyClient { public static void main(String[] args) throws InterruptedException { //客户需要一个事件循环组 EventLoopGroup group = new NioEventLoopGroup(); try { //创建客户端启动对象 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中加入ProtoBufEncoder pipeline.addLast("encoder",new ProtobufEncoder()); pipeline.addLast(new NettyClientHandler()); } }); System.out.println("客户端 ok.."); //启动客户端去连接服务器端 //关于channelFuture要分析,涉及到netty都得异步模型 ChannelFuture channelFuture = bootstrap.connect("127.0.0.1",6668).sync(); //给关闭通道进行监听 channelFuture.channel().closeFuture().sync(); } finally { group.shutdownGracefully(); } } }
package com.feng.netty.codec; 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; /** * 客户端 * @author cpms */ public class NettyClientHandler extends ChannelInboundHandlerAdapter { /** * 当通道就绪就会触发该方法 * @param ctx 上下文, */ @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { //发送一个student对象到服务器 StudentPOJO.Student student = StudentPOJO.Student.newBuilder().setId(4).setName("豹子头林冲").build(); ctx.writeAndFlush(student); //System.out.println("client "+ctx); //ctx.writeAndFlush(Unpooled.copiedBuffer("hello.server:(>^ω^<)喵", CharsetUtil.UTF_8)); } /** * 当通道有读取事件时,会触发 * @param ctx 上下文 * @throws Exception */ @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ByteBuf byteBuf = (ByteBuf) msg; System.out.println("服务器回复的消息:"+byteBuf.toString(CharsetUtil.UTF_8)); System.out.println("服务器的地址"+ctx.channel().remoteAddress()); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } }
-
7.5、ProtoBuf 快速入门实例2
-
客户端可以随机发送Studnet PoJo/Worker PoJo对象到服务器(通过Protobuf编码)
-
服务端能接收Student PoJo/Worker PoJo 对象(需要判断是哪种类型),并显示信息(Protobuf编码)
-
protobuf
syntax = "proto3";//版本 option optimize_for = SPEED; //加快解析 option java_package = "com.feng.netty.codec2"; 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{//会在 StudentPOJO外部类生成一个内部类Student,他是真正发送的POJO对象 int32 id = 1;//Student 类中有一个属性 名字为id的类型为int32(Proto类型)1表示属性序号,不是值 string name =2; } message Worker{ string name=1; int32 age=2; }
-
服务端(ps:服务端的代码和上一个例子的代码大部分相同,仅展示不同部分)
public class NettyServer { ... //在pipeline中加入ProtoBufEncoder //指定哪种对象进行解码 ch.pipeline().addLast("decoder",new ProtobufDecoder(MyDataInfo.MyMessage.getDefaultInstance())); ... }
public class NettyServerHandler2 extends SimpleChannelInboundHandler<MyDataInfo.MyMessage> { @Override protected void channelRead0(ChannelHandlerContext channelHandlerContext,MyDataInfo.MyMessage message) throws Exception { if (message.getDataType()==MyDataInfo.MyMessage.DataType.StudentType){ System.out.println("学生 id="+message.getStudent().getId()+"名字="+message.getStudent().getName()); }else System.out.println("工人 age="+message.getWorker().getAge()+"名字="+message.getStudent().getName()); } ... }
-
客户端
public class NettyClientHandler extends ChannelInboundHandlerAdapter { /** * 当通道就绪就会触发该方法 * @param ctx 上下文, */ @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { //随机发送student或worker对象 int randmon = new Random().nextInt(3); MyDataInfo.MyMessage myMessage =null; if (0==randmon){ myMessage= MyDataInfo.MyMessage.newBuilder(). setDataType(MyDataInfo.MyMessage.DataType.StudentType). setStudent(MyDataInfo.Student.newBuilder() .setId(5).setName("玉麒麟卢俊义").build()).build(); }else { myMessage= MyDataInfo.MyMessage.newBuilder(). setDataType(MyDataInfo.MyMessage.DataType.WorkerType). setWorker(MyDataInfo.Worker.newBuilder() .setAge(20).setName("楚河").build()).build(); } ctx.writeAndFlush(myMessage); } }
-