【Netty】(8)---理解ChannelPipeline
ChannelPipeline
ChannelPipeline不是单独存在,它肯定会和Channel、ChannelHandler、ChannelHandlerContext关联在一起,所以有关概念这里一起讲。
一、ChannelHandler
1、概念
先看图
ChannelHandler下主要是两个子接口
ChannelInboundHandler(入站): 处理输入数据和Channel状态类型改变。
适配器: ChannelInboundHandlerAdapter(适配器设计模式)
常用的: SimpleChannelInboundHandler
ChannelOutboundHandler(出站): 处理输出数据
适配器: ChannelOutboundHandlerAdapter
每一个Handler都一定会处理出站或者入站(可能两者都处理数据),例如对于入站的Handler可能会继承SimpleChannelInboundHandler或者ChannelInboundHandlerAdapter,
而SimpleChannelInboundHandler又是继承于ChannelInboundHandlerAdapter,最大的区别在于SimpleChannelInboundHandler会对没有外界引用的资源进行一定的清理,
并且入站的消息可以通过泛型来规定。
这里为什么有设配器模式呢?
我们在写自定义Handel时候,很少会直接实现上面两个接口,因为接口中有很多默认方法需要实现,所以这里就采用了设配器模式,ChannelInboundHandlerAdapter和
ChannelInboundHandlerAdapter就是设配器模式的产物,让它去实现上面接口,实现它所有方法。那么你自己写自定义Handel时,只要继承它,就无须重写上面接口的所有方法了。
2、Channel 生命周期(执行顺序也是从上倒下)
(1)channelRegistered: channel注册到一个EventLoop。
(2)channelActive: 变为活跃状态(连接到了远程主机),可以接受和发送数据
(3)channelInactive: channel处于非活跃状态,没有连接到远程主机
(4)channelUnregistered: channel已经创建,但是未注册到一个EventLoop里面,也就是没有和Selector绑定
3、ChannelHandler 生命周期
handlerAdded: 当 ChannelHandler 添加到 ChannelPipeline 调用
handlerRemoved: 当 ChannelHandler 从 ChannelPipeline 移除时调用
exceptionCaught: 当 ChannelPipeline 执行抛出异常时调用
二、ChannelPipeline
1、概念
先看图
ChannelPipeline类是ChannelHandler实例对象的链表,用于处理或截获通道的接收和发送数据。它提供了一种高级的截取过滤模式(类似serverlet中的filter功能),让用
户可以在ChannelPipeline中完全控制一个事件以及如何处理ChannelHandler与ChannelPipeline的交互。
对于每个新的通道Channel,都会创建一个新的ChannelPipeline,并将器pipeline附加到channel中。
下图描述ChannelHandler与pipeline中的关系,一个io操作可以由一个ChannelInboundHandler或ChannelOutboundHandle进行处理,并通过调用ChannelInboundHandler
处理入站io或通过ChannelOutboundHandler处理出站IO。
2、常用方法
addFirst(...) //添加ChannelHandler在ChannelPipeline的第一个位置
addBefore(...) //在ChannelPipeline中指定的ChannelHandler名称之前添加ChannelHandler
addAfter(...) //在ChannelPipeline中指定的ChannelHandler名称之后添加ChannelHandler
addLast(...) //在ChannelPipeline的末尾添加ChannelHandler
remove(...) //删除ChannelPipeline中指定的ChannelHandler
replace(...) //替换ChannelPipeline中指定的ChannelHandler
ChannelPipeline可以动态添加、删除、替换其中的ChannelHandler,这样的机制可以提高灵活性。示例:
1. ChannelPipeline pipeline = ch.pipeline();
2. FirstHandler firstHandler = new FirstHandler();
3. pipeline.addLast("handler1", firstHandler);
4. pipeline.addFirst("handler2", new SecondHandler());
5. pipeline.addLast("handler3", new ThirdHandler());
6. pipeline.remove("“handler3“");
7. pipeline.remove(firstHandler);
8. pipeline.replace("handler2", "handler4", new FourthHandler());<span style="font-family:Microsoft YaHei;font-size:14px;">
3、入站出站Handler执行顺序
一般的项目中,inboundHandler和outboundHandler有多个,在Pipeline中的执行顺序?
重点记住: InboundHandler顺序执行,OutboundHandler逆序执行。
问题: 下面的handel的执行顺序?
ch.pipeline().addLast(new InboundHandler1());
ch.pipeline().addLast(new OutboundHandler1());
ch.pipeline().addLast(new OutboundHandler2());
ch.pipeline().addLast(new InboundHandler2());
或者:
ch.pipeline().addLast(new OutboundHandler1());
ch.pipeline().addLast(new OutboundHandler2());
ch.pipeline().addLast(new InboundHandler1());
ch.pipeline().addLast(new InboundHandler2());
其实上面的执行顺序都是一样的:
InboundHandler1--> InboundHandler2 -->OutboundHandler2 -->OutboundHandler1
结论
1)InboundHandler顺序执行,OutboundHandler逆序执行
2)InboundHandler之间传递数据,通过ctx.fireChannelRead(msg)
3)InboundHandler通过ctx.write(msg),则会传递到outboundHandler
4) 使用ctx.write(msg)传递消息,Inbound需要放在结尾,在Outbound之后,不然outboundhandler会不执行;
但是使用channel.write(msg)、pipline.write(msg)情况会不一致,都会执行,那是因为channel和pipline会贯穿整个流。
5) outBound和Inbound谁先执行,针对客户端和服务端而言,客户端是发起请求再接受数据,先outbound再inbound,服务端则相反。
三、ChannelHandlerContext
ChannelPipeline并不是直接管理ChannelHandler,而是通过ChannelHandlerContext来间接管理,这一点通过ChannelPipeline的默认实现DefaultChannelPipeline可以看出来。
DefaultChannelHandlerContext和DefaultChannelPipeline是ChannelHandlerContext和ChannelPipeline的默认实现在DefaultPipeline内部
DefaultChannelHandlerContext组成了一个双向链表。 我们看下DefaultChannelPipeline的构造函数:
/**
* 可以看到,DefaultChinnelPipeline 内部使用了两个特殊的Hander 来表示Handel链的头和尾。
*/
public DefaultChannelPipeline(AbstractChannel channel) {
if (channel == null) {
throw new NullPointerException("channel");
}
this.channel = channel; TailHandler tailHandler = new TailHandler();
tail = new DefaultChannelHandlerContext(this, null, generateName(tailHandler), tailHandler); HeadHandler headHandler = new HeadHandler(channel.unsafe());
head = new DefaultChannelHandlerContext(this, null, generateName(headHandler), headHandler); head.next = tail;
tail.prev = head;
}
所以对于DefaultChinnelPipeline它的Handel头部和尾部的Handel是固定的,我们所添加的Handel是添加在这个头和尾之前的Handel。(下面这个图更加清晰)
四、几者关系
先大致说下什么是Channel
通常来说, 所有的 NIO 的 I/O 操作都是从 Channel 开始的. 一个 channel 类似于一个 stream。在Netty中,Channel是客户端和服务端建立的一个连接通道。
虽然java Stream 和 NIO Channel都是负责I/O操作,但他们还是有许多区别的:
1)我们可以在同一个 Channel 中执行读和写操作, 然而同一个 Stream 仅仅支持读或写。
2)Channel 可以异步地读写, 而 Stream 是阻塞的同步读写。
3)Channel 总是从 Buffer 中读取数据, 或将数据写入到 Buffer 中。
几者的关系图如下:
总结:
一个Channel包含一个ChannelPipeline,创建Channel时会自动创建一个ChannelPipeline,每个Channel都有一个管理它的pipeline,这关联是永久性的。
这点从源码中就可以看出,我之前写的博客里有说到:【Netty】5 源码 Bootstrap。每一个ChannelPipeline中可以包含多个ChannelHandler。所有ChannelHandler
都会顺序加入到ChannelPipeline中,ChannelHandler实例与ChannelPipeline之间的桥梁是ChannelHandlerContext实例。
五、整个传播流程
现在将上面的整个传播流程,通过源码大致走一遍。
为了搞清楚事件如何在Pipeline里传播,让我们从Channel的抽象子类AbstractChannel开始,下面是AbstractChannel#write()方法的实现:
public abstract class AbstractChannel extends DefaultAttributeMap implements Channel {
// ...
@Override
public Channel write(Object msg) {
return pipeline.write(msg);
}
// ...
}
AbstractChannel直接调用了Pipeline的write()方法:
再看DefaultChannelPipeline的write()方法实现:
final class DefaultChannelPipeline implements ChannelPipeline {
// ...
@Override
public ChannelFuture write(Object msg) {
return tail.write(msg);
}
// ...
}
因为write是个outbound事件,所以DefaultChannelPipeline直接找到tail部分的context,调用其write()方法:
接着看DefaultChannelHandlerContext的write()方法
final class DefaultChannelHandlerContext extends DefaultAttributeMap implements ChannelHandlerContext {
// ...
@Override
public ChannelFuture write(Object msg) {
return write(msg, newPromise());
} @Override
public ChannelFuture write(final Object msg, final ChannelPromise promise) {
if (msg == null) {
throw new NullPointerException("msg");
} validatePromise(promise, true); write(msg, false, promise); return promise;
} private void write(Object msg, boolean flush, ChannelPromise promise) {
DefaultChannelHandlerContext next = findContextOutbound();
next.invokeWrite(msg, promise);
if (flush) {
next.invokeFlush();
}
} private DefaultChannelHandlerContext findContextOutbound() {
DefaultChannelHandlerContext ctx = this;
do {
ctx = ctx.prev;
} while (!ctx.outbound);
return ctx;
} private void invokeWrite(Object msg, ChannelPromise promise) {
try {
((ChannelOutboundHandler) handler).write(this, msg, promise);
} catch (Throwable t) {
notifyOutboundHandlerException(t, promise);
}
} // ...
}
context的write()方法沿着context链往前找,直至找到一个outbound类型的context为止,然后调用其invokeWrite()方法
invokeWrite()接着调用handler的write()方法
最后看看ChannelOutboundHandlerAdapter的write()方法实现:
public class ChannelOutboundHandlerAdapter extends ChannelHandlerAdapter implements ChannelOutboundHandler {
// ...
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
ctx.write(msg, promise);
}
// ...
}
默认的实现调用了context的write()方法而不做任何处理,这样write事件就沿着outbound链继续传播:
可见,Pipeline的事件传播,是靠Pipeline,Context和Handler共同协作完成的。
参考
Netty4学习笔记-- ChannelPipeline(非常感谢作者分享,让我对事件传播有了更清晰的认识)
如果一个人充满快乐,正面的思想,那么好的人事物就会和他共鸣,而且被他吸引过来。同样,一个人老带悲伤,倒霉的事情也会跟过来。
——在自己心情低落的时候,告诫自己不要把负能量带给别人。(大校18)
【Netty】(8)---理解ChannelPipeline的更多相关文章
- Netty组件理解(转载)
转载的文章,写的非常好. 一.先纵览一下Netty,看看Netty都有哪些组件? 为了更好的理解和进一步深入Netty,我们先总体认识一下Netty用到的组件及它们在整个Netty架 ...
- netty中的channelPipeline在编程中的作用
在netty编程中我们绝大多数是要是用nio的,nio相比传统的io更加高效,而nio中核心概念离不开channel,buffer,selector三个重要的对象. 那么在netty中有一个chann ...
- Netty中的ChannelPipeline源码分析
ChannelPipeline在Netty中是用来处理请求的责任链,默认实现是DefaultChannelPipeline,其构造方法如下: private final Channel channel ...
- Netty源代码学习——ChannelPipeline模型分析
參考Netty API io.netty.channel.ChannelPipeline A list of ChannelHandlers which handles or intercepts i ...
- netty的理解
netty作为nio应用的典范,在很多设计方面都值得我们在程序开发中学习. 1.事件驱动,三种事件的传播机制.一种是在channel上触发,一种是在pipeline上触发,一种是在context上触发 ...
- netty系列之:channelPipeline详解
目录 简介 ChannelPipeline 事件传递 DefaultChannelPipeline 总结 简介 我们在介绍channel的时候提到过,几乎channel中所有的实现都是通过channe ...
- Netty源码解析 -- ChannelPipeline机制与读写过程
本文继续阅读Netty源码,解析ChannelPipeline事件传播原理,以及Netty读写过程. 源码分析基于Netty 4.1 ChannelPipeline Netty中的ChannelPip ...
- Netty 系列四(ChannelHandler 和 ChannelPipeline).
一.概念 先来整体的介绍一下这篇博文要介绍的几个概念(Channel.ChannelHandler.ChannelPipeline.ChannelHandlerContext.ChannelPromi ...
- Netty 中的 handler 和 ChannelPipeline 分析
上一节我们讲了 Netty 的启动流程,从启动流程入手分析了 Reactor 模型的第一步:channel 如何绑定 Selector.然后讲到了 EventLoop 在启动的时候发挥了什么作用.整个 ...
随机推荐
- redis list命令操作
1.将值追加到列表 RPUSH key value [value ...]summary: Append one or multiple values to a listsince: 1.0.0 12 ...
- 建立CMenu菜单项,实现选中菜单项点击左键响应事件
这里我只是根据自己的项目做了一些总结,实现点击右键弹出菜单项,点左键选择菜单项: CMenu menu; VERIFY(menu.CreatePopupMenu());//新建一个cmenu菜单项 m ...
- bzoj 3629 聪明的燕姿 约数和+dfs
考试只筛到了30分,正解dfs...... 对于任意N=P1^a1*P2^a2*......*Pn^an, F(N)=(P1^0+P1^1+...+P1^a1)(P2^0+P2^1+...+P2^a2 ...
- jsp --- jquery
1 给标签 添加属性 $('#principalMoney').attr("disabled", false); $("#career1").css('dis ...
- eclipse使用javaFX写一个HelloWorkld
------------------------------------------------ 操作系统:Ubuntu18.04 EclipseVersion: Oxygen.3a Release ...
- ecs云服务器 mysql经常自动停止挂掉重启问题分析
我的ecs服务器为1g内存的配置,在部署了nginx,mysql,redis,node服务后跑起项目来,(mysql使用默认配置),每过几天便发现了经常会出现数据库自动停止挂掉,然后几分钟后重启的现象 ...
- sau交流学习社区—vue总结:使用vue的computed属性实现监控变量变化,使用vue的watch属性监控变量变化从而实现其他业务
有时候遇到这么个需求,输入框为空的时候,请求一遍接口,如果输入框不为空的时候,需要点击搜索按钮请求接口. 同步sau交流学习社区:https://www.mwcxs.top/page/464.html ...
- SpringSecurityOauth RCE (CVE-2016-4977) 分析与复现
目录 0x00 前言 0x01 调试分析 0x02 补丁分析 0x03 参考 影响版本: 2.0.0-2.0.9 1.0.0-1.0.5 0x00 前言 这个漏洞与之前那个SpringBoot的SpE ...
- Python爬虫入门教程 58-100 python爬虫高级技术之验证码篇4-极验证识别技术之一
目录 验证码类型 官网最新效果 找个用极验证的网站 拼接验证码图片 编写自动化代码 核心run方法 模拟拖动方法 图片处理方法 初步运行结果 拼接图 图片存储到本地 @ 验证码类型 今天要搞定的验证码 ...
- Protocol Buffers(3):阅读一个二进制文件
目录 Proto文件 序列化 二进制文件解析 反序列化 参考 博客:blog.shinelee.me | 博客园 | CSDN 这篇文章中,我们将定义一个相对复杂的数据结构,直接分析其序列化后的二进制 ...