死磕Netty源码之新连接接入源码解析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/RobertoHuang/article/details/81007468

前言

本博客主要是介绍Netty在新连接接入后的相关处理

关于我:http://huangth.com

GitHub地址:https://github.com/RobertoHuang

免责声明:本系列博客并非原创,主要借鉴和抄袭闪电侠占小狼等知名博主博客。如有侵权请及时联系

新连接建立

新连接建立可以分为以下三个步骤

1.检测到有新的连接
2.将新的连接注册到Worker线程组
3.注册新连接的读事件

在Reactor线程模型详解博客中我们已经知道当服务端读取到IO事件(新连接接入事件)后,会调用processSelectedKey方法对事件进行处理,此处以新连接接入事件为例它最后会调用底层的unsafe进行read操作

public void read() {
    assert eventLoop().inEventLoop();
    final ChannelPipeline pipeline = pipeline();
    final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
    do {
        int localRead = doReadMessages(readBuf);
        if (localRead == 0) {
            break;
        }
        if (localRead < 0) {
            closed = true;
            break;
        }
    } while (allocHandle.continueReading());
    int size = readBuf.size();
    for (int i = 0; i < size; i ++) {
        pipeline.fireChannelRead(readBuf.get(i));
    }
    readBuf.clear();
    pipeline.fireChannelReadComplete();
}

这里有两个主要的方法:

1.doReadMessages
2.pipeline.fireChannelRead

doReadMessages

protected int doReadMessages(List<Object> buf) throws Exception {
    SocketChannel ch = javaChannel().accept();
    try {
        if (ch != null) {
            buf.add(new NioSocketChannel(this, ch));
            return 1;
        }
    } catch (Throwable t) {
        // ...
    }
    return 0;
}

该方法主要作用是通过JDK底层的API获取到SocketChannel,然后包装成Netty自己的NioSocketChannel。NioSocketChannel与服务端启动时创建的NioServerSocketChannel最主要的区别在于它们关注的事件不同,NioSocketChannel的构造方法如下

public NioSocketChannel(Channel parent, SocketChannel socket) {
    super(parent, socket);
    config = new NioSocketChannelConfig(this, socket.socket());
}

protected AbstractNioByteChannel(Channel parent, SelectableChannel ch) {
    super(parent, ch, SelectionKey.OP_READ);
}

这里我们看到一个SelectionKey.OP_READ,说明这个Channel关心读事件而服务端的Channel关心ACCEPT事件。接下来调用父类AbstractNioChannel构造,后续过程与服务端启动流程一致此处不再赘述

pipeline.fireChannelRead

接着来看pipeline.fireChannelRead(readBuf.get(i))方法,关于Pipeline我们将在下一篇博客中详细介绍。我们知道客户端在启动的过程中会往Pipeline中添加一个ServerBootstrapAcceptor(连接处理器的东西),所以到这里服务端Channel对应的Pipeline的数据结构为:Hea⇋ServerBootstrapAcceptor⇋Tail,在调用pipeline.fireChannelRead时会依次触发这三个节点上的channelRead方法,接下来我们重点关注ServerBootstrapAcceptor的channelRead方法,代码如下

public void channelRead(ChannelHandlerContext ctx, Object msg) {
    final Channel child = (Channel) msg;
    child.pipeline().addLast(childHandler);
    setChannelOptions(child, childOptions, logger);
    for (Entry<AttributeKey<?>, Object> e: childAttrs) {
        child.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());
    }

    try {
        childGroup.register(child).addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture future) throws Exception {
                if (!future.isSuccess()) {
                    forceClose(child, future.cause());
                }
            }
        });
    } catch (Throwable t) {
        forceClose(child, t);
    }
}

首先获取我们之前实例化的NioSocketChannel,然后将我们设置的chlidHandler添加到NioSocketChannel对应的Pipeline中(这里的chlidHandler对应用户通过.childHandler()设置的Handler),代码执行到这里NioSocketChannel中Pipeline对应的数据结构为: head⇋ChannelInitializer⇋tail,接着设置NioSocketChannel对应的attr和option,然后进入到childGroup.register(child),这里的childGroup就是WorkerGroup,接下来我们进入NioEventLoopGroup的register方法

public ChannelFuture register(Channel channel) {
    return next().register(channel);
}

public ChannelFuture register(Channel channel) {
    return register(new DefaultChannelPromise(channel, this));
}

public ChannelFuture register(final ChannelPromise promise) {
    ObjectUtil.checkNotNull(promise, "promise");
    promise.channel().unsafe().register(this, promise);
    return promise;
}

这段代码和服务端启动的时候像BossGroup注册NioServerSocketChannel是类似的,通过next()方法获取到NioEventLoop然后将Channel注册到该NioEventLoop上(即将该Channel与NioEventLoop上的Selector进行绑定)。注册的逻辑最终是交给unsafe对象完成的,我们继续跟进unsafe的register方法,代码如下

public final void register(EventLoop eventLoop, final ChannelPromise promise) {
    //...
    AbstractChannel.this.eventLoop = eventLoop;
    if (eventLoop.inEventLoop()) {
        register0(promise);
    } else {
        try {
            eventLoop.execute(new Runnable() {
                @Override
                public void run() {
                    register0(promise);
                }
            });
        } catch (Throwable t) {
            //...
        }
    }
}

由于这里是在Boss线程中执行的IO操作所以不会是跟Worker线程是同一个线程,所以eventLoop.inEventLoop()返回false,最后会通过eventLoop.execute的方式去执行注册任务。在Reactor线程模型中我们讲到在调用execute的时候,如果是首次添加任务那这个NioEventLoop线程会被启动,所以从此Worker线程开始执行,接下来看下具体的注册逻辑

private void register0(ChannelPromise promise) {
    try {
        boolean firstRegistration = neverRegistered;
        doRegister();
        neverRegistered = false;
        registered = true;

        pipeline.invokeHandlerAddedIfNeeded();

        safeSetSuccess(promise);
        pipeline.fireChannelRegistered();
        if (isActive()) {
            if (firstRegistration) {
                pipeline.fireChannelActive();
            } else if (config().isAutoRead()) {
                beginRead();
            }
        }
    } catch (Throwable t) {
        //...
    }
}

和服务端启动过程一样,先是调用doRegister()执行真正的注册过程

protected void doRegister() throws Exception {
    boolean selected = false;
    for (;;) {
        try {
            selectionKey = javaChannel().register(eventLoop().selector, 0, this);
            return;
        } catch (CancelledKeyException e) {
            //...

        }
    }
}

该Channel绑定到NioEventLoop对应的Selector上去,后续该Channel的事件轮询、事件处理、异步Task执行都由此线程负责,绑定完Reactor线程之后调用pipeline.invokeHandlerAddedIfNeeded()代码如下

final void invokeHandlerAddedIfNeeded() {
    assert channel.eventLoop().inEventLoop();
    if (firstRegistration) {
        firstRegistration = false;
        callHandlerAddedForAllHandlers();
    }
}

往下跟callHandlerAddedForAllHandlers方法

private void callHandlerAddedForAllHandlers() {
    final PendingHandlerCallback pendingHandlerCallbackHead;
    synchronized (this) {
        assert !registered;

        registered = true;

        pendingHandlerCallbackHead = this.pendingHandlerCallbackHead;

        this.pendingHandlerCallbackHead = null;
    }

    PendingHandlerCallback task = pendingHandlerCallbackHead;
    while (task != null) {
        task.execute();
        task = task.next;
    }
}

这里有个对象叫pendingHandlerCallbackHead,我们发现它是在callHandlerCallbackLater方法中被初始化的

private void callHandlerCallbackLater(AbstractChannelHandlerContext ctx, boolean added) {
    assert !registered;

    PendingHandlerCallback task = added ? new PendingHandlerAddedTask(ctx) : new PendingHandlerRemovedTask(ctx);
    PendingHandlerCallback pending = pendingHandlerCallbackHead;
    if (pending == null) {
        pendingHandlerCallbackHead = task;
    } else {
        // Find the tail of the linked-list.
        while (pending.next != null) {
            pending = pending.next;
        }
        pending.next = task;
    }
}

当我们在Channel注册到之前添加或删除Handler时,此时没有EventExecutor可执行HandlerAdd或HandlerRemove事件,所以Netty为此事件生成一个相应任务等注册完成后在调用执行任务。添加或删除任务可能有很多个,DefaultChannelPipeline使用一个链表存储,链表头部为先前的字段pendingHandlerCallbackHead

接下来我们继续分析task.execute方法, 它主要是完成NioSocketChannel对应的Pipeline的初始化

void execute() {
    // ...
    callHandlerAdded0(ctx);
    // ...
}

通过上面对pendingHandlerCallbackHead的分析,这里肯定会调用ChannelInitializer的handlerAdded方法

public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
    if (ctx.channel().isRegistered()) {
        initChannel(ctx);
    }
}

private boolean initChannel(ChannelHandlerContext ctx) throws Exception {
    if (initMap.putIfAbsent(ctx, Boolean.TRUE) == null) { 
        try {
            initChannel((C) ctx.channel());
        } catch (Throwable cause) {
            exceptionCaught(ctx, cause);
        } finally {
            remove(ctx);
        }
        return true;
    }
    return false;
}

ChannelInitializer的initChannel主要完成两个功能以下两个功能

首先调用initChannel((C) ctx.channel())进入用户自定义的代码完成Pipeline的初始化

.childHandler(new ChannelInitializer<SocketChannel>() {
     @Override
     public void initChannel(SocketChannel ch) throws Exception {

     }
 })

然后在finally中调用remove方法将ChannelInitializer删除

private void remove(ChannelHandlerContext ctx) {
    try {
        ChannelPipeline pipeline = ctx.pipeline();
        if (pipeline.context(this) != null) {
            pipeline.remove(this);
        }
    } finally {
        initMap.remove(ctx);
    }
}

执行该方法前NioSocketChannel对应的Pipeline的数据结构为:head⇋ChannelInitializer⇋tail,执行该方法后ChannelInitializer被删除,NioSocketChannel对应的Pipeline的数据结构为:head⇋自定义的HandlerContext⇋tail

到目前为止我们完成了新连接的注册、pipeline的绑定,但是新连接注册的时候的感兴趣事件还是0还无法进行读写操作,新连接对读事件的绑定是在pipeline.fireChannelActive方法中完成的,它最后会调用到AbstractNioChannel的doBeginRead

protected void doBeginRead() throws Exception {
    final SelectionKey selectionKey = this.selectionKey;
    if (!selectionKey.isValid()) {
        return;
    }

    readPending = true;

    final int interestOps = selectionKey.interestOps();
    if ((interestOps & readInterestOp) == 0) {
        selectionKey.interestOps(interestOps | readInterestOp);
    }
}

前面register0()方法的时候向selector注册的事件代码是0,而readInterestOp对应的事件代码是SelectionKey.OP_READ,所以本段代码的用处是将SelectionKey.OP_READ事件注册到Selector中去,fireChannelActive的执行逻辑在服务端启动过程中有详细描述,至此已完成客户端新连接接入的操作。下一篇博客将介绍Pipeline相关的源码解析

猜你喜欢

转载自blog.csdn.net/RobertoHuang/article/details/81007468