先引用 https://www.jianshu.com/p/039fb90fa5bf 做个小结:
在Netty上,Pipeline把ChannelHandler串联在一起来组织处理逻辑。比如实现协议栈HTTP,HTTP2。而ChannelHandlerContext可以认为是Pipeline用于串联ChannelHandler的纽带。开发者的业务逻辑基本上是在ChannelHandler实现的,理解这三者以及三者之间的关联是使用Netty构建模块化、可复用程序的关键。上图是ChannelHandler的类结构图,在此总结下ChannelHandler的功能:
- 响应ChannelHandler状态变化
- 响应与其关联的Channel生命周期内的状态变化以及处理接收的数据
- 处理与其关联的Channel上的 Outbound 操作
ChannelHandlerAdapter 提供了所有各种类型 Handler 都需要的通用操作。是ChannelOutboundHandlerAdapter、 ChannelInboundHandlerAdapter 都需要继承的,因为 他们都能够处理 isSharable、 added、removed 、exceptionCaughtChannelOutboundHandlerChannelOutboundHandler 给用户机会来进一步处理用户在Channel上的操作。一些比较典型的应用场景是filter掉一些操作,即根据需求拒绝一些操作;Socket面向字节流,在实现具体 的协议比如Http时,可以让用户只处理自己关心的数据,OutboundHandler可以把这些数据封装成协议需要的数据,然后交给socket发送。...可以概括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上实现。
再谈出站和入站
官方原图:
private void addFirst0(AbstractChannelHandlerContext newCtx) { AbstractChannelHandlerContext nextCtx = head.next; newCtx.prev = head; newCtx.next = nextCtx; head.next = newCtx; // 添加到 head 之后。 nextCtx.prev = newCtx; }
head ? tail? 命名其实不太好, 不直观啊! 不如, local、remote,
ctx.xxx(..);和ctx.channel().pipeline().xxx(..); 是什么区别?
@Override public final ChannelPipeline fireChannelRead(Object msg) { AbstractChannelHandlerContext.invokeChannelRead(head, msg); return this; }
@Override public ChannelHandlerContext fireChannelRead(final Object msg) { invokeChannelRead(findContextInbound(MASK_CHANNEL_READ), msg); //从当前处理器开始,通过findContextInbound沿着pipeline入站方向找到next return this; } static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) { final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next); EventExecutor executor = next.executor(); if (executor.inEventLoop()) { next.invokeChannelRead(m); } else { executor.execute(new Runnable() { @Override public void run() { next.invokeChannelRead(m); } }); } }
![](https://img2020.cnblogs.com/blog/493842/202106/493842-20210630071407676-164099561.png)
错误使用 pipeline() 方法导致的死循环
channelRead 和 channelReadComplete什么区别,何时使用?
参考 https://segmentfault.com/q/1010000018753423 可以得到一些答案
我的理解是,对端一次写事件,可能引起本端的一次读事件,也可能有多个channelRead ,但是只会有一个channelReadComplete
why ?
因为,读 可能一次性读很多数据, 很多个 buffer,然后每个一个bugger 就是 调用一次 channelRead 调用。 全部buffer 读完了, 然后才是 channelReadComplete
到底write writeAndFlush区别是什么
有时候你会发现 write 竟然写不出去, 莫非夜路走多了遇到了鬼?
其实是这样的,两者有一些区别,前者 不一定写出去, 多个 write 可能在不确定的某一时刻写出去, 后者可以确保缓冲区的内容全部写出去!
write 是写到 写缓存buffer中, 不一定直接写出去, 需要等待缓存满了才发出去,—— 注意, 如果缓存满了, 那么之前的数据, 会全部一并 写出去! 而不是。
而 writeAndFlush 是直接写出去!! 而且是 把 之前的数据, 会全部一并 写出去!
就是说,如果有多个 write , 每次写很少数据, 然后一个writeAndFlush , 那么就一起写出去了!
这个时候, 如果多次write 被一次性读到, 那么可能就是 数据连在了一起, 没有换行.. 就可能不是很 直观。 如果分多次读到,那么自然就不会这样啦。。
到底read出站事件方法意味着什么?
同样是出站 write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) 有ctx 、msg、promise 等参数, 为什么
ChannelOutboundHandler 的read方法 就只有一个参数:
public void read(ChannelHandlerContext ctx) throws Exception {... }
那么, 到底ChannelOutboundHandler 的read出站事件方法意味着什么?什么时候被调用?
观察发现, 事件是这样触发的, 从head 开始:
head. channelActive or channelReadComplete --> readIfIsAutoRead --> channel.read(); --> pipeline.read(); --> pipeline.read(); -- > tail.read();
也就是 通道 激活之后, 才开始准备读数据! 或者 一次读完毕之后才。。
真的是 绕来绕去啊!
简单说,就是 只要读事件完毕, 就触发read,作为出站事件,从管道的 tail 传播。
@Override protected void doBeginRead() throws Exception { // Channel.read() or ChannelHandlerContext.read() was called final SelectionKey selectionKey = this.selectionKey; if (!selectionKey.isValid()) { return; } readPending = true; final int interestOps = selectionKey.interestOps(); if ((interestOps & readInterestOp) == 0) { selectionKey.interestOps(interestOps | readInterestOp); } }
doBeginRead() 做的事情很简单,拿到处理过的selectionKey,然后如果发现该selectionKey若在某个地方被移除了readInterestOp操作,这里给他加上,事实上,标准的netty程序是不会走到这一行的,只有在三次握手成功之后,如下方法被调用。