过滤器 / IoFilter

过滤器(IoFilter)是Mina.NET的核心组件之一,在程序中扮演着重要的角色,它能够过滤和处理IoService与IoHandler之间所有的I/O事件。为了方便快速开发网络程序,Mina.NET提供了许多现有的过滤器实现,如:
  • LoggingFilter,记录事件日志。
  • ProtocolCodecFilter,对IoBuffer和消息对象进行相互转换。
  • SSLFilter,SSL - TLS 支持。
  • ...

接下来,我们将了解如何实现一个IoFilter,以及关于IoFilter的方方面面。

现有的过滤器

下表列出了Mina.NET已经实现了的过滤器。

过滤器 类型 描述
Blacklist BlacklistFilter 通过黑名单阻止远程连接。
Buffered Write BufferedWriteFilter 缓冲写入请求。
ConnectionThrottle ConnectionThrottleFilter 控制连接频率,当客户端连接间隔小于指定时间时,阻止连接。
ErrorGenerating ErrorGeneratingFilter
Executor ExecutorFilter 建立一个多线程环境,允许并行处理不同会话的事件。
FileRegionWrite FileRegionWriteFilter
KeepAlive KeepAliveFilter
Logging LoggingFilter 记录事件日志,如MessageReceived、MessageSent、SessionOpened ...
Noop NoopFilter
Profiler ProfilerTimerFilter 对事件进行性能分析。
ProtocolCodec ProtocolCodecFilter 对消息进行编解码。
Reference counting ReferenceCountingFilter 对自身进行引用计数。
SessionAttributeInitializing SessionAttributeInitializingFilter
StreamWrite StreamWriteFilter
SSL SslFilter SSL支持。

重写需要的事件

除了实现IoFilter接口,也可以继承IoFilterAdapter来实现过滤器,这样,只需要重写想要处理的事件即可,其他事件将默认直接传递给下一个过滤器:

class MyFilter : IoFilterAdapter
{
  public override void SessionOpened(INextFilter nextFilter, IoSession session)
  {
    // Some logic here...
    nextFilter.SessionOpened(session);
    // Some other logic here..
  }
}

转换写入请求

如果需要对由IoSession.Write()写入的对象进行转换,需要特别注意。假设调用IoSession.Write()后写入了一个HighLevelMessage类型的对象,我们的过滤器中要将其转换为LowLevelMessage类型的对象,我们要重写FilterWrite()方法,添加合适的转换代码;这仅仅是一部分,我们还需要考虑MessageSent事件,因为当前过滤器之后的IoHandler或其他过滤器在MessageSent()方法中希望得到的是转换前的写入请求,它们写入的是一个HighLevelMessage对象,并不知道也不希望得到一个LowLevelMessage对象。因此,对写入请求进行转换时,需要同时重写FilterWrite()MessageSent()两个方法。

此外,即使转换前后的两个对象是同一类型,也需要采用上一段中描述的方式,以确保IoHandler中的MessageSent()方法能够接受到与IoSession.Write()方法传入参数完全一致的对象。

假如我们正在实现一个将字符串转换为字符数组的过滤器,重写的FilterWrite()方法可能如下:

public override void FilterWrite(INextFilter nextFilter, IoSession session, IWriteRequest writeRequest)
{
  nextFilter.FilterWrite(session, new MyWrapRequest(writeRequest, ((string)writeRequest.Message).ToCharArray()));
}

class MyWrapRequest : WriteRequestWrapper
{
  private readonly char[] _chars;

  public MyWrapRequest(IWriteRequest writeRequest, char[] chars)
    : base(writeRequest)
  {
    _chars = chars;
  }

  public override Object Message
  {
    get { return _chars; }
  }
}

接下来,在MessageSent()需要恢复原先的写入请求:

public override void MessageSent(INextFilter nextFilter, IoSession session, IWriteRequest writeRequest)
{
  MyWrapRequest wrapRequest = writeRequest as MyWrapRequest;
  if (wrapRequest != null)
    nextFilter.MessageSent(session, wrapRequest.InnerRequest);
  else
    nextFilter.MessageSent(session, writeRequest);
}

小心处理空的IoBuffer

在Mina.NET内部,空IoBuffer有时间被用于作为一种特殊的标记,这可能会导致一些诸如IndexOutOfRangeException的异常,接下来将说明如何避免出现这种异常。

ProtocolCodecFilter使用了一个空IoBuffer(即buf.HasRemaining == false)来标记消息编码的结束。如果我们的过滤器放在ProtocolCodecFilter之前,那么需要将这个空IoBuffer转发给下一个过滤器。

public override void MessageSent(INextFilter nextFilter, IoSession session, IWriteRequest writeRequest)
{
  IoBuffer buf = writeRequest.Message as IoBuffer;
  if (buf != null && !buf.HasRemaining)
  {
    nextFilter.MessageSent(session, writeRequest);
    return;
  }
}

public override void FilterWrite(INextFilter nextFilter, IoSession session, IWriteRequest writeRequest)
{
  IoBuffer buf = writeRequest.Message as IoBuffer;
  if (buf != null && !buf.HasRemaining)
  {
    nextFilter.FilterWrite(session, writeRequest);
    return;
  }
}

以下是处理空IoBuffer的几种情况:
  • 如果过滤器能够正常处理空IoBuffer,不会发生异常,那么不需要任何特殊处理代码。
  • 如果过滤器放在ProtocolCodecFilter之后,那么不需要考虑空IoBuffer。
  • 否则,则需要以上类似的处理代码。

处理空IoBuffer时,并不一定要与以上完全一致的代码,我们可以在任何需要的位置判断IoBuffer是否为空。

Last edited Apr 15, 2014 at 6:16 AM by longshine, version 1