0%

DotNetty完全教程(七)

DotNetty完全教程(七)

Excerpt

ChannelPipeline和ChannelHandleContext介绍ChannelPipeline是一系列ChannelHandler连接的实例链,这个实例链构成了应用程序逻辑处理的核心。下图反映了这种关联:ChannelHandlerContext提供了一个ChannelPipeline的上下文,用于ChannelHandler在Pipeline中的交互,这种交互十分的灵活,不仅…


版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

ChannelPipeline和ChannelHandleContext

介绍

ChannelPipeline是一系列ChannelHandler连接的实例链,这个实例链构成了应用程序逻辑处理的核心。下图反映了这种关联:

ChannelHandlerContext提供了一个ChannelPipeline的上下文,用于ChannelHandler在Pipeline中的交互,这种交互十分的灵活,不仅是信息可以交互,甚至可以改变其他Handler在Pipeline中的位置。

特性

  1. 每一个Channel都会被分配到一个ChannelPipeline,这种关联是永久性的。在Netty中是关联,在DotNetty中这种关联被进一步的强绑定,变成了一个Channel中存在一个Pipeline。
  2. 对于Pipeline来说,入站口被当作Pipeline的头部,出站口被当作尾部。虽然我们看到有两条线,但是在Pipeline中其实是线性的,在事件传播的时候,如果Pipeline发现这个事件的属性(入站出站)跟下一个Handler不匹配,就会跳过这个Handler,前进到下一个。
  3. 一个Handler可以既作为入站处理器也作为出站处理器。
  4. 修改Pipeline

  5. 为了保证ChannelHandler处理事件的高效性,在Handler中不能有阻塞代码,但是如果遇到了一些阻塞API,就需要用到DefaultEventExecutorGroup,其功能是把这个事件的处理从原先的EventLoop中移除,送到一个专门的执行事件处理器中进行处理,从而不阻塞Pipeline。

ChanelPipeline的事件


我们可以看到fire方法都是调用下一个Handler中的方法,我们可以在合适的时机调用下一个Handler中的方法以实现数据的流动。

这里我们注意一下,Write方法并不会将消息写入Socket中,而是写入消息队列中,等待Flush将数据冲刷下去。

Context的API支持


Pipeline和Context

我们可以发现,Pipeline上也有fire–的方法,Context也有类似的方法,他们的差别在于,Pipeline或者Channel上的这些方法引发的事件流将从Pipeline的头部开始移动,而Context上的方法会让事件从当前Handler开始移动,所以为了更短的事件流,我们应该尽可能的使用Context的方法。

使用ChannelHandlerContext

  1. 获取当前Channel

    1
    2
    IChannelHandlerContext ctx = ...;
    IChannel channel = ctx.Channel
  2. 获取当前pipeline

    1
    2
    3
    4
    5
    6
    7
    // 注意一下在Netty中可以直接通过context获取pipeline,在DotNetty中需要从Channel中获取
    // Netty
    IChannelHandlerContext ctx = ...;
    IChannel channel = ctx.pipeline
    // DotNetty
    IChannel channel = ctx.Channel;
    IChannelPipeline pipeline = channel.Pipeline;
  3. 写入pipeline让事件从尾端开始移动

    1
    2
    3
    4
    IChannel channel = ctx.Channel;
    IChannelPipeline pipeline = channel.Pipeline;
    channel.WriteAndFlushAsync("Hello World!");
    pipeline.WriteAndFlushAsync("Hello World!");

注意,Write是出站事件,他的流动方向是从末尾到头部,这个一定要注意。在pipeline或者channel中写入事件,都是从最末尾开始流动,在Context中写入是从当前Handler中开始移动,这个我们已经在很多地方都说明了这样的不同。

应用

  1. 协议切换
    因为我们可以通过Context获取Pipeline的引用,获取了pipeline之后又可以动态的加载和删除Handler,利用这个特性我们可以实现协议的切换,
  2. 随时随地使用Context
    这里我们补充一个知识,Context和Handler的关系是一对一的,而不是一个Context对应多个Handler,这就让我们可以缓存下Context的引用,在任何时候进行使用,这里的任何时候可以是不同的线程。举个例子就是我们之前写的回声程序是在收到信息之后发送,但是复杂一点我们需要在按下按钮的时候发送一条数据,这时候我们可以在连接之后缓存Context的引用,在按下按钮的时候使用Ctx.Write();方法来发送一条数据。

线程安全

在Netty中,如果想要将一个Handler用于多个Pipeline中,需要标注Shared,同时需要保证线程安全,因为这里可能有多线程的重入问题。

异常处理

  1. 入站异常无论在何时引发,都会顺着Pipeline继续向下流动,如果最后的Handler没有处理,则会被标记为未处理。所以为了处理所有的入站异常,我们可以在pipeline的尾端通过复写ExceptionCaught来处理所有pipeline上的异常。
  2. 在出站Handler中获取异常在Netty中需要使用ChannelFuture以及ChannelPromise这里先不做叙述