数据在网络传输的过程中需要序列化或和反序列化,也就需要用到编码器和解码器,本篇文章主要是探讨Netty中的编码解码器以及Protobuf的使用。
当我们的Netty客户端和服务端进行通信时数据在传输的过程中需要进行序列化,比如以二进制数据进行传输,那么我们的业务数据就需要有相应的编码器进行编码为二进制数据,当服务端拿到二进制数据后需要有相应的解码器进行解码得到真实的业务数据。如下图:
当Netty发送或者接受一个消息的时候,就将会发生一次数据转换。入站消息会被解码:从字节转换为另一种格式(比如java对象);如果是出站消息,它会被编码成字节。
Netty提供一系列实用的编解码器,他们都实现了ChannelInboundHadnler或者ChannelOutboundHandler接口。在这些类中,channelRead方法已经被重写了。以入站为例,对于每个从入站Channel读取的消息,这个方法会被调用。随后,它将调用由解码器所提供的decode()方法进行解码,并将已经解码的字节转发给ChannelPipeline中的下一个ChannelInboundHandler。
在Netty提供了一些编码解码器如下:
对于 ObjectEncoder 和 ObjectDecoder而言它只是针对Java对象的编码解码, 换句话说底层使用的是Java的序列化技术,而Java序列化存在如下问题
所以我们引入了 google 的 protobuf来解决这一问题
Protobuf 是 google 谷歌开源的项目,它是一种灵活,高效,自动化机制的结构数据序列化方法;它是一种跨语言、可扩展的序列化结构数据的方法,它可用于(数据)通信协议(RPC)、数据存储等。其具有以下特点:
你可以定义数据的结构,然后使用特殊生成的源代码轻松的在各种数据流中使用各种语言进行编写和读取结构数据。你甚至可以更新数据结构,而不破坏由旧数据结构编译的已部署程序
在Netty中提供了针对于protobuf的编码器 ProtobufEncoder 和 解码器 ProtobufDecoder
官方文档:http://developers.google.com/protocol-buffers/docs/proto
使用Protobuf需要使用protobuf 编译器编译器生成Java代码,步骤如下
第一步:编写.proto文件 , 可以使用IDEA编写,文件名我这里是User.proto
//定义版本
syntax = "proto3";
//生成的代码的类名和文件名
option java_outer_classname = "UserPOJO";
//使用messae管理数据,注意名字不要和java_outer_classname冲突
message User{
//定义ID属性,int32对应java的,注意这里的1指的是需要而不是值
int64 id = 1;
//定义一个name属性,类型为字符串String,注意这里的2指的是需要而不是值
string name = 2;
//定义一个数组,爱好
repeated string favorite = 3;
}
这个表格显示了在.proto文件内可以指定的类型, 与自动生成的相对类型!
第二步:安装proto.exe,下载地址:https://github.com/protocolbuffers/protobuf/releases,解压后进入bin目录
第三步:拷贝User.proto文件到bin目录,使用protoc.exe生产java文件 ,执行命令protoc.exe --java_out=. User.proto
生成的文件如下
还是以Netty的 Server/Client 模式为例来演示,只是这里的数据使用protobuf的编解码。
第一步:搭建工程,把UserPOJO.java文件拷贝到项目中,此时该类会报错,这需要导入一个依赖
<dependency>
<groupId>com.google.protobufgroupId>
<artifactId>protobuf-javaartifactId>
<version>3.4.0version>
dependency>
<dependency>
<groupId>io.nettygroupId>
<artifactId>netty-allartifactId>
<version>4.1.42.Finalversion>
dependency>
第二步:创建Netty服务端:NettyServer
public class NettyServer {
public static void main(String[] args) {
NioEventLoopGroup bossGroup = new NioEventLoopGroup();
NioEventLoopGroup workGroup = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
try {
bootstrap.group(bossGroup,workGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//服务端:使用protobuf解码器,需要指定解码器解码哪种类型
pipeline.addLast("decoder",new ProtobufDecoder(UserPOJO.User.getDefaultInstance()));
pipeline.addLast(new ServerHandler());
}
});
//启动服务器
ChannelFuture sync = bootstrap.bind(2000).sync();
sync.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
}
这里我们通过pipeline添加了protobuf的解码器
第三步:编写服务端的handler
public class ServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//把消息强转成UserPOJO.User
UserPOJO.User user = (UserPOJO.User) msg;
System.out.println("拿到数据:"+user.getName());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.channel().close();
}
}
第四步:编写客户端:NettyClient
public class NettyClient {
public static void main(String[] args) {
NioEventLoopGroup eventLoopGroup = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(eventLoopGroup)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//给客户端增加编码器,使用ProtobufEncoder编码器
pipeline.addLast("encoder",new ProtobufEncoder());
pipeline.addLast(new ClientHandler());
}
});
try {
ChannelFuture sync = bootstrap.connect("127.0.0.1", 2000).sync();
sync.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
eventLoopGroup.shutdownGracefully();
}
}
}
客户端添加了ProtobufEncoder编码器
第五步:编写客户端的handler
public class ClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//当建立连接,就发送一个对象给服务端
UserPOJO.User user = UserPOJO.User.newBuilder().setId(1).setName("zs").build();
ctx.writeAndFlush(user);
System.out.println("客户端发送 user="+user.getName());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.channel().close();
}
}
代码编写完成,最终项目结构如下
第六步:测试,依次启动NettyServer,NettyClient ,效果如下
protobuf的内容就介绍到这里把,喜欢的话给个好评把。