本章介紹
- ChannelPipeline
- ChannelHandlerContext
- ChannelHandler
- Inbound vs outbound(入站和出站)
6.1 ChannelPipeline
- addFirst(...),添加ChannelHandler在ChannelPipeline的第一個位置
- addBefore(...),在ChannelPipeline中指定的ChannelHandler名稱之前添加ChannelHandler
- addAfter(...),在ChannelPipeline中指定的ChannelHandler名稱之後添加ChannelHandler
- addLast(ChannelHandler...),在ChannelPipeline的末尾添加ChannelHandler
- remove(...),刪除ChannelPipeline中指定的ChannelHandler
- replace(...),替換ChannelPipeline中指定的ChannelHandler
ChannelPipeline pipeline = ch.pipeline();
FirstHandler firstHandler = new FirstHandler();
pipeline.addLast("handler1", firstHandler);
pipeline.addFirst("handler2", new SecondHandler());
pipeline.addLast("handler3", new ThirdHandler());
pipeline.remove("handler3");
pipeline.remove(firstHandler);
pipeline.replace("handler2", "handler4", new FourthHandler());
被添加到ChannelPipeline的ChannelHandler將通過IO-Thread處理事件,這意味了必須不能有其他的IO-Thread阻塞來影響IO的整體處理;有時候可能需要阻塞,例如JDBC。因此,Netty允許通過一個EventExecutorGroup到每一個ChannelPipeline.add*方法,自定義的事件會被包含在EventExecutorGroup中的EventExecutor來處理,默認的實現是DefaultEventExecutorGroup。6.2 ChannelHandlerContext
6.2.1 通知下一個ChannelHandler
- 調用Channel的方法
- 調用ChannelPipeline的方法
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//Event via Channel
Channel channel = ctx.channel();
channel.write(Unpooled.copiedBuffer("netty in action", CharsetUtil.UTF_8));
//Event via ChannelPipeline
ChannelPipeline pipeline = ctx.pipeline();
pipeline.write(Unpooled.copiedBuffer("netty in action", CharsetUtil.UTF_8));
}
});
}
下圖表示通過Channel或ChannelPipeline的通知:可能你想從ChannelPipeline的指定位置開始,不想流經整個ChannelPipeline,如下情況:
- 爲了節省開銷,不感興趣的ChannelHandler不讓通過
- 排除一些ChannelHandler
// Get reference of ChannelHandlerContext
ChannelHandlerContext ctx = ..;
// Write buffer via ChannelHandlerContext
ctx.write(Unpooled.copiedBuffer("Netty in Action", CharsetUtil.UTF_8));
該消息流經ChannelPipeline到下一個ChannelHandler,在這種情況下使用ChannelHandlerContext開始下一個ChannelHandler。下圖顯示了事件流:如上圖顯示的,從指定的ChannelHandlerContext開始,跳過前面所有的ChannelHandler,使用ChannelHandlerContext操作是常見的模式,最常用的是從ChannelHanlder調用操作,也可以在外部使用ChannelHandlerContext,因爲這是線程安全的。
6.2.2 修改ChannelPipeline
public class WriteHandler extends ChannelHandlerAdapter {
private ChannelHandlerContext ctx;
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
this.ctx = ctx;
}
public void send(String msg){
ctx.write(msg);
}
}
請注意,ChannelHandler實例如果帶有@Sharable註解則可以被添加到多個ChannelPipeline。也就是說單個ChannelHandler實例可以有多個ChannelHandlerContext,因此可以調用不同ChannelHandlerContext獲取同一個ChannelHandler。如果添加不帶@Sharable註解的ChannelHandler實例到多個ChannelPipeline則會拋出異常;使用@Sharable註解後的ChannelHandler必須在不同的線程和不同的通道上安全使用。怎麼是不安全的使用?看下面代碼:@Sharable
public class NotSharableHandler extends ChannelInboundHandlerAdapter {
private int count;
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
count++;
System.out.println("channelRead(...) called the " + count + " time");
ctx.fireChannelRead(msg);
}
}
上面是一個帶@Sharable註解的Handler,它被多個線程使用時,裏面count是不安全的,會導致count值錯誤。爲什麼要共享ChannelHandler?使用@Sharable註解共享一個ChannelHandler在一些需求中還是有很好的作用的,如使用一個ChannelHandler來統計連接數或來處理一些全局數據等等。
6.3 狀態模型
- channelUnregistered
- channelRegistered
- channelActive
- channelInactive
6.4 ChannelHandler和其子類
6.4.1 ChannelHandler中的方法
- handlerAdded,ChannelHandler添加到實際上下文中準備處理事件
- handlerRemoved,將ChannelHandler從實際上下文中刪除,不再處理事件
- exceptionCaught,處理拋出的異常
6.4.2 ChannelInboundHandler
- channelRegistered,ChannelHandlerContext的Channel被註冊到EventLoop;
- channelUnregistered,ChannelHandlerContext的Channel從EventLoop中註銷
- channelActive,ChannelHandlerContext的Channel已激活
- channelInactive,ChannelHanderContxt的Channel結束生命週期
- channelRead,從當前Channel的對端讀取消息
- channelReadComplete,消息讀取完成後執行
- userEventTriggered,一個用戶事件被處罰
- channelWritabilityChanged,改變通道的可寫狀態,可以使用Channel.isWritable()檢查
- exceptionCaught,重寫父類ChannelHandler的方法,處理異常
/**
* 實現ChannelInboundHandlerAdapter的Handler,不會自動釋放接收的消息對象
* @author c.k
*
*/
public class DiscardHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//手動釋放消息
ReferenceCountUtil.release(msg);
}
}
/**
* 繼承SimpleChannelInboundHandler,會自動釋放消息對象
* @author c.k
*
*/
public class SimpleDiscardHandler extends SimpleChannelInboundHandler<Object> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
//不需要手動釋放
}
}
如果需要其他狀態改變的通知,可以重寫Handler的其他方法。通常自定義消息類型來解碼字節,可以實現ChannelInboundHandler或ChannelInboundHandlerAdapter。有一個更好的解決方法,使用編解碼器的框架可以很容的實現。使用ChannelInboundHandler、ChannelInboundHandlerAdapter、SimpleChannelInboundhandler這三個中的一個來處理接收消息,使用哪一個取決於需求;大多數時候使用SimpleChannelInboundHandler處理消息,使用ChannelInboundHandlerAdapter處理其他的“入站”事件或狀態改變。ChannelInitializer用來初始化ChannelHandler,將自定義的各種ChannelHandler添加到ChannelPipeline中。
6.4.3 ChannelOutboundHandler
- bind,Channel綁定本地地址
- connect,Channel連接操作
- disconnect,Channel斷開連接
- close,關閉Channel
- deregister,註銷Channel
- read,讀取消息,實際是截獲ChannelHandlerContext.read()
- write,寫操作,實際是通過ChannelPipeline寫消息,Channel.flush()屬性到實際通道
- flush,刷新消息到通道
public class DiscardOutboundHandler extends ChannelOutboundHandlerAdapter {
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
ReferenceCountUtil.release(msg);
promise.setSuccess();
}
}
重要的是要記得釋放致遠並直通ChannelPromise,若ChannelPromise沒有被通知可能會導致其中一個ChannelFutureListener不被通知去處理一個消息。如果消息被消費並且沒有被傳遞到ChannelPipeline中的下一個ChannelOutboundHandler,那麼就需要調用ReferenceCountUtil.release(message)來釋放消息資源。一旦消息被傳遞到實際的通道,它會自動寫入消息或在通道關閉是釋放。