【轉】【Netty 】Netty Channel 源碼分析

前面,我們大致瞭解了 Netty 中的幾個核心組件。今天我們就來先來介紹 Netty 的網絡通信組件,用於執行網絡 I/O 操作 —— Channel

Netty 版本:4.1.30

概述

數據在網絡中總是以字節的形式進行流通。我們在進行網絡編程時選用何種傳輸方式編碼(OIO、NIO 等)決定了這些字節的傳輸方式。

在沒有 Netty 之前,爲了提升系統的併發能力,從 OIO 切換到 NIO 時,需要對代碼進行大量的重構,因爲相應的 Java NIO 與 IO API 大不相同。而 Netty 在這些 Java 原生 API 的基礎上做了一層封裝,對用戶提供了高度抽象而又統一的 API,從而讓傳輸方式的切換不在變得困難,只需要直接使用即可,而不需要對整個代碼進行重構。

Netty Channel UML

netty channel 族如下:

NettyChannelUML.pnguploading.4e448015.gif轉存失敗重新上傳取消NettyChannel

整個族羣中,AbstractChannel 是最爲關鍵的一個抽象類,從它繼承出了 AbstractNioChannel、AbstractOioChannel、AbstractEpollChannel、LocalChannel、EmbeddedChannel 等類,每個類代表了不同的協議以及相應的 IO 模型。除了 TCP 協議以外,Netty 還支持很多其他的連接協議,並且每種協議還有 NIO (異步 IO) 和 OIO (Old-IO,即傳統的阻塞 IO) 版本的區別。不同協議不同的阻塞類型的連接都有不同的 Channel 類型與之對應。下面是一些常用的 Channel 類型:

  • NioSocketChannel:代表異步的客戶端 TCP Socket 連接
  • NioServerSocketChannel:異步的服務器端 TCP Socket 連接
  • NioDatagramChannel:異步的 UDP 連接
  • NioSctpChannel:異步的客戶端 Sctp 連接
  • NioSctpServerChannel:異步的 Sctp 服務器端連接
  • OioSocketChannel:同步的客戶端 TCP Socket 連接
  • OioServerSocketChannel:同步的服務器端 TCP Socket 連接
  • OioDatagramChannel:同步的 UDP 連接
  • OioSctpChannel:同步的 Sctp 服務器端連接
  • OioSctpServerChannel:同步的客戶端 TCP Socket 連接

Channel API

我們先來看下最頂層接口 channel 主要的 API,常用的如下:

接口名 描述
eventLoop() Channel 需要註冊到 EventLoop 的多路複用器上,用於處理 I/O 事件,通過 eventLoop () 方法可以獲取到 Channel 註冊的 EventLoop。EventLoop 本質上就是處理網絡讀寫事件的 Reactor 線程。在 Netty 中,它不僅僅用來處理網絡事件,也可以用來執行定時任務和用戶自定義 NioTask 等任務。
pipeline() 返回 channel 分配的 ChannelPipeline
isActive() 判斷 channel 是否激活。激活的意義取決於底層的傳輸類型。例如,一個 Socket 傳輸一旦連接到了遠程節點便是活動的,而一個 Datagram 傳輸一旦被打開便是活動的
localAddress() 返回本地的 socket 地址
remoteAddress() 返回遠程的 socket 地址
flush() 將之前已寫的數據沖刷到底層 Channel 上去
write(Object msg) 請求將當前的 msg 通過 ChannelPipeline 寫入到目標 Channel 中。注意,write 操作只是將消息存入到消息發送環形數組中,並沒有真正被髮送,只有調用 flush 操作纔會被寫入到 Channel 中,發送給對方。
writeAndFlush() 等同於調用 write () 並接着調用 flush ()
metadate() 熟悉 TCP 協議的讀者可能知道,當創建 Socket 的時候需要指定 TCP 參數,例如接收和發送的 TCP 緩衝區大小,TCP 的超時時間。是否重用地址等。在 Netty 中,每個 Channel 對應一個物理鏈接,每個連接都有自己的 TCP 參數配置。所以,Channel 會聚合一個 ChannelMetadata 用來對 TCP 參數提供元數據描述信息,通過 metadata () 方法就可以獲取當前 Channel 的 TCP 參數配置。
read() 從當前的 Channel 中讀取數據到第一個 inbound 緩衝區中,如果數據被成功讀取,觸發 ChannelHandler.channelRead (ChannelHandlerContext,Object) 事件。讀取操作 API 調用完成後,緊接着會觸發 ChannelHander.channelReadComplete(ChannelHandlerContext)事件,這樣業務的 ChannelHandler 可以決定是否需要繼續讀取數據。如果已經有操作請求被掛起,則後續的讀操作會被忽略。
close(ChannelPromise promise) 主動關閉當前連接,通過 ChannelPromise 設置操作結果並進行結果通知,無論操作是否成功,都可以通過 ChannelPromise 獲取操作結果。該操作會級聯觸發 ChannelPipeline 中所有 ChannelHandler 的 ChannelHandler.close (ChannelHandlerContext,ChannelPromise) 事件。
parent() 對於服務端 Channel 而言,它的父 Channel 爲空;對於客戶端 Channel,它的父 Channel 就是創建它的 ServerSocketChannel。
id() 返回 ChannelId 對象,ChannelId 是 Channel 的唯一標識。

Channel 創建

對 Netty Channel API 以及相關的類有了一個初步瞭解之後,接下來我們來詳細瞭解一下在 Netty 的啓動過程中 Channel 是如何創建的。服務端 Channel 的創建過程,主要分爲四個步驟:1)Channel 創建;2)Channel 初始化;3)Channel 註冊;4)Channel 綁定。

Netty%20Channel%20Process.pnguploading.4e448015.gif轉存失敗重新上傳取消Netty Channel Process

我們以下面的代碼爲例進行解析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 創建兩個線程組,專門用於網絡事件的處理,Reactor線程組
// 用來接收客戶端的連接,
EventLoopGroup bossGroup = new NioEventLoopGroup();
// 用來進行SocketChannel的網絡讀寫
EventLoopGroup workGroup = new NioEventLoopGroup();

// 創建輔助啓動類ServerBootstrap,並設置相關配置:
ServerBootstrap b = new ServerBootstrap();
// 設置處理Accept事件和讀寫操作的事件循環組
b.group(bossGroup, workGroup)
         // 配置Channel類型
        .channel(NioServerSocketChannel.class)
         // 配置監聽地址
        .localAddress(new InetSocketAddress(port))
         // 設置服務器通道的選項,設置TCP屬性
        .option(ChannelOption.SO_KEEPALIVE, Boolean.TRUE)
         // 設置建立連接後的客戶端通道的選項
        .childOption(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
         // channel屬性,便於保存用戶自定義數據
        .attr(AttributeKey.newInstance("UserId"), "60293")
    	.handler(new LoggingHandler(LogLevel.INFO))
        // 設置子處理器,主要是用戶的自定義處理器,用於處理IO網絡事件
        .childHandler(new ChannelInitializer<SocketChannel>() {
            @Override
            public void initChannel(SocketChannel ch) throws Exception {
                ch.pipeline().addLast(serverHandler);
            }
        });

// 調用bind()方法綁定端口,sync()會阻塞等待處理請求。這是因爲bind()方法是一個異步過程,會立即返回一個ChannelFuture對象,調用sync()會等待執行完成
ChannelFuture f = b.bind().sync();
// 獲得Channel的closeFuture阻塞等待關閉,服務器Channel關閉時closeFuture會完成
f.channel().closeFuture().sync();

調用 channel () 接口設置 AbstractBootstrap 的成員變量 channelFactory,該變量顧名思義就是用於創建 channel 的工廠類。源碼如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
...

public B channel(Class<? extends C> channelClass) {
    if (channelClass == null) {
        throw new NullPointerException("channelClass");
    }
    // 創建 channelFactory
    return channelFactory(new ReflectiveChannelFactory<C>(channelClass));
}

...

public B channelFactory(ChannelFactory<? extends C> channelFactory) {
    if (channelFactory == null) {
        throw new NullPointerException("channelFactory");
    }
    
    if (this.channelFactory != null) {
        throw new IllegalStateException("channelFactory set already");
    }
    this.channelFactory = channelFactory;
    return (B) this;
}

...

channelFactory 設置爲 ReflectiveChannelFactory ,在我們這個例子中 clazz 爲 NioServerSocketChannel ,我們可以看到其中有個 newChannel () 接口,通過反射的方式來調用,這個接口的調用處我們後面會介紹到。源碼如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Channel工廠類
public class ReflectiveChannelFactory<T extends Channel> implements ChannelFactory<T> {

    private final Class<? extends T> clazz;

    public ReflectiveChannelFactory(Class<? extends T> clazz) {
        if (clazz == null) {
            throw new NullPointerException("clazz");
        }
        this.clazz = clazz;
    }
	
    @Override
    public T newChannel() {
        try {
            // 通過反射來進行常見Channel實例
            return clazz.newInstance();
        } catch (Throwable t) {
            throw new ChannelException("Unable to create Channel from class " + clazz, t);
        }
    }

    @Override
    public String toString() {
        return StringUtil.simpleClassName(clazz) + ".class";
    }
}

接下來我們來看下 NioServerSocketChannel 的構造函數,主要就是:

  • 生成 ServerSocketChannel 對象。NioServerSocketChannel 創建時,首先使用 SelectorProvider 的 openServerSocketChannel 打開服務器套接字通道。SelectorProvider 是 Java 的 NIO 提供的抽象類,是選擇器和可選擇通道的服務提供者。具體的實現類有 SelectorProviderImpl,EPollSelectorProvide,PollSelectorProvider。選擇器的主要工作是根據操作系統類型和版本選擇合適的 Provider:如果 LInux 內核版本 >=2.6 則,具體的 SelectorProvider 爲 EPollSelectorProvider,否則爲默認的 PollSelectorProvider。
  • 設置 ServerSocketChannelConfig 成員變量。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
private static ServerSocketChannel newSocket(SelectorProvider provider) {
    try {
        // 調用JDK底層API生成 ServerSocketChannel 對象實例
        return provider.openServerSocketChannel();
    } catch (IOException e) {
        throw new ChannelException("Failed to open a server socket.", e);
    }
}

private final ServerSocketChannelConfig config;

public NioServerSocketChannel() {
    this(newSocket(DEFAULT_SELECTOR_PROVIDER));
}

public NioServerSocketChannel(SelectorProvider provider) {
    this(newSocket(provider));
}

public NioServerSocketChannel(ServerSocketChannel channel) {
    // 調用 AbstractNioChannel 構造器,創建 NioServerSocketChannel,設置SelectionKey爲ACCEPT
    super(null, channel, SelectionKey.OP_ACCEPT);
    // 創建ChannleConfig對象,主要是TCP參數配置類
    config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}

AbstractNioChannel 的構造器如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
    // 調用 AbstractChannel 構造器
    super(parent);
    this.ch = ch;
    // 從上一步過來,這裏設置爲 SelectionKey.OP_ACCEPT
    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);
    }
}

在 AbstractChannel 構造器中,會設 Channel 關聯的三個核心對象:ChannelId、ChannelPipeline、Unsafe。

  • 初始化 ChannelId,ChannelId 是一個全局唯一的值;
  • 創建 NioMessageUnsafe 實例,該類爲 Channel 提供了用於完成網絡通訊相關的底層操作,如 connect (),read (),register (),bind (),close () 等;
  • 爲 Channel 創建 DefaultChannelPipeline,初始事件傳播管道。關於 Pipeline 的分析,請看 後文 的分析。
1
2
3
4
5
6
7
8
9
protected AbstractChannel(Channel parent) {
    this.parent = parent;
    // 設置ChannelId
    id = newId();
    // 設置Unsafe
    unsafe = newUnsafe();
    // 設置Pipeline
    pipeline = newChannelPipeline();
}

從 NioServerSocketChannelConfig 的構造函數追溯下去,在 DefaultChannelConfig 會設置 channel 成員變量。

1
2
3
4
5
6
7
8
9
public DefaultChannelConfig(Channel channel) {
    this(channel, new AdaptiveRecvByteBufAllocator());
}

protected DefaultChannelConfig(Channel channel, RecvByteBufAllocator allocator) {
    setRecvByteBufAllocator(allocator, channel.metadata());
    // 綁定channel
    this.channel = channel;
}

以上就是 channel 創建的過程,總結一下:

  • 通過 ReflectiveChannelFactory 工廠類,以反射的方式對 channel 進行創建;
  • channel 創建的過程中,會創建四個重要的對象:ChannelId、ChannelConfig、ChannelPipeline、Unsafe。

Channel 初始化

主要分爲以下兩步:

  • 將啓動器(Bootstrap)設置的選項和屬性設置到 NettyChannel 上面
  • 向 Pipeline 添加初始化 Handler,供註冊後使用

我們從 AbstractBootstrap 的 bind () 接口進去,調用鏈:bind () —> doBind (localAddress) —> initAndRegister () —> init (Channel channel),我們看下 ServerBootstrap 中 init () 接口的實現:

1
2
3
4
5
6
7
8
9
10
final ChannelFuture initAndRegister() {
    Channel channel = null;
    try {
        // 調用Channel工程類的newChannel()接口,創建channel,就是前面我們講的部分內容
        channel = channelFactory.newChannel();
        // 初始化channel
        init(channel);
    } catch (Throwable t) {
        ....
}

初始化 Channel,我們來重點看下 init (channel) 接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
void init(Channel channel) throws Exception {
    // 獲取啓動器 啓動時配置的option參數,主要是TCP的一些屬性
    final Map<ChannelOption<?>, Object> options = options0();
    // 將獲得到 options 配置到 ChannelConfig 中去
    synchronized (options) {
        setChannelOptions(channel, options, logger);
    }

    // 獲取 ServerBootstrap 啓動時配置的 attr 參數
    final Map<AttributeKey<?>, Object> attrs = attrs0();
    // 配置 Channel attr,主要是設置用戶自定義的一些參數
    synchronized (attrs) {
        for (Entry<AttributeKey<?>, Object> e: attrs.entrySet()) {
            @SuppressWarnings("unchecked")
            AttributeKey<Object> key = (AttributeKey<Object>) e.getKey();
            channel.attr(key).set(e.getValue());
        }
    }
	
    // 獲取channel中的 pipeline,這個pipeline使我們前面在channel創建過程中設置的 pipeline
    ChannelPipeline p = channel.pipeline();

    // 將啓動器中配置的 childGroup 保存到局部變量 currentChildGroup
    final EventLoopGroup currentChildGroup = childGroup;
    // 將啓動器中配置的 childHandler 保存到局部變量 currentChildHandler
    final ChannelHandler currentChildHandler = childHandler;
    final Entry<ChannelOption<?>, Object>[] currentChildOptions;
    final Entry<AttributeKey<?>, Object>[] currentChildAttrs;
    // 保存用戶設置的 childOptions 到局部變量 currentChildOptions
    synchronized (childOptions) {
        currentChildOptions = childOptions.entrySet().toArray(newOptionArray(childOptions.size()));
    }
    // 保存用戶設置的 childAttrs 到局部變量 currentChildAttrs
    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();
            // 獲取啓動器上配置的handler
            ChannelHandler handler = config.handler();
            if (handler != null) {
                // 添加 handler 到 pipeline 中
                pipeline.addLast(handler);
            }
            ch.eventLoop().execute(new Runnable() {
                @Override
                public void run() {
                    // 用child相關的參數創建出一個新連接接入器ServerBootstrapAcceptor
                    // 通過 ServerBootstrapAcceptor 可以將一個新連接綁定到一個線程上去
                    // 每次有新的連接進來 ServerBootstrapAcceptor 都會用child相關的屬性對它們進行配置,並註冊到ChaildGroup上去
                    pipeline.addLast(new ServerBootstrapAcceptor(
                            ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
                }
            });
        }
    });
}

對於新連接接入器 ServerBootstrapAcceptor 的分析 ,請查看 後文

Channel 註冊

在 channel 完成創建和初始化之後,接下來就需要將其註冊到事件輪循器 Selector 上去。我們回到 initAndRegister 接口上去:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
final ChannelFuture initAndRegister() {

    ...

    // 獲取 EventLoopGroup ,並調用它的 register 方法來註冊 channel
    ChannelFuture regFuture = config().group().register(channel);
    if (regFuture.cause() != null) {
        if (channel.isRegistered()) {
            channel.close();
        } else {
            channel.unsafe().closeForcibly();
        }
    }
    return regFuture;
}

最終會向下調用到 SingleThreadEventLoop 中的 register 接口:

如何調用到這裏,裏面的細節需要等到後面文章講到 MultithreadEventExecutorGroup 再詳細說明

1
2
3
4
5
6
7
@Override
public ChannelFuture register(final ChannelPromise promise) {
    ObjectUtil.checkNotNull(promise, "promise");
    // 調用unsafe的register接口
    promise.channel().unsafe().register(this, promise);
    return promise;
}

代碼跟蹤下去,直到 AbstractChannel 中的 AbstractUnsafe 這個類中的 register 接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@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;
    }

    // 將該Channel與eventLoop 進行綁定,後續與該channel相關的IO操作都由eventLoop來處理
    AbstractChannel.this.eventLoop = eventLoop;
	// 初次註冊時 eventLoop.inEventLoop() 返回false
    if (eventLoop.inEventLoop()) {
        // 調用實際的註冊接口register0
        register0(promise);
    } else {
        try {
            eventLoop.execute(new Runnable() {
                @Override
                public void run() {
                    // 調用實際的註冊接口register0
                    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);
        }
    }
}

register0 接口主要分爲以下三段邏輯:

  • doRegister();

  • pipeline.invokeHandlerAddedIfNeeded();

  • pipeline.fireChannelRegistered();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
private void register0(ChannelPromise promise) {
    try {
        if (!promise.setUncancellable() || !ensureOpen(promise)) {
            return;
        }
        boolean firstRegistration = neverRegistered;
        // 調用 doRegister() 接口
        doRegister();
        neverRegistered = false;
        registered = true;
        
       	// 通過pipeline的傳播機制,觸發handlerAdded事件
        pipeline.invokeHandlerAddedIfNeeded();
        safeSetSuccess(promise);
        // 通過pipeline的傳播機制,觸發channelRegistered事件
        pipeline.fireChannelRegistered();
        // 還沒有綁定,所以這裏的 isActive() 返回false.
        if (isActive()) {
            if (firstRegistration) {
                pipeline.fireChannelActive();
            } else if (config().isAutoRead()) {
                beginRead();
            }
        }
    } catch (Throwable t) {
        closeForcibly();
        closeFuture.setClosed();
        safeSetFailure(promise, t);
    }
}

我們來看 AbstractNioChannel 中的 doRegister () 接口,最終調用的就是 Java JDK 底層的 NIO API 來註冊。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Override
protected void doRegister() throws Exception {
    boolean selected = false;
    for (;;) {
        try {
            // eventLoop().unwrappedSelector():獲取selector,將在後面介紹 EventLoop 創建時會講到
            // 將selector註冊到Java NIO Channel上
            // ops 設置爲 0,表示不關心任何事件
            // att 設置爲 channel自身,表示後面還會將channel取出來用作它用(後面文章會講到)
            selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
            return;
        } catch (CancelledKeyException e) {
            if (!selected) {
                eventLoop().selectNow();
                selected = true;
            } else {
                throw e;
            }
        }
    }
}

Channel 綁定

在完成創建、初始化以及註冊之後,接下來就是 Channel 綁定操作。

本小節涉及到的 pipeline 事件傳播機制,我們放到後面的文章中去講解。

從啓動器的 bind () 接口開始,往下調用 doBind () 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
        doBind0(regFuture, channel, localAddress, promise);
        return promise;
    } else {
        ....
    }
}

doBind 方法又會調用 doBind0 () 方法,在 doBind0 () 方法中會通過 EventLoop 去執行 channel 的 bind () 任務,關於 EventLoop 的 execute 接口的分析,請看後面的 文章 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private static void doBind0(
        final ChannelFuture regFuture, final Channel channel,
        final SocketAddress localAddress, final ChannelPromise promise) {

    channel.eventLoop().execute(new Runnable() {
        @Override
        public void run() {
            if (regFuture.isSuccess()) {
                // 調用channel.bind接口
                channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
            } else {
                promise.setFailure(regFuture.cause());
            }
        }
    });
}

doBind0 () 方法往下會條用到 pipeline.bind(localAddress, promise); 方法,通過 pipeline 的傳播機制,最終會調用到 AbstractChannel.AbstractUnsafe.bind () 方法,這個方法主要做兩件事情:

  • 調用 doBind ():調用底層 JDK API 進行 Channel 的端口綁定。
  • 調用 pipeline.fireChannelActive ():

關於 Pipeline 的傳播機制,請看 後文

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Override
public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
    
    ....
    
    // wasActive 在綁定成功前爲 false
    boolean wasActive = isActive();
    try {
        // 調用doBind()調用JDK底層API進行端口綁定
        doBind(localAddress);
    } catch (Throwable t) {
        safeSetFailure(promise, t);
        closeIfClosed();
        return;
    }
	// 完成綁定之後,isActive() 返回true
    if (!wasActive && isActive()) {
        invokeLater(new Runnable() {
            @Override
            public void run() {
                // 觸發channelActive事件
                pipeline.fireChannelActive();
            }
        });
    }
    safeSetSuccess(promise);
}

我們這裏看服務端 NioServerSocketChannel 實現的 doBind 方法,最終會調用 JDK 底層 NIO Channel 的 bind 方法:

1
2
3
4
5
6
7
8
@Override
protected void doBind(SocketAddress localAddress) throws Exception {
    if (PlatformDependent.javaVersion() >= 7) {
        javaChannel().bind(localAddress, config.getBacklog());
    } else {
        javaChannel().socket().bind(localAddress, config.getBacklog());
    }
}

調用 pipeline.fireChannelActive (),開始傳播 active 事件,pipeline 首先就會調用 HeadContext 節點進行事件傳播,會調用到 DefaultChannelPipeline.HeadContext.channelActive () 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
    // 觸發heanlder 的 ChannelActive 方法
    ctx.fireChannelActive();
    // 調用接口readIfIsAutoRead
    readIfIsAutoRead();
}

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

channel.read () 方法往下會調用到 AbstractChannelHandlerContext.read () 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Override
public ChannelHandlerContext read() {
    // 獲取下一個ChannelHandlerContext節點
    final AbstractChannelHandlerContext next = findContextOutbound();
    // 獲取EventExecutor
    EventExecutor executor = next.executor();
    if (executor.inEventLoop()) {
        // 調用下一個節點的invokeRead接口
        next.invokeRead();
    } else {
        Runnable task = next.invokeReadTask;
        if (task == null) {
            next.invokeReadTask = task = new Runnable() {
                @Override
                public void run() {
                    next.invokeRead();
                }
            };
        }
        executor.execute(task);
    }
    return this;
}

通過 pipeline 的事件傳播機制,最終會調用到 AbstractChannel.AbstractUnsafe.beginRead () 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Override
public final void beginRead() {
    assertEventLoop();
    if (!isActive()) {
        return;
    }
    try {
        // 調用 doBeginRead();
        doBeginRead();
    } catch (final Exception e) {
        invokeLater(new Runnable() {
            @Override
            public void run() {
                pipeline.fireExceptionCaught(e);
            }
        });
        close(voidPromise());
    }
}

我們看下 AbstractNioChannel 對 doBeginRead 接口的實現邏輯:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 註冊一個OP_ACCEPT
@Override
protected void doBeginRead() throws Exception {
    // Channel.read() or ChannelHandlerContext.read() was called
    // 獲取channel註冊是的設置的 selectionKey
    final SelectionKey selectionKey = this.selectionKey;
    // selectionKey無效則返回
    if (!selectionKey.isValid()) {
        return;
    }
	
    readPending = true;
	// 前面講到channel在註冊的時候,這是 interestOps 設置的是 0
    final int interestOps = selectionKey.interestOps();
    // readInterestOp 在前面講到channel創建的時候,設置值爲 SelectionKey.OP_ACCEPT
    if ((interestOps & readInterestOp) == 0) {
        // 最終 selectionKey 的興趣集就會設置爲 SelectionKey.OP_ACCEPT
        // 表示隨時可以接收新連接的接入
        selectionKey.interestOps(interestOps | readInterestOp);
    }
}

總結

至此,我們就分析完了 Channel 的創建、初始化、註冊、綁定的流程。其中涉及到的 EventLoopGroup 和 Pipeline 事件傳播機制的知識點,我們放到後面的文章中去講解。

參考資料

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章