Netty是JBOSS提供的一个Java开源框架。Netty提供异步的,基于事件驱动的网络应用程序框架,用以快速开发高性能、高可靠性的网络IO程序。
Netty是一个基于NIO的网络编程框架,使用Netty可以帮你快速、简单的开发出一个网络应用,相当于简化和流程化了NIO的开发过程。
作为当前最流行的NIO框架,Netty在互联网领域、大数据分布式计算领域、游戏行业、通信行业等获得了广泛的应用。知名的ElasticSearch、Dubbo框架都使用Netty。
服务器端调用一个线程通过多路复用搞定所有的IO操作(包括连接,读,写等),编码简单,清晰明了。但是如果客户端连接数量较多,将无法支撑,我们前面的NIO案例就属于这种类型。
服务器端采用一个线程专门处理客户端连接请求,采用一个线程池负责IO 操作。在绝大多数场景下,该模型都能满足使用。
比较类似于上面的线程池模型,Netty 抽象出两组线程池,BossGroup 专门负责接收客户端连接,WorkerGroup 专门负责网络读写操作。NioEventLoop 表示一个不断循环执行处理任务的线程,每个NioEventLoop 都有一个selector,用于监听绑定在其上的socket 网络通道。NioEventLoop 内部采用串行化设计,从消息的读取->解码->处理->编码->发送,始终由IO 线程NioEventLoop 负责。
一个NioEventLoopGroup 下包含多个NioEventLoop
每个NioEventLoop 中包含有一个Selector,一个taskQueue
每个NioEventLoop 的Selector 上可以注册监听多个NioChannel
每个NioChannel 只会绑定在唯一的NioEventLoop 上
每个NioChannel 都绑定有一个自己的ChannelPipeline
FUTURE,CALLBACK和HANDLER
Netty的异步模型是建立在future和callback之上的。callback(回调)我们都比较熟悉,Java中很多地方都用到了回调。这里重点要讲的future,future的核心思想是:假设有一个方法fun,计算过程可能非常耗时,一直等待方法fun执行完成再返回显然不合适。那么在执行f调用un方法的时候,立马返回一个future,后续可以通过future来监控fun方法的处理过程。
在使用Netty进行编程时,拦截操作和转换出入站数据只需要您提供callback 或利用future 即可。这使得链式操作简单、高效, 并有利于编写可重用的、通用的代码。Netty 框架的目标就是让你的业务逻辑从网络基础应用编码中分离出来、解脱出来。
ChannelHandler 接口定义了许多事件处理的方法,我们可以通过重写这些方法去实现具体的业务逻辑。API 关系如下图所示:
我们经常需要定义一个handler类去继承ChannelInboundHandlerAdapter,然后通过重写相应方法实现业务逻辑,我们接下来看看一般都需要重写哪些方法:
public void channelActive(ChannelHandlerContext ctx),通道就绪事件
public void channelRead(ChannelHandlerContext ctx, Object msg),通道读取数据事件
public void channelReadComplete(ChannelHandlerContext ctx) ,数据读取完毕事件
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause),通道发生异常事件
ChannelPipeline 是一个Handler 的集合,它负责处理和拦截inbound 或者outbound 的事件和操作,相当于一个贯穿Netty 的链。
ChannelPipeline addFirst(ChannelHandler... handlers),把一个业务处理类(handler)添加到链中的第一个位置
ChannelPipeline addLast(ChannelHandler... handlers),把一个业务处理类(handler)添加到链中的最后一个位置
这是事件处理器上下文对象, Pipeline 链中的实际处理节点。每个处理节点ChannelHandlerContext 中包含一个具体的事件处理器ChannelHandler , 同时ChannelHandlerContext 中也绑定了对应的pipeline 和Channel 的信息,方便对ChannelHandler进行调用。常用方法如下所示:
ChannelFuture close(),关闭通道
ChannelOutboundInvoker flush(),刷新
ChannelFuture writeAndFlush(Object msg) , 将数据写到ChannelPipeline 中当前ChannelHandler 的下一个ChannelHandler 开始处理(出站)
Netty在创建了Channel实例后,一般都需要设置ChannelOption参数。ChannelOption是Socket的标准参数,而不是Netty独创的,常用的参数配置有:
对应TCP\IP协议的listen函数中的backlog参数,用来初始化服务器可连接队列大小。服务端处理客户端连接请求是顺序处理的,所以同一时间只能处理一个客户端连接。多个客户端来的时候,服务端将不能处理的客户端连接请求放在队列中等待处理,backlog 参数指定了队列的大小。
一直保持连接活动状态。
表示Channel 中异步I/O 操作的结果,在Netty 中所有的I/O 操作都是异步的,I/O 的调用会直接返回,调用者并不能立刻获得结果,但是可以通过ChannelFuture 来获取I/O 操作的处理状态。
常用方法如下所示:
Channel channel(),返回当前正在进行IO 操作的通道
ChannelFuture sync(),等待异步操作执行完毕
EventLoopGroup 是一组EventLoop 的抽象,Netty 为了更好的利用多核CPU 资源,一般会有多个EventLoop 同时工作,每个EventLoop 维护着一个Selector 实例。
EventLoopGroup 提供next 接口,可以从组里面按照一定规则获取其中一个EventLoop来处理任务。在Netty 服务器端编程中,我们一般都需要提供两个EventLoopGroup,例如:BossEventLoopGroup 和WorkerEventLoopGroup。
通常一个服务端口即一个ServerSocketChannel 对应一个Selector 和一个EventLoop 线程。BossEventLoop 负责接收客户端的连接并将SocketChannel 交给WorkerEventLoopGroup 来进行IO 处理,如下图所示:
BossEventLoopGroup 通常是一个单线程的EventLoop,EventLoop 维护着一个注册了ServerSocketChannel 的Selector 实例,BossEventLoop 不断轮询Selector 将连接事件分离出来,通常是OP_ACCEPT 事件,然后将接收到的SocketChannel 交给WorkerEventLoopGroup,WorkerEventLoopGroup 会由next 选择其中一个EventLoopGroup 来将这个SocketChannel 注册到其维护的Selector 并对其后续的IO 事件进行处理。
常用的方法如下:
public NioEventLoopGroup(),构造方法
public Future> shutdownGracefully(),断开连接,关闭线程
ServerBootstrap是Netty中的服务端启动助手,通过它可以完成服务器端的各种配置;Bootstrap 是Netty 中的客户端启动助手,通过它可以完成客户端的各种配置。常用方法如下:
public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup),该方法用于服务器端,用来设置两个EventLoop
public B group(EventLoopGroup group) ,该方法用于客户端,用来设置一个EventLoop
public B channel(Class extends C> channelClass),该方法用来设置一个服务器端的通道实现
public
public ServerBootstrap childHandler(ChannelHandler childHandler),该方法用来设置业务处理类(自定义的handler) public ChannelFuture bind(int inetPort) ,该方法用于服务器端,用来设置占用的端口号
public ChannelFuture connect(String inetHost, int inetPort) ,该方法用于客户端,用来连接服务器端
这是Netty 提供的一个专门用来操作缓冲区的工具类,常用方法如下所示:
public static ByteBuf copiedBuffer(CharSequence string, Charset charset),通过给定的数据和字符编码返回一个ByteBuf 对象(类似于NIO 中的ByteBuffer 对象)
pom 文件中引入了netty 的坐标,采用4.1.15 版本:
4.0.0
com.zdw.netty
Netty_Demo
1.0-SNAPSHOT
io.netty
netty-all
4.1.15.Final
org.apache.maven.plugins
maven-compiler-plugin
3.2
1.8
1.8
UTF-8
true
package com.zdw.netty.demo;
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;
/**
* Create By zdw on 2019/7/23
* 自定义服务器端业务处理类
*/
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
//读取数据事件
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("Server:"+ctx);
ByteBuf byteBuf = (ByteBuf)msg;
System.out.println("客户端发来的消息:"+byteBuf.toString(CharsetUtil.UTF_8));
}
//数据读取完毕事件
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(Unpooled.copiedBuffer("我就是穷到没钱了啊",CharsetUtil.UTF_8));
//将数据写到ChannelPipeline 中当前ChannelHandler 的下一个ChannelHandler 开始处理(出站)
}
//异常发生事件
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();//关闭上下文
}
}
上述代码定义了一个服务器端业务处理类,继承ChannelInboundHandlerAdapter,并分别重写了三个方法。
package com.zdw.netty.demo;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
/**
* Create By zdw on 2019/7/23
* 服务端程序类
*/
public class NettyServer {
public static void main(String[] args) throws Exception {
//1、创建一个线程组用来处理网络事件(接受客户端连接)
NioEventLoopGroup bossGroup = new NioEventLoopGroup();
//2、创建一个线程组用来处理网络事件(处理通道 I/O 读写事件)
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
//3、创建服务端启动助手来配置参数
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup,workerGroup)//4、设置两个线程组
.channel(NioServerSocketChannel.class)//5、使用NioServerSocketChannel作为服务器端的通道实现
.option(ChannelOption.SO_BACKLOG,128)//6、设置线程队列中等待的个数
.childOption(ChannelOption.SO_KEEPALIVE,true)//7、保持活动连接状态
.childHandler(new ChannelInitializer() {//8、创建一个通道初始化对象
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//9、往Pipeline 链中添加自定义的业务处理handler,NettyServerHandler服务器端自定义业务类
socketChannel.pipeline().addLast(new NettyServerHandler());
System.out.println("----------Server is Ready----------");
}
});
//10.启动服务器端并绑定端口,等待接受客户端连接(非阻塞)
ChannelFuture future = serverBootstrap.bind(9999).sync();
System.out.println("===========Server is Starting============");
//11、关闭通道,关闭线程池
future.channel().closeFuture().sync();
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
package com.zdw.netty.demo;
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;
/**
* Create By zdw on 2019/7/23
* 自定义客户端业务处理类
*/
public class NettyClientHandler extends ChannelInboundHandlerAdapter {
//通道就绪事件
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("Client:"+ctx);
ctx.writeAndFlush(Unpooled.copiedBuffer("还钱给我啊", CharsetUtil.UTF_8));//写消息
}
//通道读取事件
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf byteBuf = (ByteBuf)msg;
System.out.println("服务端发送来的数据:"+byteBuf.toString(CharsetUtil.UTF_8));
}
//通道读取完毕事件
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
}
//异常发生事件
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
package com.zdw.netty.demo;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
/**
* Create By zdw on 2019/7/23
* 客户端程序
*/
public class NettyClient {
public static void main(String[] args) throws Exception {
//1、创建一个 EventLoopGroup 线程组
EventLoopGroup group = new NioEventLoopGroup();
//2、创建客户端启动助手
Bootstrap bootstrap = new Bootstrap();
//3、设置参数
bootstrap.group(group)//3、设置EventLoopGroup线程组
.channel(NioSocketChannel.class)//4、设置NioSocketChannel作为客户端通道实现
.handler(new ChannelInitializer() {//5、创建一个通道初始化对象
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//6.往Pipeline 链中添加自定义的业务处理handler
socketChannel.pipeline().addLast(new NettyClientHandler());
System.out.println("-----------Client is Ready------------");
}
});
//7、启动客户端,等待连接上服务器端(非阻塞)
ChannelFuture future = bootstrap.connect("127.0.0.1", 9999).sync();
//8、等待连接关闭(非阻塞)
future.channel().closeFuture().sync();
}
}
测试效果:
接下来我们在入门案例的基础上再实现一个多人聊天案例,具体代码如下所示:
业务处理类通过继承SimpleChannelInboundHandler 类自定义了一个服务器端业务处理类,并在该类中重写了四个方法,当通道就绪时,输出在线;当通道未就绪时,输出下线;当通道发来数据时,读取数据;当通道出现异常时,关闭通道。
package com.zdw.netty.chat;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import java.util.ArrayList;
import java.util.List;
/**
* Create By zdw on 2019/7/23
* 网络聊天服务器端的业务处理类,注意继承的是 SimpleChannelInboundHandler,他的泛型代表的是消息的数据类型
*/
public class ChatServerHandler extends SimpleChannelInboundHandler {
public static List channels = new ArrayList<>();//存储已经就绪的通道
//通道就绪,相当于上线了
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();//得到通道
channels.add(channel);//添加到集合中
System.out.println("Server:【"+channel.remoteAddress().toString().substring(1)+"】用户在线");
}
//通道未就绪,相当于掉线了
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
channels.remove(channel);//从集合中移除
System.out.println("Server:【"+channel.remoteAddress().toString().substring(1)+"】用户掉线了");
}
//读取数据
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, String s) throws Exception {
Channel channel = channelHandlerContext.channel();//得到当前通道
//把读取到的消息广播到其他就绪通道channels
for(Channel targetChannel : channels){
if(targetChannel != channel){//排除当前通道
//把当前通道的消息 s 写给其他通道
targetChannel.writeAndFlush("Server:用户:【"+channel.remoteAddress().toString().substring(1)+"】说:"+s+"\n");
}
}
}
//发生异常
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
Channel channel = ctx.channel();
System.out.println("Server:【"+channel.remoteAddress().toString().substring(1)+"】出了异常:"+cause.getMessage());
ctx.close();
}
}
通过Netty 编写了一个服务器端程序,里面要特别注意的是:我们往Pipeline链中添加了处理字符串的编码器和解码器(这个编解码器的类型是根据我们的消息类型而定义的,Netty中还提供了Object的对象编解码器,需要注意的是:要在添加自定义的处理器之前就添加编解码器),它们加入到Pipeline 链中后会自动工作,使得我们在服务器端读写字符串数据时更加方便(不用人工处理ByteBuf)。
package com.zdw.netty.chat;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
/**
* Create By zdw on 2019/7/23
* 网络聊天的服务端
*/
public class ChatServer {
private int port;//服务器监听的端口号
public ChatServer(int port){
this.port=port;
}
//任务方法
public void run() {
//创建两个线程组,用来执行客户端的连接和IO读写操作
NioEventLoopGroup bossGroup = new NioEventLoopGroup();//连接操作
NioEventLoopGroup workerGroup = new NioEventLoopGroup();//读写 IO操作
try {
//创建服务器端启动助手,用来设置启动参数
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup,workerGroup)//设置两个线程组
.channel(NioServerSocketChannel.class)//设置服务器端的通道实现为NioServerSocketChannel
.childHandler(new ChannelInitializer() {//设置通道的初始化实现对象
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();//得到pipeline链
pipeline.addLast("decoder",new StringDecoder());//往pipeline中添加一个字符串解码器
pipeline.addLast("encoder",new StringEncoder());//往pipeline中添加一个字符串编码器
pipeline.addLast("handler",new ChatServerHandler());//往pipeline链中添加一个自定义的业务处理器
}
})
.option(ChannelOption.SO_BACKLOG,128)//设置线程队列中等待的个数
.childOption(ChannelOption.SO_KEEPALIVE,true);//保持活动连接状态
System.out.println("Netty Chat Server is Starting -------");
//启动服务器端并绑定端口,等待接受客户端连接(非阻塞)
ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
//关闭通道,
channelFuture.channel().closeFuture().sync();
}catch (Exception e){
e.printStackTrace();
}finally {
//关闭线程池
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
System.out.println("Netty Chat Server is closed...............................");
}
}
public static void main(String[] args) {
new ChatServer(9999).run();
}
}
通过继承SimpleChannelInboundHandler 自定义了一个客户端业务处理类,重写了一个方法用来读取服务器端发过来的数据。
package com.zdw.netty.chat;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
/**
* Create By zdw on 2019/7/23
* 网络聊天客户端的业务处理类,继承的是SimpleChannelInboundHandler类,String泛型代表的是消息类型
*/
public class ChatClientHandler extends SimpleChannelInboundHandler {
//通道就绪事件
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
System.out.println("Client:【"+channel.localAddress().toString().substring(1)+"】用户上线了");
}
//通道未就绪事件
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
System.out.println("Client:【"+channel.localAddress().toString().substring(1)+"】用户掉线了");
}
// 通道数据读取事件
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, String s) throws Exception {
//读取服务器端的数据消息,打印到控制台
System.out.println(s.trim());
}
//异常发生事件
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
通过Netty 编写了一个客户端程序,里面要特别注意的是:我们往Pipeline 链中添加了处理字符串的编码器和解码器,他们加入到Pipeline 链中后会自动工作,使得我们在客户端读写字符串数据时更加方便(不用人工处理ByteBuf)。
package com.zdw.netty.chat;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import java.util.Scanner;
/**
* Create By zdw on 2019/7/23
* 网络聊天的客户端代码
*/
public class ChatClient {
private final String host; //服务器端IP 地址
private final int port; //服务器端端口号
public ChatClient(String host,int port){
this.host = host;
this.port = port;
}
public void run(){
//创建一个客户端的线程池组
EventLoopGroup group = new NioEventLoopGroup();
try {
//创建客户端的启动助手,进行参数设置
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)//设置线程组
.channel(NioSocketChannel.class)//设置客户端的通道实现为NioSocketChannel
.handler(new ChannelInitializer() {//定义通道初始化器
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();//得到pipeline链
pipeline.addLast("decoder",new StringDecoder());//往pipeline中添加字符串解码器
pipeline.addLast("encoder",new StringEncoder());//往pipeline中添加字符串编码器
pipeline.addLast("handler",new ChatClientHandler());//往pipeline中添加自定义的业务处理器
}
});
//启动客户端,等待连接服务器端(非阻塞)
ChannelFuture channelFuture = bootstrap.connect(host, port).sync();
Channel channel = channelFuture.channel();
System.out.println("--------Client:"+channel.localAddress().toString().substring(1)+"--连接成功---------");
//控制台读取客户端手动录入的消息,然后发送给服务器端,服务器端进行消息的广播
Scanner scanner = new Scanner(System.in);
while(scanner.hasNextLine()){
String msg = scanner.nextLine();//得到客户录入的消息
channel.writeAndFlush(msg+"\r\n");//向服务器端发送数据
}
}catch (Exception e){
e.printStackTrace();
}finally {
group.shutdownGracefully();
}
}
public static void main(String[] args) {
new ChatClient("127.0.0.1",9999).run();
}
}
开启服务器端和两个客户端,进行测试,效果如下:
我们在编写网络应用程序的时候要注意codec(编解码器),因为数据在网络中传输的都是二进制字节码数据,而我们拿到的目标数据往往不能是二进制字节码数据,因此在发送数据时就要编码,在接收数据时就需要进行解码。
codec 的组成部分有两个:decoder(解码器)和encoder(编码器)。encoder 负责把业务数据转换成字节码数据,decoder 负责把字节码数据转换成业务数据。
其实Java 的序列化技术就可以作为codec 去使用,但是它的硬伤太多:
1. 无法跨语言,这应该是Java 序列化最致命的问题了。
2. 序列化后的体积太大,是二进制编码的5 倍多。
3. 序列化性能太低。
由于Java 序列化技术硬伤太多,因此Netty 自身提供了一些codec,如下所示:
Netty 提供的解码器:
1. StringDecoder, 对字符串数据进行解码
2. ObjectDecoder,对Java 对象进行解码 ........
Netty 提供的编码器:
1. StringEncoder,对字符串数据进行编码
2. ObjectEncoder,对Java 对象进行编码........
Netty 本身自带的ObjectDecoder 和ObjectEncoder 可以用来实现POJO 对象或各种业务对象的编码和解码,但其内部使用的仍是Java 序列化技术,所以我们不建议使用。因此对于POJO 对象或各种业务对象要实现编码和解码,我们需要更高效更强的技术。
Protobuf 是Google 发布的开源项目,全称Google Protocol Buffers,特点如下:
支持跨平台、多语言(支持目前绝大多数语言,例如C++、C#、Java、python 等)
高性能,高可靠性
使用protobuf 编译器能自动生成代码,Protobuf 是将类的定义使用.proto 文件进行描述,
然后通过protoc.exe 编译器根据.proto 自动生成.java 文件
目前在使用Netty 开发时,经常会结合Protobuf 作为codec (编解码器)去使用,具体用法如下所示。
com.google.protobuf
protobuf-java
3.6.1
假设我们要处理的数据是图书信息,那就需要为此编写proto 文件
syntax = "proto3";
option java_outer_classname = "BookMessage";
message Book{
int32 id = 1;
string name = 2;
}
把上面的Book.proto文件放到protoc工具的bin目录下,然后执行下面的命令,生成我们需要的java文件:
protoc --java_out=. Book.proto
这个类我们不要编辑它,直接拿着用即可,该类内部有一个内部类,这个内部类才是真正的POJO,一定要注意。
1、在编写客户端程序时,要向Pipeline 链中添加ProtobufEncoder 编码器对象。
2、在往服务器端发送图书(POJO)时就可以使用生成的BookMessage 类搞定,非常方便
3、在编写服务器端程序时,要向Pipeline 链中添加ProtobufDecoder 解码器对象。
4、在服务器端接收数据时,直接就可以把数据转换成POJO 使用,非常方便