简介
netty是使用java编写的高性能IO框架,旨在为高并发场景提供支持。netty可提供多种IO模型的支持,如OIO,NIO等。一般来说,非阻塞IO更适合于大规模高并发场景,我们使用netty主要也因为其封装了原生NIO,规避了其中复杂易出错的细节,更加易用、通用。
从示例讲起
netty既然是以java NIO为基础构建的(当然添加了大量特性),那就不能不了解java NIO的处理方式。NIO实现非阻塞的关键在于Selector(选择器)以及通道。下面先复习一下nio的示例,然后再对比netty。
java nio 示例
1 |
|
示例很简单,就是该服务器接受来自客户端的连接,并打印客户端的信息。解释如下:
- selector为选择器,即可以把要关注的事件注册到这里来。待该事件发生时,可给与通知。如将表示与服务器相连的serverSocketChannel注册,等有新连接过来后(accept事件),会通知该channel。
- ①处即为向选择器注册服务器channel及需关注的accept事件。
- ②处为向选择器注册接收的客户端channel,及关注的read事件
- ③处为客户端channel的read事件,处理read事件
- 从上面我们看出,selector注册了两种channel。一种是服务器channel,一种是客户端channel。前者只有一个,后者却很多,来一个请求便创建一个。且后者是前者在②处创建出来的。这两种channel有种父子关系的特征,后面netty就是用了这种概念表示。
下面看看netty的示例。学之前以为netty的非阻塞是以nio为基础创建的,应该差不多。看过来发现,果然,一点也不一样。
1 | public class NettyServer { |
解释一下
- netty和java原生nio实现方式相当不一样,它将nio和oio的实现方式做了统一,所以,上面非阻塞式的代码,只需改动一点即可实现oio的方式。
- 从概念上来讲,Bootstrap(引导器)的说法在java nio上是没有的,它相当于一个用于集成引导配置的容器。有ServerBootstrap(用于服务器)和BootStrap(用于客户端)。
- EventLoopGroup和EventLoop很重要。EventLoopGroup用于管理多个EventLoop,而EventLoop关联一个线程。同时EventLoop又充当选择器(Selector)的角色。用于选取已注册的准备好的事件。
- 还有一个点是childHandler,用于设置处理接收而来的客户端channel。而handler,则用与设置服务器Channel。
netty流程浅析
你可以以理解java nio的方式理解netty。ServerBootstrap作为服务端的引导类,作用为串联配置,启动服务器。EventLoop是netty中的重要部件,有java nio中的选择器的功能,可以选择就绪的channel,且自身关联一个Thread。看下图
这图是从《netty实战》中找的,可以简单概括出EventLoopGroup、EventLoop以及Channel的关系。
EventLoopGroup可在创建时指定EventLoop的个数,如图中为3个。同时,EventLoopGroup负责为每个新创建的Channel(客户端Channel)分配一个EventLoop。一般采用顺序循环的方式分配。如此,客户端连接一多,每个EventLoop就会负责多个Channel。EventLoop本身还关联着一个Thread。负责处理Channel的读或写等事件。每个Channel的整个生命周期的事件均由其关联的EventLoop的线程处理,这样可避免多线程环境下数据同步等问题。
对比java nio的选择器模型,可以发现一些相似之处。这里的selector同样负责多个channel的事件处理。
当channel的某个事件准备好后,就可以根据业务需要处理这些数据了(或读或写等)。netty的处理流程对应的是一个处理链。ChannelPipeline。处理链上可添加若干个单个处理逻辑:ChannelHandler。这种处理方式使得处理逻辑简单清晰(如可将处理编解码的handler和序列化以及处理业务逻辑的代码分离开)。并且当需要改变处理流程时(如出站数据需要进行加密),只需动态添加(或移除)一个ChannelHandler即可。
图中直观显示了ChannelPipeline和ChannelHandler的关系。上面示例中,设置childHandler即可设置一个ChannelHandler。
启动流程
ServerBootstrap作为server端的引导器,是串联整个流程的关键。前面也说过,netty的引导器分2种,服务端的(ServerBootStrap)和客户端的(Bootstrap)。其类继承关系如图
可见两者均继承了AbstractBootstrap,这里只分析ServerBootStrap。
ServerBootStrap的group方法用于设置EventLoopGroup。上面示例中类似于这样设置的。
group(new NioEventLoopGroup())
看其源码
1 |
|
以及
1 | /** |
这里需解释下parentGroup和childGroup的含义。parentGroup用于处理ServerSocketChannel对应的事件(也就是accept()事件),而childGroup用于处理客户端channel的读写等的事件。前面提过这两种channel有一种父子对应的关系,所以netty就这样做的命名。
从源码可以看出,如果只设置一个group,则parentGroup和childGroup共用一个group。
目前来说,一般在引导器中主动设置两个EventLoopGroup,即
1 | EventLoopGroup parentGroup = new NioEventLoopGroup(1); |
看一下NioEventLoopGroup
类的构造器方法
1 |
|
可知,传递的数字参数为线程数,跟踪代码知道,若不设线程数(无参),则最终为核心数的2倍。
1 | protected MultithreadEventLoopGroup(int nThreads, Executor executor, EventExecutorChooserFactory chooserFactory, |
ServerBootstrap最关键的方法是bind
方法。也是开启netty服务器的方法。其具体实现在父类AbstractBootstrap
。
调用链为 doBind()-> initAndRegister()->init()。init()依靠子类实现。这也是模板方法的应用。看看init()方法。
1 | void init(Channel channel) throws Exception { |
init(channel)的参数channel要说明一下。其来源于NioServerSocketChannel,经反射得到的。
serverBootstrap.group(group).channel(NioServerSocketChannel.class)
也即这个channel是与服务器相关联的channel,这些代码为设置服务端channel的pipeline和handler。看看ServerBootstrapAcceptor
。
1 |
|
ServerBootstrapAcceptor
继承了ChannelInboundHandlerAdapter
。用于负责接收客户端的连接。当连接过来后注册到childGroup
中。
handler与childHandler的区别在于前者处理服务端handler,如接收新客户端连接;后者处理客户端连接,如客户端读写等事件。
文本为简要介绍netty流程,后续尝试逐步分析。若有问题还请指正。