文档章节

《netty入门与实战》笔记-04:pipeline 与 channelHandler

Funcy1122
 Funcy1122
发布于 2018/10/21 18:58
字数 1778
阅读 109
收藏 4

这一小节,我们将会学习 Netty 里面一大核心组件: Pipeline 与 ChannelHandler

Netty 中的 pipeline 和 channelHandler 通过责任链设计模式来组织代码逻辑,并且能够支持逻辑的动态添加和删除 ,Netty 能够支持各类协议的扩展,比如 HTTP,Websocket,Redis,靠的就是 pipeline 和 channelHandler,下面,我们就来一起学习一下这部分内容。

pipeline 与 channelHandler 的构成

无论是从服务端来看,还是客户端来看,在 Netty 整个框架里面,一条连接对应着一个 Channel,这条 Channel 所有的处理逻辑都在一个叫做 ChannelPipeline 的对象里面,ChannelPipeline 是一个双向链表结构,他和 Channel 之间是一对一的关系。

ChannelPipeline 里面每个节点都是一个 ChannelHandlerContext 对象,这个对象能够拿到和 Channel 相关的所有的上下文信息,然后这个对象包着一个重要的对象,那就是逻辑处理器 ChannelHandler。

接下来,我们再来看一下 ChannelHandler 有哪些分类。

channelHandler 的分类

可以看到 ChannelHandler 有两大子接口:

第一个子接口是 ChannelInboundHandler,从字面意思也可以猜到,他是处理读数据的逻辑,比如,我们在一端读到一段数据,首先要解析这段数据,然后对这些数据做一系列逻辑处理,最终把响应写到对端, 在开始组装响应之前的所有的逻辑,都可以放置在 ChannelInboundHandler 里处理,它的一个最重要的方法就是 channelRead()。读者可以将 ChannelInboundHandler 的逻辑处理过程与 TCP 的七层协议的解析联系起来,收到的数据一层层从物理层上升到我们的应用层。

第二个子接口 ChannelOutBoundHandler 是处理写数据的逻辑,它是定义我们一端在组装完响应之后,把数据写到对端的逻辑,比如,我们封装好一个 response 对象,接下来我们有可能对这个 response 做一些其他的特殊逻辑,然后,再编码成 ByteBuf,最终写到对端,它里面最核心的一个方法就是 write(),读者可以将 ChannelOutBoundHandler 的逻辑处理过程与 TCP 的七层协议的封装过程联系起来,我们在应用层组装响应之后,通过层层协议的封装,直到最底层的物理层。

这两个子接口分别有对应的默认实现,ChannelInboundHandlerAdapter,和 ChanneloutBoundHandlerAdapter,它们分别实现了两大接口的所有功能,默认情况下会把读写事件传播到下一个 handler

说了这么多的理论,其实还是比较抽象的,下面我们就用一个具体的 demo 来学习一下这两大 handler 的事件传播机制。

ChannelInboundHandler 的事件传播

关于 ChannelInboundHandler ,我们拿 channelRead() 为例子,来体验一下 inbound 事件的传播。

我们在服务端的 pipeline 添加三个 ChannelInboundHandler

NettyServer.java

serverBootstrap
        .childHandler(new ChannelInitializer<NioSocketChannel>() {
            protected void initChannel(NioSocketChannel ch) {
                ch.pipeline().addLast(new InBoundHandlerA());
                ch.pipeline().addLast(new InBoundHandlerB());
                ch.pipeline().addLast(new InBoundHandlerC());
            }
        });

每个 inBoundHandler 都继承自 ChannelInboundHandlerAdapter,然后实现了 channelRead() 方法

public class InBoundHandlerA extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("InBoundHandlerA: " + msg);
        super.channelRead(ctx, msg);
    }
}

public class InBoundHandlerB extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("InBoundHandlerB: " + msg);
        super.channelRead(ctx, msg);
    }
}

public class InBoundHandlerC extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("InBoundHandlerC: " + msg);
        super.channelRead(ctx, msg);
    }
}

channelRead() 方法里面,我们打印当前 handler 的信息,然后调用父类的 channelRead() 方法,而这里父类的 channelRead() 方法会自动调用到下一个 inBoundHandlerchannelRead() 方法,并且会把当前 inBoundHandler 里处理完毕的对象传递到下一个 inBoundHandler,我们例子中传递的对象都是同一个 msg。

我们通过 addLast() 方法来为 pipeline 添加 inBoundHandler,当然,除了这个方法还有其他的方法,感兴趣的同学可以自行浏览一下 pipeline 的 api ,这里我们添加的顺序为 A -> B -> C,最后,我们来看一下控制台的输出

可以看到,inBoundHandler 的执行顺序与我们通过 addLast() 方法 添加的顺序保持一致,接下来,我们再来看一下 outBoundHandler 的事件传播。

ChannelOutboundHandler 的事件传播

关于 ChanneloutBoundHandler ,我们拿 write() 为例子,来体验一下 outbound 事件的传播。

我们继续在服务端的 pipeline 添加三个 ChanneloutBoundHandler

serverBootstrap
        .childHandler(new ChannelInitializer<NioSocketChannel>() {
            protected void initChannel(NioSocketChannel ch) {
                // inBound,处理读数据的逻辑链
                ch.pipeline().addLast(new InBoundHandlerA());
                ch.pipeline().addLast(new InBoundHandlerB());
                ch.pipeline().addLast(new InBoundHandlerC());
                
                // outBound,处理写数据的逻辑链
                ch.pipeline().addLast(new OutBoundHandlerA());
                ch.pipeline().addLast(new OutBoundHandlerB());
                ch.pipeline().addLast(new OutBoundHandlerC());
            }
        });

每个 outBoundHandler 都继承自 ChanneloutBoundHandlerAdapter,然后实现了 write() 方法

public class OutBoundHandlerA extends ChannelOutboundHandlerAdapter {
    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        System.out.println("OutBoundHandlerA: " + msg);
        super.write(ctx, msg, promise);
    }
}

public class OutBoundHandlerB extends ChannelOutboundHandlerAdapter {
    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        System.out.println("OutBoundHandlerB: " + msg);
        super.write(ctx, msg, promise);
    }
}

public class OutBoundHandlerC extends ChannelOutboundHandlerAdapter {
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        System.out.println("OutBoundHandlerC: " + msg);
        super.write(ctx, msg, promise);
    }
}

在 write() 方法里面,我们打印当前 handler 的信息,然后调用父类的 write() 方法,而这里父类的 write() 方法会自动调用到下一个 outBoundHandlerwrite() 方法,并且会把当前 outBoundHandler 里处理完毕的对象传递到下一个 outBoundHand

我们通过 addLast() 方法 添加 outBoundHandler 的顺序为 A -> B -> C,最后,我们来看一下控制台的输出

可以看到,outBoundHandler 的执行顺序与我们添加的顺序相反,最后,我们再来看一下 pipeline 的结构和执行顺序。

pipeline 的结构

不管我们定义的是哪种类型的 handler, 最终它们都是以双向链表的方式连接,这里实际链表的节点是 ChannelHandlerContext,这里为了让结构清晰突出,可以直接把节点看作 ChannelHandlerContext

pipeline 的执行顺序

虽然两种类型的 handler 在一个双向链表里,但是这两类 handler 的分工是不一样的,inBoundHandler 的事件通常只会传播到下一个 inBoundHandleroutBoundHandler 的事件通常只会传播到下一个 outBoundHandler,两者相互不受干扰。

总结

  1. 通过我们前面编写客户端服务端处理逻辑,引出了 pipeline 和 channelHandler 的概念。
  2. channelHandler 分为 inBound 和 outBound 两种类型的接口,分别是处理数据读与数据写的逻辑,可与 tcp 协议栈联系起来。
  3. 两种类型的 handler 均有相应的默认实现,默认会把事件传递到下一个,这里的传递事件其实说白了就是把本 handler 的处理结果传递到下一个 handler 继续处理。
  4. inBoundHandler 的执行顺序与我们实际的添加顺序相同,而 outBoundHandler 则相反。

以上内容来源于掘金小册《Netty 入门与实战:仿写微信 IM 即时通讯系统》,若想获得更多,更详细的内容,请用微信扫码订阅:

本文转载自:https://juejin.im/book/5b4bc28bf265da0f60130116/section/5b4db06d5188251afc257383

Funcy1122

Funcy1122

粉丝 11
博文 168
码字总数 221857
作品 0
广州
后端工程师
私信 提问
《netty入门与实战》笔记-05:netty内置的channelHandler

Netty 内置了很多开箱即用的 ChannelHandler。下面,我们通过学习 Netty 内置的 ChannelHandler 来逐步构建我们的 pipeline。 ChannelInboundHandlerAdapter 与 ChannelOutboundHandlerAdap...

Funcy1122
2018/10/21
164
0
Netty 源码分析之 二 贯穿Netty 的大动脉 ── ChannelPipeline (一)

目录 源码之下无秘密 ── 做最好的 Netty 源码分析教程 Netty 源码分析之 番外篇 Java NIO 的前生今世 Java NIO 的前生今世 之一 简介 Java NIO 的前生今世 之二 NIO Channel 小结 Java NIO...

永顺
2017/11/29
0
0
Netty 初步使用

1、简介 Java1.4提供了NIO使开发者可以使用Java编写高性能的服务端程序,但使用原生的NIO API就像Linux C中网络编程一样,还是需要做IO处理、协议处理等低层次工作。所以,就像C服务端程序大...

鉴客
2011/01/11
3.9K
3
Netty实战入门详解——让你彻底记住什么是Netty(看不懂你来找我)

一、Netty 简介 Netty 是基于 Java NIO 的异步事件驱动的网络应用框架,使用 Netty 可以快速开发网络应用,Netty 提供了高层次的抽象来简化 TCP 和 UDP 服务器的编程,但是你仍然可以使用底层...

爱码小士
06/14
154
0
Netty-Pipeline源码解析(创建与ChannelHandler管理)

Netty的ChannelPipeline是ChannelHandler的容器,它负责ChannelHandler的管理和事件拦截与调度。 这里看下有个Pipeline的一些初始化工作: 代码分析 ChannelPipeline的创建工作,ChannelPipe...

Real_man
01/16
0
0

没有更多内容

加载失败,请刷新页面

加载更多

最好的重试是指数后退和抖动

1. 概述 在本教程中,我们将探讨如何使用两种不同的策略改进客户端重试:指数后退和抖动。 2. 重试 在分布式系统中,多个组件之间的网络通信随时可能发生故障。 客户端应用程序通过实现重试来...

liululee
21分钟前
3
0
聊一聊大厂内部的安全管理机制

工作了两个月了体会到了很多之前做外包小项目没有的东西,不得不说大厂的还是有自己一套的完善的体制,不会像B站那样泄露自己整个后台的源码这种事情发生。 电脑办公 比如说在使用电脑办公这...

gzc426
48分钟前
4
0
如何利用deeplearning4j中datavec对图像进行处理

NativeImageLoader Labelloader = new NativeImageLoader(112, 112, 3,new FlipImageTransform(-1)); 一、导读 众所周知图像是有红绿蓝三种颜色堆叠而成,利用deeplearning对图像处理,必须把...

冷血狂魔
50分钟前
8
0
1. Context - React跨组件访问数据的利器

《react-router-dom源码揭秘》系列 2. React-Router的基本使用 3. react-router-dom源码揭秘 - BrowserRouter Context提供了一种跨组件访问数据的方法。它无需在组件树间逐层传递属性,也可以...

前端老手
59分钟前
6
0
Docker入门实战--开篇,为什么要使用Docker

前面Thrift文章中,我曾经介绍过我为什么要用Thrift。Docker的使用却是不一样的。纯属没事找事,因为我现在一个人的团队,项目只要一个project目录足够了! 那我何苦要用Docker呢 各位且听听...

后天的奇点
今天
6
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部