总体上,Netty是一个高性能网络编程框架,不仅仅是对java socket接口的封装,它的功能可概括如下:
- 事件驱动的异步编程模型(reactor模型)
- 灵活的线程池管理;
- 支持常见的传输层协议和应用层协议;
- 屏蔽了底层bug(比如NIO Epoll Bug),更加稳定高效。
总之,netty性能较好,功能较全,文档相对完善,基本成为了java领域socket编程的首选工具。
Reactor模型
很多文章将Recactor模型和IO模型混在一起介绍,是不合适的;因为这二者不在同一个层次,reactor模型层次更高,它一般基于select io模型来实现。
Reactor模型核心思想是,是通过一个reactor来同时处理多个连接,并将事件分发给不同的工作线程(处理业务逻辑的线程)来处理。因此Reactor模型也可以叫做分发者模型。
Reactor模型有三种模式:单Reactor单线程,单Reactor多线程,主从Reactor多线程。本文不打算详解Reactor模型,建议大家看看《Scalable IO in Java》。
“主从Reactor多线程模式”几乎没有任何不可伸缩的瓶颈点,被Netty所支持,下面是该模式的图解:
上图有以下几个角色:
- acceptor,处理连接请求的处理器,将新连接提交给subReactor;
- mainReactor,负责监听,并调用acceptor来处理连接请求(mainReactor也可以使用线程池);
- subReactor,处理socket连接的IO事件,并派发到对应线程来执行;
- ThreadPool,工作线程池,为IO事件的处理提供线程支持;
Netty的结构
我们通过对照一段示例代码和上面Reactor模型结构图,来领会Netty的设计结构。
示例代码
这是一段极简的,使用Netty的server端模板式代码:
public class NettyServer {
public static void main(String[] args) throws InterruptedException {
1 ServerBootstrap bootstrap = new ServerBootstrap();
2 NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
3 NioEventLoopGroup workerGroup = new NioEventLoopGroup(5);
4 bootstrap.group(bossGroup, workerGroup);
5 bootstrap.channel(NioServerSocketChannel.class);
6 bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
7 @Override
8 protected void initChannel(SocketChannel ch) {
9 ChannelPipeline pipeline = ch.pipeline();
10 pipeline.addLast(new NettyServerHandler());
11 }
12 });
13 ChannelFuture sync = bootstrap.bind(6667).sync();
14 sync.channel().closeFuture().sync();
}
}
- 行1:ServerBootstrap是Netty的启动引导类,负责将netty组件粘合起来,它的存在简化了netty程序编写;
- 行2:NioEventLoopGroup可类比reactor,同时也是一个线程池,bossGroup扮演类似mainReactor的角色;
- 行3:workerGroup扮演subReactor的角色;
- 行4:bootStrap将bossGroup和childGroup结合起来;
- 行5:bootstrap设定使用的Channel类型为NioServerSocketChannel,从名字上看与java nio对应;
- Netty同样将socket连接抽象为Channel;
- 由于Netty底层可以使用多种socket api,因此有多种Channel类型,对应java nio的是Nio(Server)SocketChannel
- 行6:设定Channel对应的事件处理器,Channel处理器被抽象为ChannelHandler;
- Netty采用事件驱动结构,所有的IO操作都作为事件被处理;
- ChannelHandler作为事件处理器,对应reactor模型图中那些诸如**“decode,encode,compute”**的逻辑;
- 行9:每个channel有一个ChannelPipeline
- channel可能有很多ChannelHandler,pipeline将它们组织起来;
- 行10:将一个handler加入到pipeline
- 行13:绑定端口,sync是同步等待绑定成功的意思;
- 行14:closeFuture也是一个ChannelFuture,等待关闭,使得主线程不退出;
- ChannelFuture就是java Future的特化版本,netty异步化操作的工具。
再看一下NettyServerHandler的实现:
public class NettyServerHandler extends SimpleChannelInboundHandler<ByteBuf> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) {
System.out.println("接受到客户端消息:" + msg.toString(StandardCharsets.UTF_8));
ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(100).writeBytes("This is Netty Server\r\n".getBytes());
ctx.writeAndFlush(byteBuf);
}
}
NettyServerHandler的channelRead0回调在收到客户端的数据时被激活,ByteBuf是字节数据的包装类型,和java nio buffer有点类似。ChannelHandlerContext和pipline和channel都有关系,暂时不用完全理解,ctx.writeAndFlush向客户端发送数据。
此时,再看网上流传一张Netty架构图,会更清晰:
- BossGroup和WorkerGroupd都是线程池
- NioEventLoop是池内的单个线程
- 之所以叫event loop,是因为它不断循环地检查并处理channel事件;
- 当然还可以执行用户提交的异步任务(runAllTasks);
- 最底下的Pipline包含一系列ChannelHandler,这些handler运行在eventLoop内。
Channel的多样性
Chanel是netty对一个通信通道的抽象,支持IO操作,可以看做底层socket链接的一个封装。通过Channel还可以获得链接的状态以及一些配置信息。
当一个连接建立时(发生在BossGroup),Netty创建一个Channel,并交给WorkGroup管理,WorkGroup会选定一个EventLoop为该Channel服务。Channel初始化过程需要绑定一个或多个ChannelHander,用来处理它的事件,这些handler组成一个Pipeline。该Pipeline运行在它所绑定的EventLoop内。
随传输层层协议的不同,有不同的Channel实现类:
- NioSocketChannel:socket通信通道;
- NioServerSocketChannel:服务端的socket监听通道;
- NioDatagramChannel:UPD通道;
- NioSctpChannel:Sctp通信通道;
- NioSctpServerChannel:Sctp服务端监听通道;
即使传输层限定为TCP,不同的底层socket接口,也可能导致不同的Channel实现类:
- NioSocketChannel:基于java封装的,平台无关的nio接口;
- KQueueSocketChannel:Mac系统下,netty直接使用kqueue接口,从而提高效率;
- EpollSocketChannel:linux系统下,netty直接使用epoll接口,从而提高效率;
Acceptor在哪?
由于接收socket连接请求的处理逻辑相对比较固定,基本不需要二次开发,所以Netty将它隐藏起来了,后续我们会将它挖掘出来。
可以提前透露,netty将acceptor自动装配为NioServerSocketChannel pipeline的头节点。