一、编码和解码
1.1 基本介绍
编写网络应用程序时,因为数据在网络中传输的都是二进制字节码数据,在发送数据时就需要编码,接收数据时就需要解码
codec(编解码器) 的组成部分有两个:decoder(解码器)和 encoder(编码器)。encoder 负责把业务数据转换成字节码数据,decoder 负责把字节码数据转换成业务数据
1.2 Netty本身的编码解码的机制和问题分析
1、Netty自身提供了一些codec(编解码器)
2、 Netty 提供的编码器
- StringEncoder,对字符串数据进行编码
- ObjectEncoder,对Java对象进行编码
- ...
3、Netty提供的解码器
- StringDecoder,对字符串数据进行解码
- ObjectEncoder,对Java对象进行解码
- ...
4、 问题Netty本身自带的ObjectDecoder和ObjectEncoder可以用来实现POJO对象或者各种业务对象的编码和解码,底层使用的仍是Java序列化技术,而Java序列化技术本身效率就不高,存在许多问题:无法跨语言、序列化后的体积太大,是二进制编码的5倍多、序列化性能太低
5、解决方案
- 引入Google的Protobuf
二、Google Protouf的引入使用
2.1 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 文件
2.2 Protobuf 入门实例
2.2.1 材料准备
1)下载protoc.exe
用于将编写好的proto文件编译为java文件此版本为3.19.1下载地址:链接:https://pan.baidu.com/s/1KSwUL08tN_3_GxIzQ2kRVw
提取码:yquo注意所用的版本要与pom.xml中引入的protobuf依赖的版本对应,否则可能会出现编译失败的情况
2)pom.xml引入依赖
<dependency> <groupId>com.google.protobuf</groupId> <artifactId>protobuf-java</artifactId> <version>3.19.1</version> </dependency>
3) idea 下载相关插件
4)编写Student.proto文件
syntax="proto3"; //版本 option optimize_for = SPEED;//加快解析 option java_package="com.codec";//指定生成到那个包下 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类的属性,1代表的不是值,是指是该类的第一个属性 string name=2; } message Worker{ string name=1; int32 age=2; }
5)将编写好的proto文件放到解压后的protoc.exe 同级目录
在这个目录下进入cmd 输入命令:
成功后,在目录下会出现一个文件夹
将文件夹下的java文件复制到项目相应的位置即可:
2.2.2 代码实现
1)NettyServer 代码
package com.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 和WorkGroup /* * 说明: * 1、创建两个线程组boosGroup和workerGroup * 2、bossGroup只是处理连接请求,真正的和客户端业务处理,会交给workGroup完成 * 3、两个都是无限循环 * */ EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workGroup = new NioEventLoopGroup(); try { //创建服务器端的启动对象,配置参数 ServerBootstrap bootstrap = new ServerBootstrap(); //使用链式编程来处理 bootstrap.group(bossGroup, workGroup)//设置两个线程组 .channel((NioServerSocketChannel.class))//使用NioSocketChannel作为服务器的通道实现 .option(ChannelOption.SO_BACKLOG, 128)//设置线程队列得到连接个数 .childOption(ChannelOption.SO_KEEPALIVE, true)//设置活动保持连接状态 .childHandler(new ChannelInitializer<SocketChannel>() {//创建一个通道测试对象(匿名对象) //给pipeLin设置处理器 @Override protected void initChannel(SocketChannel ch) throws Exception { //在pipeline中加入ProtoBufDecoder //指定对哪一个对象进行解码 ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast("decoder",new ProtobufDecoder(MyDataInfo.MyMessage.getDefaultInstance())); pipeline.addLast(new NettyServerHandler()); //加入自己的handler } });//给我们的workerGroup的eventLoop对应的管道设置处理器 System.out.println("...服务器is ready...."); //绑定一个端口并且同步,生成了一个ChannelFuture对象 //启动服务器(并绑定端口) ChannelFuture cf = bootstrap.bind(6668).sync(); //对关闭通道进行监听 cf.channel().closeFuture().sync(); }finally { bossGroup.shutdownGracefully(); workGroup.shutdownGracefully(); } } }
2)NettyServerHandler 代码
package com.codec; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.util.CharsetUtil; /* * 说明: * 1、我们自定义一个Handler,需要继承规定的某个HandlerAdapter(规范) * 2、这时我们自定义一个Handler,才能称为handler * */ public class NettyServerHandler extends SimpleChannelInboundHandler<MyDataInfo.MyMessage> { /* * 读取数据(这里我们可以读取客户端发送的消息) * 1、ChannelHandlerContext ctx:上下问对象,含有管道pipeline,通道channel,地址 * 2、Object msg:就是客户端发送的数据,默认Object * */ @Override public void channelRead0(ChannelHandlerContext ctx, MyDataInfo.MyMessage msg) throws Exception { //读取从客户端发送的实体类数据 MyDataInfo.MyMessage.DataType dataType = msg.getDataType(); if(dataType==MyDataInfo.MyMessage.DataType.StudentType){ MyDataInfo.Student student = msg.getStudent(); System.out.println("学生id:"+student.getId()+"学生名:"+student.getName()); }else if(dataType==MyDataInfo.MyMessage.DataType.WorkerType){ MyDataInfo.Worker worker = msg.getWorker(); System.out.println("工人年龄:"+worker.getAge()+"工人姓名:"+worker.getName()); }else{ System.out.println("传输的类型不正确!"); } } //数据读取完毕 @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { //writeAndFlush 是write+flush //将数据写入到缓存并刷新 //一般来讲,我们对这个发送的数据进行编码 ctx.writeAndFlush(Unpooled.copiedBuffer("hello,猿小许!",CharsetUtil.UTF_8)); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { // cause.printStackTrace(); ctx.close(); } }
3)NettyClient 代码
package com.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(); //创建客户端启动对象 //注意客户端使用的是Bootstrap 而不是ServerbOOTsTRAP Bootstrap bootstrap = new Bootstrap(); try { //设置相关参数 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("客户端 is ok ..."); //启动客户端去连接服务器端 //关于ChannelFuture 要分析,设计到netty的异步模型 ChannelFuture cf = bootstrap.connect("127.0.0.1", 6668).sync(); //异步监听关闭通道 cf.channel().closeFuture().sync(); }finally { group.shutdownGracefully(); } } }
4)NettyClientHandler 代码
package com.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; import java.util.Random; public class NettyClientHandler extends ChannelInboundHandlerAdapter { //当通道就绪就会触发该方法 @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { //随机的发送Student 或者Worker 对象 int random = new Random().nextInt(3); MyDataInfo.MyMessage message =null; if(0==random){//发送Student对象 message= MyDataInfo.MyMessage.newBuilder().setDataType(MyDataInfo.MyMessage .DataType.StudentType).setStudent(MyDataInfo.Student.newBuilder().setId(5).setName("帅哥 猿小许").build()).build(); }else {//发送一个worker对象 message= MyDataInfo.MyMessage.newBuilder().setDataType(MyDataInfo.MyMessage .DataType.WorkerType).setWorker(MyDataInfo.Worker.newBuilder().setName("靓仔 小沙弥").setAge(28).build()).build(); } ctx.writeAndFlush(message); } //当通道有读取事件是,会触发 @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ByteBuf buf = (ByteBuf) msg; System.out.println("服务器回复的消息:" + buf.toString(CharsetUtil.UTF_8)); System.out.println("服务器地址是:"+ctx.channel().remoteAddress()); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { //cause.printStackTrace(); ctx.close(); } }
2.3结果
💥推荐阅读💥