讀古今文學網 > Netty實戰 > 第8章 引導 >

第8章 引導

本章主要內容

  • 引導客戶端和服務器
  • Channel內引導客戶端
  • 添加ChannelHandler
  • 使用ChannelOption和屬性[1]

在深入地學習了ChannelPipelineChannelHandlerEventLoop之後,你接下來的問題可能是:「如何將這些部分組織起來,成為一個可實際運行的應用程序呢?」

答案是?「引導」(Bootstrapping)。到目前為止,我們對這個術語的使用還比較含糊,現在已經到了精確定義它的時候了。簡單來說,引導一個應用程序是指對它進行配置,並使它運行起來的過程——儘管該過程的具體細節可能並不如它的定義那樣簡單,尤其是對於一個網絡應用程序來說。

和它對應用程序體系架構的做法[2]一致,Netty處理引導的方式使你的應用程序[3]和網絡層相隔離,無論它是客戶端還是服務器。正如同你將要看到的,所有的框架組件都將會在後台結合在一起並且啟用。引導是我們一直以來都在組裝的完整拼圖[4]中缺失的那一塊。當你把它放到正確的位置上時,你的Netty應用程序就完整了。

8.1 Bootstrap類

引導類的層次結構包括一個抽像的父類和兩個具體的引導子類,如圖8-1所示。

圖8-1 引導類的層次結構

相對於將具體的引導類分別看作用於服務器和客戶端的引導來說,記住它們的本意是用來支撐不同的應用程序的功能的將有所裨益。也就是說,服務器致力於使用一個父Channel來接受來自客戶端的連接,並創建子Channel以用於它們之間的通信;而客戶端將最可能只需要一個單獨的、沒有父ChannelChannel來用於所有的網絡交互。(正如同我們將要看到的,這也適用於無連接的傳輸協議,如UDP,因為它們並不是每個連接都需要一個單獨的Channel。)

我們在前面的幾章中學習的幾個Netty組件都參與了引導的過程,而且其中一些在客戶端和服務器都有用到。兩種應用程序類型之間通用的引導步驟由AbstractBootstrap處理,而特定於客戶端或者服務器的引導步驟則分別由BootstrapServerBootstrap處理。

在本章中接下來的部分,我們將詳細地探討這兩個類,首先從不那麼複雜的Bootstrap類開始。

為什麼引導類是Cloneable的

你有時可能會需要創建多個具有類似配置或者完全相同配置的Channel。為了支持這種模式而又不需要為每個Channel都創建並配置一個新的引導類實例,AbstractBootstrap被標記為了Cloneable[5]。在一個已經配置完成的引導類實例上調用clone方法將返回另一個可以立即使用的引導類實例。

注意,這種方式只會創建引導類實例的EventLoopGroup的一個淺拷貝,所以,後者[6]將在所有克隆的Channel實例之間共享。這是可以接受的,因為通常這些克隆的Channel的生命週期都很短暫,一個典型的場景是——創建一個Channel以進行一次HTTP請求。

AbstractBootstrap類的完整聲明是:

public abstract class AbstractBootstrap
  <B extends AbstractBootstrap<B,C>,C extends Channel>  

在這個簽名中,子類型B是其父類型的一個類型參數,因此可以返回到運行時實例的引用以支持方法的鏈式調用(也就是所謂的流式語法)。

其子類的聲明如下:

public class Bootstrap
  extends AbstractBootstrap<Bootstrap,Channel>  

public class ServerBootstrap
  extends AbstractBootstrap<ServerBootstrap,ServerChannel>  

8.2 引導客戶端和無連接協議

Bootstrap類被用於客戶端或者使用了無連接協議的應用程序中。表8-1提供了該類的一個概覽,其中許多方法都繼承自AbstractBootstrap類。

表8-1 Bootstrap類的API

名  稱

描  述

Bootstrap group(EventLoopGroup)

設置用於處理Channel所有事件的EventLoopGroup

Bootstrap channel(
  Class<? extends C>)
Bootstrap channelFactory(
  ChannelFactory<? extends C>)

channel方法指定了Channel的實現類。如果該實現類沒提供默認的構造函數[7],可以通過調用channel- Factory方法來指定一個工廠類,它將會被bind方法調用

Bootstrap localAddress(
  SocketAddress)

指定Channel應該綁定到的本地地址。如果沒有指定,則將由操作系統創建一個隨機的地址。或者,也可以通過bind或者connect方法指定localAddress

<T> Bootstrap option(
  ChannelOption<T> option,
  T value)

設置ChannelOption,其將被應用到每個新創建的ChannelChannelConfig。這些選項將會通過bind或者connect方法設置到Channel,不管哪個先被調用。這個方法在Channel已經被創建後再調用將不會有任何的效果。支持的ChannelOption取決於使用的Channel類型。參見8.6節以及ChannelConfig的API文檔,瞭解所使用的Channel類型

<T> Bootstrap attr(
  Attribute<T> key, T value)

指定新創建的Channel的屬性值。這些屬性值是通過bind或者connect方法設置到Channel的,具體取決於誰最先被調用。這個方法在Channel被創建後將不會有任何的效果。參見8.6節

Bootstrap
handler(ChannelHandler)

設置將被添加到ChannelPipeline以接收事件通知的ChannelHandler

Bootstrap clone

創建一個當前Bootstrap的克隆,其具有和原始的Bootstrap相同的設置信息

Bootstrap remoteAddress(
  SocketAddress)

設置遠程地址。或者,也可以通過connect方法來指定它

ChannelFuture connect

連接到遠程節點並返回一個ChannelFuture,其將會在連接操作完成後接收到通知

ChannelFuture bind

綁定Channel並返回一個ChannelFuture,其將會在綁定操作完成後接收到通知,在那之後必須調用Channel. connect方法來建立連接

下一節將一步一步地講解客戶端的引導過程。我們也將討論在選擇可用的組件實現時保持兼容性的問題。

8.2.1 引導客戶端

Bootstrap類負責為客戶端和使用無連接協議的應用程序創建Channel,如圖8-2所示。

圖8-2 引導過程

代碼清單8-1中的代碼引導了一個使用NIO TCP傳輸的客戶端。

代碼清單8-1 引導一個客戶端

 EventLoopGroup group = new NioEventLoopGroup;
 Bootstrap bootstrap = new Bootstrap;   ← --  創建一個Bootstrap類的實例以創建和連接新的客戶端Channel
 bootstrap.group(group)  ← -- 設置EventLoopGroup,提供用於處理Channel事件的EventLoop
   .channel(NioSocketChannel.class)   ← --   指定要使用的Channel 實現
   .handler(new SimpleChannelInboundHandler<ByteBuf> {  ← --  設置用於Channel 事件和數據的ChannelInboundHandler
     @Override
     protected void channeRead0(
       ChannelHandlerContext channelHandlerContext,
       ByteBuf byteBuf) throws Exception {
       System.out.println("Received data");
     }
   } );
ChannelFuture future = bootstrap.connect(
  new InetSocketAddress("www.manning.com", 80));  ← --   連接到遠程主機
future.addListener(new ChannelFutureListener {
  @Override
  public void operationComplete(ChannelFuture channelFuture)
    throws Exception {
    if (channelFuture.isSuccess) {
      System.out.println("Connection established");
    } else {
      System.err.println("Connection attempt failed");
      channelFuture.cause.printStackTrace;
    }
   }
} );  

這個示例使用了前面提到的流式語法;這些方法(除了connect方法以外)將通過每次方法調用所返回的對Bootstrap實例的引用鏈接在一起。

8.2.2 Channel和EventLoopGroup的兼容性

代碼清單8-2所示的目錄清單來自io.netty.channel包。你可以從包名以及與其相對應的類名的前綴看到,對於NIO以及OIO傳輸兩者來說,都有相關的EventLoopGroupChannel實現。

代碼清單8-2 相互兼容的EventLoopGroupChannel

channel
├───nio
│     NioEventLoopGroup
├───oio
│     OioEventLoopGroup
└───socket
   ├───nio
   │     NioDatagramChannel
   │     NioServerSocketChannel
   │     NioSocketChannel
   └───oio
        OioDatagramChannel
        OioServerSocketChannel
        OioSocketChannel  

必須保持這種兼容性,不能混用具有不同前綴的組件,如NioEventLoopGroupOioSocketChannel。代碼清單8-3展示了試圖這樣做的一個例子。

代碼清單8-3 不兼容的ChannelEventLoopGroup

EventLoopGroup group = new NioEventLoopGroup;
Bootstrap bootstrap = new Bootstrap;   ← --  創建一個新的Bootstrap類的實例,以創建新的客戶端Channel
bootstrap.group(group)  ← --  指定一個適用於NIO 的EventLoopGroup 實現
  .channel(OioSocketChannel.class)  ← --   指定一個適用於OIO 的Channel實現類
  .handler(new SimpleChannelInboundHandler<ByteBuf> {  ← --  設置一個用於處理Channel的I/O 事件和數據的ChannelInboundHandler
     @Override
     protected void channelRead0(
       ChannelHandlerContext channelHandlerContext,
       ByteBuf byteBuf) throws Exception {
       System.out.println("Received data");
     }
  } );
ChannelFuture future = bootstrap.connect(
  new InetSocketAddress("www.manning.com", 80));  ← --  嘗試連接到遠程節點 
future.syncUninterruptibly;  

這段代碼將會導致IllegalStateException,因為它混用了不兼容的傳輸。

Exception in thread "main" java.lang.IllegalStateException:
incompatible event loop type: io.netty.channel.nio.NioEventLoop at
io.netty.channel.AbstractChannel$AbstractUnsafe.register(
AbstractChannel.java:571)  

關於IllegalStateException的更多討論

在引導的過程中,在調用bind或者connect方法之前,必須調用以下方法來設置所需的組件:

  • group;

  • channel或者channelFactory;

  • handler。

如果不這樣做,則將會導致IllegalStateException。對handler方法的調用尤其重要,因為它需要配置好ChannelPipeline

8.3 引導服務器

我們將從ServerBootstrap API的概要視圖開始我們對服務器引導過程的概述。然後,我們將會探討引導服務器過程中所涉及的幾個步驟,以及幾個相關的主題,包含從一個ServerChannel的子Channel中引導一個客戶端這樣的特殊情況。

8.3.1 ServerBootstrap類

表8-2列出了ServerBootstrap類的方法。

表8-2 ServerBootstrap類的方法

名  稱

描  述

group

設置ServerBootstrap要用的EventLoopGroup。這個EventLoopGroup將用於ServerChannel和被接受的子Channel的I/O處理

channel

設置將要被實例化的ServerChannel

channelFactory

如果不能通過默認的構造函數[8]創建Channel,那麼可以提供一個Channel- Factory

localAddress

指定ServerChannel應該綁定到的本地地址。如果沒有指定,則將由操作系統使用一個隨機地址。或者,可以通過bind方法來指定該localAddress

option

指定要應用到新創建的ServerChannelChannelConfigChannel- Option。這些選項將會通過bind方法設置到Channel。在bind方法被調用之後,設置或者改變ChannelOption都不會有任何的效果。所支持的ChannelOption取決於所使用的Channel類型。參見正在使用的ChannelConfig的API文檔

childOption

指定當子Channel被接受時,應用到子ChannelChannelConfigChannelOption。所支持的ChannelOption取決於所使用的Channel的類型。參見正在使用的ChannelConfig的API文檔

attr

指定ServerChannel上的屬性,屬性將會通過bind方法設置給Channel。在調用bind方法之後改變它們將不會有任何的效果

childAttr

將屬性設置給已經被接受的子Channel。接下來的調用將不會有任何的效果

handler

設置被添加到ServerChannelChannelPipeline中的ChannelHandler。更加常用的方法參見childHandler

childHandler

設置將被添加到已被接受的子ChannelChannelPipeline中的Channel- Handlerhandler方法和childHandler方法之間的區別是:前者所添加的ChannelHandler由接受子ChannelServerChannel處理,而childHandler方法所添加的ChannelHandler將由已被接受的子Channel處理,其代表一個綁定到遠程節點的套接字

clone

克隆一個設置和原始的ServerBootstrap相同的ServerBootstrap

bind

綁定ServerChannel並且返回一個ChannelFuture,其將會在綁定操作完成後收到通知(帶著成功或者失敗的結果)

下一節將介紹服務器引導的詳細過程。

8.3.2 引導服務器

你可能已經注意到了,表8-2中列出了一些在表8-1中不存在的方法:childHandlerchildAttrchildOption。這些調用支持特別用於服務器應用程序的操作。具體來說,ServerChannel的實現負責創建子Channel,這些子Channel代表了已被接受的連接。因此,負責引導ServerChannelServerBootstrap提供了這些方法,以簡化將設置應用到已被接受的子ChannelChannelConfig的任務。

圖8-3展示了ServerBootstrapbind方法被調用時創建了一個ServerChannel,並且該ServerChannel管理了多個子Channel

圖8-3 ServerBootstrapServerChannel

代碼清單8-4中的代碼實現了圖8-3中所展示的服務器的引導過程。

代碼清單8-4 引導服務器

NioEventLoopGroup group = new NioEventLoopGroup;
ServerBootstrap bootstrap = new ServerBootstrap;   ← --  創建ServerBootstrap
bootstrap.group(group)  ← --  設置EventLoopGroup,其提供了用於處理Channel 事件的EventLoop
  .channel(NioServerSocketChannel.class)  ← --  指定要使用的Channel 實現 
  .childHandler(new SimpleChannelInboundHandler<ByteBuf> {   ← --  設 置用於處理已被接受的子Channel的I/O及數據的ChannelInbound-Handler
    @Override
    protected void channelRead0(ChannelHandlerContext ctx,
      ByteBuf byteBuf) throws Exception {
      System.out.println("Received data");
    }
  } );
ChannelFuture future = bootstrap.bind(new InetSocketAddress(8080));    ← -- 通過配置好的ServerBootstrap的實例綁定該Channel
future.addListener(new ChannelFutureListener {
  @Override
  public void operationComplete(ChannelFuture channelFuture)
    throws Exception {
    if (channelFuture.isSuccess) {
      System.out.println("Server bound");
    } else {
      System.err.println("Bound attempt failed");
      channelFuture.cause.printStackTrace;
    }
  }
} ); 

8.4 從Channel引導客戶端

假設你的服務器正在處理一個客戶端的請求,這個請求需要它充當第三方系統的客戶端。當一個應用程序(如一個代理服務器)必須要和組織現有的系統(如Web服務或者數據庫)集成時,就可能發生這種情況。在這種情況下,將需要從已經被接受的子Channel中引導一個客戶端Channel

你可以按照8.2.1節中所描述的方式創建新的Bootstrap實例,但是這並不是最高效的解決方案,因為它將要求你為每個新創建的客戶端Channel定義另一個EventLoop。這會產生額外的線程,以及在已被接受的子Channel和客戶端Channel之間交換數據時不可避免的上下文切換。

一個更好的解決方案是:通過將已被接受的子ChannelEventLoop傳遞給Bootstrapgroup方法來共享該EventLoop。因為分配給EventLoop的所有Channel都使用同一個線程,所以這避免了額外的線程創建,以及前面所提到的相關的上下文切換。這個共享的解決方案如圖8-4所示。

圖8-4 在兩個Channel之間共享EventLoop

實現EventLoop共享涉及通過調用group方法來設置EventLoop,如代碼清單8-5所示。

代碼清單8-5 引導服務器

ServerBootstrap bootstrap = new ServerBootstrap;   ← --  創建ServerBootstrap 以創建ServerSocketChannel,並綁定它
bootstrap.group(new NioEventLoopGroup, new NioEventLoopGroup)  ← --  設置EventLoopGroup,其將提供用以處理Channel 事件的EventLoop
  .channel(NioServerSocketChannel.class)  ← --  指定要使用的Channel 實現 
  .childHandler(  ← --  設置用於處理已被接受的子Channel 的I/O 和數據的ChannelInboundHandler 
    new SimpleChannelInboundHandler<ByteBuf> {
      ChannelFuture connectFuture;
      @Override
      public void channelActive(ChannelHandlerContext ctx)
        throws Exception {
        Bootstrap bootstrap = new Bootstrap;  ← --  創建一個Bootstrap類的實例以連接到遠程主機
        bootstrap.channel(NioSocketChannel.class).handler(  ← --  指定Channel的實現 
          new SimpleChannelInboundHandler<ByteBuf> {   ← -- 為入站I/O 設置ChannelInboundHandler
            @Override
            protected void channelRead0(
              ChannelHandlerContext ctx, ByteBuf in)
              throws Exception {
              System.out.println("Received data");
            }
          } );
        bootstrap.group(ctx.channel.eventLoop);  ← --  使用與分配給已被接受的子Channel 相同的EventLoop
        connectFuture = bootstrap.connect(
          new InetSocketAddress("www.manning.com", 80));   ← --  連接到遠程節點
      }
      @Override
      protected void channelRead0(
        ChannelHandlerContext channelHandlerContext,
          ByteBuf byteBuf) throws Exception {
        if (connectFuture.isDone) {
          // do something with the data  ← --  當連接完成時,執行一些數據操作(如代理)
        }
      }
    } );
ChannelFuture future = bootstrap.bind(new InetSocketAddress(8080));  ← --  通過配置好的ServerBootstrap綁定該Server-SocketChannel
future.addListener(new ChannelFutureListener {
  @Override
  public void operationComplete(ChannelFuture channelFuture)
    throws Exception {
    if (channelFuture.isSuccess) {
      System.out.println("Server bound");
    } else {
      System.err.println("Bind attempt failed");
      channelFuture.cause.printStackTrace;
    }
  }
} );  

我們在這一節中所討論的主題以及所提出的解決方案都反映了編寫Netty應用程序的一個一般準則:盡可能地重用EventLoop,以減少線程創建所帶來的開銷。

8.5 在引導過程中添加多個ChannelHandler

在所有我們展示過的代碼示例中,我們都在引導的過程中調用了handler或者child- Handler方法來添加單個的ChannelHandler。這對於簡單的應用程序來說可能已經足夠了,但是它不能滿足更加複雜的需求。例如,一個必須要支持多種協議的應用程序將會有很多的ChannelHandler,而不會是一個龐大而又笨重的類。

正如你經常所看到的一樣,你可以根據需要,通過在ChannelPipeline中將它們鏈接在一起來部署盡可能多的ChannelHandler。但是,如果在引導的過程中你只能設置一個ChannelHandler,那麼你應該怎麼做到這一點呢?

正是針對於這個用例,Netty提供了一個特殊的ChannelInboundHandlerAdapter子類:

public abstract class ChannelInitializer<C extends Channel>
  extends ChannelInboundHandlerAdapter  

它定義了下面的方法:

protected abstract void initChannel(C ch) throws Exception;  

這個方法提供了一種將多個ChannelHandler添加到一個ChannelPipeline中的簡便方法。你只需要簡單地向BootstrapServerBootstrap的實例提供你的Channel-Initializer實現即可,並且一旦Channel被註冊到了它的EventLoop之後,就會調用你的initChannel版本。在該方法返回之後,ChannelInitializer的實例將會從Channel-Pipeline中移除它自己。

代碼清單8-6定義了ChannelInitializerImpl類,並通過ServerBootstrapchildHandler方法註冊它[9]。你可以看到,這個看似複雜的操作實際上是相當簡單直接的。

代碼清單8-6 引導和使用ChannelInitializer

ServerBootstrap bootstrap = new ServerBootstrap;  ← --  創建ServerBootstrap 以創建和綁定新的Channel 
bootstrap.group(new NioEventLoopGroup, new NioEventLoopGroup)  ← --  設置EventLoopGroup,其將提供用以處理Channel 事件的EventLoop
  .channel(NioServerSocketChannel.class)  ← --   指定Channel 的實現
  .childHandler(new ChannelInitializerImpl);   ← -- 註冊一個ChannelInitializerImpl 的實例來設置ChannelPipeline 
 ChannelFuture future = bootstrap.bind(new InetSocketAddress(8080));  ← -- 綁定到地址
future.sync;
final class ChannelInitializerImpl extends ChannelInitializer {[10]  ← -- 用以設置ChannelPipeline 的自定義ChannelInitializerImpl 實現
  @Override
  protected void initChannel(Channel ch) throws Exception { ← -- 將所需的ChannelHandler添加到ChannelPipeline
    ChannelPipeline pipeline = ch.pipeline;
    pipeline.addLast(new HttpClientCodec);
    pipeline.addLast(new HttpObjectAggregator(Integer.MAX_VALUE));
  }
}
  

如果你的應用程序使用了多個ChannelHandler,請定義你自己的ChannelInitializer實現來將它們安裝到ChannelPipeline中。

8.6 使用Netty的ChannelOption和屬性

在每個Channel創建時都手動配置它可能會變得相當乏味。幸運的是,你不必這樣做。相反,你可以使用option方法來將ChannelOption應用到引導。你所提供的值將會被自動應用到引導所創建的所有Channel。可用的ChannelOption包括了底層連接的詳細信息,如keep-alive或者超時屬性以及緩衝區設置。

Netty應用程序通常與組織的專有軟件集成在一起,而像Channel這樣的組件可能甚至會在正常的Netty生命週期之外被使用。在某些常用的屬性和數據不可用時,Netty提供了AttributeMap抽像(一個由Channel引導類提供的集合)以及AttributeKey<T>(一個用於插入和獲取屬性值的泛型類)。使用這些工具,便可以安全地將任何類型的數據項與客戶端和服務器Channel(包含ServerChannel的子Channel)相關聯了。

例如,考慮一個用於跟蹤用戶和Channel之間的關係的服務器應用程序。這可以通過將用戶的ID存儲為Channel的一個屬性來完成。類似的技術可以被用來基於用戶的ID將消息路由給用戶,或者關閉活動較少的Channel

代碼清單8-7展示了可以如何使用ChannelOption來配置Channel,以及如果使用屬性來存儲整型值。

代碼清單8-7 使用屬性值

final AttributeKey<Integer> id = AttributeKey.newInstance("ID"); [11]  ← --  創建一個AttributeKey以標識該屬性
Bootstrap bootstrap = new Bootstrap;  ← --  創建一個Bootstrap 類的實例以創建客戶端Channel 並連接它們
bootstrap.group(new NioEventLoopGroup)   ← --  設置EventLoopGroup,其提供了用以處理Channel事件的EventLoop
.channel(NioSocketChannel.class)  ← --  指定Channel的實現
.handler(
  new SimpleChannelInboundHandler<ByteBuf> {  ← -- 設置用以處理Channel 的I/O 以及數據的Channel-InboundHandler 
    @Override
    public void channelRegistered(ChannelHandlerContext ctx)
      throws Exception {
      Integer idValue = ctx.channel.attr(id).get;  ← -- 使用AttributeKey 檢索屬性以及它的值
      // do something with the idValue
    }
    @Override
    protected void channelRead0(
      ChannelHandlerContext channelHandlerContext,
      ByteBuf byteBuf) throws Exception {
      System.out.println("Received data");
    }
  }
);
bootstrap.option(ChannelOption.SO_KEEPALIVE,true)
  .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000);  ← --  設置ChannelOption,其將在connect或者bind方法被調用時被設置到已經創建的Channel 上
bootstrap.attr(id, 123456);  ← --  存儲該id 屬性 
ChannelFuture future = bootstrap.connect(
  new InetSocketAddress("www.manning.com", 80));  ← --  使用配置好的Bootstrap實例連接到遠程主機
future.syncUninterruptibly;  

8.7 引導DatagramChannel

前面的引導代碼示例使用的都是基於TCP協議的SocketChannel,但是Bootstrap類也可以被用於無連接的協議。為此,Netty提供了各種DatagramChannel的實現。唯一區別就是,不再調用connect方法,而是只調用bind方法,如代碼清單8-8所示。

代碼清單8-8 使用BootstrapDatagramChannel

Bootstrap bootstrap = new Bootstrap;  ← --   創建一個Bootstrap 的實例以創建和綁定新的數據報Channel
bootstrap.group(new OioEventLoopGroup).channel(  ← -- 設置EventLoopGroup,其提供了用以處理Channel 事件的EventLoop 
  OioDatagramChannel.class).handler(  ← -- 指定Channel的實現
  new SimpleChannelInboundHandler<DatagramPacket>{  ← -- 設置用以處理Channel 的I/O 以及數據的Channel-InboundHandler
    @Override
    public void channelRead0(ChannelHandlerContext ctx,
      DatagramPacket msg) throws Exception {
      // Do something with the packet
    }
  }
);
ChannelFuture future = bootstrap.bind(new InetSocketAddress(0));   ← -- 調用bind方法,因為該協議是無連接的
future.addListener(new ChannelFutureListener {
  @Override
  public void operationComplete(ChannelFuture channelFuture)
    throws Exception {
    if (channelFuture.isSuccess) {
      System.out.println("Channel bound");
    } else {
      System.err.println("Bind attempt failed");
      channelFuture.cause.printStackTrace;
    }
  }
});  

8.8 關閉

引導使你的應用程序啟動並且運行起來,但是遲早你都需要優雅地將它關閉。當然,你也可以讓JVM在退出時處理好一切,但是這不符合優雅的定義,優雅是指乾淨地釋放資源。關閉Netty應用程序並沒有太多的魔法,但是還是有些事情需要記在心上。

最重要的是,你需要關閉EventLoopGroup,它將處理任何掛起的事件和任務,並且隨後釋放所有活動的線程。這就是調用EventLoopGroup.shutdownGracefully方法的作用。這個方法調用將會返回一個Future,這個Future將在關閉完成時接收到通知。需要注意的是,shutdownGracefully方法也是一個異步的操作,所以你需要阻塞等待直到它完成,或者向所返回的Future註冊一個監聽器以在關閉完成時獲得通知。

代碼清單8-9符合優雅關閉的定義。

代碼清單8-9 優雅關閉

EventLoopGroup group = new NioEventLoopGroup;  ← --  創建處理I/O 的EventLoopGroup
Bootstrap bootstrap = new Bootstrap;  ← --  創建一個Bootstrap類的實例並配置它
bootstrap.group(group)
  .channel(NioSocketChannel.class);
...
Future<?> future = group.shutdownGracefully;  ← --  shutdownGracefully方法將釋放所有的資源,並且關閉所有的當前正在使用中的Channel
// block until the group has shutdown
future.syncUninterruptibly;  

或者,你也可以在調用EventLoopGroup.shutdownGracefully方法之前,顯式地在所有活動的Channel上調用Channel.close方法。但是在任何情況下,都請記得關閉EventLoopGroup本身。

8.9 小結

在本章中,你學習了如何引導Netty服務器和客戶端應用程序,包括那些使用無連接協議的應用程序。我們也涵蓋了一些特殊情況,包括在服務器應用程序中引導客戶端Channel,以及使用ChannelInitializer來處理引導過程中的多個ChannelHandler的安裝。你看到了如何設置Channel的配置選項,以及如何使用屬性來將信息附加到Channel。最後,你學習了如何優雅地關閉應用程序,以有序地釋放所有的資源。

在下一章中,我們將研究Netty提供的幫助你測試你的ChannelHandler實現的工具。


[1] Channel繼承了AttributeMap。——譯者注

[2] 分層抽像。——譯者注

[3] 應用程序的邏輯或實現。——譯者注

[4] 「拼圖」指的是Netty的核心概念以及組件,也包括了如何完整正確地組織並且運行一個Netty應用程序。——譯者注

[5] Java平台,標準版第8版API規範,java.lang,Interface Cloneable:http://docs.oracle.com/javase/8/docs/api/ java/lang/Cloneable.html。

[6] 被淺拷貝的EventLoopGroup。——譯者注

[7] 這裡指默認的無參構造函數,因為內部使用了反射來實現Channel的創建。——譯者注

[8] 這裡指無參數的構造函數。——譯者注

[9] 註冊到ServerChannel的子ChannelChannelPipeline。——譯者注

[10] 在大部分的場景下,如果你不需要使用只存在於SocketChannel上的方法,使用ChannelInitializer- 就可以了,否則你可以使用ChannelInitializer,其中SocketChannel擴展了Channel。——譯者注

[11] 需要注意的是,AttributeKey上同時存在newInstance(String)valueOf(String)方法,它們都可以用來獲取具有指定名稱的AttributeKey實例,不同的是,前者可能會在多線程環境下使用時拋出異常(實際上調用了createOrThrow(String)方法)——通常適用於初始化靜態變量的時候;而後者(實際上調用了getOrCreate(String)方法)則更加通用(線程安全)。——譯者注