现在的位置: 首页 > 综合 > 正文

深入理解Apache Mina (2)—- 与IoFilter相关的几个类

2018年03月30日 ⁄ 综合 ⁄ 共 10401字 ⁄ 字号 评论关闭

从名字上看知道IoFilter应该是一个过滤器,不错,它确实是一个过滤器,它和Servlet中的过滤器类似,主要用于拦截和过滤I/O操作中的各种信息。在Mina的官方文档中已经提到了IoFilter的作用:
(1)记录事件的日志(这个在本文中关于LoggingFilter的讲述中会提到)
(2)测量系统性能
(3)信息验证
(4)过载控制
(5)信息的转换 (例如:编码和解码,这个会在关于ProtocolCodecFilter的讲述中会提到)
(6)和其他更多的信息

 

还是上一篇文档一样,先提出几个问题,然后沿着这几个问题的思路一个一个的对IoFilter进行讲解。
  (1)什么时候需要用到IoFilter,如果在自己的应用中不添加过滤器可以吗?
  (2)如果在IoService中添加多个过滤器可以吗?若可以,如何进行添加,这多个过滤  
       器是如何工作的?
  (3)Mina中提供了协议编、解码器,IoFilter也可以实现IO数据的编解码功能,在实际
       的使用中如何选择?
 
在开始对上面的问题进行讨论前,为了对IoFilter提供的方法有一个具体的了解,先对Mina自身提供的一个最简单的过滤器进行一些讲解----LoggingFilter(源码在附件中,配有中文翻译)。

 

首先还是看一下LoggingFilter中提供的几个方法。列举如下(方法中的参数就不再给出,完整方法的实现请参考附件中LoggingFilter的源码):
(1)sessionCreated()
(2)sessionOpened()
(3)sessionClosed()
(4)sessionIdle()
(5)exceptionCaught()
(6)messageReceived()
(7)messageSent()
(8)filterWrite()
(9)filterClose()
这几个方法都由相应会话(或者说是连接的状态,读、写、空闲、连接的开闭等)的状态的改变来触发的。当一个会话开启时,LoggingFilter捕获到会话开启的事件,会触发sessionCreated()方法,记录该会话开启的日志信息。同样当一个会话发送数据时,Logging捕获到会话发送消息的事件会记录消息发送的日志信息。这里只是给出messageReceived()的完成方法的实现,其他方法的完整实现请参考附件中LoggingFilter的源码。

 

 /**
  * 记录会话接收信息时的信息,然后将该信息传递到过滤器链中的下一个过滤器
  * */
 public void messageReceived(NextFilter nextFilter, IoSession session,
   Object message) {
  if (SessionLog.isInfoEnabled(session)) {
   SessionLog.info(session, "RECEIVED: " + message);
  }
  nextFilter.messageReceived(session, message);
 } 

LoggingFilter继承与IoFilterAdpater,IoFilterAdpater是IoFilter的一个实现类,该类只是提供了IoFilter方法的简单实现----将传递到各方法中的消息转发到下一个过滤器中。你可以根据自己的需求继承IoFilterAdpater,并重写相关的方法。LoggingFilter就是重写了上面提到的几个方法,用于记录当前的会话各种操作的日志信息。通过上面的例子,我们可以大体的了解了IoFilter的基本功能:根据当前会话状态,来捕获和处理当前会话中所传递的消息。

 

IoFilter的UML图如下:

 

从上面的类图我们可以清晰的看到IoFilter是一个接口,它有两个具体的实现类:

IoFilterAdpater:该类提供了IoFilter所有方法的方法体,但是没有任何逻辑处理,你可以根据你具体的需求继承该类,并重写相关的方法。IoFilterAdpater是在过滤器中使用的较多的一个类。

ReferenceCountingIoFilter:该类封装IoFilter的实例,它使用监视使用该IoFilter的对象的数量,当没有任何对象使用该IoFilter时,该类会销毁该IoFilter。

IoFilterAdpater有三个子类,它们的作用分别如下:
LoggingFilter:日志工具,该类处理记录IoFilter每个状态触发时的日志信息外不对数据做任何处理。它实现了IoFilter接口的所有方法。你可以通过阅读该类的源码学习如何实现你自己的IoFilter。

ExcuterFilter:这个Mina自身提供的一个线程池,在Mina中你可以使用这个类配置你自己的线程池,由于创建和销毁一个线程,需要耗费很多资源,特别是在高性能的程序中这点尤其重要,因此在你的程序中配置一个线程池是很重要的。它有助于你提高你的应用程序的性能。关于配置Mina的线程池在后续的文档中会给出详细的配置方法。

ProtocolFilter:该类是Mina提供的一个协议编解码器,在socket通信中最重要的就是协议的编码和解码工作,Mina提供了几个默认的编解码器的实现,在下面的例子中使用了ObjectSerializationCodecFactory,这是Mina提供的一个Java对象的序列化和反序列化方法。使用这个编解码器,你可以在你的Java客户端和服务器之间传递任何类型的Java对象。但是对于不同的平台之间的数据传递需要自己定义编解码器,关于这点的介绍会在后续的文档中给出。

 

为了更加清楚的理解这个过滤器的作用我们先来看一个简单的例子,这个例子的功能就是服务器在客户端连接到服务器时创建一个会话,然后向客户端发送一个字符串(完整的源码在附件中,这里只给出程序的简要内容):

Java代码 复制代码
  1. <SPAN style="FONT-SIZE: small">ServerMain:   
  2. public class ServerMain {   
  3.   
  4.  public static void main(String[] args) throws IOException {   
  5.   SocketAddress address = new InetSocketAddress("localhost"4321);   
  6.   IoAcceptor acceptor = new SocketAcceptor();   
  7.   IoServiceConfig config = acceptor.getDefaultConfig();   
  8.   
  9.   // 配置数据的编解码器   
  10.   config.getFilterChain().addLast("codec",   
  11.     new ProtocolCodecFilter(new ObjectSerializationCodecFactory()));   
  12.   
  13.   // 绑定服务器端口   
  14.   acceptor.bind(address, new ServerHandler());   
  15.   System.out.println("  服务器开始在 8000 端口监听 .......");   
  16.  }   
  17. }   
  18.     
  19. ServerHandler:   
  20. public class ServerHandler extends IoHandlerAdapter {   
  21.   
  22.  // 创建会话   
  23.  public void sessionOpened(IoSession session) throws Exception {   
  24.   System.out.println("  服务器创建了会话  ");   
  25.   session.write("  服务器创建会话时发送的信息 。");   
  26.  }   
  27.   
  28.  // 发送信息   
  29.  public void messageSent(IoSession session, Object message) throws Exception {   
  30.  }   
  31.   
  32.  // 接收信息   
  33.  public void messageReceived(IoSession session, Object message)   
  34.    throws Exception {   
  35.  }   
  36. }   
  37.     
  38. ClientMain:   
  39. public class ClientMain {   
  40.   
  41.  public static void main(String[] args) {   
  42.   
  43.   SocketAddress address = new InetSocketAddress("localhost"4321);   
  44.   IoConnector connector = new SocketConnector();   
  45.   IoServiceConfig config = connector.getDefaultConfig();   
  46.   
  47.   // 配置数据的编解码器   
  48.   config.getFilterChain().addLast("codec",   
  49.     new ProtocolCodecFilter(new ObjectSerializationCodecFactory()));   
  50.      
  51.   config.getFilterChain().addLast("logger"new LoggingFilter());   
  52.   
  53.   // 连接到服务器   
  54.   connector.connect(address, new ClientHandler());   
  55.   System.out.println(" 已经连接到了服务器 " + address);   
  56.  }   
  57. }   
  58.     
  59. ClientHandler:   
  60. public class ClientHandler extends IoHandlerAdapter {   
  61.   
  62.  // 发送信息   
  63.  public void messageSent(IoSession session, Object message) throws Exception {   
  64.  }   
  65.   
  66.  // 接收信息   
  67.  public void messageReceived(IoSession session, Object message)   
  68.    throws Exception {   
  69.   System.out.println("  客户端接收到的服务器的信息是  " + message);   
  70.  }   
  71. }    
  72. </SPAN>  

 

其中ServerMain和ClientMain分别是服务器和客户端的主程序,ServerHandler和ClientHandler是服务器和客户端的数据处理句柄,关于IoHandler会在后面的文档中做详细的讲解,这里只是简单说明一下,IoHandler主要是对数据进行逻辑操作,也可以理解为程序的业务逻辑层。其中:

Java代码 复制代码
  1. <SPAN style="FONT-SIZE: small">// 配置数据的编解码器   
  2.   config.getFilterChain().addLast("codec",   
  3.     new ProtocolCodecFilter(new ObjectSerializationCodecFactory())); </SPAN>  

 
这行代码的功能是将网络传输中的数据在发送时编码成二进制数据,解码时将二进制数据还原成一个对象或者是基本类型的数据。

Java代码 复制代码
  1. <SPAN style="FONT-SIZE: small">运行这个程序会得到如下结果:   
  2.  已经连接到了服务器 localhost/127.0.0.1:4321  
  3. 2009-7-9 23:36:46 org.apache.mina.util.SessionLog info   
  4. 信息: [localhost/127.0.0.1:4321] CREATED   
  5. 2009-7-9 23:36:46 org.apache.mina.util.SessionLog info   
  6. 信息: [localhost/127.0.0.1:4321] OPENED   
  7. 2009-7-9 23:36:46 org.apache.mina.util.SessionLog info   
  8. 信息: [localhost/127.0.0.1:4321] RECEIVED:   服务器创建会话时发送的信息 。   
  9.   客户端接收到的服务器的信息是    服务器创建会话时发送的信息 。 </SPAN>  

 
其中的红字部分是LoggingFilter打印出的事件信息。黑体部分是程序中System.out的输出。在ClientMain中的这两行代码是向过滤器链中添加IoFilter:

Java代码 复制代码
  1. <SPAN style="FONT-SIZE: small">  // 配置数据的编解码器   
  2.   config.getFilterChain().addLast("codec",   
  3.     new ProtocolCodecFilter(new ObjectSerializationCodecFactory()));   
  4.   config.getFilterChain().addLast("logger"new LoggingFilter());//添加日志工具 </SPAN>  

 

上图表示了数据在本程序中通过过滤器链的过程,日志过滤器是根据会话(IoSession)的状态(创建、开启、发送、接收、异常等等)来记录会话的事件信息的,编解码器是根据会话的接收和发送数据来触发事件的,从这里我们也可以了解通过过滤器我们可以专门针对会话的某个或某几个状态来专门处理相关的事件,如异常事件,我们可以专门定义一个Exception的IoFilter来处理Mina在通信中所发生的异常信息。

 

还有一个比较有意思的问题是,假如我们将上面过滤器的顺序该成下面的样子:

Java代码 复制代码
  1. <SPAN style="FONT-SIZE: small">  config.getFilterChain().addLast("logger"new LoggingFilter());//添加日志工具   
  2.         // 配置数据的编解码器   
  3.   config.getFilterChain().addLast("codec",   
  4.     new ProtocolCodecFilter(new ObjectSerializationCodecFactory()));    
  5.   
  6.   
  7. 程序的执行结果如下:   
  8.  已经连接到了服务器 localhost/127.0.0.1:4321  
  9. 2009-7-10 0:30:12 org.apache.mina.util.SessionLog info   
  10. 信息: [localhost/127.0.0.1:4321] CREATED   
  11. 2009-7-10 0:30:12 org.apache.mina.util.SessionLog info   
  12. 信息: [localhost/127.0.0.1:4321] OPENED   
  13. 2009-7-10 0:30:12 org.apache.mina.util.SessionLog info   
  14. 信息: [localhost/127.0.0.1:4321] RECEIVED: DirectBuffer[pos=0 lim=56 cap=102400 00 00 34 AC ED 00 05 74 00 2D 20 20 E6 9C 8D E5 8A A1 E5 99 A8 E5 88 9B E5 BB BA E4 BC 9A E8 AF 9D    
  15.   
  16. E6 97 B6 E5 8F 91 E9 80 81 E7 9A 84 E4 BF A1 E6 81 AF 20 E3 80 82]   
  17.   客户端接收到的服务器的信息是    服务器创建会话时发送的信息 。    
  18. </SPAN>  

 
很明显的是在顺序变化了之后,日志中多了接收到的二进制数据,这是因为在上面数据已经有解码器将数据还原成了Java对象,所以我们就看不到二进制数据了,而在顺序变换后,由于先执行的是打印信息,此时的数据还没有还原成java对象,所以接收到的数据是二进制的。

 

在上面的例子中我们清楚了整个IoFilter或者是IoFilter的工作流程,那么IoFilter在Mina中的作用如何?所有的数据在发送到Mina程序中时,数据都是先通过IoFilter,经过处理后再转发到业务层。这里IoFilter就起到了一个承上启下的作用。

 

到这里我们就可以回答本文开始提到的问题了:

 (1)什么时候需要用到IoFilter,如果在自己的应用中不添加过滤器可以吗?
在你自己的程序中可以添加过滤器,也可以不添加,但是在数据发送之前,所发送的数据必须转换成二进制数据,这个可以有IoFilter完成,也可以由ProtocolCodecFilter完成(关于这个问题会在后面的文章中详细讲述),否则Mina会抛出Write requests must be transformed to class org.apache.mina.common.ByteBuffer: 异常。这是因为网络中传输的数据只能是二进制数据。因此无论添加不添加过滤器,都必须将要发送的数据转换成二进制数据。

(2)如果在IoService中添加多个过滤器可以吗?若可以,如何进行添加,这多个过滤器是如何工作的?
     在IoService中可以添加多个过滤器,这个在上面的程序中已经给处理,添加的方式也很简单,通过程序一目了然。

(3)Mina中提供了协议编、解码器,IoFilter也可以实现IO数据的编解码功能,在实际的使用中如何选择?
     Mina的编解码器在Mina的使用中是最关键的一个问题,特别是在不同语言之间进行通信的时候,比如Java和C/C++等,由于Mina自身没有提供这些编解码器,所以需要自己来实现。Mina提供了一个Decoder/Encoder,你可以实现两个类来完成不同平 之间的通信。关于这个问题会在后面的文档给出具体的实习方法。

 

至此,关于IoFilter的作用就讲述完了,希望对你能有所帮助。:)

抱歉!评论已关闭.