前言
ChannelHandler是netty中的核心處理部分,我們使用netty的絕大部分程式碼都寫在這部分,所以瞭解它的一些機制和特性是很有必要的
Channel
Channel介面抽象了底層socket的一些狀態屬性以及呼叫方法
針對不同型別的socket提供不同的子類實現。Channel生命週期
ChannelHandler
ChannelHandler介面裡面只定義了三個生命週期方法,我們主要實現它的子介面ChannelInboundHandler和ChannelOutboundHandler,為了便利,框架提供了ChannelInboundHandlerAdapter,ChannelOutboundHandlerAdapter和ChannelDuplexHandler這三個適配類提供一些預設實現,在使用的時候只需要實現你關注的方法即可
ChannelHandler生命週期方法
ChannelHandler裡面定義三個生命週期方法,分別會在當前ChannelHander加入ChannelHandlerContext中,從ChannelHandlerContext中移除,以及ChannelHandler回撥方法出現異常時被回撥ChannelInboundHandler
介紹一下這些回撥方法被觸發的時機
回撥方法 | 觸發時機 |
---|---|
channelRegistered | 當前channel註冊到EventLoop |
channelUnregistered | 當前channel從EventLoop取消註冊 |
channelActive | 當前channel活躍的時候 |
channelInactive | 當前channel不活躍的時候,也就是當前channel到了它生命週期末 |
channelRead | 當前channel從遠端讀取到資料 |
channelReadComplete | channel read消費完讀取的資料的時候被觸發 |
userEventTriggered | 使用者事件觸發的時候 |
channelWritabilityChanged | channel的寫狀態變化的時候觸發 |
可以注意到每個方法都帶了ChannelHandlerContext作為引數,具體作用是,在每個回撥事件裡面,處理完成之後,使用ChannelHandlerContext的fireChannelXXX方法來傳遞給下個ChannelHandler,netty的codec模組和業務處理程式碼分離就用到了這個鏈路處理
ChannelOutboundHandler
回撥方法 | 觸發時機 |
---|---|
bind | bind操作執行前觸發 |
connect | connect 操作執行前觸發 |
disconnect | disconnect 操作執行前觸發 |
close | close操作執行前觸發 |
deregister | deregister操作執行前觸發 |
read | read操作執行前觸發 |
write | write操作執行前觸發 |
flush | flush操作執行前觸發 |
注意到一些回撥方法有ChannelPromise這個引數,我們可以呼叫它的addListener註冊監聽,當回撥方法所對應的操作完成後,會觸發這個監聽 下面這個程式碼,會在寫操作完成後觸發,完成操作包括成功和失敗
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
ctx.write(msg,promise);
System.out.println("out write");
promise.addListener(new GenericFutureListener<Future<? super Void>>() {
@Override
public void operationComplete(Future<? super Void> future) throws Exception {
if(future.isSuccess()){
System.out.println("OK");
}
}
});
}
複製程式碼
ChannelInboundHandler和ChannelOutboundHandler的區別
個人感覺in和out的區別主要在於ChannelInboundHandler的channelRead和channelReadComplete回撥和ChannelOutboundHandler的write和flush回撥上,ChannelOutboundHandler的channelRead回撥負責執行入棧資料的decode邏輯,ChannelOutboundHandler的write負責執行出站資料的encode工作。其他回撥方法和具體觸發邏輯有關,和in與out無關。
ChannelHandlerContext
每個ChannelHandler通過add方法加入到ChannelPipeline中去的時候,會建立一個對應的ChannelHandlerContext,並且繫結,ChannelPipeline實際維護的是ChannelHandlerContext 的關係 在DefaultChannelPipeline原始碼中可以看到會儲存第一個ChannelHandlerContext以及最後一個ChannelHandlerContext的引用
final AbstractChannelHandlerContext head;
final AbstractChannelHandlerContext tail;
複製程式碼
而在AbstractChannelHandlerContext原始碼中可以看到
volatile AbstractChannelHandlerContext next;
volatile AbstractChannelHandlerContext prev;
複製程式碼
每個ChannelHandlerContext之間形成雙向連結串列
ChannelPipeline
在Channel建立的時候,會同時建立ChannelPipeline
protected AbstractChannel(Channel parent) {
this.parent = parent;
id = newId();
unsafe = newUnsafe();
pipeline = newChannelPipeline();
}
複製程式碼
在ChannelPipeline中也會持有Channel的引用
protected DefaultChannelPipeline newChannelPipeline() {
return new DefaultChannelPipeline(this);
}
複製程式碼
ChannelPipeline會維護一個ChannelHandlerContext的雙向連結串列
final AbstractChannelHandlerContext head;
final AbstractChannelHandlerContext tail;
複製程式碼
連結串列的頭尾有預設實現
protected DefaultChannelPipeline(Channel channel) {
this.channel = ObjectUtil.checkNotNull(channel, "channel");
succeededFuture = new SucceededChannelFuture(channel, null);
voidPromise = new VoidChannelPromise(channel, true);
tail = new TailContext(this);
head = new HeadContext(this);
head.next = tail;
tail.prev = head;
}
複製程式碼
我們新增的自定義ChannelHandler會插入到head和tail之間,如果是ChannelInboundHandler的回撥,根據插入的順序從左向右進行鏈式呼叫,ChannelOutboundHandler則相反
具體關係如下,但是下圖沒有把預設的head和tail畫出來,這兩個ChannelHandler做的工作相當重要
上面的整條鏈式的呼叫是通過Channel介面的方法直接觸發的,如果使用ChannelContextHandler的介面方法間接觸發,鏈路會從ChannelContextHandler對應的ChannelHandler開始,而不是從頭或尾開始
HeadContext
HeadContext實現了ChannelOutboundHandler,ChannelInboundHandler這兩個介面
class HeadContext extends AbstractChannelHandlerContext
implements ChannelOutboundHandler, ChannelInboundHandler
複製程式碼
因為在頭部,所以說HeadContext中關於in和out的回撥方法都會觸發 關於ChannelInboundHandler,HeadContext的作用是進行一些前置操作,以及把事件傳遞到下一個ChannelHandlerContext的ChannelInboundHandler中去 看下其中channelRegistered的實現
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
invokeHandlerAddedIfNeeded();
ctx.fireChannelRegistered();
}
複製程式碼
從語義上可以看出來在把這個事件傳遞給下一個ChannelHandler之前會回撥ChannelHandler的handlerAdded方法 而有關ChannelOutboundHandler介面的實現,會在鏈路的最後執行,看下write方法的實現
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
unsafe.write(msg, promise);
}
複製程式碼
這邊的unsafe介面封裝了底層Channel的呼叫,之所以取名為unsafe,是不需要使用者手動去呼叫這些方法。這個和阻塞原語的unsafe不是同一個 也就是說,當我們通過Channel介面執行write之後,會執行ChannelOutboundHandler鏈式呼叫,在鏈尾的HeadContext ,在通過unsafe回到對應Channel做相關呼叫 從netty Channel介面的實現就能論證這個
public ChannelFuture write(Object msg) {
return pipeline.write(msg);
}
複製程式碼
TailContext
TailContext實現了ChannelInboundHandler介面,會在ChannelInboundHandler呼叫鏈最後執行,只要是對呼叫鏈完成處理的情況進行處理,看下channelRead實現
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
onUnhandledInboundMessage(msg);
}
複製程式碼
如果我們自定義的最後一個ChannelInboundHandler,也把處理操作交給下一個ChannelHandler,那麼就會到TailContext,在TailContext會提供一些預設處理
protected void onUnhandledInboundMessage(Object msg) {
try {
logger.debug(
"Discarded inbound message {} that reached at the tail of the pipeline. " +
"Please check your pipeline configuration.", msg);
} finally {
ReferenceCountUtil.release(msg);
}
}
複製程式碼
比如channelRead中的onUnhandledInboundMessage方法,會把msg資源回收,防止記憶體洩露
強調一點的是,如果要執行整個鏈路,必須通過呼叫Channel方法觸發,ChannelHandlerContext引用了ChannelPipeline,所以也能間接操作channel的方法,但是會從當前ChannelHandlerContext繫結的ChannelHandler作為起點開始,而不是ChannelHandlerContext的頭和尾 這個特性在不需要呼叫整個鏈路的情況下可以使用,可以增加一些效率
上述元件之間的關係
- 每個Channel會繫結一個ChannelPipeline,每個ChannelPipeline會持有一個Channel
- 每個ChannelHandler對應一個ChannelHandlerContext,ChannelPipeline持有ChannelHandlerContext連結串列,也就相當於持有ChannelHandler連結串列
- ChannelHandlerContext作為上下文,持有ChannelPipeline和它對應ChannelHandler的引用,持有ChannelPipeline相當於間接持有Channel,同時持有它上/下一個ChannelHandlerContext的引用
示例程式
public class NettyServer {
public static void main(String[] args) throws InterruptedException {
//兩個執行緒池
NioEventLoopGroup eventLoopGroup = new NioEventLoopGroup();
NioEventLoopGroup childEventLoopGroup = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap
.group(eventLoopGroup,childEventLoopGroup)
.channel(NioServerSocketChannel.class)
.localAddress(new InetSocketAddress(9999))
//對serversocketchannel的回撥
.handler(new ChannelInitializer<ServerSocketChannel>() {
@Override
protected void initChannel(ServerSocketChannel socketChannel) throws Exception {
socketChannel.pipeline()
.addLast(new TestInboundChannelHandler("server in "))
.addLast(new TestOutboundChannelHandler("server out "));
}
})
//對socketchannel的回撥
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline()
.addLast(new TestInboundChannelHandler("first in "))
.addLast(new TestInboundChannelHandler("second in "))
.addLast(new EchoChannelHandler())
.addLast(new TestOutboundChannelHandler("first out "))
.addLast(new TestOutboundChannelHandler("second out "));
}
});
//等到繫結完成
ChannelFuture channelFuture = bootstrap.bind().sync();
//等到serversocketchannel close在退出
channelFuture.channel().closeFuture().sync();
}
static class TestInboundChannelHandler extends ChannelInboundHandlerAdapter{
private String name;
public TestInboundChannelHandler(String name) {
this.name = name;
}
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
System.out.println(name+"channelRegistered spread");
ctx.fireChannelRegistered();
}
@Override
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
System.out.println(name+"channelUnregistered not spread");
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println(name+"channelActive not spread");
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println(name+"channelInactive not spread");
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println(name+"channelRead spread");
ctx.fireChannelRead(msg);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
System.out.println(name+"channelReadComplete spread");
ctx.fireChannelReadComplete();
}
}
static class EchoChannelHandler extends ChannelInboundHandlerAdapter{
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
System.out.println("echo channelRegistered");
//ctx.writeAndFlush(Unpooled.copiedBuffer("Hello\r\n".getBytes()));
}
@Override
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
System.out.println("echo channelUnregistered");
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println("echo channelInactive");
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("echo channelActive");
ctx.writeAndFlush(Unpooled.copiedBuffer("Hello\r\n".getBytes()));
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("echo channelRead");
ByteBuf byteBuf = (ByteBuf)msg;
System.out.println(new String(ByteBufUtil.getBytes(byteBuf)));
//ctx.write(msg);
ctx.channel().write(msg);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
System.out.println("echo channelReadComplete");
ctx.channel().flush();
}
}
static class TestOutboundChannelHandler extends ChannelOutboundHandlerAdapter{
private String name;
public TestOutboundChannelHandler(String name) {
this.name = name;
}
@Override
public void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception {
//super.bind(ctx,localAddress,promise);
System.out.println(name+ "bind");
}
@Override
public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) throws Exception {
super.connect(ctx,remoteAddress,localAddress,promise);
System.out.println(name+"connect");
}
@Override
public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
//super.disconnect(ctx,promise);
System.out.println(name+"disconnect");
}
@Override
public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
//super.close(ctx,promise);
System.out.println(name+"close");
}
@Override
public void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
System.out.println(name+"deregister");
}
@Override
public void read(ChannelHandlerContext ctx) throws Exception {
ctx.read();
System.out.println(name+"read");
}
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
//super.write(ctx,msg,promise);
ctx.write(msg,promise);
System.out.println(name+"write");
//promise.setSuccess();
promise.addListener(new GenericFutureListener<Future<? super Void>>() {
@Override
public void operationComplete(Future<? super Void> future) throws Exception {
if(future.isSuccess()){
System.out.println(name+" listener trigger");
}
}
});
}
@Override
public void flush(ChannelHandlerContext ctx) throws Exception {
System.out.println(name+"flush");
ctx.flush();
}
}
}
複製程式碼
這個demo主要測試了ChannelHandler鏈的傳遞以及觸發時機
最後
希望大家關注下我的公眾號,歡迎朋友們投稿