58. Netty源代碼分析-ServerBootstrap bind 過程-1

一. 開始

接上一篇 ServerBootstrap的初始化
http://blog.51cto.com/483181/2119149

二. bind過程

2.1 代碼

先看下調用的源代碼

public void bind(int port) throws Exception {
        ...
        try {
            ...

            ChannelFuture f = b.bind(port).sync(); //bind過程
            ...
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

2.2 bind

public ChannelFuture bind(int inetPort) {
        return bind(new InetSocketAddress(inetPort));
    }

public ChannelFuture bind(SocketAddress localAddress) {
        validate();
        if (localAddress == null) {
            throw new NullPointerException("localAddress");
        }
        return doBind(localAddress);
}

從上面代碼可以看出幾點:

  1. bind方法邏輯很簡單,經過一系列的判斷後最後調用doBind()方法
  2. 發現Netty代碼裏面,從外面調用進去後,內部方法一般用doxxx,xxx0這種命名;以前自己看安卓源代碼的時候,安卓一般喜歡用xxxInner的命名。風格而已,也許自己以後寫代碼可以參考(看源代碼除了瞭解原理外,學習別人的代碼架構方法也是一種收穫)。

繼續看doBind

2.3 doBind

private ChannelFuture doBind(final SocketAddress localAddress) {
        final ChannelFuture regFuture = initAndRegister(); //1. init和register
        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

2.4 initAndRegister

顧名思義,這個方法包含初始化和註冊兩個步驟,代碼如下:

final ChannelFuture initAndRegister() {
        Channel channel = null;
        try {
            channel = channelFactory.newChannel();
            init(channel);
        } catch (Throwable t) {
            ...
        }

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

從上面代碼,我們可以看到幾點:

  1. channel = channelFactory.newChannel();
    channelFactory是什麼?它的類型是ReflectiveChannelFactory,如果大家不記得了,可以看看上一篇channel設置那個地方。
    http://blog.51cto.com/483181/2119149
public B channel(Class<? extends C> channelClass) {
        return channelFactory(new ReflectiveChannelFactory<C>(channelClass));
    }

public class ReflectiveChannelFactory<T extends Channel> implements ChannelFactory<T> {
    @Override
    public T newChannel() {
        try {
            return clazz.getConstructor().newInstance();
        } catch (Throwable t) {

        }
    }   
}       

它的newChannel方法也是非常的簡單,直接實例化傳入的channel對象,也就是NioServerSocketChannel (可以看上一篇初始化的分析)
代碼如下:

ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)

我們先看看NioServerSocketChannel的實現

2.5 NioServerSocketChannel

先看下NioServerSocketChannel的繼承關係
58. Netty源代碼分析-ServerBootstrap bind 過程-1

NioServerSocketChannel提供了一個無參構造函數,然後分別有SelectorProvider,ServerSocketChannel的構造函數,如下:

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

    private static ServerSocketChannel newSocket(SelectorProvider provider) {
        try {
            return provider.openServerSocketChannel();
        } catch (IOException e) {
        }
    }

    private final ServerSocketChannelConfig config;

    /**
     * Create a new instance
     */
    public NioServerSocketChannel() {
        this(newSocket(DEFAULT_SELECTOR_PROVIDER));
    }

    /**
     * Create a new instance using the given {@link ServerSocketChannel}.
     */
    public NioServerSocketChannel(ServerSocketChannel channel) {
        super(null, channel, SelectionKey.OP_ACCEPT);
        config = new NioServerSocketChannelConfig(this, javaChannel().socket());
    }

無參構造函數裏面調用newSocket(xx),參數是SelectorProvider DEFAULT_SELECTOR_PROVIDER = SelectorProvider.provider();
先看看SelectorProvider.provider()

private static SelectorProvider provider = null;
public static SelectorProvider provider() {
        synchronized (lock) {
            if (provider != null)
                return provider;
                ...
        }
    }

可以看到provider是個單例,不知道大家是否記得上上一篇文章(NioEventLoopGroup實例化)分析的時候也有provider,類型是KQueueSelectorProvider
具體可以看: http://blog.51cto.com/483181/2118817

回到newSocket裏面,調用的是provider.openServerSocketChannel()

代碼是SelectorProviderImpl裏面,返回的是 ServerSocketChannel

public ServerSocketChannel openServerSocketChannel() throws IOException {
        return new ServerSocketChannelImpl(this);
    }

得到ServerSocketChannel之後,繼續調用構造函數

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

這個構造方法裏面做了兩件事

  1. 調用父類的構造方法
  2. 利用剛剛生成好的ServerSocketChannel實例化了一個NioServerSocketChannelConfig

看它的父類構造函數是怎麼實現的

首先是AbstractNioChannel.java

protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
        super(parent);
        this.ch = ch;
        this.readInterestOp = readInterestOp;
        try {
            ch.configureBlocking(false);
        } catch (IOException e) {
        }
    }
  1. 繼續調用父類的構造方法
  2. 首先吧傳入的ServerSocketChannel保存起來,變量是ch
  3. 然後把readInterestOp存起來,變量是readInterestOp,值是SelectionKey.OP_ACCEPT
  4. 調用ch.configureBlocking(false);把channel設置成非阻塞。
    這裏稍微介紹下SelectionKey.OP_ACCEPT
    SelectionKey有4種類型,是java提供的,分別是
public static final int OP_READ = 1 << 0;

public static final int OP_WRITE = 1 << 2;

public static final int OP_CONNECT = 1 << 3;

public static final int OP_ACCEPT = 1 << 4;

然後繼續看AbstractNioChannel的父類構造方法,也就是AbstractChannel

private final ChannelId id;
protected abstract AbstractUnsafe newUnsafe();
private final DefaultChannelPipeline pipeline;

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

可以看到這幾點:

  1. Channel parent變量,null
  2. 初始化ChannelId id
  3. 初始化unsafe
  4. 初始化pipeline

先看unsafe的初始化

2.6 newUnsafe

在AbstractChannel裏面,它是一個抽象類

protected abstract AbstractUnsafe newUnsafe();

實現類在子類AbstractNioMessageChannel裏面,如下,類型是NioMessageUnsafe

@Override
    protected AbstractNioUnsafe newUnsafe() {
        return new NioMessageUnsafe();
    }

NioMessageUnsafe代碼後面再看。

繼續看pipeline的初始化,初始化了一個 DefaultChannelPipeline

protected DefaultChannelPipeline newChannelPipeline() {
        return new DefaultChannelPipeline(this);
    }
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;
    }

在DefaultChannelPipeline裏面初始化了一個head和tail,分別是HeadContext和TailConext類型,而且head和tail組成雙向鏈表。
head和tail的區別之一就是inbound和outbound值是相反的,如下:

節點 inbound outbound
head false true
tail true false
HeadContext(DefaultChannelPipeline pipeline) {
            super(pipeline, null, HEAD_NAME, false, true);
            unsafe = pipeline.channel().unsafe();
            setAddComplete();
        }

TailContext(DefaultChannelPipeline pipeline) {
            super(pipeline, null, TAIL_NAME, true, false);
            setAddComplete();
        }               

借一張圖顯示下ChannelInBound和ChannelOutBound,如下。head是發送出去的入口,tail是接收消息的入口。

58. Netty源代碼分析-ServerBootstrap bind 過程-1

另外我們來看一下添加一個ChannelHandler的流程,比如addLast

public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
        final AbstractChannelHandlerContext newCtx;
        synchronized (this) {
            checkMultiplicity(handler);

            newCtx = newContext(group, filterName(name, handler), handler);

            addLast0(newCtx);

            ...
                    return this;
    }

private AbstractChannelHandlerContext newContext(EventExecutorGroup group, String name, ChannelHandler handler) {
        return new DefaultChannelHandlerContext(this, childExecutor(group), name, handler);
    }       

private void addLast0(AbstractChannelHandlerContext newCtx) {
        AbstractChannelHandlerContext prev = tail.prev;
        newCtx.prev = prev;
        newCtx.next = tail;
        prev.next = newCtx;
        tail.prev = newCtx;
    }       
  1. 首先它初始化了一個DefaultChannelHandlerContext對象,裏面封裝了要add的channelHandler,這個很重要,在Netty的pipeLine裏面,都是通過ChannelHandlerContext來描述的,不是直接添加channelHandler。

  2. addLast0()裏面就是簡單的雙向鏈表添加的方法,把封裝了channelHandler的ChannelHandlerContext對象添加到tail的前一個節點。

那,我們來總結下NioServerSocketChannel的初始化過程:

1. NioServerSocketChannel提供了一個無參構造函數,裏面SelectorProvider DEFAULT_SELECTOR_PROVIDER,它是一個單例,類型是KQueueSelectorProvider。

2. 我們調用KQueueSelectorProvider.openServerSocketChannel()方法,得到一個ServerSocketChannel

3. 我們用生成的ServerSocketChannel對象創建了一個ServerSocketChannelConfig config,具體是NioServerSocketChannelConfig對象,存在NioServerSocketChannel裏面

4. 我們用生成的ServerSocketChannel調用它的父類構造函數,先來到了AbstractNioChannel

5. 在AbstractNioChannel會把ServerSocketChannel存起來,變量是ch,然後把channel設置成非阻塞。

6. AbstractNioChannel還會把readInterestOp存起來,類型是SelectionKey.OP_ACCEPT

7. 繼續調用父類構造函數,來到AbstractChannel

8. AbstractChannel裏面的parent設置成null

9. AbstractChannel初始化channel id

10. AbstractChannel初始化unsafe,類型是NioMessageUnsafe.

11. AbstractChannel初始化pipeline,類型是DefaultChannelPipeline, 每個Channel都有一個自己的Pipeline

看完NioServerSocketChannel的實例化方法後,我們繼續往下看init

2.7 init

abstract void init(Channel channel) throws Exception;

AbstractBootstrap裏面的init(channel)方法是一個抽象方法,參數是Channel類型,其實就是上一步實例化好的NioServerSocketChannel對象。

具體實現方法在它的子類ServerBootstrap和Bootstrap(給客戶端啓動使用的),那我們是分析服務端的代碼,所以看ServerBootstrap裏面的實現。

void init(Channel channel) throws Exception {
        final Map<ChannelOption<?>, Object> options = options0();
        synchronized (options) { //1. 設置options
            setChannelOptions(channel, options, logger);
        }

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

        ChannelPipeline p = channel.pipeline();

        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(final Channel ch) throws Exception {
                final ChannelPipeline pipeline = ch.pipeline();
                ChannelHandler handler = config.handler();
                if (handler != null) {
                    pipeline.addLast(handler);
                }

                ch.eventLoop().execute(new Runnable() {
                    @Override
                    public void run() {
                        pipeline.addLast(new ServerBootstrapAcceptor(
                                ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
                    }
                });
            }
        });
    }

先來看設置options

2.8 setOptions

final Map<ChannelOption<?>, Object> options = options0();
        synchronized (options) { //1. 設置options
            setChannelOptions(channel, options, logger);
        }

static void setChannelOptions(
            Channel channel, Map<ChannelOption<?>, Object> options, InternalLogger logger) {
        for (Map.Entry<ChannelOption<?>, Object> e: options.entrySet()) {
            setChannelOption(channel, e.getKey(), e.getValue(), logger);
        }
    }

private static void setChannelOption(
            Channel channel, ChannelOption<?> option, Object value, InternalLogger logger) {
        try {
            if (!channel.config().setOption((ChannelOption<Object>) option, value)) {
            }
        } catch (Throwable t) {}
    }       

這段代碼我們這樣看

  1. options是哪來的?
    options是一個map,服務器代碼是這樣設置的
 b.xxxx.
    .option(ChannelOption.SO_BACKLOG, 100)
  1. 它其實調用的是channel.config()對象去設置option,那config對象是什麼呢?這個上面分析Channel初始化的時候說過,它是NioServerSocketChannelConfig對象,NioServerSocketChannelConfig的類繼承關係如下:

58. Netty源代碼分析-ServerBootstrap bind 過程-1`

  1. 所以setOption的實現在DefaultServerSocketChannelConfig裏面
@Override
    public <T> boolean setOption(ChannelOption<T> option, T value) {
        validate(option, value);

        if (option == SO_RCVBUF) {
            setReceiveBufferSize((Integer) value);
        } else if (option == SO_REUSEADDR) {
            setReuseAddress((Boolean) value);
        } else if (option == SO_BACKLOG) {
            setBacklog((Integer) value);
        } else {
            return super.setOption(option, value);
        }

        return true;
    }

父類 DefaultChannelConfig.java

public <T> boolean setOption(ChannelOption<T> option, T value) {
        validate(option, value);

        if (option == CONNECT_TIMEOUT_MILLIS) {
            setConnectTimeoutMillis((Integer) value);
        } else if (option == MAX_MESSAGES_PER_READ) {
            setMaxMessagesPerRead((Integer) value);
        } else if (option == WRITE_SPIN_COUNT) {
            setWriteSpinCount((Integer) value);
        } else if (option == ALLOCATOR) {
            setAllocator((ByteBufAllocator) value);
        } else if (option == RCVBUF_ALLOCATOR) {
            setRecvByteBufAllocator((RecvByteBufAllocator) value);
        } else if (option == AUTO_READ) {
            setAutoRead((Boolean) value);
        } else if (option == AUTO_CLOSE) {
            setAutoClose((Boolean) value);
        } else if (option == WRITE_BUFFER_HIGH_WATER_MARK) {
            setWriteBufferHighWaterMark((Integer) value);
        } else if (option == WRITE_BUFFER_LOW_WATER_MARK) {
            setWriteBufferLowWaterMark((Integer) value);
        } else if (option == WRITE_BUFFER_WATER_MARK) {
            setWriteBufferWaterMark((WriteBufferWaterMark) value);
        } else if (option == MESSAGE_SIZE_ESTIMATOR) {
            setMessageSizeEstimator((MessageSizeEstimator) value);
        } else if (option == SINGLE_EVENTEXECUTOR_PER_GROUP) {
            setPinEventExecutorPerGroup((Boolean) value);
        } else {
            return false;
        }

        return true;
    }       

根據傳入的屬性不行,用不同的方法進行設置,這些屬性的值大家可以去單獨百度,可能不同的環境配置不同的值對服務器性能有好處。

那繼續往下面看,設置attr

2.9 setAttr

setAttr是封裝了一個Attribute的類,然後存儲key,value,大傢俱體要看的話,可以看DefaultAttributeMap.java

繼續往下看

2.10 addLast

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

                ch.eventLoop().execute(new Runnable() {
                    @Override
                    public void run() {
                        pipeline.addLast(new ServerBootstrapAcceptor(
                                ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
                    }
                });
            }
        });

上面這段代碼,我們一步步看

  1. 首先,config.handler()是哪裏來的?其實就是我們設置的handler,這一點可以從上一篇分析看到
    http://blog.51cto.com/483181/2119149
b..handler(new LoggingHandler(LogLevel.INFO));

所以 pipeline.addLast(handler); 就是把我們設置的handler添加到pipeline裏面。

  1. 然後又實例化了一個ServerBootstrapAcceptor,把childHandler那些參數都傳了進去,具體在ServerBootstrapAcceptor裏面怎麼使用這些childHandler的.
ch.eventLoop().execute(new Runnable() {
                    @Override
                    public void run() {
                        pipeline.addLast(new ServerBootstrapAcceptor(
                                ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
                    }
                });

ServerBootstrapAcceptor是把客戶端連接的channel從bossGroup轉移到workGroup,代碼如下:
ServerBootstrap.java

@Override
        @SuppressWarnings("unchecked")
        public void channelRead(ChannelHandlerContext ctx, Object msg) {
            final Channel child = (Channel) msg;

            child.pipeline().addLast(childHandler);

            setChannelOptions(child, childOptions, logger);

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

上面這段代碼把客戶端的channel讀進來轉換成一個channel類型,然後調用childGroup,然後把channel註冊進去,這樣workGroup就接手了channel後面的事情。

那init就看完了,總結一下init做的事情

  1. 設置options,參數有很多,不同的服務器業務可以用不用的參數。
  2. 設置attr
  3. 把handler添加到pipeLine的尾部
  4. 初始化了一個ServerBootstrapAcceptor,裏面封裝了childHandler的那些參數。

其實看到這裏,我們會發現init還只是初始化參數,把handler添加到pipeLine裏面,做好一切準備,並沒有bind服務器端口。

那我們繼續看

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

2.12 register

先繼續貼一下initAndRegister的代碼,因爲上面講的東西有點多,大家可能忘記initAndRegister裏面的代碼了。

final ChannelFuture initAndRegister() {
        Channel channel = null;
        try {
            channel = channelFactory.newChannel(); //1. NioServerSocketChannel的初始化已經講了
            init(channel); //2. init過程已經講了
        } catch (Throwable t) {
        }

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

        return regFuture;
    }

如同上面的註釋,我們講register過程

  1. config.group()是什麼呢?參考我們上一篇的ServerBootstrap初始化,config.group()指的bossGroup,類型是NioEventLoopGroup
    ServerBootstrap初始化
EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)

由於NioEventLoopGroup繼承自MultithreadEventLoopGroup,所以調用的是MultithreadEventLoopGroup的register(channel)方法,如下:

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

@Override
    public EventExecutor next() {
        return chooser.next();
    }       

那next()又是什麼呢?在上篇 NioEventLoopGroup實例化 裏面我們分析了,NioEventLoopGroup裏面初始化了跟傳入線程數目相同的NioEventLoop對象,而next()方法有兩種算法選出下一個NioEventLoop對象是什麼。

這兩種算法是PowerOfTwoEventExecutorChooser和GenericEventExecutorChooser,所以我們就可以知道繼續會調用NioEventLoop對象的register(channel)對象。

而NioEventLoop類並沒有實現register(channel)方法,它繼承自SingleThreadEventLoop,它裏面有實現register(channel)方法,如下:

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

這個方法裏面實例化了一個DefaultChannelPromise對象,它其實就是保存channel和當前的NioEventLoop對象,做了一層封裝而已,如下:

public DefaultChannelPromise(Channel channel, EventExecutor executor) {
        super(executor);
        this.channel = checkNotNull(channel, "channel");
    }

public DefaultPromise(EventExecutor executor) {
        this.executor = checkNotNull(executor, "executor");
    }       

所以我們可以暫時不管它,繼續往下面走.

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

調用的是unsafe.register(this, promise)

那unsafe是什麼對象呢?從上面2.6可以看到unsafe()初始化的是NioMessageUnsafe對象

protected AbstractNioUnsafe newUnsafe() {
        return new NioMessageUnsafe();
    }

由於NioMessageUnsafe並沒有重寫register(EventLoop eventLoop, ChannelPromise promise)方法,所以追蹤它的父類,最後在AbstractUnsafe裏面看到了register(EventLoop eventLoop, ChannelPromise promise),如下:
先附上NioMessageUnsafe的繼承關係圖:
58. Netty源代碼分析-ServerBootstrap bind 過程-1

AbstractUnsafe.java

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) {
                    ...
                }
            }
        }

都會走到register0(promise)這個方法裏面,繼續看register0(promise)

private void register0(ChannelPromise promise) {
            try {
                ...

                boolean firstRegistration = neverRegistered;
                doRegister(); //1. 
                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) {
                ...
            }
        }

先看doRegister

2.13 doRegister

這個方法在AbstractChannel裏面,是個空實現

/**
     * Is called after the {@link Channel} is registered with its {@link EventLoop} as part of the register process.
     *
     * Sub-classes may override this method
     */
    protected void doRegister() throws Exception {
        // NOOP
    }

在AbstractNioChannel裏面有重寫

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

protected SelectableChannel javaChannel() {
        return ch;
}       
  1. 首先,ch是ServerSocketChannelImpl類型,這個可以從上面 2.5 NioServerSocketChannel的初始化可以看出來來
public ServerSocketChannel openServerSocketChannel() throws IOException {
        return new ServerSocketChannelImpl(this);
}

ServerSocketChannelImpl是JDK提供的類,那javaChannel().register(xxx)就是調用JDK nio的方法實現register,那就不繼續深入下去了。

  1. 但是這裏有個疑惑,調用register的時候傳入的ops是0,並沒有使用上面4種監聽類型的任何一種,這個先記下來。
public static final int OP_READ = 1 << 0;

public static final int OP_WRITE = 1 << 2;

public static final int OP_CONNECT = 1 << 3;

public static final int OP_ACCEPT = 1 << 4;
  1. eventLoop().unwrappedSelector()是什麼呢?
    從上一篇NioEventGroupLoop初始化 2.2.3分析可以知道,它是一個KQueueSelectorImpl,繼承自Selector

58. Netty源代碼分析-ServerBootstrap bind 過程-1

那我們可以這樣理解,上面這段代碼是把一個Selector對象註冊到Java的 Channel裏面,這個Channel和我們上面講的Netty Channel不是一個東西。

繼續看register0()

2.14 pipeline.fireChannelRegistered()

private void register0(ChannelPromise promise) {
            try {
                ...
                doRegister(); //1. 把selector註冊到Java channel, ops = 0
                ...
                pipeline.fireChannelRegistered(); //2. 通知handler channel已經註冊

                                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) {
                ...
            }
        }

pipeline裏面維護channelHandler的列表,通過鏈表的方法,如DefaultChannelPipeline.java裏面

final AbstractChannelHandlerContext head;
final AbstractChannelHandlerContext tail;

然後通知channel registered,如果channelHandler有重寫channelRegitstered(ChannelHandlerContext ctx)的話,就會被回調。如LoggingHandler就會打印

58. Netty源代碼分析-ServerBootstrap bind 過程-1

然後判斷isActive(),isActive()是一個多態方法,對於服務器,它是判斷監聽是否啓動;
NioServerSocketChannle.java

@Override
    public boolean isActive() {
        return javaChannel().socket().isBound();
    }

對於客戶端,它是判斷TCP連接是否完成
NioSocketChannel.java

@Override
    public boolean isActive() {
        SocketChannel ch = javaChannel();
        return ch.isOpen() && ch.isConnected();
    }

我們這裏直講服務器,如果isActive(),那麼就會調用 pipeline.fireChannelActive(); 通知channelHander已經active,這樣就會回調他們的channelActive方法。

繼續看pipeline.fireChannelActive();

DefaultChannelPipeline.java

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

AbstractChannelHandlerContext.invokeChannelActive方法就不看了,就是調用參數的channelActive。由於參數是head,那麼我們去看channelActive方法。

DefaultChannelPipeline.java

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

            readIfIsAutoRead();
        }

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

調用的是channel.read(),channel是NioServerSocketChannel,它的實現是在父類AbstractChannel.java裏面

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

DefaultChannelPipeline.java

@Override
    public final ChannelPipeline read() {
        tail.read();
        return this;
    }

AbstractChannelHandlerContext.java

@Override
    public ChannelHandlerContext read() {
        final AbstractChannelHandlerContext next = findContextOutbound();
        EventExecutor executor = next.executor();
        if (executor.inEventLoop()) {
            next.invokeRead();
        } else {
            ...
        }

        return this;
    }

private AbstractChannelHandlerContext findContextOutbound() {
        AbstractChannelHandlerContext ctx = this;
        do {
            ctx = ctx.prev;
        } while (!ctx.outbound);
        return ctx;
    }       

首先要尋找findContextOutbound,由於head的inbound=false,outbound=true,所以next=head,那麼就是調用head的read方法,如下:
DefaultChannelPipeline.java

@Override
        public void read(ChannelHandlerContext ctx) {
            unsafe.beginRead();
        }

AbstractChannel.java

@Override
        public final void beginRead() {
            assertEventLoop();

            if (!isActive()) {
                return;
            }

            try {
                doBeginRead();
            } catch (final Exception e) {
                ...
            }
        }

直接看doBeginRead()

AbstractNioChannel.java

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

還記得我們初始化NioServerSocketChannel的時候,我們傳給父類的readInterestOp嗎?沒錯,就是SelectionKey.OP_ACCEPT,如下:

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;

                ...
}               

上面doReadBegin就是把我們設置的readInterestOp重新設置到java selector上面,代表我們監聽的類型是SelectionKey.OP_ACCEPT,不在是最開始的0了。

到這裏,initAndRegister方法就基本講完了,再貼一次它的代碼,加深下印象。

final ChannelFuture initAndRegister() {
        Channel channel = null;
        try {
            channel = channelFactory.newChannel(); //1. 實例化NioServerSocketChannel
            init(channel); //2. 初始化
        } catch (Throwable t) {
        }

        ChannelFuture regFuture = config().group().register(channel); //3. 註冊selector到Java channel上面,註冊類型是0
        if (regFuture.cause() != null) {
            if (channel.isRegistered()) {
                channel.close();
            } else {
                channel.unsafe().closeForcibly();
            }
        }

        return regFuture;
    }

我們再來回憶一下initAndRegister方法

1. 實例化NioServerSocketChannel對象,channelFactory.newChannel()
a. 傳入父類的ops是SelectionKey.OP_ACCEPT
b. 它的父類AbstractNioChannel把channel設置成非阻塞,然後把SelectionKey.OP_ACCEPT存起來
c. 父類AbstractChannel初始化了ChannelId
d. AbstractChannel初始化了unsafe,類型是NioMessageUnsafe。
e. AbstractChannel初始化了pipeline,類型是DefaultChannelPipeline,每個channel都有自己的pipleline,它維護了channelHandler列表,如果有事件發生,那麼pipeline就負責把事件從頭傳到尾。

2. init方法
a. 它是在子類ServerBootstrap裏面實現,子類Bootstrap實現的是客戶端的。
b. setOptions()設置屬性,類型有很多,不同的業務場景可以設置不同的屬性。
c. addLast把我們設置的channelHandler添加到pipeline
d. 實例化了一個ServerBootstrapAcceptor,裏面封裝了childChannel,也添加到pipeline裏面

3. register
a. register調用的是bossGroup NioEventLoopGroup的register方法,NioEventLoopGroup regitster方法調用的next().regitster,next()調用chooser.next.
b. chooser有兩種PowerOfTwoEventExecutorChooser和GenericEventExecutorChooser,它們負責選擇NioEventLoopGroup裏面下一個NioEventLoop(NioEventLoopGroup裏面有nThreads個NioEventLoop,nThreads表示線程數,默認是cpu*2)
c. NioEventLoop.register調用的是它的父類SingleThreadEventLoop.register,所以它調用的是unsafe.register。從上面的初始化就可以知道,unsafe指的是NioMessageUnsafe,所以調用的是NioMessageUnsafe.register
d. NioMessageUnsafe並沒有實現register,所以調用的是它的父類AbstractUnsafe.regitster,然後調用register0
e. 在doRegitster裏面把selector註冊到Java的channel,key=0
f. 調用pipeline.fireChannelRegistered(),通知pipeline維護的channelHander,channel已經註冊了,回調了它們的channelRegitstered方法。

那initAndRegister就講完了,bind過程還沒有結束,因爲篇幅有點多了,下一篇繼續介紹doBind0:

private ChannelFuture doBind(final SocketAddress localAddress) {
        final ChannelFuture regFuture = initAndRegister(); //1. 這一篇的內容
        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); //2. 下一篇講doBind0()
            return promise;
        } else {
            ...
            });
            return promise;
        }
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章