Netty4.x源码笔记之服务端启动

本文详细分析了Netty4.x服务端的启动过程,包括ServerBootstrap的创建、channel的初始化、注册到selector以及地址绑定。通过源码解读,揭示了Netty如何创建NioServerSocketChannel,设置非阻塞模式,以及如何通过反射创建并初始化Channel,最后注册到NioEventLoop和Selector,完成服务端的启动准备,等待客户端连接。
摘要由CSDN通过智能技术生成

引言

Netty是由JBOSS提供的一个java开源框架。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。因其基于异步的、事件驱动的特性,Netty具有很优异的性能,接下来我会展开一系列的Netty源码的学习,源码分析基于Netty版本V4.1.0。希望自己能有所收获。

服务端启动示例

在分析学习之前我们先按照国际惯例,我们先看看Netty服务端是怎么启动的:

ServerBootstrap bootStrap = new ServerBootstrap();
		bootStrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
		        .option(ChannelOption.SO_BACKLOG, 1024)
				.childHandler(MockChannelInitializer);
				
	    bootStrap.bind(port).sync();			

首先需要先创建ServerBootstrap,设置双线程组(boss线程组和worker线程组,Netty的Reactor线程模型),配置好网络处理的属性如SO_BACKLOG(正在与客户端进行三次握手的队列,分两个队列,客户端调用connect给服务端发送syn包,进行第一次握手,服务端收到syn包返回一个shn包和ack标志进行第二次握手时,放进一个握手初始队列,当服务端接收到客户端返回的ack包时,tcp内核会将连接从握手初始队列转到新的队列中,最后在应用程序accept完成后从新队列中取出,如果SO_BACKLOG太小,accept线程处理不过来,服务端会拒绝新的连接)。最后绑定端口就ok了。值得一提的是Netty使用调用链的方式进行设值(主要是因为参数较多,类结构较为复杂,采用建造器模式),比较符合本人的胃口,手动滑稽。

channel创建与初始化

看完服务端启动示例,我们进入正题,开始分析源码。我们先直接看bootStrap.bind(port)方法。bind方法调用链如下

ServerBootstrap.bind -> AbstractBootstrap.bind -> AbstractBootstrap.doBind

经过层层嵌套调用,参数校验后最终调用了关键的AbstractBootstrap.doBind方法,该方法做了两件事情,第一,初始化NioServerSocketChannel实例和异步把channel注册到selector上;第二在注册成功后进行绑定端口。

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 {
            // 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方法看看具体实现。

final ChannelFuture initAndRegister() {
        //这个channelFactory实际是ReflectiveChannelFactory实例,该实例封装了NioServerChannel,调用newChannel()方法是通过反射拿到NioServerChannel实例
        final Channel channel = channelFactory.newChannel();
        try {
            init(channel);
        } catch (Throwable t) {
            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 = group().register(channel);
        if (regFuture.cause() != null) {
            if (channel.isRegistered()) {
                channel.close();
            } else {
                channel.unsafe().closeForcibly();
            }
        }

        return regFuture;

上面的initAndRegister()方法调用了ReflectiveChannelFactory的newChannel方法,使用反射创建了NioServerSocketChannel实例(还记得在创建ServerBootstrap的时候channel(NioServerSocketChannel.class)么,是他是他就是他),NioServerChannel的构造函数比较关键,我们先来看看NioServerChannel创建实例的过程。

public NioServerSocketChannel() {
        this(newSocket(DEFAULT_SELECTOR_PROVIDER));
    }
    
    public NioServerSocketChannel(ServerSocketChannel channel) {
        super(null, channel, SelectionKey.OP_ACCEPT);
        config = new NioServerSocketChannelConfig(this, javaChannel().socket());
    }
    
    private static ServerSocketChannel newSocket(SelectorProvider provider) {
        //忽略无关代码
            return provider.openServerSocketChannel();
    }

通过channelFactory反射创建channel实例使用的是无参构造方法,而这个构造函数先通过newSocket方法创建java原生的ServerSocketChannel
实例,然后调用父类AbstractNioMessageChannel构造函数,值得注意的是这里把OP_ACCEPT的操作位传给了父类,而最终在祖父类AbstractNioChannel中设置了readInterestOp参数,同时把channel设置成非阻塞的。

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

在调用完父类的构造函数后NioServerSocketChannel还做了一件事,创建ChannelConfig实例,该实例是每个channel一些参数配置,比如高低水位设置,连接超时参数设置等,这个参数配置在下面channel初始化的时候会再见面的。到这NioServerSocketChannel的实例化已完成。

最后我们回到AbstractBootstrap的initAndRegister()方法,再看看实例化过后的channel初始化过程。

@Override
    void init(Channel channel) throws Exception {
        //第一步,加载用户自定义的tcp服务端的options参数,比如上面提到的SO_BACKLOG,该参数关注的是有连接建立的时候情况,只在server端有
        final Map<ChannelOption<?>, Object> options = options0();
        synchronized (options) {
          
           channel.config().setOptions(options);
        }
        //第二步,加载用户自定义的attrs属性,初始化channel就设定的,一般是全局属性
        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
        ChannelPipeline p = channel.pipeline();
        //第四步,拿到childOptions和childAttrs,这里的childOptions是设置接收客户端连接的参数的,比如连接超时,与发送客户端数据buffer的高低水位等,而childAttrs则一般为与客户端相关的参数,比如通过channel识别用户的一些数据可以维护到这里,childAttrs是线程安全的。childOptions关注的是channel accept之后的事情。
        final EventLoopGroup currentChildGroup = childGroup;
        final ChannelHandler currentChildHandler = childHandler;
        final Entry<ChannelOption<?>, Object>[] currentChildOptions;
        final Entry<AttributeKey<?>, Object>[] currentChildAttrs;
        synchronized (childOptions) {
            currentChildOptions = childOptions.entrySet().toArray(newOptionArray(childOptions.size()));
        }
        synchronized (childAttrs) {
            currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(childAttrs.size()));
        }

        p.addLast(new ChannelInitializer<Channel>() {
            @Override
            public void initChannel(Channel ch) throws Exception {
                ChannelPipeline pipeline = ch.pipeline();
                ChannelHandler handler = config.handler();
                if (handler != null) {
                    pipeline.addLast(handler);
                }
                pipeline.addLast(new ServerBootstrapAcceptor(
                        currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
            }
        });
    }

channel的初始化就是把服务端配置和接入客户端的配置的上下文属性设置到channel中,也就是把上下文参数设置到上面我们说到过的ChannelConfig实例上。细节在这就不说了,有兴趣的可以查看源码,具体链路如下

AbstractBootstrap.init -> ServerBootstrap.init -> AbstractBootstrap.setChannelOption -> NioServerSocketChannel.NioServerSocketChannelConfig.setOption

到这channel的实例化和初始化就讲完了,总结下来就是以下几点:

  1. ServerBootstrap.channel()绑定NioServerSocketChannel类型
  2. 调用AbstractBootstrap.doBind方法
  3. 使用反射把绑定的NioServerSocketChannel实例化
  4. 实例化创建java原生ServerSocketChannel实例
  5. 调用NioServerSocketChannel父类构造函数,设置readInterestOp为OP_ACCEPT(16)操作位,并把channel设置为非阻塞模式。
  6. 实例化ChannelConfig。
  7. 初始化channelConfig的各tcp连接option参数和channel的Attribute参数,添加Netty内部的channelHandler。

Channel注册

在初始化完成之后,就开始了真正的注册,让我们回到initAndRegister()方法中看看注册过程,initAndRegister()方法拿到NioEventLoopGroup,也就是前面初始化ServerBootstrap的boss线程(accept线程),从group中拿出一个线程执行注册任务,去掉层层嵌套,真正注册的是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;

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

register0方法首先判断调用register0的promise任务是否还在注册进行中就被取消了,接下来调用doRegister()方法,该方法实现就是调用了java原生的注册方法,将channel往selector上面注册。

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

上面javaChannel()方法就是拿到了java原生的SelectableChannel。通过调用该channel的register方法,来把该channel注册到与eventLoop绑定的selector上面。值得注意的是这里面的设置监听的操作位应该是OP_ACCEPT(16)的,监听accept事件的,而这里却是个0。只是注册一下,什么都不做。其实这里设置0只注册而不监听事件的原因是该类AbstractNioChannel是个公用的类,服务端NioServerSocketChannel和客户端NioSocketChannel都是用这个抽象的channel,而真正的感兴趣的事件在初始化NioServerSocketChannel和NioSocketChannel设定了,因为selectKey可以使用interestOps方法方便设置。还记得我们刚刚看NioSocketChannel实例化设置了readInterestOp为OP_ACCEPT了么?

注册完成后往pipeline触发channelRegistered的事件,该事件只在inboundHandler中传播。最后再判断channel是否active了,如果active了再触发channelActive事件。最后再看看AbstractNioChannel这个门面类,该类代理的各种java原生API,比如初始化selector等。

地址绑定

在把channel注册到NioEventLoop上的selector之后,最后我们再倒回来看看bind0()方法,这个方法除了绑定本地地址和端口,最重要的方法就把上面的只注册了0的操作位给修正为真正OP_ACCEPT(16)到selectionKey上。请看下面的代码。

private static void doBind0(
            final ChannelFuture regFuture, final Channel channel,
            final SocketAddress localAddress, final ChannelPromise promise) {

        // This method is invoked before channelRegistered() is triggered.  Give user handlers a chance to set up
        // the pipeline in its channelRegistered() implementation.
        channel.eventLoop().execute(new Runnable() {
            @Override
            public void run() {
                if (regFuture.isSuccess()) {
                    channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
                } else {
                    promise.setFailure(regFuture.cause());
                }
            }
        });
    }

可以看到doBind0方法是将bind的功能封装成一个任务提交到了accept线程池中,经过层层嵌套最终是ChannelPipeline的头结点head调用了unsafe.bind()方法,调用链路如下

AbstractChannel#bind(xxx)->DefaultChannelPipeline#bind(xx)->tail.bind(xxx)->tail.invokeBind(xxx)->head.bind(xxx)->unsafe.bind(xxx))

关于Pipeline我们可以看成调用链,和servlet的filterChain一样使用责任链模式在事件触发后事件在pipeline中流转,pipeline我们在源码分析其他文章中再细聊,这里不是我们关注的重点,这里我们的重点是这个unsafe对象。我们看看这个unsafe对象。

interface Unsafe {

        RecvByteBufAllocator.Handle recvBufAllocHandle();

        SocketAddress localAddress();

        SocketAddress remoteAddress();

        void register(EventLoop eventLoop, ChannelPromise promise);

        void bind(SocketAddress localAddress, ChannelPromise promise);

        void connect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise);

        void disconnect(ChannelPromise promise);

        void close(ChannelPromise promise);

        void closeForcibly();

        void deregister(ChannelPromise promise);

        void beginRead();

        void write(Object msg, ChannelPromise promise);

        void flush();
        
        ChannelPromise voidPromise();

        ChannelOutboundBuffer outboundBuffer();
    }
}

unsafe对象是个接口,具体实现有两个:AbstractNioMessageChannel.NioMessageUnsafe和AbstractNioByteChannel.NioByteUnsafe,分别对应NioServerSocketChannel和NioSocketChannel。看接口定义我们大概就知道unsafe类是用来封装java底层的通信方法,使用unsafe实现类作为代理类代理底层java通信实现。现在我们来看下这个unsafe.bind方法。

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() {
                        pipeline.fireChannelActive();
                    }
                });
            }

            safeSetSuccess(promise);
        }

可以看到第一件事就是调用java原生的bind方法绑定地址和接口然后channel被激活,激活之后立马调用pipleline.fireChannelActive()方法,事件在pipleline中流转。

 @Override
    public final ChannelPipeline fireChannelActive() {
        AbstractChannelHandlerContext.invokeChannelActive(head);
        return this;
    }

事件最先在头结点获取,而头结点获取到channelActive事件后做了两件事,先把事件传递下去,然后调用readIfIsAutoRead()方法,而该方法先会判断channel.config().isAutoRead(),如果true那么调用AbstractChannel的read方法。

        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            ctx.fireChannelActive();

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

这个config()其实就是在初始化NioServerSocketChannel的时候初始化的。

public NioServerSocketChannel(ServerSocketChannel channel) {
        super(null, channel, SelectionKey.OP_ACCEPT);
        config = new NioServerSocketChannelConfig(this, javaChannel().socket());
    }

就是这个NioServerSocketChannelConfig。回到isAutoRead()方法public boolean isAutoRead() { return autoRead == 1; }就这个方法判断的,而autoRead是全局变量初始化就是1所以一定是true。所以AbstractChannel的read方法一定会调用。

@Override
    public Channel read() {
        pipeline.read();
        return this;
    }

看上面的代码,调用pipeline.read()方法,所以read()方法又经过层层调用(tail.invokeRead()->head.read()->unsafe.beginRead()),来到unsafe.beginRead()方法,而这个beginRead()是会修改selectionKey的interestOps的也就是在初始化NioServerSocketChannel的时候设置的SO_ACCEPT(16)。到这就完成了服务端的accept过程,只要有客户端连接进来,selector就能够select进来。这部分我们在讲EventLoopGroup的时候细讲。

    @Override
    protected void doBeginRead() throws Exception {
        // Channel.read() or ChannelHandlerContext.read() was called
        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的服务端启动源码分析就结束了。总结一下就是以下几点:

  1. 实例化ServerBootstrap对象,设置具体的NioServerSocketChannel.class类。
  2. 调用initAndRegister方法初始化NioServerSocketChannel实例,在初始化Channel实例时拿到了java原生的ServerSocketChannel实例,并且实例化ChannelConfig,最后将channel设置为非阻塞模式,把readInterestOp属性设为OP_ACCEPT操作位。
  3. 实例化NioServerSocketChannel时调用父类构造函数,生成channel唯一id,实例化unsafe对象这个java底层通信的代理类,实例化Pipeline。
  4. 实例化过后根据用户设置的options和childOptions参数设置到ChannelConfig中。
  5. 把channel注册到NioEventLoop上
  6. 把channel注册到与NioEventLoop绑定的Selector上
  7. 绑定本地ip和端口触发channelActive事件,调用unsafe.doBeginRead方法把selectionKey监听的操作位修改为OP_ACCEPT操作位。开始accept客户端的连接。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值