扫描下方二维码或者微信搜索公众号
菜鸟飞呀飞
,即可关注微信公众号,阅读更多Spring源码分析
和Java并发编程
文章。
1.问题
当 netty 的服务端启动以后,就可以开始接收客户端的连接了。那么在 netty 中,服务端是如何来进行新连接的创建的呢?在开始进行源码阅读之前,可以先思考以下三个问题。
- 服务端是如何检测到有新的客户端请求接入的(后面简称新连接接入)?
- 在 JDK 原生的 NIO 中,服务端会通过ServerSocketChannel.accept() 来为新接入的客户端创建对应的客户端 channel,那么在 netty 中服务端又是如何来处理新连接的接入的呢?
- 在 netty 中网络 IO 的读写操作都是在 NioEventLoop 线程中进行的,那么客户端 channel 是如何和工作线程池中的 NioEventLoop 绑定的呢?
2.检测新连接接入
在上一篇文章Netty 源码分析系列之 NioEventLoop 的执行流程中,分析了 NioEventLoop 线程在启动后,会不停地去循环处理网络 IO 事件、普通任务和定时任务。在处理网络 IO 事件时,当轮询到 IO 事件类型为 OP_ACCEPT 时(如下代码所示),就表示有新客户端来连接服务端了,也就是检测到了新连接。这个时候,服务端 channel 就会进行新连接的读取。
if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
unsafe.read();
}
可以看到,当是 OP_ACCEPT 事件时,就会调用unsafe.read() 方法来进行新连接的接入。此时 unsafe 对象是 NioMessageUnsafe 类型的实例,为什么呢?因为只有服务端 channel 才会对 OP_ACCEPT 事件感兴趣,而服务端 channel 中 unsafe 属性保存的是 NioMessageUnsafe 类型的实例。
read()方法的源码很长,但它主要干了两件事,第一:调用 doReadMessages()方法来读取连接;第二:将读取到的连接通过服务端 channel 中的 pipeline 来进行传播,最终执行每一个 handler 中的 channelRead()方法。
3.创建客户端 channel
服务端 channel 在监听到 OP_ACCEPT 事件后,会为新连接创建一个客户端 channel,后面数据的读写均是通过这个客户端 channel 来进行的。而这个客户端 channel 是通过 doReadMessages()方法来创建的,该方法是定义在 NioServerSocketChannel 中的,下面是其源码。
protected int doReadMessages(List<Object> buf) throws Exception {
SocketChannel ch = SocketUtils.accept(javaChannel());
try {
if (ch != null) {
// 将原生的客户端channel包装成netty中的客户端channel:NioSocketChannel
buf.add(new NioSocketChannel(this, ch));
return 1;
}
} catch (Throwable t) {
// 异常日志打印等...
}
return 0;
}
在该方法中,首先会通过 javaChannel()获取到 JDK 原生的服务端 channel,即 ServerSocketChannel,这个原生的服务端 channel 是被保存在 NioServerSocketChannel 的ch属性中,在初始化 NioServerSocketChannel 时会对ch属性赋值(可以参考这篇文章:Netty 源码分析系列之服务端 Channel 初始化)。创建完 JDK 原生的服务端 channel 后,会通过 SocketUtils 这个工具类来创建一个 JDK 原生的客户端 channel,即 SocketChannel。SocketUtils 这个工具类的底层实现,实际上就是调用 JDK 原生的 API,即 ServerSocketChannel.accept()。
在创建完原生的 SocketChannel 后,netty 需要将其包装成 netty 中定义的服务端 channel 类型,即:NioSocketChannel。如何包装的呢?通过 new 关键字调用 NioSocketChannel 的构造方法来进行包装。在构造方法中,做了很多初始化工作。跟踪源码,发现会调用到 AbstractNioChannel 类的如下构造方法。
protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
// 此时的parent = NioServerSocketChannel,ch = SocketChannel(JDK原生的客户端channel),readInterestOp = OP_READ
super(parent);
// 保存channel和感兴趣的事件
this.ch = ch;
this.readInterestOp = readInterestOp;
try {
// 设置为非阻塞
ch.configureBlocking(false);