此接口將一個消息、發送地址和接收地址封裝到了一塊兒
html
此接口表示到網絡socket或者組件(component)的一個鏈接,其提供了IO操做的一些功能,好比read, write, connect, and bind.一個channel能夠給用戶提供以下功能:1.當前channel的狀態(open、connected等)。2.channel的配置參數(如receive buffer size)。3.該channel支持的全部IO操做(read, write, connect, and bind)。4.還能夠提供與此channel關聯的ChannelPipeline,此pipeline主要負責處理該channel的全部IO事件和請求。
java
全部的IO操做都是異步的。在Netty中全部的IO操做都是異步的。這就意味着任何IO操做都調用以後都會當即返回,不能保證IO操做在調用結束的時候完成。調用IO操做以後會返回一個
apiChannelFuture
對象,該對象會在IO操做成功失敗取消的時候進行notify.
通道是有層次關係的。根據channel的建立方式,channel能夠有一個parent,例如,一個由
安全ServerSocketChannel接受請求建立的
SocketChannel,調用其parent()方法,會返回
。層次結構的語義取決於channel依賴的傳輸具體實現,例如,你能夠建立一個channel實現類,其能夠建立一個和其共享一個socket連接的子channel,就像BEEP和SSHServerSocketChannel
網絡 向下轉型訪問特殊操做。有些傳輸實現會暴露一些該實現特有的操做,經過向下轉型能夠調用這些操做,例如,對於舊的數據報傳輸,咱們能夠講channel轉換成
DatagramChannel,而後就能夠調用其特有的multicast join / leave等操做。
併發 釋放資源。對一個channel操做完畢以後,必定要調用
close()
或 close(ChannelPromise)
方法來釋放資源。
此類封裝了channel配置屬性信息異步
若是須要特殊的配置信息,須要作向下轉換,具體代碼以下:socket
Channel ch = ...; SocketChannelConfig cfg = (SocketChannelConfig) ch.getConfig(); cfg.setTcpNoDelay(false);
選項map(Option map)。是一個動態只寫的屬性,其提供了另一種方式來設置屬性,而不須要向下進行轉換。經過setOptions(Map)
.方法能夠更新option map。好比上面的代碼,咱們能夠不用將ch轉換爲具體的SocketChannelConfig 具體代碼以下:ide
Channel ch = ...; cfg.setsetOption(ChannelOption.TCP_NODELAY,false);
建立channel的工廠post
其封裝了異步IO操做的結果
Netty中全部的IO操做都是異步的。這就意味着任何IO調用都會當即返回,並且不保證IO操做在調用結束的時候完成,調用IO操做會返回一個ChannelFurniture對象,經過這個對象你能夠獲得IO操做的狀態信息和結果。channelfuture對象要麼是未完成狀態(uncompleted),要麼是完成狀態(completed)。當一個IO操做開始的時候會建立一個channelfuture對象,初始的channelfuture對象是未完成狀態,它既不是成功(succeeded),也不是失敗(failed),更沒有取消(cancelled),由於IO操做尚未完成(finished)。若是IO操做完成了,有可能成功(succeeded),失敗(failed),或者是取消(cancelled),channelfuture對象會被標記爲完成狀態(completed),並會附有相信的信息,好比失敗的緣由,須要注意的是失敗(failed)和取消(cancelled)都屬於完成狀態。未完成和完成 與成功、失敗、取消是兩個不一樣的維度。下面圖表示channelfuture的狀態,左邊是初始未完成狀態,右邊是完成狀態,可能有三種成功失敗和取消:
+---------------------------+ | Completed successfully | +---------------------------+ +----> isDone() = true | +--------------------------+ | | isSuccess() = true | | Uncompleted | | +===========================+ +--------------------------+ | | Completed with failure | | isDone() = false | | +---------------------------+ | isSuccess() = false |----+----> isDone() = true | | isCancelled() = false | | | cause() = non-null | | cause() = null | | +===========================+ +--------------------------+ | | Completed by cancellation | | +---------------------------+ +----> isDone() = true | | isCancelled() = true | +---------------------------+
此接口提供了不少方法來幫助你檢查IO操做狀態好比是否已經完成或者獲取IO操做的結果,你能夠添加ChannelFutureListener來監聽channelfuture對象,這樣當IO操做完成的時候,你會被通知到。
推薦使用
addListener,不建議使用await。
addListener方法實在channelfuture上監聽事件,是非阻塞的方法,當IO調用結束的時候,你會收到通知,在這以前你能夠作別的事情,能夠提高效率。而await方法是阻塞的。一旦調用以後,當前線程會阻塞,直到IO操做完成,並且會增長死鎖的風險。
不要在
ChannelHandler中調用
channelfuture
的await方法。在
ChannelHandler中的時間處理方法是由IO線程調用的,一旦await方法被IO線程調用,IO操做將會等待永遠不會完成,由於await方法阻塞了他等待的IO操做,就形成了死鎖,代碼以下:
// BAD - NEVER DO THIS @Override//永遠不要這樣用 public void channelRead(ChannelHandlerContext ctx, GoodByeMessage msg) { ChannelFuture future = ctx.channel().close(); future.awaitUninterruptibly(); // Perform post-closure operation // ... } // GOOD @Override//正確的作法 public void channelRead(ChannelHandlerContext ctx, GoodByeMessage msg) { ChannelFuture future = ctx.channel().close(); future.addListener(new ChannelFutureListener() { public void operationComplete(ChannelFuture future) { // Perform post-closure operation // ... } }); }
儘管await方法有上述缺點,可是調用await顯然更簡便,若是必定要調用await方法,請記住不要再IO線程裏調用channelfuture的await方法,不然系統爲了防止死鎖,會拋出BlockingOperationException
不要混淆IO超
await timeout時
(IO timeout)和await超時()。
調用方法
Future.await(long)
,Future.await(long, TimeUnit)
, Future.awaitUninterruptibly(long)
, 或者Future.awaitUninterruptibly(long, TimeUnit)的超時與IO超時沒有任何關係。若是IO超時channelfuture對象會被標記爲帶失敗的完成狀態(completed with failure),IO超時的參數能夠經過option設置,代碼以下:
// BAD - NEVER DO THIS 永遠不要這樣作 Bootstrap b = ...; ChannelFuture f = b.connect(...); f.awaitUninterruptibly(10, TimeUnit.SECONDS);//IO超時應該設置到channelconfig,而不是channelfuture if (f.isCancelled()) { // Connection attempt cancelled by user } else if (!f.isSuccess()) { // You might get a NullPointerException here because the future // might not be completed yet. f.cause().printStackTrace(); } else { // Connection established successfully } // GOOD 正確的作法 Bootstrap b = ...; // Configure the connect timeout option. b.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000);//這裏最終會經過channelconfig來配置 ChannelFuture f = b.connect(...); f.awaitUninterruptibly(); // Now we are sure the future is completed. assert f.isDone(); if (f.isCancelled()) { // Connection attempt cancelled by user } else if (!f.isSuccess()) { f.cause().printStackTrace(); } else { // Connection established successfully }
用來監聽channelfuture的結果,調用ChannelFuture.addListener(GenericFutureListener)
方法以後,異步IO的操做完成以後會通知channelfuturelistener
GenericFutureListener.operationComplete(Future)是直接被IO線程調用的,所以若是在該方法中調用耗時任務或者是阻塞的操做會致使意外停頓。若是你確實須要執行一個耗時操做或耗時操做,請用線程池另起一個線程來執行耗時操做。
此接口負責處理一個IO事件,或者攔截一個IO操做。並將事件或操做轉發給ChannelPipeline中的下一個channelhandler對象。
建議繼承ChannelHandlerAdapter代替實現channelHandler接口。
由於channelhandler接口有不少方法須要實現,而ChannelhandlerAdaptor默認實現了一些方法,大部分狀況下你只須要實現一些必要的方法就能夠了。
上下文對象(The context object)
。channelhandler須要ChannelHandlerContext對象。channelhandler對象經過channelhandlercontext對象與channelhandler的所屬的channelpipeline交互。經過context對象,channelhandler能夠將事件轉發給他的上游和下游,或者動態修改pipeline,對於特殊的handler能夠存儲信息(經過AttributeKeys)。
狀態管理。channelhandler常常須要存儲一些狀態信息,最簡單的推薦的方法是使用成員變量,代碼以下:
public interface Message { // your methods here } public class DataServerHandler extends SimpleChannelInboundHandler<Message> { private boolean loggedIn; @Override protected void messageReceived(ChannelHandlerContext ctx, Message message) { Channel ch = e.getChannel(); if (message instanceof LoginMessage) { authenticate((LoginMessage) message); loggedIn = true; } else (message instanceof GetDataMessage) { if (loggedIn) { ch.write(fetchSecret((GetDataMessage) message)); } else { fail(); } } } ... }
上面代碼中由於channelhandler實例中有一個變量來專門表示一個連接的狀態,即一個鏈接有一個狀態,因此你必須爲每個新channel建立一個新的channelhandler實例,避免競爭條件下一個未經受權的客戶端獲取重要信息。正確代碼以下:
// Create a new handler instance per channel. // See ChannelInitializer.initChannel(Channel). public class DataServerInitializer extends ChannelInitializer<Channel> { @Override public void initChannel(Channel channel) { channel.pipeline().addLast("handler", new DataServerHandler()); } }
Using AttributeKey
s 雖然建議使用成員變量來存儲channelhandler的狀態,可是爲了考慮安全問題須要爲每一個channel建立一個channelhandler實例,有些狀況下你可能不想建立那麼多實例,在這種狀況下,你須要用到AttributeKey
s,他能夠附着到(attached)channelhandlercontext上,代碼以下:
public interface Message { // your methods here } @Sharable //這個註解很重要後面會介紹 public class DataServerHandler extends SimpleChannelInboundHandler<Message> { private final AttributeKey<Boolean> auth = AttributeKey.valueOf("auth"); @Override protected void messageReceived(ChannelHandlerContext ctx, Message message) { Attribute<Boolean> attr = ctx.attr(auth); Channel ch = ctx.channel(); if (message instanceof LoginMessage) { authenticate((LoginMessage) o); attr.set(true); } else (message instanceof GetDataMessage) { if (Boolean.TRUE.equals(attr.get())) { ch.write(fetchSecret((GetDataMessage) o)); } else { fail(); } } } ... }
經過上面的代碼能夠將channelhandler的狀態attach到channelhandlercontext上,你能夠將這個channelhandler實例添加到不一樣的pipeline,代碼以下:
public class DataServerInitializer extends ChannelInitializer<Channel> { private static final DataServerHandler SHARED = new DataServerHandler(); @Override public void initChannel(Channel channel) { channel.pipeline().addLast("handler", SHARED); } }
@Sharable註解
上面的用attributekey實例代碼中用到了@Sharable註解,
若是channelhandler
加上了@sharable註解
,意味着你能夠只建立一個實例,而後你能夠將該實例放到任意不一樣的pipeline中,而沒必要考慮競爭條件。若是不加這個註解
,你每次向pipeline中添加channelhandler,都須要建立一個新的實例,不然會有併發問題。