本章主要內容
- 引導客戶端和服務器
- 從
Channel
內引導客戶端 - 添加
ChannelHandler
- 使用
ChannelOption
和屬性[1]
在深入地學習了ChannelPipeline
、ChannelHandler
和EventLoop
之後,你接下來的問題可能是:「如何將這些部分組織起來,成為一個可實際運行的應用程序呢?」
答案是?「引導」(Bootstrapping)。到目前為止,我們對這個術語的使用還比較含糊,現在已經到了精確定義它的時候了。簡單來說,引導一個應用程序是指對它進行配置,並使它運行起來的過程——儘管該過程的具體細節可能並不如它的定義那樣簡單,尤其是對於一個網絡應用程序來說。
和它對應用程序體系架構的做法[2]一致,Netty處理引導的方式使你的應用程序[3]和網絡層相隔離,無論它是客戶端還是服務器。正如同你將要看到的,所有的框架組件都將會在後台結合在一起並且啟用。引導是我們一直以來都在組裝的完整拼圖[4]中缺失的那一塊。當你把它放到正確的位置上時,你的Netty應用程序就完整了。
8.1 Bootstrap類
引導類的層次結構包括一個抽像的父類和兩個具體的引導子類,如圖8-1所示。
圖8-1 引導類的層次結構
相對於將具體的引導類分別看作用於服務器和客戶端的引導來說,記住它們的本意是用來支撐不同的應用程序的功能的將有所裨益。也就是說,服務器致力於使用一個父Channel
來接受來自客戶端的連接,並創建子Channel
以用於它們之間的通信;而客戶端將最可能只需要一個單獨的、沒有父Channel
的Channel
來用於所有的網絡交互。(正如同我們將要看到的,這也適用於無連接的傳輸協議,如UDP,因為它們並不是每個連接都需要一個單獨的Channel
。)
我們在前面的幾章中學習的幾個Netty組件都參與了引導的過程,而且其中一些在客戶端和服務器都有用到。兩種應用程序類型之間通用的引導步驟由AbstractBootstrap
處理,而特定於客戶端或者服務器的引導步驟則分別由Bootstrap
或ServerBootstrap
處理。
在本章中接下來的部分,我們將詳細地探討這兩個類,首先從不那麼複雜的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
,其將被應用到每個新創建的Channel
的ChannelConfig
。這些選項將會通過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傳輸兩者來說,都有相關的EventLoopGroup
和Channel
實現。
代碼清單8-2 相互兼容的EventLoopGroup
和Channel
channel
├───nio
│ NioEventLoopGroup
├───oio
│ OioEventLoopGroup
└───socket
├───nio
│ NioDatagramChannel
│ NioServerSocketChannel
│ NioSocketChannel
└───oio
OioDatagramChannel
OioServerSocketChannel
OioSocketChannel
必須保持這種兼容性,不能混用具有不同前綴的組件,如NioEventLoopGroup
和OioSocketChannel
。代碼清單8-3展示了試圖這樣做的一個例子。
代碼清單8-3 不兼容的Channel
和EventLoopGroup
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
指定要應用到新創建的ServerChannel
的ChannelConfig
的Channel- Option
。這些選項將會通過bind
方法設置到Channel
。在bind
方法被調用之後,設置或者改變ChannelOption
都不會有任何的效果。所支持的ChannelOption
取決於所使用的Channel
類型。參見正在使用的ChannelConfig
的API文檔
childOption
指定當子Channel
被接受時,應用到子Channel
的ChannelConfig
的ChannelOption
。所支持的ChannelOption
取決於所使用的Channel
的類型。參見正在使用的ChannelConfig
的API文檔
attr
指定ServerChannel
上的屬性,屬性將會通過bind
方法設置給Channel
。在調用bind
方法之後改變它們將不會有任何的效果
childAttr
將屬性設置給已經被接受的子Channel
。接下來的調用將不會有任何的效果
handler
設置被添加到ServerChannel
的ChannelPipeline
中的ChannelHandler
。更加常用的方法參見childHandler
childHandler
設置將被添加到已被接受的子Channel
的ChannelPipeline
中的Channel- Handler
。handler
方法和childHandler
方法之間的區別是:前者所添加的ChannelHandler
由接受子Channel
的ServerChannel
處理,而childHandler
方法所添加的ChannelHandler
將由已被接受的子Channel
處理,其代表一個綁定到遠程節點的套接字
clone
克隆一個設置和原始的ServerBootstrap
相同的ServerBootstrap
bind
綁定ServerChannel
並且返回一個ChannelFuture
,其將會在綁定操作完成後收到通知(帶著成功或者失敗的結果)
下一節將介紹服務器引導的詳細過程。
8.3.2 引導服務器
你可能已經注意到了,表8-2中列出了一些在表8-1中不存在的方法:childHandler
、childAttr
和childOption
。這些調用支持特別用於服務器應用程序的操作。具體來說,ServerChannel
的實現負責創建子Channel
,這些子Channel
代表了已被接受的連接。因此,負責引導ServerChannel
的ServerBootstrap
提供了這些方法,以簡化將設置應用到已被接受的子Channel
的ChannelConfig
的任務。
圖8-3展示了ServerBootstrap
在bind
方法被調用時創建了一個ServerChannel
,並且該ServerChannel
管理了多個子Channel
。
圖8-3 ServerBootstrap
和ServerChannel
代碼清單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
之間交換數據時不可避免的上下文切換。
一個更好的解決方案是:通過將已被接受的子Channel
的EventLoop
傳遞給Bootstrap
的group
方法來共享該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
中的簡便方法。你只需要簡單地向Bootstrap
或ServerBootstrap
的實例提供你的Channel-Initializer
實現即可,並且一旦Channel
被註冊到了它的EventLoop
之後,就會調用你的initChannel
版本。在該方法返回之後,ChannelInitializer
的實例將會從Channel-Pipeline
中移除它自己。
代碼清單8-6定義了ChannelInitializerImpl
類,並通過ServerBootstrap
的childHandler
方法註冊它[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 使用Bootstrap
和DatagramChannel
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
的子Channel
的ChannelPipeline
。——譯者注
[10] 在大部分的場景下,如果你不需要使用只存在於SocketChannel
上的方法,使用ChannelInitializer-
就可以了,否則你可以使用ChannelInitializer
,其中SocketChannel
擴展了Channel
。——譯者注
[11] 需要注意的是,AttributeKey
上同時存在newInstance(String)
和valueOf(String)
方法,它們都可以用來獲取具有指定名稱的AttributeKey
實例,不同的是,前者可能會在多線程環境下使用時拋出異常(實際上調用了createOrThrow(String)
方法)——通常適用於初始化靜態變量的時候;而後者(實際上調用了getOrCreate(String)
方法)則更加通用(線程安全)。——譯者注