目錄java
Netty 源碼分析之 番外篇 Java NIO 的前生今世segmentfault
Netty 源碼分析之 一 揭開 Bootstrap 神祕的紅蓋頭ui
在分析客戶端的代碼時, 咱們已經對 Bootstrap 啓動 Netty 有了一個大體的認識, 那麼接下來分析服務器端時, 就會相對簡單一些了.
首先仍是來看一下服務器端的啓動代碼:
public final class EchoServer { static final boolean SSL = System.getProperty("ssl") != null; static final int PORT = Integer.parseInt(System.getProperty("port", "8007")); public static void main(String[] args) throws Exception { // Configure SSL. final SslContext sslCtx; if (SSL) { SelfSignedCertificate ssc = new SelfSignedCertificate(); sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build(); } else { sslCtx = null; } // Configure the server. EventLoopGroup bossGroup = new NioEventLoopGroup(1); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .option(ChannelOption.SO_BACKLOG, 100) .handler(new LoggingHandler(LogLevel.INFO)) .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline p = ch.pipeline(); if (sslCtx != null) { p.addLast(sslCtx.newHandler(ch.alloc())); } //p.addLast(new LoggingHandler(LogLevel.INFO)); p.addLast(new EchoServerHandler()); } }); // Start the server. ChannelFuture f = b.bind(PORT).sync(); // Wait until the server socket is closed. f.channel().closeFuture().sync(); } finally { // Shut down all event loops to terminate all threads. bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } }
和客戶端的代碼相比, 沒有很大的差異, 基本上也是進行了以下幾個部分的初始化:
EventLoopGroup: 不管是服務器端仍是客戶端, 都必須指定 EventLoopGroup. 在這個例子中, 指定了 NioEventLoopGroup, 表示一個 NIO 的EventLoopGroup, 不過服務器端須要指定兩個 EventLoopGroup, 一個是 bossGroup, 用於處理客戶端的鏈接請求; 另外一個是 workerGroup, 用於處理與各個客戶端鏈接的 IO 操做.
ChannelType: 指定 Channel 的類型. 由於是服務器端, 所以使用了 NioServerSocketChannel.
Handler: 設置數據的處理器.
咱們在分析客戶端的 Channel 初始化過程時, 已經提到, Channel 是對 Java 底層 Socket 鏈接的抽象, 而且知道了客戶端的 Channel 的具體類型是 NioSocketChannel, 那麼天然的, 服務器端的 Channel 類型就是 NioServerSocketChannel 了.
那麼接下來咱們按照分析客戶端的流程對服務器端的代碼也一樣地分析一遍, 這樣也方便咱們對比一下服務器端和客戶端有哪些不同的地方.
一樣的分析套路, 咱們已經知道了, 在客戶端中, Channel 的類型實際上是在初始化時, 經過 Bootstrap.channel() 方法設置的, 服務器端天然也不例外.
在服務器端, 咱們調用了 ServerBootstarap.channel(NioServerSocketChannel.class), 傳遞了一個 NioServerSocketChannel Class 對象. 這樣的話, 按照和分析客戶端代碼同樣的流程, 咱們就能夠肯定, NioServerSocketChannel 的實例化是經過 BootstrapChannelFactory 工廠類來完成的, 而 BootstrapChannelFactory 中的 clazz 字段被設置爲了 NioServerSocketChannel.class, 所以當調用 BootstrapChannelFactory.newChannel() 時:
@Override public T newChannel() { // 刪除 try 塊 return clazz.newInstance(); }
就獲取到了一個 NioServerSocketChannel 的實例.
最後咱們也來總結一下:
ServerBootstrap 中的 ChannelFactory 的實現是 BootstrapChannelFactory
生成的 Channel 的具體類型是 NioServerSocketChannel.
Channel 的實例化過程, 其實就是調用的 ChannelFactory.newChannel 方法, 而實例化的 Channel 的具體的類型又是和在初始化 ServerBootstrap 時傳入的 channel() 方法的參數相關. 所以對於咱們這個例子中的服務器端的 ServerBootstrap 而言, 生成的的 Channel 實例就是 NioServerSocketChannel.
首先仍是來看一下 NioServerSocketChannel 的實例化過程.
下面是 NioServerSocketChannel 的類層次結構圖:
首先, 咱們來看一下它的默認的構造器. 和 NioSocketChannel 相似, 構造器都是調用了 newSocket 來打開一個 Java 的 NIO Socket, 不過須要注意的是, 客戶端的 newSocket 調用的是 openSocketChannel, 而服務器端的 newSocket 調用的是 openServerSocketChannel. 顧名思義, 一個是客戶端的 Java SocketChannel, 一個是服務器端的 Java ServerSocketChannel.
private static ServerSocketChannel newSocket(SelectorProvider provider) { return provider.openServerSocketChannel(); } public NioServerSocketChannel() { this(newSocket(DEFAULT_SELECTOR_PROVIDER)); }
接下來會調用重載的構造器:
public NioServerSocketChannel(ServerSocketChannel channel) { super(null, channel, SelectionKey.OP_ACCEPT); config = new NioServerSocketChannelConfig(this, javaChannel().socket()); }
這個構造其中, 調用父類構造器時, 傳入的參數是 SelectionKey.OP_ACCEPT. 做爲對比, 咱們回想一下, 在客戶端的 Channel 初始化時, 傳入的參數是 SelectionKey.OP_READ. 有 Java NIO Socket 開發經驗的朋友就知道了, Java NIO 是一種 Reactor 模式, 咱們經過 selector 來實現 I/O 的多路複用複用. 在一開始時, 服務器端須要監聽客戶端的鏈接請求, 所以在這裏咱們設置了 SelectionKey.OP_ACCEPT, 即通知 selector 咱們對客戶端的鏈接請求感興趣.
接着和客戶端的分析一下, 會逐級地調用父類的構造器 NioServerSocketChannel <- AbstractNioMessageChannel <- AbstractNioChannel <- AbstractChannel.
一樣的, 在 AbstractChannel 中會實例化一個 unsafe 和 pipeline:
protected AbstractChannel(Channel parent) { this.parent = parent; unsafe = newUnsafe(); pipeline = new DefaultChannelPipeline(this); }
不過, 這裏有一點須要注意的是, 客戶端的 unsafe 是一個 AbstractNioByteChannel#NioByteUnsafe 的實例, 而在服務器端時, 由於 AbstractNioMessageChannel 重寫了newUnsafe 方法:
@Override protected AbstractNioUnsafe newUnsafe() { return new NioMessageUnsafe(); }
所以在服務器端, unsafe 字段實際上是一個 AbstractNioMessageChannel#AbstractNioUnsafe 的實例.
咱們來總結一下, 在 NioServerSocketChannsl 實例化過程當中, 所須要作的工做:
調用 NioServerSocketChannel.newSocket(DEFAULT_SELECTOR_PROVIDER) 打開一個新的 Java NIO ServerSocketChannel
AbstractChannel(Channel parent) 中初始化 AbstractChannel 的屬性:
parent 屬性置爲 null
unsafe 經過newUnsafe() 實例化一個 unsafe 對象, 它的類型是 AbstractNioMessageChannel#AbstractNioUnsafe 內部類
pipeline 是 new DefaultChannelPipeline(this) 新建立的實例.
AbstractNioChannel 中的屬性:
SelectableChannel ch 被設置爲 Java ServerSocketChannel, 即 NioServerSocketChannel#newSocket 返回的 Java NIO ServerSocketChannel.
readInterestOp 被設置爲 SelectionKey.OP_ACCEPT
SelectableChannel ch 被配置爲非阻塞的 ch.configureBlocking(false)
NioServerSocketChannel 中的屬性:
ServerSocketChannelConfig config = new NioServerSocketChannelConfig(this, javaChannel().socket())
服務器端和客戶端的 ChannelPipeline 的初始化一致, 所以就再也不單獨分析了.
服務器端和客戶端的 Channel 的註冊過程一致, 所以就再也不單獨分析了.
在客戶端的時候, 咱們只提供了一個 EventLoopGroup 對象, 而在服務器端的初始化時, 咱們設置了兩個 EventLoopGroup, 一個是 bossGroup, 另外一個是 workerGroup. 那麼這兩個 EventLoopGroup 都是幹什麼用的呢? 其實呢, bossGroup 是用於服務端 的 accept 的, 即用於處理客戶端的鏈接請求. 咱們能夠把 Netty 比做一個飯店, bossGroup 就像一個像一個前臺接待, 當客戶來到飯店吃時, 接待員就會引導顧客就坐, 爲顧客端茶送水等. 而 workerGroup, 其實就是實際上幹活的啦, 它們負責客戶端鏈接通道的 IO 操做: 當接待員 招待好顧客後, 就能夠稍作休息, 而此時後廚裏的廚師們(workerGroup)就開始忙碌地準備飯菜了.
關於 bossGroup 與 workerGroup 的關係, 咱們能夠用以下圖來展現:
首先, 服務器端 bossGroup 不斷地監聽是否有客戶端的鏈接, 當發現有一個新的客戶端鏈接到來時, bossGroup 就會爲此鏈接初始化各項資源, 而後從 workerGroup 中選出一個 EventLoop 綁定到此客戶端鏈接中. 那麼接下來的服務器與客戶端的交互過程就所有在此分配的 EventLoop 中了.
口說無憑, 咱們仍是以源碼說話吧.
首先在ServerBootstrap 初始化時, 調用了 b.group(bossGroup, workerGroup) 設置了兩個 EventLoopGroup, 咱們跟蹤進去看一下:
public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) { super.group(parentGroup); ... this.childGroup = childGroup; return this; }
顯然, 這個方法初始化了兩個字段, 一個是 group = parentGroup, 它是在 super.group(parentGroup) 中初始化的, 另外一個是 childGroup = childGroup. 接着咱們啓動程序調用了 b.bind 方法來監聽一個本地端口. bind 方法會觸發以下的調用鏈:
AbstractBootstrap.bind -> AbstractBootstrap.doBind -> AbstractBootstrap.initAndRegister
AbstractBootstrap.initAndRegister 是咱們的老朋友了, 咱們在分析客戶端程序時, 和它打過不少交到了, 咱們再來回顧一下這個方法吧:
final ChannelFuture initAndRegister() { final Channel channel = channelFactory().newChannel(); ... 省略異常判斷 init(channel); ChannelFuture regFuture = group().register(channel); return regFuture; }
這裏 group() 方法返回的是上面咱們提到的 bossGroup, 而這裏的 channel 咱們也已經分析過了, 它是一個是一個 NioServerSocketChannsl 實例, 所以咱們能夠知道, group().register(channel) 將 bossGroup 和 NioServerSocketChannsl 關聯起來了.
那麼 workerGroup 是在哪裏與 NioSocketChannel 關聯的呢?
咱們繼續看 init(channel) 方法:
@Override void init(Channel channel) throws Exception { ... ChannelPipeline p = channel.pipeline(); final EventLoopGroup currentChildGroup = childGroup; final ChannelHandler currentChildHandler = childHandler; final Entry<ChannelOption<?>, Object>[] currentChildOptions; final Entry<AttributeKey<?>, Object>[] currentChildAttrs; p.addLast(new ChannelInitializer<Channel>() { @Override public void initChannel(Channel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); ChannelHandler handler = handler(); if (handler != null) { pipeline.addLast(handler); } pipeline.addLast(new ServerBootstrapAcceptor( currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs)); } }); }
init 方法在 ServerBootstrap 中重寫了, 從上面的代碼片斷中咱們看到, 它爲 pipeline 中添加了一個 ChannelInitializer, 而這個 ChannelInitializer 中添加了一個關鍵的 ServerBootstrapAcceptor handler. 關於 handler 的添加與初始化的過程, 咱們留待下一小節中分析, 咱們如今關注一下 ServerBootstrapAcceptor 類.
ServerBootstrapAcceptor 中重寫了 channelRead 方法, 其主要代碼以下:
@Override @SuppressWarnings("unchecked") public void channelRead(ChannelHandlerContext ctx, Object msg) { final Channel child = (Channel) msg; child.pipeline().addLast(childHandler); ... childGroup.register(child).addListener(...); }
ServerBootstrapAcceptor 中的 childGroup 是構造此對象是傳入的 currentChildGroup, 即咱們的 workerGroup, 而 Channel 是一個 NioSocketChannel 的實例, 所以這裏的 childGroup.register 就是將 workerGroup 中的摸個 EventLoop 和 NioSocketChannel 關聯了. 既然這樣, 那麼如今的問題是, ServerBootstrapAcceptor.channelRead 方法是怎麼被調用的呢? 其實當一個 client 鏈接到 server 時, Java 底層的 NIO ServerSocketChannel 會有一個 SelectionKey.OP_ACCEPT 就緒事件, 接着就會調用到 NioServerSocketChannel.doReadMessages:
@Override protected int doReadMessages(List<Object> buf) throws Exception { SocketChannel ch = javaChannel().accept(); ... 省略異常處理 buf.add(new NioSocketChannel(this, ch)); return 1; }
在 doReadMessages 中, 經過 javaChannel().accept() 獲取到客戶端新鏈接的 SocketChannel, 接着就實例化一個 NioSocketChannel, 而且傳入 NioServerSocketChannel 對象(即 this), 由此可知, 咱們建立的這個 NioSocketChannel 的父 Channel 就是 NioServerSocketChannel 實例 .
接下來就經由 Netty 的 ChannelPipeline 機制, 將讀取事件逐級發送到各個 handler 中, 因而就會觸發前面咱們提到的 ServerBootstrapAcceptor.channelRead 方法啦.
服務器端的 handler 的添加過程和客戶端的有點區別, 和 EventLoopGroup 同樣, 服務器端的 handler 也有兩個, 一個是經過 handler() 方法設置 handler 字段, 另外一個是經過 childHandler() 設置 childHandler 字段. 經過前面的 bossGroup 和 workerGroup 的分析, 其實咱們在這裏能夠大膽地猜想: handler 字段與 accept 過程有關, 即這個 handler 負責處理客戶端的鏈接請求; 而 childHandler 就是負責和客戶端的鏈接的 IO 交互.
那麼其實是不是這樣的呢? 來, 咱們繼續經過代碼證實.
在 關於 bossGroup 與 workerGroup 小節中, 咱們提到, ServerBootstrap 重寫了 init 方法, 在這個方法中添加了 handler:
@Override void init(Channel channel) throws Exception { ... ChannelPipeline p = channel.pipeline(); final EventLoopGroup currentChildGroup = childGroup; final ChannelHandler currentChildHandler = childHandler; final Entry<ChannelOption<?>, Object>[] currentChildOptions; final Entry<AttributeKey<?>, Object>[] currentChildAttrs; p.addLast(new ChannelInitializer<Channel>() { @Override public void initChannel(Channel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); ChannelHandler handler = handler(); if (handler != null) { pipeline.addLast(handler); } pipeline.addLast(new ServerBootstrapAcceptor( currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs)); } }); }
上面代碼的 initChannel 方法中, 首先經過 handler() 方法獲取一個 handler, 若是獲取的 handler 不爲空,則添加到 pipeline 中. 而後接着, 添加了一個 ServerBootstrapAcceptor 實例. 那麼這裏 handler() 方法返回的是哪一個對象呢? 其實它返回的是 handler 字段, 而這個字段就是咱們在服務器端的啓動代碼中設置的:
b.group(bossGroup, workerGroup) ... .handler(new LoggingHandler(LogLevel.INFO))
那麼這個時候, pipeline 中的 handler 狀況以下:
根據咱們原來分析客戶端的經驗, 咱們指定, 當 channel 綁定到 eventLoop 後(在這裏是 NioServerSocketChannel 綁定到 bossGroup)中時, 會在 pipeline 中發出 fireChannelRegistered 事件, 接着就會觸發 ChannelInitializer.initChannel 方法的調用.
所以在綁定完成後, 此時的 pipeline 的內如以下:
前面咱們在分析 bossGroup 和 workerGroup 時, 已經知道了在 ServerBootstrapAcceptor.channelRead 中會爲新建的 Channel 設置 handler 並註冊到一個 eventLoop 中, 即:
@Override @SuppressWarnings("unchecked") public void channelRead(ChannelHandlerContext ctx, Object msg) { final Channel child = (Channel) msg; child.pipeline().addLast(childHandler); ... childGroup.register(child).addListener(...); }
而這裏的 childHandler 就是咱們在服務器端啓動代碼中設置的 handler:
b.group(bossGroup, workerGroup) ... .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline p = ch.pipeline(); if (sslCtx != null) { p.addLast(sslCtx.newHandler(ch.alloc())); } //p.addLast(new LoggingHandler(LogLevel.INFO)); p.addLast(new EchoServerHandler()); } });
後續的步驟就沒有什麼好說的了, 當這個客戶端鏈接 Channel 註冊後, 就會觸發 ChannelInitializer.initChannel 方法的調用, 此後的客戶端鏈接的 ChannelPipeline 狀態以下:
最後咱們來總結一下服務器端的 handler 與 childHandler 的區別與聯繫:
在服務器 NioServerSocketChannel 的 pipeline 中添加的是 handler 與 ServerBootstrapAcceptor.
當有新的客戶端鏈接請求時, ServerBootstrapAcceptor.channelRead 中負責新建此鏈接的 NioSocketChannel 並添加 childHandler 到 NioSocketChannel 對應的 pipeline 中, 並將此 channel 綁定到 workerGroup 中的某個 eventLoop 中.
handler 是在 accept 階段起做用, 它處理客戶端的鏈接請求.
childHandler 是在客戶端鏈接創建之後起做用, 它負責客戶端鏈接的 IO 交互.
下面咱們用一幅圖來總結一下服務器端的 handler 添加流程:
這是 Netty 源碼分析 系列教程的第一篇, 按個人計劃, 這一篇文章是一個簡述性質的, 即這裏會涉及到 Netty 各個功能模塊, 可是我只是簡單地提了一下, 而沒有深刻地探索它們內部的實現機理. 之因此這樣作, 第一, 是由於若是一上來就從細節分析, 那麼未免會陷入各類瑣碎的細節中難以自拔; 第二, 我想給讀者展現一個一個完整的 Netty 的運行流程, 讓讀者從一個總體上對 Netty 有一個感性的認識.
此篇文章涉及的模塊比較多, 面比較廣, 所以寫起來不免有一點跳躍, 而且我感受寫着寫着見見有點不知所云, 邏輯混亂了, 汗. 唉, 仍是感受本身功力不夠, hold 不住.
接下來的幾篇文章, 我會根據 Netty 的各個模塊深刻分析一下, 但願之後的文章可以組織的調理更加清晰一些.
本文由 yongshun 發表於我的博客, 採用 署名-相同方式共享 3.0 中國大陸許可協議.
Email: yongshun1228@gmail.com
本文標題爲: Netty 源碼分析之 一 揭開 Bootstrap 神祕的紅蓋頭 (服務器端)
本文連接爲: http://www.javashuo.com/article/p-ngtqdeei-ho.html