Netty源码解析
任务的执行
NioEventLoop的run()
- 先整体看下这个run()方法:
- 主要完成三件事:① 选择就绪channel;② 处理就绪channel的IO;③ 处理任务队列的任务。
选择就绪channel
- 先跟踪下calculateStrategy()方法:
- 再跟踪下select()方法:若当前任务队列中没有任务,则会执行阻塞式选择
- 该select方法主要完成4件事,下面详细分析。
① 处理定时任务队列中的马上就到执行时间的第一个定时任务
- 跟踪一下delayNanos()方法:
- 其中 START_TIME:
// 该时间为所有定时任务开始计时的时间点
private static final long START_TIME = System.nanoTime();
② 处理具有新添加任务的情况
③ 处理阻塞式选择的情况
④ 解决NIO的Bug
- SELECTOR_AUTO_REBUILD_THRESHOLD的默认值512:
处理就绪channel的IO
- 跟踪processSelectedKeys()方法:
selectKeys
- 其是一个优化过的set:将set集合变为了数组,读取效率更高。
处理优化过的selectedKey
处理任务队列的任务
- 跟踪 runAllTasks()方法:
fetchFromScheduledTaskQueue()
- 从定时任务队列中取出所有需要马上执行的任务,并放入到taskQueue
pollTask()
- 从任务队列获取一个任务:
afterRunningAllTasks()
- 若该任务为null,则执行收尾任务:
继续绑定doBand0()
- 回过头再来跟踪下前面没有说的doBand0()继续绑定方法:
- 继续往下跟踪:
至此,绑定操作已经跟踪到jdk底层了。
Client端启动
Channel的初始化
initAndRegister()
channel的创建
注册register()
- 和Netty Server端的注册代码一样,最终会进入doRegister()方法完成注册:
连接doResolveAndConnect0
地址解析resolve()
处理连接doConnect()
- 继续跟踪 invokeConnect() 方法:
Pipeline
Pipeline的创建
TailContext
- tail节点是一个inbound处理器,用于释放资源。
- 再看一下 TAIL_NAME,了解下节点名称怎么命名的:
- 继续跟踪下TailContext的父类构造器:
- 继续跟踪核心方法 ChannelHandlerMask.mask0():
HeadContext
- head节点既是一个inbound处理器,又是一个outbound处理器。
- 作为inbound处理器,其用于将消息向下一个节点传递;作为outbound处理器,其用于完成直接的底层操作。
ChannelInitializer
Server端添加ChannelInitializer
- 对于 Netty Server 而言,在服务端启动时,ChannelInitializer这个处理器只会创建一次:
- 当有客户端连接Server时,会添加这个处理器,并且是同一个。
Client端添加ChannelInitializer
- 对于 Netty Client 而言,ChannelInitializer这个处理器是在 Bootstrap 的 init() 初始化方法中被添加到 pipeline 中的:
initChannel()方法是怎么被触发执行的
- handlerAdded() 方法:当 当前处理器被添加到pipeline后,该方法就会被调用。
- 对于Netty Server端,客户端每连接一次,该方法就执行一次;而对于Netty Client端,该方法只会执行一次。
处理器的添加
处理器节点所绑定的eventLoop
- 处理器节点绑定的 eventLoop 的来源有三种情况:
- 与当前处理器节点所添加的 pipeline 所在的 channel 所绑定的 eventLoop 是同一个。【同步方式】
- 在 addLast() 中指定 group,再通过一种 option 设置,可以让使用这个 group 的所有处理器均使用同一个 eventLoop,这个 eventLoop 来自于指定的 group。
- 在 addLast() 中指定 group,再通过一种 option 设置,可以让每一个处理器均拥有一个不同的 eventLoop,这个 eventLoop 来自于指定的 group。
整体分析
检查处理器是否被多次添加
创建新节点
- 获取节点名称:
- 先跟踪下构造器:
- childExecutor(group):
插入新节点
获取当前处理器节点的eventLoop
执行hanndlerAdded()回调
处理器的删除
- 初始化channel后的收尾工作,就会去删除处理器。
pipeline.context()
- pipeline.context()方法:从头节点的下一个节点开始,遍历整个pipeline,要么找到了返回,要么返回null。
收尾工作
消息的传递与处理
Inbound事件的传递
- 服务端启动类:
- 定义服务端处理器:
public class ChannelInboundHandler1 extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("111");
ctx.fireChannelRead(msg);
}
/**
* 当channel被激活时会触发该方法的执行,一个pipeline中只会执行第一个被重写的channelActive()
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.channel().pipeline().fireChannelRead("Hello World 111");
}
}
public class ChannelInboundHandler2 extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("222");
ctx.fireChannelRead(msg);
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.channel().pipeline().fireChannelRead("Hello World 222");
}
}
public class ChannelInboundHandler3 extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("333");
ctx.fireChannelRead(msg);
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.channel().pipeline().fireChannelRead("Hello World 333");
}
}
- 测试:启动服务端,通过
telnet localhost 8888
连接Server,输出如下
Outbound操作的传递
- 服务端启动类:
- 定义服务端处理器:
public class ChannelOutboundHandler1 extends ChannelOutboundHandlerAdapter {
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
System.out.println("1111 " + msg);
ctx.write(msg, promise);
}
}
public class ChannelOutboundHandler2 extends ChannelOutboundHandlerAdapter {
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
System.out.println("2222 " + msg);
ctx.write(msg, promise);
}
@Override
public void handlerAdded(final ChannelHandlerContext ctx) throws Exception {
ctx.executor().schedule(new Runnable() {
@Override
public void run() {
ctx.channel().write("Hello World 2222");
}
}, 1, TimeUnit.SECONDS);
}
}
public class ChannelOutboundHandler3 extends ChannelOutboundHandlerAdapter {
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
System.out.println("3333 " + msg);
ctx.write(msg, promise);
}
}
- 测试:启动服务端,通过
telnet localhost 8888
连接Server,输出如下
- 如果修改 ChannelOutboundHandler2 中 handlerAdded() 方法如下:
为什么会是这样呢?
- 首先,
TimeUnit.SECONDS.sleep(1);
和ctx.executor().schedule(...)
使用的线程是不一样的。 - 其次,服务端在添加处理器时,先添加 ChannelOutboundHandler1,再添加 ChannelOutboundHandler2,此时触发 ChannelOutboundHandler2 的handlerAdded() 方法的执行时,sleep(1)阻塞线程1秒,ChannelOutboundHandler3还没有添加到 pipeline 中。
- ctx.channel().write(…) 执行时,会从 pipeline 中的尾节点开始执行,所以会先执行 ChannelOutboundHandler2、再执行 ChannelOutboundHandler1。
异常的传递与处理
- 服务端启动类:
- 修改 ChannelInboundHandler2 产生异常:
- 修改 ChannelInboundHandler1 和 ChannelInboundHandler3 :
public class ChannelInboundHandler1 extends ChannelInboundHandlerAdapter {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("exception -- InboundHandler111");
ctx.fireExceptionCaught(cause);
}
}
public class ChannelInboundHandler3 extends ChannelInboundHandlerAdapter {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("exception -- InboundHandler333");
ctx.fireExceptionCaught(cause);
}
}
- 修改 ChannelOutboundHandler1 、ChannelOutboundHandler2:
public class ChannelOutboundHandler1 extends ChannelOutboundHandlerAdapter {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("exception -- OutboundHandler111");
ctx.fireExceptionCaught(cause);
}
}
public class ChannelOutboundHandler2 extends ChannelOutboundHandlerAdapter {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("exception -- OutboundHandler222");
ctx.fireExceptionCaught(cause);
}
}
- 我们发现 ChannelOutboundHandlerAdapter 中的 exceptionCaught() 方法已经过时,但我们在这里仅仅就是为了演示异常信息的传递方向,所以没有妨碍。
- 定义异常处理器:
public class ExceptionCaughtHandler extends ChannelInboundHandlerAdapter {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
if (cause instanceof ArrayIndexOutOfBoundsException) {
System.out.println("发生ArrayIndexOutOfBoundsException异常");
}
// 不再向下传递
// ctx.fireExceptionCaught(cause);
}
}
- 那么异常怎么传递的呢?测试跑一下,看看结果:启动服务端,通过
telnet localhost 8888
连接Server,并发送数据,查看结果