Netty框架源码篇 - 深入分析服务端启动流程

本文详细分析了Netty服务端启动的核心流程,从doBind方法开始,探讨了initAndRegister方法中的Channel实例化、初始化和注册过程。重点解析了NioServerSocketChannel的创建、初始化及注册到Selector的过程,最后介绍了doBind0方法中的bind操作和事件激活。整个启动流程揭示了Netty基于NIO的实现方式。
摘要由CSDN通过智能技术生成


前言

先回顾下,在服务端配置的常规代码。首先会先实例化EventLoopGroup和ServerBootstrap实例,之后,通过ServerBootstrap实例设置一系列参数,包括:指定传输的Channel类型、指定监听端口以及添加子ChannelHandler。接下来,最关键的是调用bind方法,而 bind 方法内部完成了服务端启动的核心逻辑,下面进行具体的分析

public void startServer(){
		//创建服务端的handler
		EchoServerHandler handler=new EchoServerHandler();
		//创建线程组
		EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
		try {
			//指定服务端Bootstrap
			ServerBootstrap serverBootstrap=new ServerBootstrap();
			//加入线程组
			//绑定监听端口
			serverBootstrap.group(eventLoopGroup) //添加线程组
			.channel(NioServerSocketChannel.class)//指定使用NIO模式进行网络传输
			.localAddress(new InetSocketAddress(port))//指定服务器监听的端口
			//为服务端接受连接后生成的channel,添加handle
			//注意:服务端每接收到一个连接请求,就会新启一个socket通信,也就是channel
			.childHandler(new ChannelInitializer<SocketChannel>() {
				@Override
				protected void initChannel(SocketChannel channel) throws Exception {
					//添加到该channel的pipeline的尾部
					channel.pipeline().addLast(handler);
				}
			});
			//异步绑定到服务器,sync()会阻塞直到完成
			ChannelFuture future = serverBootstrap.bind().sync();
			//阻塞直到服务端的channel关闭
			future.channel().closeFuture().sync();
		} catch (Exception e) {
			e.printStackTrace();
		}finally{
			//优雅关闭线程组
			try {
				eventLoopGroup.shutdownGracefully().sync();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}

启动流程

doBind()

当调用serverBootstrap的 bind 方法时,实际上执行了AbstractBootstrap类的 doBind 方法。而doBind方法中,关键的是调用了 initAndRegister 和 doBind0 方法

private ChannelFuture doBind(final SocketAddress localAddress) {
        //核心方法:初始化以及注册
        final ChannelFuture regFuture = initAndRegister();
        final Channel channel = regFuture.channel();
        if (regFuture.cause() != null) {
            return regFuture;
        }

        if (regFuture.isDone()) {
            // At this point we know that the registration was complete and successful.
            ChannelPromise promise = channel.newPromise();
            //核心方法:实现监听端口
            doBind0(regFuture, channel, localAddress, promise);
            return promise;
        } else {
            //未操作完成,注册一个监听,在监听里面调用doBind0
            // Registration future is almost always fulfilled already, but just in case it's not.
            final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
            regFuture.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    Throwable cause = future.cause();
                    if (cause != null) {
                        // Registration on the EventLoop failed so fail the ChannelPromise directly to not cause an
                        // IllegalStateException once we try to access the EventLoop of the Channel.
                        promise.setFailure(cause);
                    } else {
                        // Registration was successful, so set the correct executor to use.
                        // See https://github.com/netty/netty/issues/2586
                        promise.registered();

                        doBind0(regFuture, channel, localAddress, promise);
                    }
                }
            });
            return promise;
        }
    }

initAndRegister方法

// An highlighted block
final ChannelFuture initAndRegister() {
        Channel channel = null;
        try {
            // 1、实例化具体的channel
            channel = channelFactory.newChannel();
            //2、初始化channel
            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);
            }
            // as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
            return new DefaultChannelPromise(new FailedChannel(), GlobalEventExecutor.INSTANCE).setFailure(t);
        }
        //3、注册这个channel
        ChannelFuture regFuture = config().group().register(channel);
        if (regFuture.cause() != null) {
            if (channel.isRegistered()) {
                channel.close();
            } else {
                channel.unsafe().closeForcibly();
            }
        }
        return regFuture;
    }

initAndRegister顾名思义就是负责进行初始化和注册,而具体做的实际上可以分为三件事情:

  1. 实例化一个Channel实例
  2. 初始化这个Channel
  3. 注册这个Channel

那具体是怎么实现的,下面分别进行分析:

实例化一个Channel

实例化Channel时,实际上是调用了ReflectiveChannelFactory类的newChannel方法,进行实例化,而实例化时指定的clazz是ServerBootstrap实例调用 channel 方法时指定的类型,即NioServerSocketChannel

public class ReflectiveChannelFactory<T extends Channel> implements ChannelFactory<T> {
    ...
    @Override
    public T newChannel() {
        try {
            //实例化指定的channel类型
            return clazz.getConstructor().newInstance();
        } catch (Throwable t) {
            throw new ChannelException("Unable to create Channel from class " + clazz, t);
        }
    }
}

当NioServerSocketChannel实例化时,内部会初始化一些属性,包括创建JDK原生的ServerSocketChannel。并且会传递给其父类,父类再完成一些配置,具体的可以看下面的流程图:

在这里插入图片描述
特别说明一点: 实例化完成后,当前的NioServerSocketChannel内部会拥有一个Pipeline,而该Pipeline内部维护了HeadContextTailContext,两个Handler形成了头尾两个链表,对于后续添加的其它Handler,都会放在这两者之间。而其中HeadContext 一定是入站的第一个Handler和出站的最后一个Handler, TailContext 则是入站的最后一个Handler

初始化Channel

public class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, ServerChannel> {
    ...
    @Override
    void init(Channel channel) throws Exception {
        final Map<ChannelOption<?>, Object> options = options0();
        synchronized (options) {
            setChannelOptions(channel, options, logger);
        }

        final Map<AttributeKey<?>, Object> attrs = attrs0();
        synchronized (attrs) {
            for (Entry<AttributeKey<?>, Object> e: attrs.entrySet()) {
                @SuppressWarnings("unchecked")
                AttributeKey<Object> key = (AttributeKey<Object>) e.getKey();
                channel.attr(key).set(e.getValue());
            }
        }

        ChannelPipeline p = channel.pipeline();

        final EventLoopGroup currentChildGroup = childGroup;
        //子channelhandler
        final ChannelHandler currentChildHandler = childHandler;
        final Entry<ChannelOption<?>, Object>[] currentChildOptions;
        final Entry<AttributeKey<?>, Object>[] currentChildAttrs;
        synchronized (childOptions) {
            //子channel的一些配置选项
            currentChildOptions = childOptions.entrySet().toArray(newOptionArray(0));
        }
        synchronized (childAttrs) {
            //子channel的一些属性
            currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(0));
        }
        //添加handler
        p.addLast(new ChannelInitializer<Channel>() {
            @Override
            public void initChannel(final Channel ch) throws Exception {
                final ChannelPipeline pipeline = ch.pipeline();
                ChannelHandler handler = config.handler();
                if (handler != null) {
                    pipeline.addLast(handler);
                }
                //异步提交一个添加处理连接的handler
                ch.eventLoop().execute(new Runnable() {
                    @Override
                    public void run() { 
                        pipeline.addLast(new ServerBootstrapAcceptor(
                                ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
                    }
                });
            }
        });
    }
}

调用 init 方法初始化时,首先,为当前Channel设置一些通信参数和属性,同时收集子Channel的通信参数和属性。之后,向当前Channel对应的Pipeline中添加一个ChannelInitializer实例。而当该Channel完成注册后,会调用到当前ChannelInitializer实例的handlerAdded方法,然后由该方法调用到这里的 initChannel 方法。而该 initChannel 方法会向当前Channel对应的Pipeline中加入一个用于处理客户端新连接的handler,即ServerBootstrapAcceptor

可以看下下面的流程图,梳理下大概的流程:
在这里插入图片描述
说明一点: 当调用完当前的ChannelInitializer实例的 initChannel 方法时,此时当前Channel对应的Pipeline中有三个handler,分别是 HeadContext->ServerBootstrapAcceptor->TailContext

注册Channel

public abstract class MultithreadEventLoopGroup extends MultithreadEventExecutorGroup implements EventLoopGroup {
    ...
    @Override
    public ChannelFuture register(Channel channel) {
        return next().register(channel);
    }
}

注册时,实际上会调用到 MultithreadEventLoopGroup 抽象类的 register 方法,而其中又会先调用next方法,从EventLoopGroup中维护的EventLoop数组中选择一个EventLoop,之后,再调用到SingleThreadEventLoop的 register 方法,并最终进入到AbstractChannel 的内部抽象类 AbstractUnsafe 的 register 方法

protected abstract class AbstractUnsafe implements Unsafe {
        ....
        //核心:对当前channel进行注册
        @Override
        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提交一个注册任务
                    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);
                }
            }
        }
}

当调用register方法时,主要是先判断,当前执行register方法的线程是否为 EventLoop 本身所持有的那个线程,是则直接执行 register0 方法,否则会通过一个 Runnable 来执行 register0 方法(而此时,执行的是我们的业务线程,必然不是 EventLoop 对应的线程),并将Runnable提交给当前的eventLoop来异步执行

public abstract class SingleThreadEventExecutor extends AbstractScheduledEventExecutor implements OrderedEventExecutor {
    ...
    @Override
    public void execute(Runnable task) {
        if (task == null) {
            throw new NullPointerException("task");
        }

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

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

在异步执行机制上,eventLoop.execute 方法实际执行 SingleThreadEventExecutor 的 execute 方法。而execute 执行过程中,首先会先判定,当前执行 execute 方法的对应的线程是否为 EventLoop 本身所持有的那个线程,然后将当前任务加入队列

接下来,如果不是EventLoop本身持有的线程,则先执行startThread方法,再执行到 doStartThread 方法,最终执行了 executor.execute 方法。对于每个 NioEventLoop 的执行器为 ThreadPerTaskExecutor,所以实际执行的是 ThreadPerTaskExecutor 的 execute 方法

private void doStartThread() {
        assert thread == null;
        executor.execute(new Runnable() {
            @Override
            public void run() {
                //在这里设置了Eventloop对应的线程,将线程与Eventloop进行了绑定
                thread = Thread.currentThread();
                if (interrupted) {
                    thread.interrupt();
                }

                boolean success = false;
                updateLastExecutionTime();
                try {
                    //核心:这里实际上执行了NioEventLoop的run方法,在run方法内部通过Selector选择器进行事件处理
                    SingleThreadEventExecutor.this.run();
                    success = true;
                } catch (Throwable t) {
                    logger.warn("Unexpected exception from an event executor: ", t);
                } finally {
                    ....
                }
            }
        });
    }

执行ThreadPerTaskExecutor 的 execute 方法时,会先将 SingleThreadEventExecutor 持有的 Thread 会和当前执行 executor.execute 方法的线程进行挂钩,于是每个 NioEventLoop 有了自己的独有线程。并且又执行了 SingleThreadEventExecutor.this.run() 方法,而这里的SingleThreadEventExecutor从具体的实现类来说其实就是 NioEventLoop,而 NioEventLoop 的 run 方法中,所做的事情就是不断的进行 select 和事件集的处理以及其他系统任务的处理。

protected abstract class AbstractUnsafe implements Unsafe {
    ...
    private void register0(ChannelPromise promise) {
            try {
                //确认channel是否已经打开
                if (!promise.setUncancellable() || !ensureOpen(promise)) {
                    return;
                }
                boolean firstRegistration = neverRegistered;
                //核心:将JDK的ServerSocketChannel注册到Selector选择器上
                doRegister();
                neverRegistered = false;
                registered = true;
                //调用pipeline中重写了handlerAdded方法的handler,这里就会执行前面初始化时向pipeline中加入的ChannelInitializer的handlerAdded方法
                pipeline.invokeHandlerAddedIfNeeded();
                //设置成功,执行监听回调方法
                safeSetSuccess(promise);
                //调用pipeline中从head开始的handler的channelRegistered方法
                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中从head开始的handler的fireChannelActive方法
                        pipeline.fireChannelActive();
                    } else if (config().isAutoRead()) {
                        //不是第一次注册时,也就是子channel注册时,就会执行该beginRead方法
                        beginRead();
                    }
                }
            } catch (Throwable t) {
                // Close the channel directly to avoid FD leak.
                closeForcibly();
                closeFuture.setClosed();
                safeSetFailure(promise, t);
            }
        }
}
public abstract class AbstractNioChannel extends AbstractChannel {
    ...
    @Override
    protected void doRegister() throws Exception {
        boolean selected = false;
        for (;;) {
            try {
                //将当前的channel注册到Selector选择器上,但未监听任何事件
                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;
                }
            }
        }
    }
}

register0 方法实际调用了 AbstractNioChannel 中的 doRegister方法,然后将当前的channel注册到Selector选择器上,但未监听任何事件,这也就意味着,其实到了这个时候,还没真正开始监听网络连接事件

可以看下下面的流程图,梳理下大概的流程:
在这里插入图片描述

到了这一步,initAndRegister 方法的主要工作已经做完了,接下来就是 doBind0 方法

doBind0 方法

调用 doBind0 方法前,会先确认注册Channel的操作是否已经完成。如果已经完成,则直接调用 doBind0 方法,否则,向对应的ChannelFuture添加一个监听,在监听内部来调用 doBind0 方法。而该监听会在Channel完成注册后,被触发执行

调用 doBind0 方法,实际上调用了 DefaultChannelPipeline 的 bind 方法,而对于 bind 来说,它是个出站事件,所以只会由出站 handler 进行处理,在Pipeline的handler链表中,会从 TailContext 开始往出站方向进行传播,目前来说,只有 HeadContext 会处理出站事件,所以由 HeadContext 来处理,而最终进入了 AbstractChanne.AbstractUnsafe 进行绑定

  final class HeadContext extends AbstractChannelHandlerContext
            implements ChannelOutboundHandler, ChannelInboundHandler { 
       ....     
       @Override
       public void bind(
                ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise)
                throws Exception {
            //服务端绑定端口
            unsafe.bind(localAddress, promise);
        }
}
protected abstract class AbstractUnsafe implements Unsafe {
        ...        
        @Override
        public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
            assertEventLoop();

            if (!promise.setUncancellable() || !ensureOpen(promise)) {
                return;
            }

            // See: https://github.com/netty/netty/issues/576
            if (Boolean.TRUE.equals(config().getOption(ChannelOption.SO_BROADCAST)) &&
                localAddress instanceof InetSocketAddress &&
                !((InetSocketAddress) localAddress).getAddress().isAnyLocalAddress() &&
                !PlatformDependent.isWindows() && !PlatformDependent.maybeSuperUser()) {
                // Warn a user about the fact that a non-root user can't receive a
                // broadcast packet on *nix if the socket is bound on non-wildcard address.
                logger.warn(
                        "A non-root user can't receive a broadcast packet if the socket " +
                        "is not bound to a wildcard address; binding to a non-wildcard " +
                        "address (" + localAddress + ") anyway as requested.");
            }

            boolean wasActive = isActive();
            try {
               //真正的绑定实现
                doBind(localAddress);
            } catch (Throwable t) {
                safeSetFailure(promise, t);
                closeIfClosed();
                return;
            }

            if (!wasActive && isActive()) {
                invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        //异步的方法,进行传播Active事件
                        pipeline.fireChannelActive();
                    }
                });
            }

            safeSetSuccess(promise);
        }
}

而执行doBind方法,实际上是调用了 NioServerSocketChanne 实现的 doBind 方法,当然真正的绑定端口,是通过JDK原生的ServerSocketChannel 进行了绑定

public class NioServerSocketChannel extends AbstractNioMessageChannel
                             implements io.netty.channel.socket.ServerSocketChannel {
    ....
    //通过JDK 原生的ServerSocketChannel 完成绑定
    @Override
    protected void doBind(SocketAddress localAddress) throws Exception {
        if (PlatformDependent.javaVersion() >= 7) {
            //获取JDK原生的ServerSocketChannel,并绑定端口
            javaChannel().bind(localAddress, config.getBacklog());
        } else {
            javaChannel().socket().bind(localAddress, config.getBacklog());
        }
    }
}

接下来,绑定完成后,会提交一个处理ChannelActive事件的任务,然后由Eventloop内部线程进行调度执行。当异步调度执行时,调用pipeline.fireChannelActive() 这个方法会通知当前Channel对应的 Pipeline,当前 Channel 被激活,事件开始在 Pipeline 上流动,ChannelActive 是个入站事件,会按照 head->tail 的顺序执行 Inbound 入站处理器,其中 ServerBootstrapAcceptor 和 TailContext 的 channelActive 方法都没有做任何实质性的事情,只有 head 有实质性代码

final class HeadContext extends AbstractChannelHandlerContext
            implements ChannelOutboundHandler, ChannelInboundHandler {
        ...
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            ctx.fireChannelActive();

            readIfIsAutoRead();
        }

        private void readIfIsAutoRead() {
            if (channel.config().isAutoRead()) {
                channel.read();
            }
        }
}

在HeadContext中,执行的 channelActive 方法,实际上又调用了 AbstractChannel 的 read 方法,而其中又会执行 DefaultChannelPipeline 的 read 方法,而对于 read 来说,它是个出站事件,所以只会由出站 handler 进行处理,在Pipeline的handler链表中,会从 TailContext 开始往出站方向进行传播 。目前来说,只有 HeadContext 会处理出站事件,所以还是由 HeadContext 来处理

final class HeadContext extends AbstractChannelHandlerContext
            implements ChannelOutboundHandler, ChannelInboundHandler {
        ...
        @Override
        public void read(ChannelHandlerContext ctx) {
            unsafe.beginRead();
        }
 }

对于HeadContext 执行的 read 方法,实际上最终会调用到 AbstractNioChannel 的 doBeginRead 方法,将 NioServerSocketChannel 感兴趣的事件设置为 OP_ACCEPT(16)。 到了这一步 AbstractBootstrap 的 doBind 方法执行完成,服务器的启动也算完成了

public abstract class AbstractNioChannel extends AbstractChannel {
    ....
    @Override
    protected void doBeginRead() throws Exception {
        // Channel.read() or ChannelHandlerContext.read() was called
        //获取当前channel对应的SelectionKey
        final SelectionKey selectionKey = this.selectionKey;
        //判断是否有效
        if (!selectionKey.isValid()) {
            return;
        }

        readPending = true;
        
        final int interestOps = selectionKey.interestOps();
        if ((interestOps & readInterestOp) == 0) {
            //注册对应的监听事件类型
            selectionKey.interestOps(interestOps | readInterestOp);
        }
    }
}

可以看下下面的流程图,梳理下大概的流程:
在这里插入图片描述

总结

本文从我们编写的常规Netty服务端代码进行了分析,详细介绍了服务端的启动的核心流程。从整个过程可以看到,实际上,Netty是基于NIO 模式进行开发的,底层还是使用 NIO 的相关内容。只是Netty对其进行包装。但是目前只介绍了如何实现注册 Channel 以及添加监听事件,还没分析Netty是如何进行事件处理以及客户端是如何连接的,后续会对这一块进行详细分析

由于本人能力有限,分析不恰当的地方和文章有错误的地方的欢迎批评指出,非常感谢!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值