前言
到目前为止,已经看了不少框架的源码了,但如何对源码做笔记并写博客,不同框架适合的做笔记方式也不同
对于Netty框架而言,我认为以对比学习+回答问题的方式来浅析源码会好一些,因为Netty本质上也就是基于NIO的一个非阻塞网络编程框架,那么本篇笔记就对比一个简单的NIO非阻塞网络编程,看看Netty是如何实现的
流程解析
看Netty源码主要就是去看其如何实现Reactor模式的,关于Reactor的演变,在《初识Netty》中我做了对比,不进行赘述
本节主要比较一下NIO的传统非阻塞方式与Netty的多Reactor模式的区别
NIO传统非阻塞
Netty多Reactor
区别
Reactor一般就是指selector,在netty中,NioEventLoop既是一个单线程,又绑定了一个selector,绑定到selector上的Channel触发的IO事件都由NioEventLoop执行
- Netty使用两种selector,一种负责处理accept,当有客户端请求ServerSocketChannel时,将对应的SocketChannel绑定到第二个selector中,第二种selector就负责处理绑定到自己身上的Channel对应的事件
- 上述两个流程图虽然看上去改了很多,但核心代码段都是相同的(自己写一遍就知道),所以看源码的时候就对比着看
接下来,就通过NIO的非阻塞代码,看看Netty是怎么实现的
NIO网络编程例子
该例子来源于《尚硅谷的netty教程》
在注释中,我写明了每一步对应netty中的实现
public class NIOServer {
public static void main(String[] args) throws IOException {
// ServerBootstrap进行bind的时候,执行initAndRegistry会创建ServerSocketChannel
// 并将ServerSocketChannel绑定到一个NioServerSocketChannel中
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
// NioEventLoop在构造方法进行初始化时,就会创建唯一一个selector
Selector selector = Selector.open();
// ServerBootstrap进行bind的时候,执行initAndRegistry
// 会使用NioEventLoop来执行register进行异步注册
// 并且注册时不会绑定任何事件
// 直到bind监听好端口后,才会通过HeadContext该Handler通过执行ChannelActive方法将Accept事件绑定
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
// ServerBootstrap在将SeverSocketChannel注册到selector后就会进行bin
serverSocketChannel.socket().bind(new InetSocketAddress(6666));
// NioEventLoop中执行run方法,进行循环
while (true){
// run方法中,如果队列中没有任务,则调用select进行阻塞
if (selector.select(1000)==0){
continue;
}
// 获取到事件,则执行processSelectedKeys来处理对应IO事件,整个流程大概就是如下逻辑
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = selectionKeys.iterator();
// processSelectedKey对每个事件进行判断和处理
while (keyIterator.hasNext()){
SelectionKey key = keyIterator.next();
// Accept事件只会在SeverSocketChannel中发生
// 会在ServerBootstrapAcceptor的ChannelRead方法中完成下述过程
// 该Handler在ServerBootstrap启动时就被加入Pipline中
if (key.isAcceptable()){
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
// 启动一个childEventLoop来执行注册逻辑
// 同样的,注册时没有绑定事件,事件由Head绑定
socketChannel.register(selector,SelectionKey.OP_READ, ByteBuffer.allocate(1024));
}
// Read事件,触发NioByteUnsafe的read方法
// 会将缓冲区的数据写入ByteBuffer,并传递到pipline中
// 依次执行pipline中的handler的channelRead方法完成回调
if (key.isReadable()) {
SocketChannel channel = (SocketChannel)key.channel();
ByteBuffer buffer = (ByteBuffer) key.attachment();
channel.read(buffer);
}
keyIterator.remove();
}
}
}
}
根据注释,可以比较容易地去看懂netty整个框架怎么运作的
Netty源码
netty的源码大部分跟下来还是比较容易的,在本节主要根据前一节的注释中,一些不太好理解的方法进行浅析,以问答的形式
NioServerSocketChannel与ServerSocketChannel关系
- NioServerSocketChannel是netty自定义的Channel类,其中绑定了Pipline,NioEventLoop等成员变量,但其网络功能,如bind,都要通过ServerSocketChannel来完成,因此NioServerSocketChannel会对应一个ServerSocketChannel
- 我们知道,在使用select方法获取SelectionKeys时,只能从SelectionKey中获取到触发事件对应的NIO原生Channel,那NioServerSocketChannel该如何获取呢?方法就是注册的时候将NioServerSocketChannel作为ServerSocketChannel注册到selector的附件
相关代码在 AbstractNioChannel的doRegister()中
@Override
protected void doRegister() throws Exception {
boolean selected = false