Netty 源码解析: Netty 的 ChannelPipeline
ChannelPipeline和Inbound、Outbound

比如客户端在发起请求的时候,需要 1️⃣connect 到服务器,然后 2️⃣write 数据传到服务器,再然后 3️⃣read 服务器返回的数据,前面的 connect 和 write 就是 out 事件,后面的 read 就是 in 事件。
. pipeline.addLast(new StringDecoder()); . pipeline.addLast(new StringEncoder()); . pipeline.addLast(new BizHandler());
. pipeline.addLast(new StringDecoder());
. pipeline.addLast(new StringEncoder());
. pipeline.addLast(new BizHandler());
- 客户端连接进来的时候,读取(read)客户端请求数据的操作是 Inbound 的,e 操作是 Outbound 的,此时使用的是 2。 
- 处理完数据后,返回给客户端数据的 write 操作是 Outbound 的,此时使用的是 2。 
如果我们在上面的基础上,加上下面的第四行,这是一个 OutboundHandler:. pipeline.addLast(new OutboundHandlerA());那么执行顺序是不是就是 1 -> 3 -> 2 -> 4 呢?答案是:不是的。对于 Inbound 操作,按照添加顺序执行每个 Inbound 类型的 handler;而对于 Outbound 操作,是反着来的,从后往前,顺次执行 Outbound 类型的 handler。所以,上面的顺序应该是先 1 后 3,它们是 Inbound 的,然后是 4,最后才是 2,它们两个是 Outbound 的。说实话,这种组织方式对新手应该很是头疼。那我们在开发的时候怎么写呢?其实也很简单,从最外层开始写,一步步写到业务处理层,把 Inbound 和 Outbound 混写在一起。比如 encode 和 decode 是属于最外层的处理逻辑,先写它们。假设 decode 以后是字符串,那再进来一层应该可以写进来和出去的日志。再进来一层可以写 字符串 <=> 对象 的相互转换。然后就应该写业务层了。

protected AbstractChannel(Channel parent) {
    this.parent = parent;    // 给每个 channel 分配一个唯一 id
    id = newId();    // 每个 channel 内部需要一个 Unsafe 的实例
    unsafe = newUnsafe();    // 每个 channel 内部都会创建一个 pipeline
    pipeline = newChannelPipeline();
}    
Unsafe 类的构造方法是 private 的,但是它提供了 getUnsafe() 这个静态方法:Unsafe unsafe = Unsafe.getUnsafe();大家可以试一下,上面这行代码编译没有问题,但是执行的时候会抛java.lang.SecurityException异常,因为它就不是给我们的代码用的。但是如果你就是想获取 Unsafe 的实例,可以通过下面这个代码获取到:Field f = Unsafe.class.getDeclaredField("theUnsafe");f.setAccessible(true);Unsafe unsafe = (Unsafe) f.get(null);
不过,对于我们源码分析来说,我们还是会有很多时候需要分析 Unsafe 中的源码的
protected DefaultChannelPipeline newChannelPipeline() { return new DefaultChannelPipeline(this);}
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;
}

注意,在不同的版本中,源码也略有差异,head 不一定是 in + out,大家知道这点就好了。从上面的 head 和 tail 我们也可以看到,其实 pipeline 中的每个元素ChannelHandlerContext 的实例,而不是 ChannelHandler 的实例,context 包装了一下 handler,但是,后面我们都会用 handler 来描述一个 pipeline 上的节点,而不是使用 context,希望读者知道这一点。

我们说过 childHandler 中指定的 handler 不是给 NioServerSocketChannel 使用的,是给 NioSocketChannel 使用的,所以这里我们不看它。
final ChannelFuture initAndRegister() {
    Channel channel = null;
    try {         // 1. 构造 channel 实例,同时会构造
            pipeline 实例, // 现在 pipeline 中有 head 和 tail 两个 handler 了
             channel = channelFactory.newChannel();         // 2. 看这里
             init(channel);
        }
    catch (Throwable t) {    ......}
}
@Override void init(Channel channel) throws Exception {
    ......
    // 拿到刚刚创建的 channel 内部的 pipeline 实例
    ChannelPipeline p = channel.pipeline();
    ...
    // 开始往 pipeline 中添加一个 handler,这个 handler 是 ChannelInitializer 的实例
    p.addLast(new ChannelInitializer<Channel>() {
        // 我们以后会看到,下面这个 initChannel 方法何时会被调用
        @Override
        public void initChannel(final Channel ch) throws Exception {
            final ChannelPipeline pipeline = ch.pipeline();
            // 这个方法返回我们最开始指定的 LoggingHandler 实例
            ChannelHandler handler = config.handler();
            if (handler != null) {
                // 添加 LoggingHandler
                pipeline.addLast(handler);
                }
            // 先不用管这里的 eventLoop
            ch.eventLoop().execute(new Runnable() {
                @Override
                public void run() {
                    // 添加一个 handler 到 pipeline 中:
                    ServerBootstrapAcceptor
                    // 从名字可以看到,这个 handler 的目的是用于接收客户端请求
                    pipeline.addLast(new ServerBootstrapAcceptor(
                            ch, currentChildGroup,
                            currentChildHandler,
                            currentChildOptions,
                            currentChildAttrs));
                    }
                });
            }
        });
    }

void init(Channel channel) throws Exception {
     ChannelPipeline p = channel.pipeline();
     p.addLast(config.handler());
     ...
}    



援引原文链接:https://juejin.im/post/5eacc88f6fb9a0437f73a713
Netty 源码解析: Netty 的 ChannelPipeline的更多相关文章
- Netty 源码解析(四): Netty 的 ChannelPipeline
		今天是猿灯塔“365篇原创计划”第四篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源码解析(二): Netty 的 Channel Netty ... 
- Netty源码解析—客户端启动
		Netty源码解析-客户端启动 Bootstrap示例 public final class EchoClient { static final boolean SSL = System.getPro ... 
- Netty源码解析---服务端启动
		Netty源码解析---服务端启动 一个简单的服务端代码: public class SimpleServer { public static void main(String[] args) { N ... 
- Netty 源码解析(三): Netty 的 Future 和 Promise
		今天是猿灯塔“365篇原创计划”第三篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源码解析(二): Netty 的 Channel 当前:Ne ... 
- Netty 源码解析(九): connect 过程和 bind 过程分析
		原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 今天是猿灯塔“365篇原创计划”第九篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源 ... 
- Netty 源码解析(八): 回到 Channel 的 register 操作
		原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 今天是猿灯塔“365篇原创计划”第八篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源 ... 
- Netty 源码解析(七): NioEventLoop 工作流程
		原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 今天是猿灯塔“365篇原创计划”第七篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源 ... 
- Netty 源码解析(六): Channel 的 register 操作
		原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 今天是猿灯塔“365篇原创计划”第六篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一 ):开始 Netty ... 
- Netty 源码解析(五): Netty 的线程池分析
		今天是猿灯塔“365篇原创计划”第五篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源码解析(二): Netty 的 Channel Netty ... 
随机推荐
- JS 把数字转换成字母
			JS 把数字转换成字母 2013-03-12 22:28:11 分类: JavaScript String.fromCharCode(addcount+65) 位运算alert(1<<0 ... 
- Poj1328  用雷达覆盖所有的岛屿
			(此配图来自http://blog.csdn.net/zhengnanlee/article/details/9613161) 图中ABCD为海岛的位置.题目中会给出几个海岛的坐标位置,雷达覆盖半径d ... 
- Angular SPA基于Ocelot API网关与IdentityServer4的身份认证与授权(四)
			在上一讲中,我们已经完成了一个完整的案例,在这个案例中,我们可以通过Angular单页面应用(SPA)进行登录,然后通过后端的Ocelot API网关整合IdentityServer4完成身份认证.在 ... 
- GO 使用Webhook 实现github 自动化部署
			通常大家开发大部分是本地git push 提交,服务器上git pull 手动更新.git 可以使用webhook实现自动部署.webhook是仓库平台的一个钩子事件,通过hook 钩子监听代码,回调 ... 
- web项目——javax.servlet.ServletException: Circular view path [registerForm]
			报错: 控制台输出: 三月 21, 2019 10:12:32 上午 org.springframework.web.servlet.PageNotFound noHandlerFound 警告: N ... 
- PHP常量的定义和用法
			我们通常把不经常变的值定义成常量,常量一般用全部大写来表示,前面不加美元符号,也可减少团队开发的出错.那么define和const有什么区别呢? 1.const是一个语言结构:而define是一个函数 ... 
- [Python基础]010.os模块(2)
			os模块(2) 介绍 os 常量 路径 判断路径属性 路径变换 文件属性 相同文件 介绍 - os.path模块,主要处理路径操作,包含了各种处理文件和文件名的方法. os.path 常量 os.pa ... 
- 创建多线程的方式&Thread类的常用方法
			创建多线程的第一种方式:继承java.lang.Thread类 注意:1.一个线程只能执行一次start() 2.不能通过Thread实现类对象的 run()去启动一个线程 3.增加加一个线程,需要新 ... 
- FHQ-Treap学习笔记
			平衡树与FHQ-Treap 平衡树(即平衡二叉搜索树),是通过一系列玄学操作让二叉搜索树(BST)处于较平衡的状态,防止在某些数据下退化(BST在插入值单调时,树形不平衡,单次会退化成 \(\math ... 
- java的Interger自动包装带来的问题
			1 首先看一下以下代码: Integer b=7; Integer c=7; Integer r=234; Integer d=234; System.out.println(b==c); Syste ... 
