目录
1.启动流程
1.1 回顾nio中的启动流程
//1 netty 中使用 NioEventLoopGroup (简称 nio boss 线程)来封装线程和 selector
Selector selector = Selector.open();
//2 创建 NioServerSocketChannel,同时会初始化它关联的 handler,以及为原生 ssc 存储 config
NioServerSocketChannel attachment = new NioServerSocketChannel();
//3 创建 NioServerSocketChannel 时,创建了 java 原生的 ServerSocketChannel,serverSocketChannel的作用可以理解为一个注册器,netty中的ssc作为附件注册到原生的ssc上
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
//4 启动 nio boss 线程执行接下来的操作
//5 注册(仅关联 selector 和 NioServerSocketChannel),未关注事件
SelectionKey selectionKey = serverSocketChannel.register(selector, 0, attachment);
//6 head -> 初始化器 -> ServerBootstrapAcceptor -> tail,初始化器是一次性的,只为添加 acceptor
//7 绑定端口
serverSocketChannel.bind(new InetSocketAddress(8080));
//8 触发 channel active 事件,在 head 中关注 op_accept 事件
//表示当客户端接入的时候触发这个事件
selectionKey.interestOps(SelectionKey.OP_ACCEPT);
对于Nio中的启动流程,主要分为以下几步:
- 创建selector选择器 -> Selector selector = Selector.open();选择器是在new NioEventLoopGroup() 创建好的,EventLoop 里面是维护了一个选择器和一个线程。这块在第二节介绍。
- 创建java原生的SSC -> ServerSocketChannel.open();
- 通过注册,将selector与NioSSC关联起来,用于后续线程的切换 -> ssc.register(selector, 0, attachment);
- 绑定端口 -> bind(new InetSocketAddress(“localhost”, 8080));
- 监听事件,这里指Accept事件 -> key.interestOps(SelectionKey.OP_ACCRPT);
debug代码
public class AllocServer {
public static void main(String[] args) {
new ServerBootstrap().group(new NioEventLoopGroup())
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buffer = ctx.alloc().buffer();
System.out.println(buffer.getClass());
}
});
}
})
.bind(8080);
}
}
我们将断点打到bind方法,debug类型设置为Thread并点击make default作为默认类型。
1.2 initAndRegister()
我们看下bind方法的内部调用链,找到doBind方法。
1.2.1 初始化
在initAndRegister()方法中,我们先看初始化的逻辑,即
channel = channelFactory.newChannel();
init(channel);
1.2.2 注册
代码
ChannelFuture regFuture = config().group().register(channel);
1、注册的代码层级比较深,这里先标一下层级调用链。
2、注册核心逻辑
调起Nio线程执行注册的核心逻辑register0()方法。
debug线程也切换到NioEventLoop线程。
doRegister()方法
pipeline.invokeHandlerAddedIfNeeded()方法
在执行完doRegister()方法方法后,在invokeHandlerAddedIfNeeded中会回调initAndRegister()方法中添加的处理器中的initChannel方法,给pipeline上又添加了一个ServerBootstrapAcceptor处理器。
safeSetSuccess(promise):将promise对象中的结果设置为success,表明已经完成注册
1.2.3 dobind绑定
我们去掉前面的断点,然后将断点打到doBind0方法上。
bind方法的层级比较深,建议通过debug模式往下看,不要自己点。我这里把流程标一下。
最终,核心代码在AbstractChannel.java中的bind方法。里面有两个重要的方法,如下图。
1.2.4 小结
- init 和 register regFuture 处理
- init (main线程执行)
a. 创建NioServerSocketChanne
l (main线程执行)
b. 添加NioServerSocketChannel
初始化 handler (main线程执行)
初始化 handler等待调用 (main未调用)
向 nio ssc 中加入了accept handler
(在 accept 事件发生后建立连接) - register(切换线程)
a. 启动 nio boss 线程(main线程执行)
b. 原生 ssc(ServerSocketChannel
) 注册到 selector 未关注事件(nio-thread执行)
c. 执行NioServerSocketChannel
初始化 handler(nio-thread执行)
- regFuture 等待回调 doBind0
原生ServerSocketChannel
绑定(nio-thread执行)
触发NioServerSocketChannel
active 事件(nio-thread执行)