1

基本组件

  • NioEventLoop: 监听客户端连接和处理客户端读写
  • Channel: 对一个Socket连接的封装,可进行数据的读写
  • Pipline: 对数据的逻辑处理链
  • ChannelHandler: Pipline里的一个处理逻辑
  • ByteBuf: 字节缓冲的容器

EventLoopGroup、NioEventLoop、Channel之间的关系

bossGroup一般只创建一个EventLoop,负责接受请求,workGroup默认让它创建 2*CPU 个EventLoop,负责通信。
一个EventLoop可以绑定多个Channel,一个Channel只能绑定到一个NioEventLoop。

Netty服务端启动步骤:

1.创建服务端Channel

创建JDK定义的Channel,将它包装成netty的Channel,并创建一些基本组件绑定在Channel上

3-2-3

newChannel方法里通过反射调用clazz.newInstance()返回一个Channel对象,这个clazz的类型是在创建ServerBootStrap的时候传入的NioServerSocketChannel。

ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
    .channel(NioServerSocketChannel.class)
    .childOption(ChannelOption.TCP_NODELAY, true)
    .childAttr(AttributeKey.newInstance("childAttr"), "childAttrValue")
    .handler(new ServerHandler())
    .childHandler(null);

NioServerSocketChannel的创建流程:

3-2-4

2.初始化服务端Channel

初始化一些基本属性,添加一些逻辑处理

3-3-1

3.注册Selector

将JDK的Channel注册到事件轮询器Selector上,并把netty的服务端Channel作为一个attachment绑定在JDK的Channel上

3-4-1

4.端口绑定

调用JDK API,实现对本地端口的监听

AbstractChannel#bind

public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
    ...

    // wasActive表示端口绑定之前的状态
    boolean wasActive = isActive();
    try {
        // 调用javaChannel().bind将JDK的channel绑定到指定端口
        doBind(localAddress);
    } catch (Throwable t) {
        safeSetFailure(promise, t);
        closeIfClosed();
        return;
    }
    // 端口绑定之后,isActive返回true
    if (!wasActive && isActive()) {
        invokeLater(new Runnable() {
            @Override
            public void run() {
                pipeline.fireChannelActive();
            }
        });
    }

    safeSetSuccess(promise);
}

fireChannelActive方法传播事件调用到doBeginRead方法,添加selectionKey感兴趣的事件。
就是在服务端端口绑定成功之后,开始接受连接请求

protected void doBeginRead() throws Exception {
    // selectionKey是注册服务端channel到selector上的时候返回的
    final SelectionKey selectionKey = this.selectionKey;
    if (!selectionKey.isValid()) {
        return;
    }

    readPending = true;
    // 获取当前感兴趣的事件,初始值是0
    final int interestOps = selectionKey.interestOps();
    if ((interestOps & readInterestOp) == 0) {
        // 添加感兴趣的事件,readInterestOp代表的是accept事件,接受请求
        selectionKey.interestOps(interestOps | readInterestOp);
    }
}

NioEventLoop

NioEventLoop创建

NioEventLoop在NioEventLoopGroup构造函数中被创建,默认创建 2*CPU 核数个NioEventLoop
每个NioEventLoop持有一个线程。

4-2-1

newChild()方法创建NioEventLoop,主要做了三件事:

  • 保存线程执行器ThreadPerTaskExecutor
  • 为NioEventLoop创建一个Selector
  • 创建一个MpscQueue任务队列

ThreadPerTaskExecutor: 执行每个任务都去创建一个线程去执行

chooserFactory.newChooser()
chooser的作用: 采用循环的方式为新连接绑定一个NioEventLoop

NioEventLoop启动

NioEventLoop启动触发条件

  • 服务端启动绑定端口

  • 新连接接入通过chooser绑定一个NioEventLoop

NioEventLoop执行

protected void run() {
    for (;;) {
        try {
            switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
                case SelectStrategy.CONTINUE:
                    continue;
                case SelectStrategy.SELECT:
                    // 轮询I/O事件
                    select(wakenUp.getAndSet(false));

                    if (wakenUp.get()) {
                        selector.wakeup();
                    }
                default:
                    // fallthrough
            }

            cancelledKeys = 0;
            needsToSelectAgain = false;
            final int ioRatio = this.ioRatio;
            if (ioRatio == 100) {
                try {
                    processSelectedKeys();
                } finally {
                    // Ensure we always run tasks.
                    runAllTasks();
                }
            } else {
                final long ioStartTime = System.nanoTime();
                try {
                    processSelectedKeys();
                } finally {
                    // Ensure we always run tasks.
                    final long ioTime = System.nanoTime() - ioStartTime;
                    runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
                }
            }
        } catch (Throwable t) {
            handleLoopException(t);
        }
        // Always handle shutdown even if the loop processing threw an exception.
        try {
            if (isShuttingDown()) {
                closeAll();
                if (confirmShutdown()) {
                    return;
                }
            }
        } catch (Throwable t) {
            handleLoopException(t);
        }
    }
}

整个执行过程主要做三件事:

  • select(): 检查是否有I/O事件
  • processSelectKeys(): 处理I/O事件
  • runAllTasks: 处理异步任务队列
  1. select():

select()方法也是在一个 for(;;) 循环里执行

  • 判断select操作是否超过截止时间
  • 执行阻塞式的select
  • 为避免jkd空轮训的bug,检查空轮训次数;如果超过512就创建一个新的selector,并将原有selector的selectionKey赋给新的selector
  1. processSelectKeys()

对NioEventLoop读取到的selectionKey对应的事件进行处理

  1. runAllTasks()

任务有两种,一种是普通的任务,另一种是定时任务。在处理之前,任务被添加到了不同的队列里,所以要先把两种任务合并到一个任务队列,然后依次执行。
此外,还要检查当前时间是否超过了允许执行任务的时间,如果超时就直接中断,进行下一次NioEventLoop的执行循环。

新连接接入

检测新连接

总体流程

在服务端的NioEventLoop执行的第二个过程processSelectKeys中检测出accept事件后, 通过JDK的accept方法创建JDK的一个channel

创建NioSocketChannel

逐层调用父类构造函数,将服务端NioServerSocketChannel和accept()创建的channel作为参数;设置阻塞模式为false,保存读事件,创建unsafe、pipline组件,禁止Nagel算法

服务端channel在检测到新连接并创建完NioSocketChannel后,会通过ServerBootStrapAcceptor对NioSocketChannel做一些处理:

  • 添加childHandler
  • 设置options和attrs
  • 通过chooser在childGroup中选择NioEventLoop并注册selector

创建完成之后,调用pipeline.fireChannelActive()向selector注册读事件,开始准备接受I/O数据。

在执行任务的时候,经常会用到inEventLoop()方法,这个方法是用来判断当前线程是否是EventLoop中的线程。
如果是直接执行,不是就把任务放到MpscQueue(Multi producer single consumer)任务队列中,这样任务交给指定的NioEventLoop执行,不必加锁。

pipeline

初始化

pipeline在创建channel的时候被创建

protected AbstractChannel(Channel parent) {
    this.parent = parent;
    id = newId();
    unsafe = newUnsafe();
    pipeline = newChannelPipeline();
}

pipeline中的节点是ChannelHandlerContext类型的,双向链表结构;在初始化时就会创建head、tail两个节点

protected DefaultChannelPipeline(Channel channel) {
    this.channel = ObjectUtil.checkNotNull(channel, "channel");
    succeededFuture = new SucceededChannelFuture(channel, null);
    voidPromise =  new VoidChannelPromise(channel, true);

    tail = new TailContext(this);
    head = new HeadContext(this);

    head.next = tail;
    tail.prev = head;
}

tail和head分别是inbound和outbound类型的。

channelHandler的添加在用户代码配置childHandler时完成

childHandler(new ChannelInitializer<SocketChannel>() {
    @Override
    public void initChannel(SocketChannel ch) {
        ch.pipeline().addLast(new AuthHandler());
    }
})

添加时首先判断是否重复添加,然后创建节点(HandlerContext)添加至链表,最后回调添加完成的事件。

channelHandler的删除操作与添加类似,首先找到节点将它从链表上删除,最后回调删除handler事件。

事件传播

事件传播有两种方式:

  • pipeline传播: 这种方式会从pipeline的head节点开始传播
  • channelContext传播: 从当前节点开始传播

inbound事件

inBound事件包括register、active、active事件

每次传播都会找到下一个inbound类型的节点,最后传播给tail节点(tail也是inbound类型)

outBound事件

outBound事件传播方向与inBound相反,是从tail向head传播

异常事件传播与以上两种不同,它不关心是inBoundhandler还是outboundhandler,并且只会从发生异常的节点开始向后传播,到达tail节点。
所以,最好在tail节点前创建一个专门用于处理异常的节点,重写exceptionCaught方法,这样所有异常都能被捕获。


tianqibucuo
8 声望0 粉丝

下一篇 »
Netty-ByteBuf

引用和评论

0 条评论