现在已经是World Wide Web的时代,无数的web应用框架被创造出来从而大大的提高了web开发的速度。抛开WWW的这个优势,我们知道还有很多协议是HTTP协议所无法替代的。有时,我们仍然需要构造c/s应用来实现适当的协议。
=== MINA是什么? ===
你有没有曾经使用java或者其他语言实现过某个协议栈?就像你所经历过的那样,编写网络应用即使对于有经验的开发者也不是容易的事情。这归咎于以下几个方面:
* 没有为开发者设计的合适的网络应用框架.
* 使你无法在有限的时间内创建你的应用.
* 网络I/O编码,消息的编/解码,业务逻辑常常纠缠在一起.
* 使程序失去可维护性和可复用性
* 网络应用难于进行单元测试
* 你失去了敏捷性
MINA是一个网络应用框架,在不牺牲性能和可扩展性的前提下用于解决上面的所有问题。
== I/O 层: 编写一个 Echo Server ==
MINA包含两层:IO层和协议层。我们首先仅使用IO层来实现一个echo服务,因为协议层通常是建立在IO层之上的。
attachment:Arch1.gif
上面的图展示了MINA的IO层同客户端的交互。IoAcceptor执行所有底层IO,将他们翻译成抽象的IO事件,并把翻译过的事件和关联的IoSession
发送给IoHandler。
=== IoSession ===
attachment:IoSession.gif
一个代表了IoSession程序同一个远程实体的IO连接。通过IoSession,你可以写出message到远程实体,访问session的配置,并且更改session的属性。
=== IoHandler ===
attachment:IoHandler.gif
* sessionCreated: 当一个IO连接建立时被调用,这个方法在任何IO操作之前被调用,以便socket参数或session属性能够最先被设置。
* sessionOpened: 在sessionCreated调用之后被调用。
* sessionClosed: 当IO连接被关闭时被调用。
* sessionIdle: 当在远程实体和用户程序之间没有数据传输的时候被调用。
* exceptionCaught: 当IoAcceptor 或者你的IoHandler.中出现异常时被调用。
* messageReceived: 当接收到新的协议消息时被调用。可以在这里实现你的控制流程。
* messageSent: 当用户请求的消息通过 IoSession#write(Object) 确实发送后被调用。
下面我们看看如何实现echo协议的IoHandler。
=== 实现 IoHandler 以及启动代码 ===
通常,应用需要继承IoHandlerAdapter并实现需要的方法:
![](https://i-blog.csdnimg.cn/blog_migrate/4f1150b881333f12a311ae9ef34da474.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/4f1150b881333f12a311ae9ef34da474.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/4f1150b881333f12a311ae9ef34da474.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/4f1150b881333f12a311ae9ef34da474.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/4f1150b881333f12a311ae9ef34da474.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/4f1150b881333f12a311ae9ef34da474.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/4f1150b881333f12a311ae9ef34da474.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/4f1150b881333f12a311ae9ef34da474.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/4f1150b881333f12a311ae9ef34da474.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/1fa987a29c6482f53d401256f96355eb.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/d18c02628675d0a2c816449d98bda930.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/97e794c86028c5f5b5461ae5ef440a4c.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/97e794c86028c5f5b5461ae5ef440a4c.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/d18c02628675d0a2c816449d98bda930.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/ecedf933ec37d714bd4c2545da43add2.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
刚刚我们使用MINA实现echo协议,现在我们将handler绑定到一个server端口上。
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/d18c02628675d0a2c816449d98bda930.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
=== 添加IoFilters ===
IoFilter提供了更加有力的方式来扩展MINA。它拦截所有的IO事件进行事件的预处理和后处理。你可以把它想象成Servlet的filters。IoFilter能够实现以下几种目的:
* 事件日志
* 性能检测
* 数据转换(e.g. SSL support)
* 防火墙…等等
attachment:Arch2.gif
我们的echo协议handler不对任何IO事件进行日志。我们可以通过添加一个filter来增加日志能力。MINA提供了IoLoggingFilter来进行日志。我们只要添加日志filter到ServiceRegistry即可。
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/1fa987a29c6482f53d401256f96355eb.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/d18c02628675d0a2c816449d98bda930.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/8f1ba5b45633e9678d1db480c16cae3f.gif)
想使用SSL?MINA也提供了一个SSL的filter,但它需要JDK1.5。
![](https://i-blog.csdnimg.cn/blog_migrate/4f1150b881333f12a311ae9ef34da474.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/9b8a8a44dd1c74ae49c20a7cd451974e.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/4f1150b881333f12a311ae9ef34da474.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/4f1150b881333f12a311ae9ef34da474.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
== 协议层: 实现反转Echo协议 ==
在上面我们通过简单的echo server的例子学习了如何使用IO层,但是如果想实现复杂的如LDAP这样的协议怎么办呢?它似乎是一个恶梦,因为IO层没有帮助你分离‘message解析’和‘实际的业务逻辑(比如访问一个目录数据库)’。MINA提供了一个协议层来解决这个问题。协议层将ByteBuffer事件转换成高层的POJO事件:
attachment:Arch3.gif
使用协议层必须实现5个接口:ProtocolHandler, ProtocolProvider, ProtocolCodecFactory,
ProtocolEncoder, 和 ProtocolDecoder:
attachment:ProtocolClasses.gif
可能看上去有点麻烦,但是请注意ProtocolCodecFactory, ProtocolEncoder, 和
ProtocolDecoder是可以完全复用的;Apache的ASN1项目为MINA提供了ASN.1解码器,更通用的解码器如:XML、java对象序列化和简单的文本将在MINA的下一个版本中提供。一旦你实现了一个灵活的解码器,你可以在未来的应用中复用它,即使你不打算复用你的解码器,MINA也提供了一个很简单的方法来实现复杂的协议。(请参考高级主题)
在这一章中,我们添加一个‘反转’server,它用于反转它接到的所有文本,我们通过它来示范如何编写一个协议层。
=== ProtocolSession ===
attachment:ProtocolSession.gif
ProtocolSession同IO层的IoSession同样继承自Session。就像前面提到的,你只需撰写面向POJO的message而不是ByteBuffer的。ProtocolEncoder
将message对象解释成ByteBuffers以便IO层能够将他们输出到socket。=== ProtocolHandler ===
ProtocolHandler类似于IO层的IoHandler.dataRead和dataWritten方法被替换成messageReceived和messageSent。这是因为ProtocolDecoder
已经将IO层接收到的 ByteBuffers转换成了message对象。
![](https://i-blog.csdnimg.cn/blog_migrate/4f1150b881333f12a311ae9ef34da474.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/4f1150b881333f12a311ae9ef34da474.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/4f1150b881333f12a311ae9ef34da474.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/4f1150b881333f12a311ae9ef34da474.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/4f1150b881333f12a311ae9ef34da474.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/4f1150b881333f12a311ae9ef34da474.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/4f1150b881333f12a311ae9ef34da474.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/1fa987a29c6482f53d401256f96355eb.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/d18c02628675d0a2c816449d98bda930.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/97e794c86028c5f5b5461ae5ef440a4c.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/d18c02628675d0a2c816449d98bda930.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
=== ProtocolEncoder 和 ProtocolDecoder ===
attachment:ProtocolCodec.gif
ProtocolEncoder 和ProtocolDecoder只有一个方法。ProtocolEncoder将message对象转换成一个ByteBuffer,而ProtocolDecoder将一个ByteBuffer转换成message对象。下面我们将学习如何实现这些接口。要实现反转协议要实作的唯一接口就是ProtocolProvider。它非常简单:
(注:Provider用于在一个统一的类中提供该协议相关的Handler、Decoder和Encoder。)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/d18c02628675d0a2c816449d98bda930.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
这样,反转协议就被完全实现了。启动的部分同echo server非常相似:
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/1fa987a29c6482f53d401256f96355eb.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/d18c02628675d0a2c816449d98bda930.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/d18c02628675d0a2c816449d98bda930.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/d18c02628675d0a2c816449d98bda930.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/97e794c86028c5f5b5461ae5ef440a4c.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
=== 添加 ProtocolFilters ==
ProtocolFilter 同IO层的IoFilter类似:
attachment:Arch4.gif
添加IoLoggingFilter来记录底层IO事件是为了debug。我们可以用ProtocolLoggingFilter代替它来记录高层事件:
![](https://i-blog.csdnimg.cn/blog_migrate/4f1150b881333f12a311ae9ef34da474.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/1fa987a29c6482f53d401256f96355eb.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/d18c02628675d0a2c816449d98bda930.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/d18c02628675d0a2c816449d98bda930.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/d18c02628675d0a2c816449d98bda930.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/8f1ba5b45633e9678d1db480c16cae3f.gif)
=== ByteBuffers ===
MINA没有直接使用使用java NIO的ByteBuffer类。它使用一个自制的ByteBuffer来扩展java
NIO ByteBuffer的功能。
以下是它们的一些区别:
* MINA ByteBuffer是一个抽象类,用户可以自由的扩展它
* MINA 管理 MINA ByteBuffers 并对其提供对象池. Users can control the point the
buffers are released by providing acquire() and release() methods.
* MINA ByteBuffer提供很多便利的方法,如:无符号数值的getter和基于String的getter和putter
如果你使用MINA,你将不需要直接使用NIO buffers,因为仅使用MINA buffers就可以完成大多数buffer操作。
==== ByteBuffer 池 ====
MINA有一个全局的ByteBuffer池,它被在同一个虚拟机下的所有MINA应用共享。任何分配的buffers将在IO操作或者事件处理方法被执行之后被释放。所以你可以调用ByteBuffer.allocate()来从池中得到一个ByteBuffer而不需要将它返回到池中。请查阅ByteBuffer
JavaDocs获得更多信息。
=== 线程模式 ===
MINA通过它灵活的filter机制来提供多种线程模型。没有线程池过滤器被使用时MINA运行在一个单线程模式。如果添加了一个IoThreadPoolFilter
到IoAcceptor,你将得到一个leaderfollower模式的线程池。如果再添加一个ProtocolThreadPoolFilter,你的server将有两个线程池;一个(IoThreadPoolFilter)被用于对message对象进行转换,另外一个(ProtocolThreadPoolFilter)被用于处理业务逻辑。
SimpleServiceRegistry加上IoThreadPoolFilter和ProtocolThreadPoolFilter的缺省实现即可适用于需要高伸缩性的应用。如果你想使用自己的线程模型,请查看SimpleServiceRegistry的源代码,并且自己初始化Acceptor。显然,这是个繁琐的工作。
![](https://i-blog.csdnimg.cn/blog_migrate/4f1150b881333f12a311ae9ef34da474.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/4f1150b881333f12a311ae9ef34da474.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/4f1150b881333f12a311ae9ef34da474.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/4f1150b881333f12a311ae9ef34da474.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/4f1150b881333f12a311ae9ef34da474.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/4f1150b881333f12a311ae9ef34da474.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/9b8a8a44dd1c74ae49c20a7cd451974e.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/4f1150b881333f12a311ae9ef34da474.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/4f1150b881333f12a311ae9ef34da474.gif)
![](http://cnc.qzs.qq.com/ac/b.gif)
=== 更复杂的协议支持 ===
‘Reverser’示例相对于其他复杂的协议来说仍然过于简单。要想让一个server工作,仍然有许多message类型和它们的转换的工作需要作。MINA提供了一下工具类来提供帮助:
* DemuxingProtocolHandler
* DemuxingProtocolCodecFactory
更多细节请参考 JavaDocs 。
=== VM 内部管道通讯 === 你一定已经知道协议层是建立在IO层之上的,但是有时也不一定。虽然我们通常使用协议层来包装IO层,但仍有一种特殊的协议层实现,称作:’inVM pipe communication’ 让我们假设你需要使用MINA实现一个SMTP server和一个Spam Filter server。SMTPserver可能需要同Spam Filter server通讯以便发现spam message或者RBL中列出的客户端。如果这两个server是在同一个java虚拟机中,一个IO层是多余的,你可以绕过message对象的编解码的过程。InVMpipe communication可以使你使用同样的代码而不管spam filter server是否在同一个虚拟机中。 请查看随源码分发的’ Tennis’示例。