netty服务端Chanel注册与线程模型详解(附带源码分析)

一、概述

在上一篇博客中,介绍了netty其实是有其背后的理论支撑的——reactor模式,并且详细介绍了reactor模式。而这边博客将会详细介绍netty是怎么运用reactor模式的,代码是怎么设计和封装的。

二、服务端代码

现在看一段使用netty编写的服务端代码:

public class MyServer {

    public static void main(String[] args) throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
                    .childHandler(new MyServerInitializer());

            ChannelFuture channelFuture = serverBootstrap.bind(8899).sync();
            channelFuture.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}
public class MyServerInitializer extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();

        pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));
        pipeline.addLast(new LengthFieldPrepender(4));
        pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
        pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
        pipeline.addLast(new MyServerHandler());
    }
}
public class MyServerHandler extends SimpleChannelInboundHandler<String> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        System.out.println(ctx.channel().remoteAddress() + ", " + msg);

        ctx.channel().writeAndFlush("from server: " + UUID.randomUUID());
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }

}

这里做一个简单介绍,第一段代码是服务端启动类代码,而且这个启动类代码套用的都是一个通用的模板,一个bossGroup,一个workerGroup。然后再新建一个ServerBootstrap实例,并且调用这个实例的相关方法将新建好的bossGroup和workerGroup传入,然后传入要创先的Channel类型以及childHandler。而ChildHandler一般是我们自己编写的ChannelHandler。

ChildHandler我这里使用的是ChannelInitializer,这是一种特殊的ChannelInboundHandler,用于注册的时候给绑定的Channel做一些初始化工作,比如给Channel关联的ChannelPipeline添加Handler,包括用户自己编写(上面的MyServerHandler就是用户自己编写的)的以及Netty自带的。值得注意的是ChannelInitializer的initChannel方法只会被调用一次,initChannel调用返回后,当前的ChannelInitializer实例会被从绑定的Channel关联的ChannelPipeline中移除。

这里值得一说的是:使用netty,用户编写的代码是很简单的,套用的是一套通用的代码模板,并且如果你不想使用NIO,有时为了兼容以前代码还得继续使用OIO,Netty也提供了支持,只需要把NioEventLoopGroup改成OioEventLoopGroup,channel方法的参数由NioServerSocketChannel.class改成OioServerSocketChannel.class就可以了,但是模板使用的还是那一套。对用户来说,使用起来非常简单,大量的复杂的初始化工作以及实现细节都有netty框架本身进行了封装和实现。还有Netty框架本身的代码抽象、封装、设计也非常棒,层次结构很清晰,有兴趣的可以根据上面的代码,跟进去看下Bootstrap、EventLoopGroup、EventLoop、Channel、ChannelPipeline、ChannelHandler、ChannelHandlerContext这些组件的源码,真的很棒,值得学习。

三、Channel注册

根据上面的代码,使用的是NioEventLoopGroup,可以猜测的出底层使用的是NIO来进行注册的。如果使用的是NIO,那么肯定是在绑定端口号后,进行NIO的Channel注册,然后后面通过Selector的select方法来实现通道IO事件的监听。那么我们直接跟进上面代码的bind方法找下注册的代码。
在AbstractBootstrap里的doBind方法可以看到以下的一段代码:
netty服务端Chanel注册与线程模型详解(附带源码分析)_第1张图片

看名字,initAndRegister我们大概就知道这个方法是用来初始化和注册用的。跟进去:

final ChannelFuture initAndRegister() {
        Channel channel = null;
        try {
            channel = channelFactory.newChannel();
            init(channel);
        } catch (Throwable t) {
            if (channel != null) {
                // channel can be null if newChannel crashed (eg SocketException("too many open files"))
                channel.unsafe().closeForcibly();
            }
            // as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
            return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
        }

        ChannelFuture regFuture = config().group().register(channel);
        if (regFuture.cause() != null) {
            if (channel.isRegistered()) {
                channel.close();
            } else {
                channel.unsafe().closeForcibly();
            }
        }

这里解释以下:

channel = channelFactory.newChannel();
init(channel);

这两行代码,显然是用来创建Channel和初始化Channel的,这里的Channel并不是java NIO原生的Channel,而是Netty自己写的Channel接口,其中这个Channel的一个实现NioServerSocketChannel内部在实例生成时会创建一个Java Nio的ServerSocketChannel用于注册。可以看下代码:

	private static final SelectorProvider DEFAULT_SELECTOR_PROVIDER = SelectorProvider.provider();

	public NioServerSocketChannel() {
        this(newSocket(DEFAULT_SELECTOR_PROVIDER));
    }
    
    private static ServerSocketChannel newSocket(SelectorProvider provider) {
        try {
            /**
             *  Use the {@link SelectorProvider} to open {@link SocketChannel} and so remove condition in
             *  {@link SelectorProvider#provider()} which is called by each ServerSocketChannel.open() otherwise.
             *
             *  See #2308.
             */
            return provider.openServerSocketChannel();
        } catch (IOException e) {
            throw new ChannelException(
                    "Failed to open a server socket.", e);
        }
    }
    
    public NioServerSocketChannel(ServerSocketChannel channel) {
        super(null, channel, SelectionKey.OP_ACCEPT);
        config = new NioServerSocketChannelConfig(this, javaChannel().socket());
    }

	// 父类构造方法
	protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
        super(parent);
        this.ch = ch;
        this.readInterestOp = readInterestOp;
        try {
            ch.configureBlocking(false);
        } catch (IOException e) {
            try {
                ch.close();
            } catch (IOException e2) {
                if (logger.isWarnEnabled()) {
                    logger.warn(
                            "Failed to close a partially initialized socket.", e2);
                }
            }

            throw new ChannelException("Failed to enter non-blocking mode.", e);
        }
    }

接着回来看注册的代码:

ChannelFuture regFuture = config().group().register(channel);

解释下,config()返回的是ServerBootstrapConfig,

private final ServerBootstrapConfig config = new ServerBootstrapConfig(this);

这里可以看到,这个ServerBootstrapConfig里面会包含之前已经初始化好的ServerBootstrap。
接着看group方法:

	public final EventLoopGroup group() {
        return bootstrap.group();
    }

上面代码中的bootstrap的真是类型其实就是之前初始化好的ServerBootstrap。跟进去:

	public final EventLoopGroup group() {
        return group;
    }

这里的group就是之前我们自己创建的NioEventLoopGroup(跟代码其实是bossGroup,由此可见Channel注册,其实是bossGroup来做的)。

接着看register方法(MultithreadEventLoopGroup的register方法):

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

next()方法逻辑感兴趣的可以自行查看,我这里简单介绍下这个方法功能。我们知道EventLoopGroup是有多个EventLoop构成的,而这个next方法的功能就是根据一定算法从EventLoopGroup中选择一个EventLoop出来。所以继续看NioEventLoop的register方法,其实实现在其父类SingleThreadEventLoop:

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;
}

最终的实现在AbstractChannel里:

public final void register(EventLoop eventLoop, final ChannelPromise promise) {
            if (eventLoop == null) {
                throw new NullPointerException("eventLoop");
            }
            if (isRegistered()) {
                promise.setFailure(new IllegalStateException("registered to an event loop already"));
                return;
            }
            if (!isCompatible(eventLoop)) {
                promise.setFailure(
                        new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName()));
                return;
            }

            AbstractChannel.this.eventLoop = eventLoop;

            if (eventLoop.inEventLoop()) {
                register0(promise);
            } else {
                try {
                    eventLoop.execute(new Runnable() {
                        @Override
                        public void run() {
                            register0(promise);
                        }
                    });
                } catch (Throwable t) {
                    logger.warn(
                            "Force-closing a channel whose registration task was not accepted by an event loop: {}",
                            AbstractChannel.this, t);
                    closeForcibly();
                    closeFuture.setClosed();
                    safeSetFailure(promise, t);
                }
            }
        }

从上面的代码可以看出,netty保证了注册的逻辑必须是由NioEventLoopGroup中的NioEventLoop持有的那个线程去执行的。看register0()方法:

private void register0(ChannelPromise promise) {
            try {
                // check if the channel is still open as it could be closed in the mean time when the register
                // call was outside of the eventLoop
                if (!promise.setUncancellable() || !ensureOpen(promise)) {
                    return;
                }
                boolean firstRegistration = neverRegistered;
                doRegister();
                neverRegistered = false;
                registered = true;

                // Ensure we call handlerAdded(...) before we actually notify the promise. This is needed as the
                // user may already fire events through the pipeline in the ChannelFutureListener.
                pipeline.invokeHandlerAddedIfNeeded();

                safeSetSuccess(promise);
                pipeline.fireChannelRegistered();
                // Only fire a channelActive if the channel has never been registered. This prevents firing
                // multiple channel actives if the channel is deregistered and re-registered.
                if (isActive()) {
                    if (firstRegistration) {
                        pipeline.fireChannelActive();
                    } else if (config().isAutoRead()) {
                        // This channel was registered before and autoRead() is set. This means we need to begin read
                        // again so that we process inbound data.
                        //
                        // See https://github.com/netty/netty/issues/4805
                        beginRead();
                    }
                }
            } catch (Throwable t) {
                // Close the channel directly to avoid FD leak.
                closeForcibly();
                closeFuture.setClosed();
                safeSetFailure(promise, t);
            }
        }
protected void doRegister() throws Exception {
        boolean selected = false;
        for (;;) {
            try {
                selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
                return;
            } catch (CancelledKeyException e) {
                if (!selected) {
                    // Force the Selector to select now as the "canceled" SelectionKey may still be
                    // cached and not removed because no Select.select(..) operation was called yet.
                    eventLoop().selectNow();
                    selected = true;
                } else {
                    // We forced a select operation on the selector before but the SelectionKey is still cached
                    // for whatever reason. JDK bug ?
                    throw e;
                }
            }
        }
    }

这个地方感兴趣的ops=0表示所有的IO都监听,read、write、connect、accept。
注册完成后会调用 pipeline.fireChannelRegistered();,我们添加到ChannelPipeline中的Handler的channelRegistered()方法会被调用。
这里讲的是服务端NioServerSocketChannel的注册,客户端使用的NioSocketChannel的注册逻辑一样,只不过Channel的初始化逻辑不一样。NioServerSocketChannel初始化的时候回向ChannelPipeline添加一个ServerBootstrapAcceptor(实际上是一个ChannelInboundHandler),主要做的事是将NioSocketChannel注册到workerGroup上。注册的逻辑还是使用的上面一套代码。

// 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);
    }
}

四、线程模型

上面说的注册操作,通过上面的分析,我们知道一定是由NioEventLoop中的持有的那个线程去执行的。所以要了解Netty的线程模型,那就要先研究下NioEventLoop这个类。而NioEventLoop又是被NioEventLoopGroup所持有,并且一个NioEventLoopGroup会持有一个或者多个NioEventLoop。先看NioEventLoopGroup的创建,最后执行的是下面代码:

	protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
                                            EventExecutorChooserFactory chooserFactory, Object... args) {
        if (nThreads <= 0) {
            throw new IllegalArgumentException(String.format("nThreads: %d (expected: > 0)", nThreads));
        }

        if (executor == null) {
            executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
        }

        children = new EventExecutor[nThreads];

        for (int i = 0; i < nThreads; i ++) {
            boolean success = false;
            try {
                children[i] = newChild(executor, args);
                success = true;
            } catch (Exception e) {
                // TODO: Think about if this is a good exception type
                throw new IllegalStateException("failed to create a child event loop", e);
            } finally {
                if (!success) {
                    for (int j = 0; j < i; j ++) {
                        children[j].shutdownGracefully();
                    }

                    for (int j = 0; j < i; j ++) {
                        EventExecutor e = children[j];
                        try {
                            while (!e.isTerminated()) {
                                e.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS);
                            }
                        } catch (InterruptedException interrupted) {
                            // Let the caller handle the interruption.
                            Thread.currentThread().interrupt();
                            break;
                        }
                    }
                }
            }
        }

        chooser = chooserFactory.newChooser(children);

        final FutureListener<Object> terminationListener = new FutureListener<Object>() {
            @Override
            public void operationComplete(Future<Object> future) throws Exception {
                if (terminatedChildren.incrementAndGet() == children.length) {
                    terminationFuture.setSuccess(null);
                }
            }
        };

        for (EventExecutor e: children) {
            e.terminationFuture().addListener(terminationListener);
        }

        Set<EventExecutor> childrenSet = new LinkedHashSet<EventExecutor>(children.length);
        Collections.addAll(childrenSet, children);
        readonlyChildren = Collections.unmodifiableSet(childrenSet);
    }

这里会初始化一个执行器executor,然后把这个执行器传入给每一个child。children[i] = newChild(executor, args);表示给每个child进行初始化,跟进去其实就是创建一个NioEventLoop。

	protected EventLoop newChild(Executor executor, Object... args) throws Exception {
        return new NioEventLoop(this, executor, (SelectorProvider) args[0],
            ((SelectStrategyFactory) args[1]).newSelectStrategy(), (RejectedExecutionHandler) args[2]);
    }

前面 在看注册的时候,有这么一行代码:

AbstractChannel.this.eventLoop = eventLoop;

表示在注册时,会把从NioEventLoopGroup中选出来的那个NioEventLoop跟当前的Channel进行绑定,那之后Channel上的所有的操作都会使用这个绑定的NioEventLoop。所以我们使用NioEventLoop来执行Channel上的操作一般会这么来操作:

channel.eventLoop().execute(Runnable r);

所以来看一看这里面的实现,找到NioEventLoop的execute方法,其实现其实是在它的父类SingleThreadEventExecutor:

	public void execute(Runnable task) {
        if (task == null) {
            throw new NullPointerException("task");
        }

        boolean inEventLoop = inEventLoop();
        if (inEventLoop) {
            addTask(task);
        } else {
            startThread();
            addTask(task);
            if (isShutdown() && removeTask(task)) {
                reject();
            }
        }

        if (!addTaskWakesUp && wakesUpForTask(task)) {
            wakeup(inEventLoop);
        }
    }

这里同样保证了执行一定是在NioEventLoop所持有的那个线程去执行的。如果不是,并且当前NioEventLoop没有绑定线程,就会开启一个新的线程,并且把这个线程跟当前的NioEventLoop进行绑定。如果当前NioEventLoop绑定了线程,就直接添加任务,任务会在未来某个时刻被NioEventLoop持有的线程去执行。
可以看看startThread方法逻辑:

private void startThread() {
    if (state == ST_NOT_STARTED) {
        if (STATE_UPDATER.compareAndSet(this, ST_NOT_STARTED, ST_STARTED)) {
            doStartThread();
        }
    }
}
private void doStartThread() {
        assert thread == null;
        executor.execute(new Runnable() {
            @Override
            public void run() {
                thread = Thread.currentThread();
                if (interrupted) {
                    thread.interrupt();
                }

                boolean success = false;
                updateLastExecutionTime();
                try {
                    SingleThreadEventExecutor.this.run();
                    success = true;
                } catch (Throwable t) {
                    logger.warn("Unexpected exception from an event executor: ", t);
                } finally {
                    for (;;) {
                        int oldState = state;
                        if (oldState >= ST_SHUTTING_DOWN || STATE_UPDATER.compareAndSet(
                                SingleThreadEventExecutor.this, oldState, ST_SHUTTING_DOWN)) {
                            break;
                        }
                    }

                    // Check if confirmShutdown() was called at the end of the loop.
                    if (success && gracefulShutdownStartTime == 0) {
                        logger.error("Buggy " + EventExecutor.class.getSimpleName() + " implementation; " +
                                SingleThreadEventExecutor.class.getSimpleName() + ".confirmShutdown() must be called " +
                                "before run() implementation terminates.");
                    }

                    try {
                        // Run all remaining tasks and shutdown hooks.
                        for (;;) {
                            if (confirmShutdown()) {
                                break;
                            }
                        }
                    } finally {
                        try {
                            cleanup();
                        } finally {
                            STATE_UPDATER.set(SingleThreadEventExecutor.this, ST_TERMINATED);
                            threadLock.release();
                            if (!taskQueue.isEmpty()) {
                                logger.warn(
                                        "An event executor terminated with " +
                                                "non-empty task queue (" + taskQueue.size() + ')');
                            }

                            terminationFuture.setSuccess(null);
                        }
                    }
                }
            }
        });
    }

上面使用的executor就是NioEventLoopGroup初始化的时候创建的那个executor,它的execute逻辑是:

	public void execute(Runnable command) {
        threadFactory.newThread(command).start();
    }

就是创建一个线程启动,而doStartThread方法里面我们看到,执行逻辑中,会把当前执行任务的线程跟NioEventLoop进行绑定。

看到这里,我们可以做几点总结:

1. 一个EventLoopGroup当中会包含一个或者多个EventLoop。
2. 一个EventLoop在它的整个生命周期当中都只会与唯一的一个Thread进行绑定。
3. 所有的EventLoop所处理的I/O事件都将在它所关联的那个Thread上进行处理。
4. 一个Channel在它的整个生命周期中只会注册在一个EventLoop上。
5. 一个EventLoop在运行的过程当中,会被分配给一个或者多个Channel。

下面用两张图来展示,这样看起来更加清晰:
EventLoop的执行逻辑:
netty服务端Chanel注册与线程模型详解(附带源码分析)_第2张图片
Netty NIO线程模型:
netty服务端Chanel注册与线程模型详解(附带源码分析)_第3张图片

注意上面的这个模型图是基于非阻塞传输的(Nio和Aio)。我们知道Netty同样支持阻塞传输(Oio)。Oio的模型图会有所不同:
netty服务端Chanel注册与线程模型详解(附带源码分析)_第4张图片

图中EventLoop跟Channel是一一对应的,这个也跟我们传统的Oio编程模型相吻合。

最后在来一张Netty实现Reactor模式的总的模型图:
netty服务端Chanel注册与线程模型详解(附带源码分析)_第5张图片

你可能感兴趣的:(Netty)