基于MINA构建简单高性能的NIO应用(一)

2014-11-24 02:33:32 · 作者: · 浏览: 9

前言
MINA是Trustin Lee最新制作的Java通讯框架。通讯框架的主要作用是封装底层IO操作,提供高级的操作API。比较出名的通讯框架有C++的ACE、Python的Twisted,而Java的通讯框架还有QuickServer、Netty2、Cindy、Grizzly等。
2004年6月,Trustin Lee发布了一个通讯框架Netty2,是Java界第一个事件模型架构的通讯框架,Cindy也从中借鉴了不少思想。由于Netty2的架构不是很好,Trustin Lee在2004年底加入Apache Directory组之后,重写了整个框架,取名为MINA。MINA是一个基于Java NIO的通讯框架,Java从1.4开始引入NIO,提供了一个非阻塞、高性能的IO底层。
目前使用MINA的产品并不是很多,比较出名的就有Apache Directory、Openfire(Jive出品的一个XMPP产品)、red5(研究flash流媒体flv技术的朋友应该很清楚这个东西,adobe fms的竞争者,国内也有视频网站在使用)等等。
笔者在07年初的时候,公司新项目需要用Java实现一个Socket Server,对比了Netty2、Cindy、QuickServer和MINA。当时Netty2已经停止开发,也找不到官方网站和代码,比较了另外三个框架之后,毅然选择了当时文档比较缺乏和使用群较少的MINA,一年以来的使用经验来看,感觉还是很不错的,MINA有着清晰的架构,很方便做自定义的扩充。在1.0发布之后,官方网站充实了很多,增加了不少文档,也听到越来越多的朋友开始使用MINA。后来专门针对JDK 1.5发布了1.1的版本,使用JDK内置的concurrent代替backport-util-concurrent。目前1.0和1.1同时存在,但已经不再增加新功能,仅仅发布bug fix的版本,新功能都在2.0中实现,2.0调整了架构,性能有更大的提升,目前还在开发中。
基本特性
通过Java NIO支持TCP和UDP协议,另外还支持RS232和VM内通讯。由于MINA有清晰的架构,你也能很简单地实现一个底层网络协议。目前不支持阻塞IO,似乎还没有计划支持,当然你可以在其之上实现一个阻塞的模型,不过按照笔者的经验来说,非阻塞IO更适合Server端编程
一个类似ServletFilter的过滤器模型。这是笔者认为MINA的精髓所在,通过引入过滤器模型,可以将一些非业务的功能独立开来,层次更清晰,很有AOP的思想,可以很方便地进行日志、协议转换、压缩等等功能,还能在运行中动态增加或去掉功能。
可以直接使用底层的ByteBuffer,也可以使用用户定义的消息Object和编码方式。
高度可定制的线程模型,单线程、一个线程池,或者类似SEDA的多个线程池。
SSL支持,攻击防御和流量控制,mock测试友好,JMX支持,Spring集成,你还需要更多吗。
一个简单的例子
MINA使用非常简单,笔者以前做过一段时间传统的Java Socket开发,不过一直对Java NIO不是很理解,但是MINA很快就上手了,MINA封装了NIO繁琐的部分,使你可以更专注于业务功能实现。话不多说,让我们来看一个简单的例子,一个很常见的例子,时间服务器。
我们的实现目标是一个能响应多个客户端的请求,然后返回服务器当前的系统时间的功能。传统的Java Socket程序,我们需要每accept一个客户端连接,就创建一个新的线程来响应,这会令到系统整体负载能力有较大的限制,而且我们必须手工编写连接管理等代码。让我们来看看MINA是怎么处理的。
首先我们从官方网站下载MINA 1.1,这里我们假设JDK为1.5以上的版本,如果你使用的是JDK 1.4,请下载MINA 1.0,MINA 1.0跟1.1几乎一样,但是强烈建议使用JDK 1.5以上以获得更好的性能。
解开压缩包之后,能看见很多jar包,这里暂不介绍每个包的具体作用,可以把所有包都导入项目。值得留意的是MINA使用了一个slf4j的日志库,该日志库大有取缔common-logging之势。 这里是我们的主程序,非常简单。
首先我们需要一个IoAcceptor,这里我们选择了一个SocketAcceptor,也就是TCP协议。
然后,我们给应用加上日志过滤器和协议编码过滤器。
最后,我们把acceptor bind到本机的8123端口,并且使用TimeServerHandler来实现协议。
TimeServerHandler是我们实现具体业务功能的地方。 IoHandlerAdapter提供了7个事件方法,我们要做的事情仅仅是挑选我们需要做出响应的事件进行重载。在我这个例子了,我重载了两个方法。sessionCreated会在客户端连接的时候调用,通常我们会在这里进行一些初始化操作,我这里仅仅是打印一条信息。messageReceived就是整个Handler的中心部分,每一个从客户端发过来的消息都会转化成对该方法的调用。由于我们加入了协议编码过滤器,因此这里获得的Object msg是一个String,而不是默认的ByteBuffer(下文会详细介绍ProtocolCodecFilter)。这里我们实现了一个很简单的业务功能,如果用户输入的是quit,就断开连接,否则就输入当前时间。可以看出,IoSession封装了对当前连接的操作。
至此,我们就实现了一个时间服务器。
01.public class TimeServer {

02. public static void main(String[] args) throws IOException {
03. IoAcceptor acceptor = new SocketAcceptor();
04.
05. SocketAcceptorConfig cfg = new SocketAcceptorConfig();
06. cfg.getFilterChain().addLast( "logger", new LoggingFilter() );
07. cfg.getFilterChain().addLast( "codec", new ProtocolCodecFilter( new TextLineCodecFactory()));
08.
09. acceptor.bind( new InetSocketAddress(8123), new TimeServerHandler(), cfg);
10. System.out.println("Time server started.");
11. }
12.}
01.public class TimeServerHandler extends IoHandlerAdapter {
02. public void messageReceived(IoSession session, Object msg) throws Exception {
03. String str = (String) msg;
04. if( "quit".equalsIgnoreCase(str) ) {
05. session.close();
06. return;
07. }
08.
09. Date date = new Date();
10. sess