Netty架构篇 - NioServerSocketChannel、NioSocketChannel
前言
在解析Netty中的Channel如何应用之前,首先看下如下的代码:
这段代码,一些使用过netty的朋友们,都蛮了解的。
再看一段nio的代码。
ServerSocketChannel channel = ServerSocketChannel.open();
channel.configureBlocking(false);
channel.socket().bind(new InetSocketAddress("192.168.0.106", 8080), 1024);
Selector selector = SelectorProvider.provider().openSelector();
channel.register(selector, SelectionKey.OP_ACCEPT);
在下面的源码分析中,你可以看到这段nio代码如何被封装到netty中。
接下来从server.channel(NioServerSocketChannel.class)
这行代码开始本篇文章的解析旅程。
服务端Channel的实例化
概述:
设置非阻塞模式,建立NioServerSocketChannel与ServerSocket的关联关系,然后通过ReflectiveChannelFactory完成NioServerSocketChannel的实例化。
首先看下外层的channelFactory(…)方法做了什么事。
很显然是设置ChannelFactory属性为一个ReflectiveChannelFactory实例。
然后看下ReflectiveChannelFactory实例如何构造的。
由前文可知,方法的参数,我们传递的是NioServerSocketChannel.class
。这里利用反射调用它的无参构造器进行实例化。
后面会分析何时调用这个newChannel()方法。
接下来看下NioServerSocketChannel这个类,声明如下:
接着看下它的无参构造器。
先看下newSocket(…)方法的处理逻辑。
这里的SelectorProvider,通过在ide工具中debug,我们可以知道实际上是KQueueSelectorProvider
。(由SelectorProvider.provider()方法生成)
可以看出得到的是nio原生的ServerSocketChannelImpl这个实例,并且将KQueueSelectorProvider
作为参数传入。
回到它的构造器方法。
- javaChannel():实际上是我们前面看到的ServerSocketChannelImpl。
- socket():构造一个ServerSocket实例。
由此,NioServerSocketChannelConfig将NioServerSocketChannel、ServerSocket与之关联。如同它的名字一样,存储相关的配置。
接着看调用其父类的构造器做了什么事。
- ch:ServerSocketChannelImpl类型。
由ch.configureBlocking(false)
这行代码,可以看出设置非阻塞模式。同时将readInterestOps属性设置为SelectionKey.OP_ACCEPT
,表示监听接收客户端的连接。
接着往下看。
对parent、id、unsafe、pipeline属性进行赋值。
在后续的AbstractBootstrap#initAndRegister()
方法有这样一行代码。
由此,由ReflectiveChannelFactory完成NioServerSocketChannel的实例化。
服务端Channel的初始化
这部分从代码样例的server.bind(port)
这行代码开始分析。
概述:
将ServerBoostrap#option(…)、attr(…)等方法设置的属性传递给NioServerSocketChannel。然后在DefaultChannelPipeline中添加ServerBootstrap#handler(…)方法定义的ChannelHandler以及ServerBootstrapAcceptor。
bind(…)方法会走到doBind(…)方法,如下:
doBind0()方法经过层层方法,会走到NioServerSocketChannel的如下方法中:
对应nio的ServerSocketChannel#bind(new InetSocketAddress(hostname, port), backlog)方法。
下面重点关注下initAndRegister()
方法。(剩余的方法内容会在注册中接着分析)
- channelFactory:ReflectiveChannelFactory类型。
- newChannel():由ReflectiveChannelFactory完成NioServerSocketChannel的实例化。
在ServerBootstrap#option(…)方法设置的参数传递给NioServerSocketChannelConfig,也就是NioServerSocketChannel的属性。
在ServerBootstrap#attr(AttributeKey, Object)方法设置的key、value传递给NioServerSocketChannel。
接着看init(…)剩余的方法。
在ServerBootstrap#childGroup(…)、childHandler(…)、childOption(…)以及childAttr(…)方法设置的参数传递给ServerBootstrapAcceptor。
在DefaultChannelPipeline中,添加了一个ChannelInitializer。其在initChannel(…)方法,定义了在DefaultChannelPipeline中添加ServerBootstrap#handler(…)方法定义的ChannelHandler、以及ServerBootstrapAcceptor实例。
ChannelInitializer,一个特定的ChannelInboundHandler,提供了便捷的方法(也就是initChannel(…)方法)用于初始化一个Channel。
ServerBootstrapAcceptor,同样是一个ChannelInboundHandler。其封装了如下属性:
服务端Channel的注册
概述:
核心是调用nio原生的ServerSocketChannelImpl#register(Selector sel, int ops, Object att)方法,将Netty的NioServerSocketChannel作为attr参数进行传入,从而与nio原生的ServerSocketChannelImpl建立关联性,最后完成Channel在Selector上的注册。
接着AbstractBootstrap#initAndRegister()方法,展开分析,有如下一行代码:
- group():ServerBootstrap#group(…)方法指定的NioEventLoopGroup#bossGroup。
首先看下next()方法,如下:
这里是调用父类的next()方法,然后进行类型转换。EventLoop是一个ScheduledExecutorService。
- chooser:PowerOfTwoEventExecutorChooser类型。
- executors:实际上是NioEventLoop数组。
使用轮询的方式选择一个线程进行channel的注册。
现在,回过头看下register(…)的处理逻辑。
- channel:NioServerSocketChannel。
- this:指代NioEventLoop。
从register(…)方法中,挑选了如下代码,也就是注册Channel的核心代码。
接着跟进代码(有省略)。
- javaChannel():nio原生的ServerSocketChannelImpl。
- unwrappedSelector():KQueueSelectorImpl。
- this:指代NioServerSocketChannel。
可以看出实质上是nio原生方法 AbstractSelectableChannel - register(Selector sel, int ops, Object att)
。将ServerSocketChannelImpl注册到Selector上,同时将Netty的NioServerSocketChannel与之关联。
最后我们看下unwrappedSelector()实际的处理。
此时的unwrappedSelector是获取SelectorTuple#unwrappedSelector属性。
对应nio原生的Selector selector = SelectorProvider.provider().openSelector()
实际上底层就是调用KQueueSelectorProvider#openSelector()方法。
客户端Channel的实例化
与服务端的不同的是,这里是NioSocketChannel。
这段代码与前面所说的服务端Channel的实例化相同,这里不再分析。
客户端Channel的初始化
从bootstrap.connect(host, port)
方法开始分析。
在Bootstrap#doResolveAndConnect(…)方法中找到如下代码:
与我们之前的服务端Channel的初始化相同。这里不再分析。
客户端Channel的注册
接着上面的initAndRegister()方法,找到如下代码:
同样,还是与服务端Channel的注册完全相同。这里不再分析。