前言
数据在网络传输的过程中需要序列化或和反序列化,也就需要用到编码器和解码器,本篇文章主要是探讨Netty中的编码解码器以及Protobuf的使用。
Netty中的编码解码
当我们的Netty客户端和服务端进行通信时数据在传输的过程中需要进行序列化,比如以二进制数据进行传输,那么我们的业务数据就需要有相应的编码器进行编码为二进制数据,当服务端拿到二进制数据后需要有相应的解码器进行解码得到真实的业务数据。如下图:
当Netty发送或者接受一个消息的时候,就将会发生一次数据转换。入站消息会被解码:从字节转换为另一种格式(比如java对象);如果是出站消息,它会被编码成字节。
Netty提供一系列实用的编解码器,他们都实现了ChannelInboundHadnler或者ChannelOutboundHandler接口。在这些类中,channelRead方法已经被重写了。以入站为例,对于每个从入站Channel读取的消息,这个方法会被调用。随后,它将调用由解码器所提供的decode()方法进行解码,并将已经解码的字节转发给ChannelPipeline中的下一个ChannelInboundHandler。
在Netty提供了一些编码解码器如下:
- StringEncoder : 针对于字符串的编码器
- StringDecoder : 针对于字符串的解码器
- ObjectEncoder : 针对于Java对象的编码器
- ObjectDecoder : 针对于Java对象的解码器
- MessageToByteEncoder:把message转换成byte的编码器 。
- ByteToMessageDecoder :把byte转换成message的解码器,会有粘包拆包问题。
- ReplayingDecoder:对ByteToMessageDecoder的扩展
对于 ObjectEncoder 和 ObjectDecoder而言它只是针对Java对象的编码解码, 换句话说底层使用的是Java的序列化技术,而Java序列化存在如下问题
- 效率低下
- 无法夸语言,只能支持java
- 序列化后体积庞大,远远大于二进制编码
所以我们引入了 google 的 protobuf来解决这一问题
Protobuf认识
Protobuf 是 google 谷歌开源的项目,它是一种灵活,高效,自动化机制的结构数据序列化方法;它是一种跨语言、可扩展的序列化结构数据的方法,它可用于(数据)通信协议(RPC)、数据存储等。其具有以下特点:
- 语言无关、平台无关。即 ProtoBuf 支持 Java、C++、Python 等多种语言,支持多个平台
- 高效。类比XML,比 XML 更小(3 ~ 10倍)、更快(20 ~ 100倍)、更为简单
- 扩展性、兼容性好。你可以更新数据结构,而不影响和破坏原有的旧程序
你可以定义数据的结构,然后使用特殊生成的源代码轻松的在各种数据流中使用各种语言进行编写和读取结构数据。你甚至可以更新数据结构,而不破坏由旧数据结构编译的已部署程序
在Netty中提供了针对于protobuf的编码器 ProtobufEncoder 和 解码器 ProtobufDecoder
使用Protobuf生成POJO
官方文档:http://developers.google.com/protocol-buffers/docs/proto
使用Protobuf需要使用protobuf 编译器编译器生成Java代码,步骤如下
- 定义以.proto结尾的文件
- 使用protoc.exe编译.proto文件生成java代码
- 编写Netty案例
第一步:编写.proto文件 , 可以使用IDEA编写,文件名我这里是User.proto
//定义版本syntax="proto3"; //生成的代码的类名和文件名optionjava_outer_classname="UserPOJO"; //使用messae管理数据,注意名字不要和java_outer_classname冲突messageUser{ //定义ID属性,int32对应java的,注意这里的1指的是需要而不是值int64id=1; //定义一个name属性,类型为字符串String,注意这里的2指的是需要而不是值stringname=2; //定义一个数组,爱好repeatedstringfavorite=3; }
这个表格显示了在.proto文件内可以指定的类型, 与自动生成的相对类型!
第二步:安装proto.exe,下载地址:https://github.com/protocolbuffers/protobuf/releases,解压后进入bin目录
第三步:拷贝User.proto文件到bin目录,使用protoc.exe生产java文件 ,执行命令protoc.exe --java_out=. User.proto
生成的文件如下
Netty使用Protobuf编解码
还是以Netty的 Server/Client 模式为例来演示,只是这里的数据使用protobuf的编解码。
第一步:搭建工程,把UserPOJO.java文件拷贝到项目中,此时该类会报错,这需要导入一个依赖
<dependency><groupId>com.google.protobuf</groupId><artifactId>protobuf-java</artifactId><version>3.4.0</version></dependency><dependency><groupId>io.netty</groupId><artifactId>netty-all</artifactId><version>4.1.42.Final</version></dependency>
第二步:创建Netty服务端:NettyServer
publicclassNettyServer { publicstaticvoidmain(String[] args) { NioEventLoopGroupbossGroup=newNioEventLoopGroup(); NioEventLoopGroupworkGroup=newNioEventLoopGroup(); ServerBootstrapbootstrap=newServerBootstrap(); try { bootstrap.group(bossGroup,workGroup) .channel(NioServerSocketChannel.class) .childHandler(newChannelInitializer<SocketChannel>() { protectedvoidinitChannel(SocketChannelch) throwsException { ChannelPipelinepipeline=ch.pipeline(); //服务端:使用protobuf解码器,需要指定解码器解码哪种类型pipeline.addLast("decoder",newProtobufDecoder(UserPOJO.User.getDefaultInstance())); pipeline.addLast(newServerHandler()); } }); //启动服务器ChannelFuturesync=bootstrap.bind(2000).sync(); sync.channel().closeFuture().sync(); } catch (InterruptedExceptione) { e.printStackTrace(); }finally { bossGroup.shutdownGracefully(); workGroup.shutdownGracefully(); } } }
这里我们通过pipeline添加了protobuf的解码器
第三步:编写服务端的handler
publicclassServerHandlerextendsChannelInboundHandlerAdapter { publicvoidchannelRead(ChannelHandlerContextctx, Objectmsg) throwsException { //把消息强转成UserPOJO.UserUserPOJO.Useruser= (UserPOJO.User) msg; System.out.println("拿到数据:"+user.getName()); } publicvoidexceptionCaught(ChannelHandlerContextctx, Throwablecause) throwsException { ctx.channel().close(); } }
第四步:编写客户端:NettyClient
publicclassNettyClient { publicstaticvoidmain(String[] args) { NioEventLoopGroupeventLoopGroup=newNioEventLoopGroup(); Bootstrapbootstrap=newBootstrap(); bootstrap.group(eventLoopGroup) .channel(NioSocketChannel.class) .handler(newChannelInitializer<SocketChannel>() { protectedvoidinitChannel(SocketChannelch) throwsException { ChannelPipelinepipeline=ch.pipeline(); //给客户端增加编码器,使用ProtobufEncoder编码器pipeline.addLast("encoder",newProtobufEncoder()); pipeline.addLast(newClientHandler()); } }); try { ChannelFuturesync=bootstrap.connect("127.0.0.1", 2000).sync(); sync.channel().closeFuture().sync(); } catch (InterruptedExceptione) { e.printStackTrace(); }finally { eventLoopGroup.shutdownGracefully(); } } }
客户端添加了ProtobufEncoder编码器
第五步:编写客户端的handler
publicclassClientHandlerextendsChannelInboundHandlerAdapter { publicvoidchannelRead(ChannelHandlerContextctx, Objectmsg) throwsException { } publicvoidchannelActive(ChannelHandlerContextctx) throwsException { //当建立连接,就发送一个对象给服务端UserPOJO.Useruser=UserPOJO.User.newBuilder().setId(1).setName("zs").build(); ctx.writeAndFlush(user); System.out.println("客户端发送 user="+user.getName()); } publicvoidexceptionCaught(ChannelHandlerContextctx, Throwablecause) throwsException { ctx.channel().close(); } }
代码编写完成,最终项目结构如下
第六步:测试,依次启动NettyServer,NettyClient ,效果如下
客户端:
服务端:
protobuf的内容就介绍到这里把,喜欢的话给个好评把。