我是个懒人,懒得出其的懒人,很多年来一直只索取,不回报的,因为太懒了,所以虽说注册时间比较早,但文章可以用一个手掌数过来。。。。。这次被领导逼的,写了个短文发个企业内部通讯上,所以就帖上来了,我还真是懒啊。。。格式都没改直接帖了。。。。。当然这其中 有些内容是参考了一些网上的文章,介绍mina的部分类似的语句还是有一些滴,但主要还是为了说明问题,重点在使用心得上面,所以介绍mina如何使用的东西不是特别多,这类文章网上到处都是,相对来说比较适合对MINA有一点了解的童鞋阅读。
大并发量 socket 通信的解决方案
--MINA 框架的使用心得及相关要点
目录
前言 ... 1
What is MINA .. 2
使用案例: ... 2
Apache 直属 MINA 的子项目: ... 3
同类框架: ... 3
MINA 快速入门 ... 3
预备知识: ... 3
资源下载: ... 4
Hello world 的关键关键代码: ... 4
MINA 深度了解 ... 5
Mina 的应用层 ... 5
MINA 的内部流程 ... 5
选择 MINA 的理由 ... 7
传统 socket 编程 ... 7
改进的 Socket 编程 ... 7
使用 MINA 框架的编程 ... 8
扩展知识 ... 9
IoBuffer 接口 ... 9
sendUrgentData() 方法的使用 ... 10
Concurrent 包下的一些类 ... 12
结尾语 ... 12
笔者之前的工作主要是做 java 的 web 端开发,后因工作原因参与了一个国家级的大项目,主要负责其中底层通讯的前置机模块。几经波折,将该系统完成后,结果在第一轮的测试中就惨败退回。其根本原因就在于原设计文档的要求单“通信机”与“终端”( 注一 )之间的并发量要达到 2W 以上的连接通信,而实际运行并发量只能达到 2600 个相差了近十倍左右。经过代码调优、扩展 JVM 内存等等手段,但因基础数据相差过大,所取得的优化效果十分有限。后考虑在根本着手,只有更改整个系统的通信接口,才有可能达到设计文档上的要求。某天在某个技术 QQ 群里一次讨论中,有网友向我推荐了一个框架,这就是本文要介绍的主角 -MINA 。
注一 : 前置机分成了三个部分,其设计的结构图如下所示:
Apache MINA(Multipurpose Infrastructure for Network Applications) 是 Apache 组织一个较新的项目,它为开发高性能和高可用性的网络应用程序提供了非常便利的框架。当前发行的 MINA 版本支持基于 Java NIO 技术的 TCP/UDP 应用程序开发、串口通讯程序(只在最新的预览版中提供), MINA 所支持的功能也在进一步的扩展中
目前正在使用 MINA 的软件包括有: Apache Directory Project 、 AMQP ( Advanced Message Queuing Protocol )、 RED5 Server ( Macromedia Flash Media RTMP )、 ObjectRADIUS 、 Openfire 等等。
FTPServer , AsyncWeb , SSHD
其实在有人推荐了 MINA 之后,本人就上网 google 了一把,搜索一番下来之后才发现自己的眼界过于狭隘了,原来有相同功能的开源框架还真不少,看来以后得继续多泡论坛和 QQ 了(有正当理由上班泡坛子,聊 QQ 了,偷笑一个 ^_^ 。。。。。。)
Netty2: 具有很好的构架,并且该框架非常有名,使用该框架的项目不少,这意味着开发团队持续更新的动力非常大。同时 Netty2 的文档非常齐全,并且支持 JMX 。 Netty2 的缺点就是实现代码的质量不是非常好,最大的缺点就是只支持普通的 TCP 。
Cindy : 起源于 Netty2 之后,借鉴了 Netty2 中 MessageRecognizer 类的设计,在当前的版本中已经全面支持普通 TCP/Secure TCP/UDP 单播 /UDP 多播 /Pipe ,可以使用同一个模型进行同步 / 异步 IO 处理。 Cindy 目前缺点是文档相对较少以及应用的项目比较少。
Grizzl : 的设计与一般的 nio 框架相比是比较不同的,主要不同点在于读和写都是采用 blocking 方式,并且使用临时 selector ;线程模型高度可配置。性能据说比 MINA 还高,但是学习曲线很高。 QuickServer: http://www.quickserver.org/
Xscocket : 是一个轻量级的解决方案,核心思想是屏蔽,简化 nio 方式的的开发,并不需要过多的学习。
对于这些框架的基本使用和基础架构,本人都经过了一番研究,发现这些框架要么重者过重,要么轻者过轻。鉴于项目的规模及时间的紧迫,再三比较之下,本人还是选择了学习曲线低,性能优异的 MINA. 至于这些优点是如何体现出来的,本文以下内容将继续为您解读。
闲话少说,借用大师级的写书经验,咱们先来一个 hello world 。当然在这前我们还是得先做一些准备的。
JAVA NIO
多线程
Socket
以上知识对本文的阅读理解有一定帮助,但并非一定必需。
MINA2.0 : 暂时分为 1.x 和 2.x 两个主版本,本文只涉及 2.X 的版本,至于为什么只讲 2.X 而不讲 1.X ,比较冠冕堂皇的回答是 ---- 因为有一位伟人曾经说过:要以发展的眼光看世界。。。。。。而真实原因嘛。。。。。。咳咳。。。。大家都知道的。。我就不便多言了。下载地址: http://mina.apache.org/downloads.html 。项目中使用的是 2.03, 截止本文发稿为止,最新版本为: 2.04
log4j : 因为其中缺少 log4j 的包,所以做试验的朋友还需要去下一个 log4j 的包。
开发工具: eclipse
Jdk: 1.6x
监视测试工具: Oracle JRockit Mission Control 4.0.1 强烈推荐,简称 JRMC ,开发过程中,用它解决了很多性能瓶颈的问题,具体使用方法,因为篇幅所限在此不做详述,请自行查询相关文档。
Server 端的 Main 函数:
IoAcceptor acceptor = new NioSocketAcceptor(); // 建立监控器
//acceptor.getSessionConfig().setReadBufferSize(2048);
//acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE , 10);
acceptor.getFilterChain().addLast("codec ",
New ProtocolCodecFilter(new TextLineCodecFactory(Charset.forName("UTF-8"),
LineDelimiter.WINDOWS.getValue(), LineDelimiter.WINDOWS.getValue())));// 加载解 / 编码工厂
acceptor.setHandler(new StandBaowenHandler ()); // 设置处理类,其主要内容见下。
acceptor.bind(new InetSocketAddress(9998));// 绑定的监听端口,可多次绑定,也可同时绑定多个。
StandBaowenHandler 的关键代码
public void messageReceived(IoSession session, Object message) throws Exception { session.getService().getManagedSessions();
String baowenSrc = message.toString();// 原始报文
System.out.println (baowenSrc );
}
鉴于篇幅关系,类没有写全,更具体的内容,请大家参考 mina 压缩包里自带的 demo 。但实际上服务端需要手写的部分确实只有以上二块内容,服务端就算是写好了,由次可以看出 mina 的入门之快。现在我们来测试。打开 cmd( 这个对于同仁们来说,就不用详述了吧。。。 - - !! ) ,输入 telnet 127.0.0.1 9998 (这里的 9998 要与上面代码绑定的端口一致)随便输入一些字符,敲下回车后就会在控制台显示您所输入的信息了。
做得这一步的童鞋可以说对 MINA 的使用可以说已经初步入门了,是不是觉得太简单了?这也太假了吧,这就算入门了?呵呵。。。个人认为这就是 MINA 的强悍之一,简单的几行代码就搞定了一个服务端,要入门简直是太简单了。但任何事物都有其二面性,真要把一个东西学好,用好,还要不出错,我们需要了解的东西就太多太多了。下面我们就从 MINA 框架的底层说起,因为在实际应用当中,要解决一些碰到的难点及问题,对框架的整个运作体系必须要有个全面深入的了解。只有这样才能在碰到问题时,有目的,有针对性的去找到症结所在。
图 1
一个设计成熟的开源框架,总是会仅可能的减少侵入性,并在整个项目中找到合适的位置,而不应对整个项目的构架设计产生过多的影响,图 1 就是 MINA 的应用层示意图。从图中和上节的 DEMO 中我们可以看到, MINA 很好的把业务代码和底层的通信隔离了开来,我们要做的仅仅是建立好监听,然后写上我们需要实现的业务逻辑就 OK 了。
图 2
(1) IoService :这个接口在一个线程上负责套接字的建立,拥有自己的 Selector ,监听是否有连接被建立。
(2) IoProcessor :这个接口在另一个线程上负责检查是否有数据在通道上读写,也就是说它也拥有自己的 Selector ,这是与我们使用 JAVA NIO 编码时的一个不同之处,
通常在 JAVA NIO 编码中,我们都是使用一个 Selector ,也就是不区分 IoService 与 IoProcessor 两个功能接口。另外, IoProcessor 也是 MINA 框架的核心组件之一 . 在 MINA 框架启动时,会用一个线程池来专门生成线程,来负责调用注册在 IoService 上的过滤器,并在过滤器链之后调用 IoHandler 。在默认情况 IoProcessor 会用 N+1 个线程来轮流询问监视的端口是否有数据传送,其中 n 为 cpu 的内核个数。按一般的多线程设计概念来说, IoProcessor 的线程数是越多越好,但实际上并非如此,因为大家都知道, IO 的操作是非常占用资源的,所以项目中的 IoProcessor 的线程数应该根据实际需要来定,而这个数字可以在生成 IoAcceptor 对象时进行设定。 Eg IoAcceptor acceptor = new NioSocketAcceptor( N );
(3.) IoFilter :这个接口定义一组拦截器,这些拦截器可以包括日志输出、黑名单过滤 ( 参见之前代码示例的注释部份 ) ,甚至是在过滤器链中利用 AOP 写上权限控制(笔者负责的部分没有涉及到这权限部分,但根据笔者对框架的理解要实现这一点应该问题不大,有需要的可以自行试验)。数据的编码( write 方向)与解码( read 方向)等功能,其中数据的 encode 与 decode 是最为重要的、也是您在使用 Mina 时最主要关注的地方(笔者曾经为了 decode 的解码编写,重写了不下十几种实现方式才找到准确无误适合项目的解码方法)。
(4.) IoHandler :这个接口负责编写业务逻辑,也就是接收、发送数据的地方。只本文的代码实例中,可以看到真正的业务代码只有一句 :System.out.println(str); 真实项目中当然不可能如此简单,但如果大家把业务处理写好,并写好业务接口,真正要用时,呆需要在此处替换即可,再次见证了 MINA 分层的彻底。
说了这么多,以上内容也只能让大家对 MINA 有个基础的了解,对于 MINA 框架优势的认识可能还不是很多,那么下面的内容,就此展开对比讨论,以便大家对 MINA 的适用场景及优点有个更全面的了解。
在传统 I/O 中,最简单实现高并发服务器的编程方式就是对每一个客户开启一个线程。但是这种方式有如下几个弊端:
客户端上限很大的情况下不能及时响应
服务器硬件资源受限,性能也会急剧下降
受制于操作系统的限制
但这种设计方式优点还是有的 :
编码简单,实现容易
一定数量的连接性能比较好。
笔者的项目开发中,最开始就是采用的这种方式,写起来方便,控制起来也方便,但遗憾的是 JVM 最多只能开到 2K 多的线程,就会报 - can not create new thread 的错误。
(实现结构图见下:)
图 3 (一对一的结构图。)
为了解决每一个线程一个连接的模型,笔者最开始想到用多个线程处理 N 个用户,这样既可以保证处理多个用户的同时,线程开销降到系统的临界点。
这样的方式与前一个模型优势在于同样的多线程,但线程数固定,充分运用系统的优势性能,又不存在多余的开销。但是缺点也是显而易见的:
轮询的消耗不可避免。
一但产生 io 阻塞,其阻塞的时间纯属浪费。
客户数量固定的时候没有前一模型响应快
编码更加复杂。
图 4( 一对多 )
为了解决上述的矛盾,最终的解决方案只能是异步的 NIO, 而随着笔者对 JAVA NIO 的研究发现,要实现异步的 NIO ,并应用到实际项目中,必须对 NIO 有着比较深刻的了解和把握,笔者曾尝试着利用 JAVA 原生 NIO 接口写了一个 DEMO (具体的使用方法,感兴趣的童鞋可以 GOOGLE 一把,你会发现用原生 NIO 写程序与使用 MINA 写程序对比起来是多么的痛苦。。。。 -_-!! ),但由于笔者在这方面的底子过薄,试验结果不如人意,但要对 NIO 进行更为深入的学习,时间上面也不允许。直到 MINA 框架的映入眼帘,以上难题不再是问题。。。。以下是利用 MINA 的实现方式的一个简图。
图 5
其中 IoService 接口会专门起一个线程来轮询是否有新的连接产生,一旦有连接产生则通知 IoProcessor, 而 IoProcessor 则起 n+1 个线程来检查连接是否有数据在上面读写 (注二 ) 。一旦有连接产生,并有数据读写,则通知 decode 或 ENCODE ,进行报文的解码或编码,将处理后的报文再交给业务类进行业务处理。其中 IoProcessor 是处理请求的分配,包括选择 Selector ,超时验证,状态记录等。总之这个类和 IoService 一起配合工作,封装了 NIO 底层的实现以及 MINA 框架内部的功能的支持 . 由于过于复杂,篇幅所限所以不作详细讲解 .
结合实例,并根据以上的图文讲解,我们可以很轻易的总结出利用 MINA 编程的几个大致步骤:
创建一个实现了 IoService 接口的类
设置一个实现了 IoFilter 接口的过滤器(如果有需要的情况下)
设置一个 IoHandler 接口实现的处理类,用于处理事件(必须)
对 IoService 绑定一个端口开始工作
关于 MINA 的大致运行流程及使用步骤,我们就暂时分析到这,具体更细节的关于一些核心类的使用方法及自定义编码器的方法,大家可以直接参考 mina 中所带的几个案例,写得非常详细,足够解决大家在项目中碰到的大部分问题,接下来要与大家交流的是使用 MINA 时非常有可能遇到的一些扩展知识。
注二 :这一点请特别注意,因 IoProcessor 也是相当于轮询机制,这导致在报文过长时,或其它原因导致报文不能一次传输完毕的情况下,必须保存同一连接 ( 在 MINA 中是以 IoSession 类生成的对象 ) 下的上一次状态,这样才能截取到一个完成的报文,而这也是 Decode( 编码器 ) 需要做的核心工作 , 新手往往就在这上面要跌跟斗。
这部分的内容要说起来跟 MINA 的使用关联不大,但实际情况是用上了 MINA 框架的项目基本上多多少少都会涉及到这一块,那就是多线程的编程。多线程的编程历来是 JAVA 编程中的重难点,很多新手碰到此类编程问题,往往都找不出原因所在,甚至一些有多年编程经验的程序员也会在这上面偶而犯下错,在这里笔者也没有能力通过很短的篇来解说多线程,那么就向大家介绍几个类及一些小常识吧,希望能给大家带来帮助。