ServerBootstrapAcceptor将建立好连接的socketChannel注册到workerGroup中的某个EventLoop(或者说是Selector)上,而且将用户编写的childHandler加到了每个socketChannel的ChannelPipeline中。ServerBootstrapAcceptor相当于起了转发的作用,建立好连接后Channel实际的读写IO事件是由workerGroup中的EventLoop来处理。
再回过头来,看Reactor模式的多Reactor版本(一主多从),不知道你是否能get到其中的含义?
注意:上面代码里的childGroup就是来自我们在写Server端NettyServer代码时定义的workerGroup EventLoopGroup workerGroup = new NioEventLoopGroup();
我觉得能坚持看到这个地方的朋友应该能明白,只是这里又啰嗦了一下。
讲到这里,我觉得其实后面Client端的情况都不用讲了,已经很清晰了。不过为了文章的完整性,还是写下去比较好。
Server端accept连接请求后,Client端此时同样也有了IO事件。同样还是走processSelectedKey()那个方法,不过执行的分支不一样。
int readyOps = k.readyOps(); // We first need to call finishConnect() before try to trigger a read(...) or write(...) as otherwise // the NIO JDK channel implementation may throw a NotYetConnectedException. if ((readyOps & SelectionKey.OP_CONNECT) != 0) { // remove OP_CONNECT as otherwise Selector.select(..) will always return without blocking // See https://github.com/netty/netty/issues/924 int ops = k.interestOps(); ops &= ~SelectionKey.OP_CONNECT; k.interestOps(ops); unsafe.finishConnect(); }最终调用doFinishConnect(),如下:
io.netty.channel.socket.nio.NioSocketChannel
@Override protected void doFinishConnect() throws Exception { if (!javaChannel().finishConnect()) { throw new Error(); } }之后,Client端与Server端就可以通过Channel读写数据,通过ChannelPipeline中的ChannelHandler对数据decode、compute、encode。
写在后面至此,本篇就大致讲清楚了Netty的Server端和Client端的整个启动并通信的过程以及如何对nio进行封装的。这里再贴一张在网络上流传较广的Netty工作原理图,相信此时再看这张图应该无比亲切吧。
整个过程确实比较绕。但回过头再看,有一个清晰的思路,然后时刻记着与nio的代码做对比,多点耐心也还能坚持下去,另外遇到搞不明白的地方再配合debug,会轻松许多。最后,由于本人能力有限,文中如有错误的理解、不恰当的描述,欢迎指出!