簡介
上一節我們講解了netty中的Channel,知道了channel是事件處理器和外部聯通的橋樑。今天本文將會詳細講解netty的剩下幾個非常總要的部分Event、Handler和PipeLine。
ChannelPipeline
pipeLine是連線Channel和handler的橋樑,它實際上是一個filter的實現,用於控制其中handler的處理方式。
當一個channel被建立的時候,和它對應的ChannelPipeline也會被建立。
先看下ChannelPipeline的定義:
public interface ChannelPipeline
extends ChannelInboundInvoker, ChannelOutboundInvoker, Iterable
首先ChannelPipeline繼承自Iterable,表示它是可遍歷的,而遍歷的結果就是其中一個個的Handler。
作為一個合格的Iterable,ChannelPipeline提供了一系列的add和remote方法,通過這些方法就可以向ChannelPipeline中新增或者移除Handler。因為ChannelPipeline是一個filter,而過濾器是需要指定對應的filter的順序的,所以ChannelPipeline中有addFirst和addLast這種新增不同順序的方法。
然後可以看到ChannelPipeline繼承了ChannelInboundInvoker和ChannelOutboundInvoker兩個介面。
先看一張channelPipeline的工作流程圖:
可以看出ChannelPipeline主要有兩種操作,一種是讀入Inbound,一種是寫出OutBound。
對於Socket.read()這樣的讀入操作,呼叫的實際上就是ChannelInboundInvoker中的方法。對於外部的IO寫入的請求,呼叫的就是ChannelOutboundInvoker中的方法。
注意,Inbound和outbound的處理順序是相反的,比如下面的例子:
ChannelPipeline p = ...;
p.addLast("1", new InboundHandlerA());
p.addLast("2", new InboundHandlerB());
p.addLast("3", new OutboundHandlerA());
p.addLast("4", new OutboundHandlerB());
p.addLast("5", new InboundOutboundHandlerX());
上面的程式碼中我們向ChannelPipeline新增了5個handler,其中2個InboundHandler,2個OutboundHandler和一個同時處理In和Out的Handler。
那麼當channel遇到inbound event的時候,就會按照1,2,3,4,5的順序進行處理,但是隻有InboundHandler才能處理Inbound事件,所以,真正執行的順序是1,2,5。
同樣的當channel遇到outbound event的時候,會按照5,4,3,2,1的順序進行執行,但是隻有outboundHandler才能處理Outbound事件,所以真正執行的順序是5,4,3.
簡單的說,ChannelPipeline指定了Handler的執行順序。
ChannelHandler
netty是一個事件驅動的框架,所有的event都是由Handler來進行處理的。ChannelHandler可以處理IO、攔截IO或者將event傳遞給ChannelPipeline中的下一個Handler進行處理。
ChannelHandler的結構很簡單,只有三個方法,分別是:
void handlerAdded(ChannelHandlerContext ctx) throws Exception;
void handlerRemoved(ChannelHandlerContext ctx) throws Exception;
void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;
根據inbound和outbound事件的不同,ChannelHandler可以分為兩類,分別是ChannelInboundHandler 和ChannelOutboundHandler.
因為這兩個都是interface,實現起來比較麻煩,所以netty為大家提供了三個預設的實現:ChannelInboundHandlerAdapter,ChannelOutboundHandlerAdapter和ChannelDuplexHandler。前面兩個很好理解,分別是inbound和outbound,最後一個可以同時處理inbound和outbound。
ChannelHandler是由ChannelHandlerContext提供的,並且和ChannelPipeline的互動也是通過ChannelHandlerContext來進行的。
ChannelHandlerContext
ChannelHandlerContext可以讓ChannelHandler和ChannelPipeline或者其他的Handler進行互動。它就是一個上下文環境,使得Handler和Channel可以相互作用。
如可以在ChannelHandlerContext中,呼叫channel()獲得繫結的channel。可以通過呼叫handler()獲得繫結的Handler。通過呼叫fire*方法來觸發Channel的事件。
看下ChannelHandlerContext的定義:
public interface ChannelHandlerContext extends AttributeMap, ChannelInboundInvoker, ChannelOutboundInvoker
可以看到他是一個AttributeMap用來儲存屬性,還是一個ChannelInboundInvoker和ChannelOutboundInvoker用來觸發和傳播相應的事件。
對於Inbound來說傳播事件的方法有:
ChannelHandlerContext.fireChannelRegistered()
ChannelHandlerContext.fireChannelActive()
ChannelHandlerContext.fireChannelRead(Object)
ChannelHandlerContext.fireChannelReadComplete()
ChannelHandlerContext.fireExceptionCaught(Throwable)
ChannelHandlerContext.fireUserEventTriggered(Object)
ChannelHandlerContext.fireChannelWritabilityChanged()
ChannelHandlerContext.fireChannelInactive()
ChannelHandlerContext.fireChannelUnregistered()
對於Outbound來說傳播事件的方法有:
ChannelHandlerContext.bind(SocketAddress, ChannelPromise)
ChannelHandlerContext.connect(SocketAddress, SocketAddress, ChannelPromise)
ChannelHandlerContext.write(Object, ChannelPromise)
ChannelHandlerContext.flush()
ChannelHandlerContext.read()
ChannelHandlerContext.disconnect(ChannelPromise)
ChannelHandlerContext.close(ChannelPromise)
ChannelHandlerContext.deregister(ChannelPromise)
這些方法,在一個Handler中呼叫,然後將事件傳遞給下一個Handler,如下所示:
public class MyInboundHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) {
System.out.println("Connected!");
ctx.fireChannelActive();
}
}
public class MyOutboundHandler extends ChannelOutboundHandlerAdapter {
@Override
public void close(ChannelHandlerContext ctx, ChannelPromise promise) {
System.out.println("Closing ..");
ctx.close(promise);
}
}
ChannelHandler中的狀態變數
ChannelHandler是一個Handler類,一般情況下,這個類的例項是可以被多個channel共同使用的,前提是這個ChannelHandler沒有共享的狀態變數。
但有時候,我們必須要在ChannelHandler中保持一個狀態,那麼就涉及到ChannelHandler中的狀態變數的問題,看下面的一個例子:
public interface Message {
// your methods here
}
public class DataServerHandler extends SimpleChannelInboundHandler<Message> {
private boolean loggedIn;
@Override
public void channelRead0(ChannelHandlerContext ctx, Message message) {
if (message instanceof LoginMessage) {
authenticate((LoginMessage) message);
loggedIn = true;
} else (message instanceof GetDataMessage) {
if (loggedIn) {
ctx.writeAndFlush(fetchSecret((GetDataMessage) message));
} else {
fail();
}
}
}
...
}
這個例子中,我們需要在收到LoginMessage之後,對訊息進行認證,並儲存認證狀態,因為業務邏輯是這樣的,所以必須要有一個狀態變數。
那麼這樣帶有狀態變數的Handler就只能繫結一個channel,如果繫結多個channel就有可能出現狀態不一致的問題。一個channel繫結一個Handler例項,很簡單,只需要在initChannel方法中使用new關鍵字新建一個物件即可。
public class DataServerInitializer extends ChannelInitializer<Channel> {
@Override
public void initChannel(Channel channel) {
channel.pipeline().addLast("handler", new DataServerHandler());
}
}
那麼除了新建handler例項之外,還有沒有其他的辦法呢?當然是有的,那就是 ChannelHandlerContext 中的AttributeKey屬性。還是上面的例子,我們看一下使用AttributeKey應該怎麼實現:
public interface Message {
// your methods here
}
@Sharable
public class DataServerHandler extends SimpleChannelInboundHandler<Message> {
private final AttributeKey<Boolean> auth =
AttributeKey.valueOf("auth");
@Override
public void channelRead(ChannelHandlerContext ctx, Message message) {
Attribute<Boolean> attr = ctx.attr(auth);
if (message instanceof LoginMessage) {
authenticate((LoginMessage) o);
attr.set(true);
} else (message instanceof GetDataMessage) {
if (Boolean.TRUE.equals(attr.get())) {
ctx.writeAndFlush(fetchSecret((GetDataMessage) o));
} else {
fail();
}
}
}
...
}
上例中,首先定義了一個AttributeKey,然後使用ChannelHandlerContext的attr方法將Attribute設定到ChannelHandlerContext中,這樣該Attribute繫結到這個ChannelHandlerContext中了。後續即使使用同一個Handler在不同的Channel中該屬性也是不同的。
下面是使用共享Handler的例子:
public class DataServerInitializer extends ChannelInitializer<Channel> {
private static final DataServerHandler SHARED = new DataServerHandler();
@Override
public void initChannel(Channel channel) {
channel.pipeline().addLast("handler", SHARED);
}
}
注意,在定義DataServerHandler的時候,我們加上了@Sharable註解,如果一個ChannelHandler使用了@Sharable註解,那就意味著你可以只建立一次這個Handler,但是可以將其繫結到一個或者多個ChannelPipeline中。
注意,@Sharable註解是為java文件準備的,並不會影響到實際的程式碼執行效果。
非同步Handler
之前介紹了,可以通過呼叫pipeline.addLast方法將handler加入到pipeline中,因為pipeline是一個filter的結構,所以加入的handler是順序進行處理的。
但是,我希望某些handler是在新的執行緒中執行該怎麼辦?如果我們希望這些新的執行緒中執行的Handler是無序的又該怎麼辦?
比如我們現在有3個handler分別是MyHandler1,MyHandler2和MyHandler3。
順序執行的寫法是這樣的:
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("MyHandler1", new MyHandler1());
pipeline.addLast("MyHandler2", new MyHandler2());
pipeline.addLast("MyHandler3", new MyHandler3());
如果要讓MyHandler3在新的執行緒中執行,則可以加入group選項,從而讓handler在新的group中執行:
static final EventExecutorGroup group = new DefaultEventExecutorGroup(16);
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("MyHandler1", new MyHandler1());
pipeline.addLast("MyHandler2", new MyHandler2());
pipeline.addLast(group,"MyHandler3", new MyHandler3());
但是上例中DefaultEventExecutorGroup加入的Handler也是會順序執行的,如果確實不想順序執行,那麼可以嘗試考慮使用UnorderedThreadPoolEventExecutor 。
總結
本文講解了Event、Handler和PipeLine,並舉例說明他們之間的關係和相互作用。後續會從netty的具體實踐出發,進一步加深對netty的理解和應用,希望大家能夠喜歡。
本文已收錄於 http://www.flydean.com/05-netty-channelevent/
最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!
歡迎關注我的公眾號:「程式那些事」,懂技術,更懂你!