Netty详解(三):Pipeline介绍

Pipeline是一条Netty管道流水线,一条管道需要很多Handler处理器来处理业务。Netty的业务处理器流水线ChannelPipeline是基于责任链设计模式(Chain of Responsibility)来设计的,内部是一个双向链表结构,能够支持动态地添加和删除Handler业务处理器。

Channel通道

通道是Netty的核心概念之一,代表着网络连接,由它负责同对端进行网络通信,可以写入数据到对端,也可以从对端读取数据。Netty通道抽象类AbstractChannel的构造函数如下:

protected AbstractChannel(Channel parent) {
	this.parent = parent; //父通道
	id = newId();
	unsafe = newUnsafe(); //新建一个底层的 NIO 通道,完成实际的 IO 操作
	pipeline = newChannelPipeline(); //新建一条通道流水线
}

AbstractChannel内部有一个pipeline属性,表示处理器的流水线。Netty在对通道进行初始化的时候,将pipeline属性初始化为DefaultChannelPipeline的实例。以上代码表明,每个通道拥有一条ChannelPipeline处理器流水线。

EmbeddedChannel嵌入式通道

在Netty的实际开发中,底层通信传输的基础工作Netty已经替大家完成。实际上,更多的工作是设计和开发ChannelHandler业务处理器。处理器开发完成后,需要投入单元测试。一般单元测试的大致流程是:

  • 需要将Handler业务处理器加入到通道的Pipeline流水线中;
  • 接下来先后启动Netty服务器、客户端程序;
  • 相互发送消息,测试业务处理器的效果。

这些复杂的工序存在一个问题,如果每开发一个业务处理器,都进行服务器和客户端的重复启动,这整个的过程是非常的烦琐和浪费时间的。如何解决这种徒劳的、低效的重复工作呢?Netty提供了一个专用通道——名字叫EmbeddedChannel(嵌入式通道)。

EmbeddedChannel仅仅是模拟入站与出站的操作,底层不进行实际的传输,不需要启动Netty服务器和客户端。除了不进行传输之外,EmbeddedChannel的其他的事件机制和处理流程和真正的传输通道是一模一样的。因此,使用EmbeddedChannel,开发人员可以在单元测试用例中方便、快速地进行ChannelHandler业务处理器的单元测试。

名称说明
writeInbound(…)向通道写入入站数据,模拟真实通道收到数据的场景。也就是说,这些写入的数据会被流水线上的入站处理器所处理。
readInbound(…)从EmbeddedChannel中读取入站数据,返回经过流水线最后一个入站处理器处理完成之后的入站数据。如果没有数据,则返回 null。
writeOutbound(…)向通道写入出站数据,模拟真实通道发送数据。也就是说,这些写入的数据会被流水线上的出站处理器处理。
readOutbound(…)从EmbeddedChannel中读取出站数据,返回经过流水线最后一个出站处理器处理之后的出站数据。如果没有数据,则返回 null。
finish()结束EmbeddedChannel,它会调用通道的close方法。

Handler业务处理器

整个的IO处理操作环节的前后两个环节,包括从通道读数据包和由通道发送到对端,由Netty的底层负责完成,不需要用户程序负责。

用户程序主要涉及的Handler环节为:数据包解码、业务处理、目标数据编码、把数据包写到通道中。

在这里插入图片描述

ChannelInboundHandler入站处理器

ChannelInboundHandler的主要操作如下:

在这里插入图片描述

  1. channelRegistered

    当通道注册完成后,Netty会调用fireChannelRegistered方法,触发通道注册事件。而在通道流水线注册过的入站处理器Handler的channelRegistered回调方法,将会被调用到。

  2. channelActive

    当通道激活完成后,Netty会调用fireChannelActive方法,触发通道激活事件。而在通道流水线注册过的入站处理器的channelActive回调方法,会被调用到。

  3. channelRead

    当通道缓冲区可读,Netty会调用fireChannelRead,触发通道可读事件。而在通道流水线注册过的入站处理器的channelRead回调方法,会被调用到,以便完成入站数据的读取和处理。

  4. channelReadComplete

    当通道缓冲区读完,Netty会调用fireChannelReadComplete,触发通道缓冲区读完事件。而在通道流水线注册过的入站处理器的channelReadComplete回调方法,会被调用到。

  5. channelInactive

    当连接被断开或者不可用时,Netty会调用fireChannelInactive,触发连接不可用事件。而在通道流水线注册过的入站处理器的channelInactive回调方法,会被调用到。

  6. exceptionCaught

    当通道处理过程发生异常时,Netty会调用fireExceptionCaught,触发异常捕获事件。而在通道流水线注册过的入站处理器的exceptionCaught方法,会被调用到。注意,这个方法是在通道处理器中ChannelHandler定义的方法,入站处理器、出站处理器接口都继承到了该方法。

ChannelOutboundHandler出站处理器

当业务处理完成后,需要发送数据时,通过一系列ChannelOutboundHandler出站处理器处理,完成底层Socket操作。ChannelOutboundHandler接口操作如下:

在这里插入图片描述

  1. bind

    监听地址(IP+端口)绑定:完成底层Java IO通道的IP地址绑定。如果使用TCP传输协议,这个方法用于服务器端。

  2. connect

    连接服务端:完成底层Java IO通道的服务器端的连接操作。如果使用TCP传输协议,这个方法用于客户端。

  3. write

    写数据到底层:完成Netty通道向底层Java IO 通道的数据写入操作。此方法仅仅是触发一下操作而已,并不是完成实际的数据写入操作。

  4. flush

    将底层缓存区的数据腾空,立即写出到对端。

  5. read

    从底层读数据:完成Netty通道从Java IO通道的数据读取。

  6. disConnect

    断开服务器连接:断开底层Java IO通道的socket连接。如果使用TCP传输协议,此方法主要用于客户端。

  7. close

    主动关闭通道:关闭底层的通道,例如服务器端的新连接监听通道。

Pipeline通道流水线

前面讲到,一条Netty通道需要很多的Handler业务处理器来处理业务。每条通道内部都有一条流水线(Pipeline)将Handler装配起来。Netty的业务处理器流水线ChannelPipeline是基于责任链设计模式(Chain of Responsibility)来设计的,内部是一个双向链表结构,能够支持动态地添加和删除Handler业务处理器。

在这里插入图片描述

每个节点是一个ChannelHandlerContext,每个context包含一个Handler。在Netty的设计中Handler是无状态的,不保存和Channel有关的信息。Handler的目标,是将自己的处理逻辑做得很通用,可以给不同的Channel使用。与Handler有不同的是,Pipeline是有状态的,保存了Channel的关系。于是乎,Handler和Pipeline之间,需要一个中间角色,把他们联系起来。这个中间角色是谁呢?这就是——ChannelHandlerContext 。

不管我们定义的是哪种类型的Handler业务处理器, 最终它们都是以双向链表的方式保存在流水线中。这里流水线的节点类型,并不是前面的Handler业务处理器基类,而是其包装类型:ChannelHandlerContext通道处理器上下文类。当Handler业务处理器被添加到流水线中时,会为其专门创建一个通道处理器上下文ChannelHandlerContext实例,主要封装了ChannelHandler通道处理器和ChannelPipeline通道流水线之间的关联关系。

所以流水线ChannelPipeline中的双向链接,实质是一个由ChannelHandlerContext组成的双向链表。而无状态的Handler,作为Context的成员,关联在ChannelHandlerContext中。

ChannelHandlerContext中包含了有许多方法,主要可以分为两类:第一类是获取上下文所关联的Netty组件实例,如所关联的通道、所关联的流水线、上下文内部Handler业务处理器实例等;第二类是入站和出站处理方法。

如果通过Channel或ChannelPipeline的实例来调用这些出站和入站处理方法,它们就会在整条流水线中传播。然而,如果是通过ChannelHandlerContext上下文调用出站和入站处理方法,就只会从当前的节点开始,往同类型的下一站处理器传播,而不是在整条流水线从头至尾进行完整的传播。

比如context.channel.writeAndFlush( )和context.writeAndFlush( )所传播的路径不同

HeadContext和TailContext

实际上,通道流水线在没有加入任何处理器之前,装配了两个默认的处理器上下文:一个头部上下文叫做HeadContext、一个尾部上下文叫做TailContext。pipeline的创建、初始化除了保存一些必要的属性外,核心就在于创建了HeadContext头节点和TailContext尾节点。

每个pipeline中双向链表结构,从一开始就存在了HeadContext和TailContext两个节点,后面添加的处理器上下文节点,都在添加在HeadContext实例和TailContext实例之间。在添加了一些必要的解码器、业务处理器、编码器之后,一条流水线的结构大致如下图所示:

在这里插入图片描述

  1. TailContext

    流水线尾部的TailContext不仅仅是一个上下文类,而且是一个入站处理器类,实现了所有入站处理回调方法,这些回调实现的主要工作,基本上都是收尾处理的,如释放缓冲区对象、完成异常处理等。

  2. HeadContext

    流水线头部的HeadContext则比TailContext复杂得多,既是一个出站处理器、也是一个入站处理器,还保存了一个unsafe(完成实际通道传输的类)实例,也就是HeadContext还需要负责最终的通道传输工作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

雨路行人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值