前面,我們大致瞭解了 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 族如下:
整個族羣中,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 綁定。
我們以下面的代碼爲例進行解析:
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 事件傳播機制的知識點,我們放到後面的文章中去講解。