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

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

Netty 中的 pipeline 和 channelHandler 经过责任链设计模式来组织代码逻辑,而且可以支持逻辑的动态添加和删除 ,Netty 可以支持各种协议的扩展,好比 HTTP,Websocket,Redis,靠的就是 pipeline 和 channelHandler,下面,咱们就来一块儿学习一下这部份内容。设计模式

pipeline 与 channelHandler 的构成

不管是从服务端来看,仍是客户端来看,在 Netty 整个框架里面,一条链接对应着一个 Channel,这条 Channel 全部的处理逻辑都在一个叫作 ChannelPipeline 的对象里面,ChannelPipeline 是一个双向链表结构,他和 Channel 之间是一对一的关系。api

ChannelPipeline 里面每一个节点都是一个 ChannelHandlerContext 对象,这个对象可以拿到和 Channel 相关的全部的上下文信息,而后这个对象包着一个重要的对象,那就是逻辑处理器 ChannelHandler。promise

接下来,咱们再来看一下 ChannelHandler 有哪些分类。微信

channelHandler 的分类

能够看到 ChannelHandler 有两大子接口:框架

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

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

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

说了这么多的理论,其实仍是比较抽象的,下面咱们就用一个具体的 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 即时通信系统》,若想得到更多,更详细的内容,请用微信扫码订阅:

相关文章
相关标签/搜索