【转】Netty那点事(三)Channel中的Pipeline
【原文】https://github.com/code4craft/netty-learning/blob/master/posts/ch3-pipeline.md
Channel是理解和使用Netty的核心。Channel的涉及内容较多,这里我使用由浅入深的介绍方法。在这篇文章中,我们主要介绍Channel部分中Pipeline实现机制。为了避免枯燥,借用一下《盗梦空间》的“梦境”概念,希望大家喜欢。
一层梦境:Channel实现概览
在Netty里,Channel
是通讯的载体,而ChannelHandler
负责Channel中的逻辑处理。
那么ChannelPipeline
是什么呢?我觉得可以理解为ChannelHandler的容器:一个Channel包含一个ChannelPipeline,所有ChannelHandler都会注册到ChannelPipeline中,并按顺序组织起来。
在Netty中,ChannelEvent
是数据或者状态的载体,例如传输的数据对应MessageEvent
,状态的改变对应ChannelStateEvent
。当对Channel进行操作时,会产生一个ChannelEvent,并发送到ChannelPipeline
。ChannelPipeline会选择一个ChannelHandler进行处理。这个ChannelHandler处理之后,可能会产生新的ChannelEvent,并流转到下一个ChannelHandler。
例如,一个数据最开始是一个MessageEvent
,它附带了一个未解码的原始二进制消息ChannelBuffer
,然后某个Handler将其解码成了一个数据对象,并生成了一个新的MessageEvent
,并传递给下一步进行处理。
到了这里,可以看到,其实Channel的核心流程位于ChannelPipeline
中。于是我们进入ChannelPipeline的深层梦境里,来看看它具体的实现。
二层梦境:ChannelPipeline的主流程
Netty的ChannelPipeline包含两条线路:Upstream和Downstream。Upstream对应上行,接收到的消息、被动的状态改变,都属于Upstream。Downstream则对应下行,发送的消息、主动的状态改变,都属于Downstream。ChannelPipeline
接口包含了两个重要的方法:sendUpstream(ChannelEvent e)
和sendDownstream(ChannelEvent e)
,就分别对应了Upstream和Downstream。
对应的,ChannelPipeline里包含的ChannelHandler也包含两类:ChannelUpstreamHandler
和ChannelDownstreamHandler
。每条线路的Handler是互相独立的。它们都很简单的只包含一个方法:ChannelUpstreamHandler.handleUpstream
和ChannelDownstreamHandler.handleDownstream
。
Netty官方的javadoc里有一张图(ChannelPipeline
接口里),非常形象的说明了这个机制(我对原图进行了一点修改,加上了ChannelSink
,因为我觉得这部分对理解代码流程会有些帮助):
什么叫ChannelSink
呢?ChannelSink包含一个重要方法ChannelSink.eventSunk
,可以接受任意ChannelEvent。"sink"的意思是"下沉",那么"ChannelSink"好像可以理解为"Channel下沉的地方"?实际上,它的作用确实是这样,也可以换个说法:"处于末尾的万能Handler"。最初读到这里,也有些困惑,这么理解之后,就感觉简单许多。只有Downstream包含ChannelSink
,这里会做一些建立连接、绑定端口等重要操作。为什么UploadStream没有ChannelSink呢?我只能认为,一方面,不符合"sink"的意义,另一方面,也没有什么处理好做的吧!
这里有个值得注意的地方:在一条“流”里,一个ChannelEvent
并不会主动的"流"经所有的Handler,而是由上一个Handler显式的调用ChannelPipeline.sendUp(Down)stream
产生,并交给下一个Handler处理。也就是说,每个Handler接收到一个ChannelEvent,并处理结束后,如果需要继续处理,那么它需要调用sendUp(Down)stream
新发起一个事件。如果它不再发起事件,那么处理就到此结束,即使它后面仍然有Handler没有执行。这个机制可以保证最大的灵活性,当然对Handler的先后顺序也有了更严格的要求。
顺便说一句,在Netty 3.x里,这个机制会导致大量的ChannelEvent对象创建,因此Netty 4.x版本对此进行了改进。twitter的finagle框架实践中,就提到从Netty 3.x升级到Netty 4.x,可以大大降低GC开销。有兴趣的可以看看这篇文章:https://blog.twitter.com/2013/netty-4-at-twitter-reduced-gc-overhead
下面我们从代码层面来对这里面发生的事情进行深入分析,这部分涉及到一些细节,需要打开项目源码,对照来看,会比较有收获。
三层梦境:深入ChannelPipeline内部
DefaultChannelPipeline的内部结构
ChannelPipeline
的主要的实现代码在DefaultChannelPipeline
类里。列一下DefaultChannelPipeline的主要字段:
public class DefaultChannelPipeline implements ChannelPipeline { private volatile Channel channel;
private volatile ChannelSink sink;
private volatile DefaultChannelHandlerContext head;
private volatile DefaultChannelHandlerContext tail;
private final Map<String, DefaultChannelHandlerContext> name2ctx =
new HashMap<String, DefaultChannelHandlerContext>(4);
}
这里需要介绍一下ChannelHandlerContext
这个接口。顾名思义,ChannelHandlerContext保存了Netty与Handler相关的的上下文信息。而咱们这里的DefaultChannelHandlerContext
,则是对ChannelHandler
的一个包装。一个DefaultChannelHandlerContext
内部,除了包含一个ChannelHandler
,还保存了"next"和"prev"两个指针,从而形成一个双向链表。
因此,在DefaultChannelPipeline
中,我们看到的是对DefaultChannelHandlerContext
的引用,而不是对ChannelHandler
的直接引用。这里包含"head"和"tail"两个引用,分别指向链表的头和尾。而name2ctx则是一个按名字索引DefaultChannelHandlerContext用户的一个map,主要在按照名称删除或者添加ChannelHandler时使用。
sendUpstream和sendDownstream
前面提到了,ChannelPipeline
接口的两个重要的方法:sendUpstream(ChannelEvent e)
和sendDownstream(ChannelEvent e)
。所有事件的发起都是基于这两个方法进行的。Channels
类有一系列fireChannelBound
之类的fireXXXX
方法,其实都是对这两个方法的facade包装。
下面来看一下这两个方法的实现。先看sendUpstream(对代码做了一些简化,保留主逻辑):
public void sendUpstream(ChannelEvent e) {
DefaultChannelHandlerContext head = getActualUpstreamContext(this.head);
head.getHandler().handleUpstream(head, e);
} private DefaultChannelHandlerContext getActualUpstreamContext(DefaultChannelHandlerContext ctx) {
DefaultChannelHandlerContext realCtx = ctx;
while (!realCtx.canHandleUpstream()) {
realCtx = realCtx.next;
if (realCtx == null) {
return null;
}
}
return realCtx;
}
这里最终调用了ChannelUpstreamHandler.handleUpstream
来处理这个ChannelEvent。有意思的是,这里我们看不到任何"将Handler向后移一位"的操作,但是我们总不能每次都用同一个Handler来进行处理啊?实际上,我们更为常用的是ChannelHandlerContext.handleUpstream
方法(实现是DefaultChannelHandlerContext.sendUpstream
方法):
public void sendUpstream(ChannelEvent e) {
DefaultChannelHandlerContext next = getActualUpstreamContext(this.next);
DefaultChannelPipeline.this.sendUpstream(next, e);
}
可以看到,这里最终仍然调用了ChannelPipeline.sendUpstream
方法,但是它会将Handler指针后移。
我们接下来看看DefaultChannelHandlerContext.sendDownstream
:
public void sendDownstream(ChannelEvent e) {
DefaultChannelHandlerContext prev = getActualDownstreamContext(this.prev);
if (prev == null) {
try {
getSink().eventSunk(DefaultChannelPipeline.this, e);
} catch (Throwable t) {
notifyHandlerException(e, t);
}
} else {
DefaultChannelPipeline.this.sendDownstream(prev, e);
}
}
与sendUpstream好像不大相同哦?这里有两点:一是到达末尾时,就如梦境二所说,会调用ChannelSink进行处理;二是这里指针是往前移的,所以我们知道了:
UpstreamHandler是从前往后执行的,DownstreamHandler是从后往前执行的。在ChannelPipeline里添加时需要注意顺序了!
DefaultChannelPipeline里还有些机制,像添加/删除/替换Handler,以及ChannelPipelineFactory
等,比较好理解,就不细说了。
回到现实:Pipeline解决的问题
好了,深入分析完代码,有点头晕了,我们回到最开始的地方,来想一想,Netty的Pipeline机制解决了什么问题?
我认为至少有两点:
一是提供了ChannelHandler的编程模型,基于ChannelHandler开发业务逻辑,基本不需要关心网络通讯方面的事情,专注于编码/解码/逻辑处理就可以了。Handler也是比较方便的开发模式,在很多框架中都有用到。
二是实现了所谓的"Universal Asynchronous API"。这也是Netty官方标榜的一个功能。用过OIO和NIO的都知道,这两套API风格相差极大,要从一个迁移到另一个成本是很大的。即使是NIO,异步和同步编程差距也很大。而Netty屏蔽了OIO和NIO的API差异,通过Channel提供对外接口,并通过ChannelPipeline将其连接起来,因此替换起来非常简单。
理清了ChannelPipeline的主流程,我们对Channel部分的大致结构算是弄清楚了。可是到了这里,我们依然对一个连接具体怎么处理没有什么概念,下篇文章,我们会分析一下,在Netty中,捷径如何处理连接的建立、数据的传输这些事情。
PS: Pipeline这部分拖了两个月,终于写完了。中间写的实在缓慢,写个高质量(至少是自认为吧!)的文章不容易,但是仍不忍心这部分就此烂尾。中间参考了一些优秀的文章,还自己使用netty开发了一些应用。以后这类文章,还是要集中时间来写完好了。
参考资料:
【转】Netty那点事(三)Channel中的Pipeline的更多相关文章
- Netty那点事: 概述, Netty中的buffer, Channel与Pipeline
Netty那点事(一)概述 Netty和Mina是Java世界非常知名的通讯框架.它们都出自同一个作者,Mina诞生略早,属于Apache基金会,而Netty开始在Jboss名下,后来出来自立门户ne ...
- Netty源码分析--创建Channel(三)
恩~,没错,其实这一篇才是真正的开始分析源码,你打我呀~. 先看一下我Netty的启动类 private void start() throws Exception { EventLoopGroup ...
- Netty 零拷贝(三)Netty 对零拷贝的改进
Netty 零拷贝(三)Netty 对零拷贝的改进 Netty 系列目录 (https://www.cnblogs.com/binarylei/p/10117436.html) Netty 的&quo ...
- Netty核心概念(5)之Channel
1.前言 上一节讲了Netty的第一个关键启动类,启动类所做的一些操作,和服务端的channel固定的handler执行过程,谈到了不管是connect还是bind方法最终都是调用了channel的相 ...
- MySQL事务在MGR中的漫游记—路线图
欢迎访问网易云社区,了解更多网易技术产品运营经验. MGR即MySQL Group Replication,是MySQL官方推出的基于Paxos一致性协议的数据高可靠.服务高可用方案.MGR在20 ...
- 一个I/O线程可以并发处理N个客户端连接和读写操作 I/O复用模型 基于Buf操作NIO可以读取任意位置的数据 Channel中读取数据到Buffer中或将数据 Buffer 中写入到 Channel 事件驱动消息通知观察者模式
Tomcat那些事儿 https://mp.weixin.qq.com/s?__biz=MzI3MTEwODc5Ng==&mid=2650860016&idx=2&sn=549 ...
- javascript基础程序(算出一个数的平方值、算出一个数的阶乘、输出!- !- !- !- !- -! -! -! -! -! 、函数三个数中的最大数)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- ytu 1061: 从三个数中找出最大的数(水题,模板函数练习 + 宏定义练习)
1061: 从三个数中找出最大的数 Time Limit: 1 Sec Memory Limit: 128 MBSubmit: 154 Solved: 124[Submit][Status][We ...
- 求三数中Max和猜拳游戏
方法一: Console.WriteLine("请输入三个数字:"); int a = int.Parse(Console.ReadLine()); int b = int.Par ...
随机推荐
- 散列表 (Hash table,也叫哈希表)
散列表是根据关键字(Key value)而直接访问在内存存储位置的数据结构.也就是说,它通过把键值通过一个函数的计算,映射到表中一个位置来访问记录,这加快了查找速度.这个映射函数称做散列函数,存放记录 ...
- windows下配置环境变量时,在cmd窗口执行配置的命令时无效的原因
一个原因肯定就是配置错误,这个就要自己仔细去检查了,如果确信配置正确,可能是你的cmd窗口在环境变量配置之前就打开的,在配置好环境变量之后,在cmd窗口执行命令是看不到效果的,可以关掉cmd窗口再重新 ...
- 【HDOJ】4347 The Closest M Points
居然是KD解. /* 4347 */ #include <iostream> #include <sstream> #include <string> #inclu ...
- 【HDOJ】3601 Coach Yehr’s punishment
RMQ+dp+二分.最好还是离散化一下再处理,通过dp求得每个位置的上一次出现的位置pre数组,从而求得不重复的长度len.然后RMQ可以预处理区间的最大值,pre是个单调非递减数列.每次查询时,二分 ...
- word文档左侧显示目录
word2007 选择word的视图,然后选择文档结构图
- Descending Order
Descending Order Description: Your task is to make a function that can take any non-negative integer ...
- poj1151Atlantis(离散化+扫描线)
http://poj.org/problem?id=1151 http://www.cnblogs.com/kane0526/archive/2013/02/26/2934214.html这篇博客写的 ...
- poj 3393 Lucky and Good Months by Gregorian Calendar(模拟)
题目:http://poj.org/problem?id=3393一道题目挺长的模拟题,参考了网上大神的题解. #include <iostream> #include <cstdi ...
- SSH信任
配置SSH的目的就是使得两个节点的主机之间的相同用户可以无障碍的通信,SSH主要包括两条命令,即scp和ssh.当用户在一个节点上安装和配置RAC软件时,SSH将通过scp命令,以对等用户的身份,将软 ...
- MVC三个IOC注入点之Ninject使用示例
群里一个技术大牛说MVC有三个注入点,但我只会一个DefaultControllerFactory. 在群友的帮助下,我大致了解了下: IControllerFactory=>IDependen ...