在Netty上,Pipeline
把ChannelHandler
串联在一起来组织处理逻辑。比如实现协议栈HTTP
,HTTP2
。而ChannelHandlerContext
可以认为是Pipeline
用于串联ChannelHandler
的纽带。开发者的业务逻辑基本上是在ChannelHandler
实现的,理解这三者以及三者之间的关联是使用Netty构建模块化、可复用程序的关键。
ChannelHandler
上图是
ChannelHandler
的类结构图,在此总结下ChannelHandler
的功能。
- 响应
ChannelHandler
状态变化 - 响应与其关联的
Channel
生命周期内的状态变化以及处理接收的数据 - 处理与其关联的
Channel
上的Outbound
操作
下面针对这三个方面详细说明。
Channel的生命周期
ChannelInboundHandler
ChannelInboundHandler
的方法与其紧密对应。
-
channelRegistered
Channel 注册到其EventLoop上,此时就可以处理I/O了。 -
channelActive
对于NioSocketChannel
其active状态是如下定义:
public boolean isActive() {
SocketChannel ch = javaChannel();
return ch.isOpen() && ch.isConnected();
}
对于NioServerSocketChannel
@Override
public boolean isActive() {
return javaChannel().socket().isBound();
}
-
channelRead
Channel读取数据后触发 -
channelReadComplete
一次读取周期完成,从下面的读取逻辑可以看出,channelReadComplete的在Channel读缓冲区读取完时触发。
try {
do {
//RecvByteBufAllocator 默认使用AdaptiveRecvByteBufAllocator,其根据上一次读取的字节数动态调整本次读取字节数
byteBuf = allocHandle.allocate(allocator);
allocHandle.lastBytesRead(doReadBytes(byteBuf));
if (allocHandle.lastBytesRead() <= 0) {
// nothing was read. release the buffer.
byteBuf.release();
byteBuf = null;
close = allocHandle.lastBytesRead() < 0;
break;
}
allocHandle.incMessagesRead(1);
readPending = false;
pipeline.fireChannelRead(byteBuf);
byteBuf = null;
/**
* 继续读取的逻辑:
* config.isAutoRead() &&
attemptedBytesRead == lastBytesRead &&
totalMessages < maxMessagePerRead(默认为1) &&
totalBytesRead < Integer.MAX_VALUE;
* */
} while (allocHandle.continueReading());
allocHandle.readComplete();
pipeline.fireChannelReadComplete();
-
channelWritabilityChanged
当Channel的可写状态发生变化时触发。当channel不可写时用户可以用此来控制数据发送不至于太快,当Channel可写时用户可以继续发送数据。Channel的可写性可以由用户来进行设置。可以把此理解为用户空间的写缓存,和socket的系统缓存无关。
Channel.config().setWriteHighWaterMark()
Channel.config().setWriteLowWaterMark().
ChannelOutboundHandler
ChannelOutboundHandler
给用户机会来进一步处理用户在Channel上的操作。一些比较典型的应用场景是filter掉一些操作,即根据需求拒绝一些操作;Socket面向字节流,在实现具体 的协议比如Http时,可以让用户只处理自己关心的数据,OutboundHandler可以把这些数据封装成协议需要的数据,然后交给socket发送。
method | 描述 |
---|---|
bind(ChannelHandlerContext,SocketAddress,ChannelPromise) | |
connect(ChannelHandlerContext,SocketAddress,SocketAddress,ChannelPromise) | |
disconnect(ChannelHandlerContext,ChannelPromise) | |
close(ChannelHandlerContext,ChannelPromise) | |
deregister(ChannelHandlerContext,ChannelPromise) | |
flush(ChannelHandlerContext) | |
write(ChannelHandlerContext,ChannelPromise) | 。。。。 |
ChannelPipeline
上图基本上可以概括Socket 和 ChannelPipeline
以及ChannelHandler
和ChannelPipeline
之间的关系。
-
ChannelPipeline
把ChannelHandler
串联在一起来拦截处理Channel产生的inbound和outbound事件,这些ChannelHandler构成了应用程序的数据和事件处理逻辑。 - 每个Channel会与唯一一个
ChannelPipeline
实例进行绑定。 - inbound事件的流向Head -> Tail,
-
outbound事件的流向是Tail->Head,这有点像网络协议栈,而且Netty本身实现的协议比如Http,Http2都是基于ChannelHandler,在其上实现数据的解码与编码。我的理解是
ChannelPipeline
把用户的逻辑同socket关联起来,我觉着其上面最适合实现的逻辑应该是协议的实现,至于用户的业务逻辑不应该在ChannelHandler上实现。
ChannelPipeline提供的method
ChannelPipeline提供了大量的方法,可以分为三类,
- 触发outbound事件的方法;
- 触发inbound事件的方法
- 对Pipeline上Handler以及Context进行管理的方法
ChannelHandlerContext
ChannelPipeline
并不是直接把ChannelHandler
串联起来的,而是通过ChannelHandlerContext
。可以认为ChannelPipeline
是双向链表, 而链表上的节点就是ChannelHandlerContext
。因此,handler的处理完后的事件实际是通过ChannelHandlerContext
传递给下一个Handler的。
上图是ChannelHandlerContext的类图,从这里可以看出其提供的方法同ChannelPipeline
类似,但是与前者不同的是,前者触发的事件被整个Pipeline处理,而Context触发的事件仅从下一个handler开始处理。如下图所示:
总结
- 每个
ChannelHandler
实例唯一关联一个ChannelHandlerContext
实例 -
ChannelPipeline
通过ChannelHandlerContext
把ChannelHandler
串联成一个双向链表结构,用来处理Channel产生的inbound和Outbound事件。这种处理方式和网络协议栈的实现方式很像,因此我觉着特别适合基于Netty实现各种应用协议。而Pipeline就是协议的编码和解码的实现逻辑。 - 个人见解,
ChannelHandler
的实现最好只与协议的编码和解码有关,与具体业务相关的逻辑不是很适合在其上进行实现。