JavaEE 企业级分布式高级架构师(十九)异步事件驱动框架Netty(4)

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,并发送数据,查看结果

在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

讲文明的喜羊羊拒绝pua

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值