论坛首页 Java企业应用论坛

MINA使用心得及相关要点,有一个bug的解决方案,不知道其它朋友是否遇到过(一)

浏览 47479 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2011-06-29   最后修改:2011-06-29

我是个懒人,懒得出其的懒人,很多年来一直只索取,不回报的,因为太懒了,所以虽说注册时间比较早,但文章可以用一个手掌数过来。。。。。这次被领导逼的,写了个短文发个企业内部通讯上,所以就帖上来了,我还真是懒啊。。。格式都没改直接帖了。。。。。当然这其中 有些内容是参考了一些网上的文章,介绍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

 

注一 前置机分成了三个部分,其设计的结构图如下所示:

What is 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 等等。

Apache 直属 MINA 的子项目

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. 至于这些优点是如何体现出来的,本文以下内容将继续为您解读。

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 ,开发过程中,用它解决了很多性能瓶颈的问题,具体使用方法,因为篇幅所限在此不做详述,请自行查询相关文档。

Hello world 的关键关键代码:

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 框架的底层说起,因为在实际应用当中,要解决一些碰到的难点及问题,对框架的整个运作体系必须要有个全面深入的了解。只有这样才能在碰到问题时,有目的,有针对性的去找到症结所在。

MINA 深度了解

Mina 的应用层

 

图片1

 

                                     1

 

一个设计成熟的开源框架,总是会仅可能的减少侵入性,并在整个项目中找到合适的位置,而不应对整个项目的构架设计产生过多的影响,图 1 就是 MINA 的应用层示意图。从图中和上节的 DEMO 中我们可以看到, MINA 很好的把业务代码和底层的通信隔离了开来,我们要做的仅仅是建立好监听,然后写上我们需要实现的业务逻辑就 OK 了。

 

MINA 的内部流程

 

图片2

                                     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 的适用场景及优点有个更全面的了解。

选择 MINA 的理由

传统 socket 编程

在传统 I/O 中,最简单实现高并发服务器的编程方式就是对每一个客户开启一个线程。但是这种方式有如下几个弊端:

客户端上限很大的情况下不能及时响应

服务器硬件资源受限,性能也会急剧下降

受制于操作系统的限制

但这种设计方式优点还是有的 :

编码简单,实现容易

一定数量的连接性能比较好。

笔者的项目开发中,最开始就是采用的这种方式,写起来方便,控制起来也方便,但遗憾的是 JVM 最多只能开到 2K 多的线程,就会报 - can not create new thread 的错误。

 

(实现结构图见下:)

图片3

3 (一对一的结构图。)

改进的 Socket 编程

为了解决每一个线程一个连接的模型,笔者最开始想到用多个线程处理 N 个用户,这样既可以保证处理多个用户的同时,线程开销降到系统的临界点。

这样的方式与前一个模型优势在于同样的多线程,但线程数固定,充分运用系统的优势性能,又不存在多余的开销。但是缺点也是显而易见的:

轮询的消耗不可避免。

一但产生 io 阻塞,其阻塞的时间纯属浪费。

客户数量固定的时候没有前一模型响应快

编码更加复杂。

图片4

4( 一对多 )

使用 MINA 框架的编程

为了解决上述的矛盾,最终的解决方案只能是异步的 NIO, 而随着笔者对 JAVA  NIO 的研究发现,要实现异步的 NIO ,并应用到实际项目中,必须对 NIO 有着比较深刻的了解和把握,笔者曾尝试着利用 JAVA 原生 NIO 接口写了一个 DEMO (具体的使用方法,感兴趣的童鞋可以 GOOGLE 一把,你会发现用原生 NIO 写程序与使用 MINA 写程序对比起来是多么的痛苦。。。。 -_-!! ),但由于笔者在这方面的底子过薄,试验结果不如人意,但要对 NIO 进行更为深入的学习,时间上面也不允许。直到 MINA 框架的映入眼帘,以上难题不再是问题。。。。以下是利用 MINA 的实现方式的一个简图。

图片5

 

                                     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 编程中的重难点,很多新手碰到此类编程问题,往往都找不出原因所在,甚至一些有多年编程经验的程序员也会在这上面偶而犯下错,在这里笔者也没有能力通过很短的篇来解说多线程,那么就向大家介绍几个类及一些小常识吧,希望能给大家带来帮助。

 

  • 大小: 42.4 KB
  • 大小: 15.6 KB
  • 大小: 35 KB
  • 大小: 17.8 KB
  • 大小: 32.2 KB
  • 大小: 29.8 KB
   发表时间:2011-06-29  
IoBuffer 接口

IoBuffer 是 MINA 中的独有接口,主要继承实现的是 java NIO 中的 ByteBuffer ,所以从使用方法上来看二者区别不大,唯一比较大的区别就是, IoBuffer 支持可变长的数据填充,对于这个类有三个关键属性,分别是

capacity( 容量 ) : 是它所包含的元素的数量。缓冲区的容量不能为负并且不能更改。 limit( 限制 ) :      是第一个不应该读取或写入的元素的索引。

position ( 位置 ) :    是下一个要读取或写入的元素的索引。

以上三个属性的值都不能为负,其关系遵守以下不变式:

            0<= 位置 <= 限制 <= 容量

在这里还要郑重的介绍三个方法分别是:

clear() : 使缓冲区为一系列新的通道读取或相对放置 操作做好准备:它将限制设置为容量大小,将位置设置为 0 。

flip() : 使缓冲区为一系列新的通道写入或相对获取 操作做好准备:它将限制设置为当前位置,然后将位置设置为 0 。

rewind() : 使缓冲区为重新读取已包含的数据做好准备:它使限制保持不变,将位置设置为 0 。

从本质上讲这三个方法都是对之前所介绍的三个属性进行操作,所以这里就会有一个误区,很多人从字面上解读 clear() 这个方法,以为这个方法就是将数据擦除。而实际上 clear 方法并不有擦除数据,仅仅是做了二件事,一是将 limit=capacity, 二是 position=0 ,这使得数据好像被清除,以便接收下一次的读写。但请大家千万注意 ,在写自定义解(编)码器时一定不要随便使用这些方法,请确保在对这些方法有了足够的认识后(最好的方法就是解读源码),才去使用它。特别是直接使用 clear() 时,一旦使用不当,在截取数据非常容易发生死循环的情况,因为前面已经说过, clear 并不是清除数据,而只是改变属性值,也就是说原数据依旧保留,这会导致某些时候会不停的重置这三个属性,去读同一段索引的数据,从而导致死循环。网上好多 demo 都是直接使用的 clear() ,其实都是存在一定隐患的,希望能看到这里的朋友能引起警惕 , 个人认为最好的清除方式就是人为的控制 Iobuffer 的三个属性值。 以下是参考代码 :



        int oldPos = in.position();

      int oldLimit = in.limit();

    ....... 开始 ..........

    处理数据

    ....... 结束 ..........

        int pos = in.position();

        in.limit(oldLimit);

    in.position(pos);

注: 以上代码仅适用于一次读取不完,需要从iobuffer 中多次读取的情况,具体情况请具体对待,核心思想就是操作iobuffer 的三个属性。
sendUrgentData() 方法的使用

这个方法的作用,只要是用过 socket 编程的童鞋都知道它的作用。没错,它就是通过发送一个紧急字符到服务端 (对 socket 来说实际上并不存在严格意义上的客户端或服务端,谁主动谁就是客户端) ,用来测试连接是否保持的一个方法。使用这个方法有二个限制。一是必须对方的 OS 支持该方法,二是要求服务端的 SO_OOBINLINE 为 false. 否则 服务 端将会把这个紧急字符认为是正常报文一并接收,而不是抛弃。反之当 SO_OOBINLINE 为 true 时,这个紧急字符仅仅用来确认通讯是否正常之用,服务端接收后会立刻抛弃不予处理的。默认情况下 SO_OOBINLINE 的值就是 false, 所以一般情况下,客户端直接用这个方法就可以测试连接是否保持了。

按理来说这个默认设置是个好事,但在笔者的项目开发中曾经因为这个 sendUrgentData () 被困扰了近一周的时间。事情的起因要从性能测试开始说起。

测试人员在测试过程中发现当前置机启动后有连接产生时就会让 CPU 占用率高居不下,开始笔者不是很在意,认为这个时间里有 IO 操作, CPU 高居不下很正常,后来进一步测试发生在没有数据发送的情况下 CPU 也会占到近 50% 左右,这个现象就很不正常了,于是折腾开始。

先是确认前置机的哪个部分会占用 cpu, 很快将目标锁定到了调度机,接下来对调度机进行代码排查,没有发生任何问题,头大了,再次进入 QQ 群讨论该问题,有人向我推荐了 JRMC (这也是我要向大家强烈推荐该工具的原因,网络的力量是强大的!!!嘿嘿。。。),通过这个监视工具,本人很快就再次缩小目标,将目标定位到一个叫 Ioprocesse-1 的线程上面,从名字及本文之前介绍的内容来看,很明显这个 MINA 框架内部线程导致的,随后就到网上查找是否有同类的现象,很遗憾本人可能是遇到一个前所未有的问题了,网络上提到使用 MINA 导致 CPU 占用率过高的内容几乎没有,无奈之下本人试着换 JDK 版本、 MINA 版本、甚至改写 MINA 源代码,一番折腾下来,结果是统统做了无用功。因为这个问题暂时不会影响测试和使用再加上时间过紧,后来就暂时将这个问题搁置了起来。某天,在开发的过程中笔者忽然想到:前置机的三个部分都是独立程序,通信机的接口也都是用 mina 改写的,为什么终端与通讯机没有这种现象发生呢?一番推理之下,本人反而将目标锁定到了占用率正常的通机机上面了,通过反复的排查,最终将问题锁定到了方法级,那就是 sendUrgentData 这个罪魁祸首。

本人试验发现,只要没有调用 sendUrgentData 方法所有一切都很正常, 但通信机 一旦调用 sendUrgentData 方法用来测试与调度机是否保持通信时,就会让调度机的 CPU 占用率瞬间飙升。但一个通信程序测试连接的方法是必不可少,而且暂时没有更好的能代替 sendUrgentData 的方法,所以就想着去改造 sendUrgentData 的源代码,结果一番跟踪下来才傻了眼,原来 sendUrgentData 方法 的底层实现是 native 类型( 注三 )根本就无从改起,最终本人将注意打到了调度机的解码器上面。代码比较多笔者就不帖了,只写上具体的解决步骤,有实际需求的,欢迎讨论。

步骤:

1 、在客户端(本例中客户端为通讯机)使用 sendUrgentData ()方法时,请设置一个报   文    中不可能出现的数字,本案例中使用的是 -16 。

2、  在服务端的处理类(即实现 IoHandlerAdapter 的类,为必须类)的 sessionCreated 方法内设置 SO_OOBINLINE 为 true. 代码如下:

  @Override

       public void sessionCreated(IoSession session) throws Exception {

              IoSessionConfig cfg = session.getConfig();

                if (cfg instanceof SocketSessionConfig) {

                 ((SocketSessionConfig) cfg).setOobInline( true );

                }

       }

3、  在服务端的解码器中将接受到的 -16 的字符全部手动抛弃(注意 IoBuff 三属性的重新设定)。

至此问题解决,但从解决方法来看,这个方法并不具有代表性,当碰到以下情况是并不一定适用:

情况一: 报文的内容本身有可能无所不包,那么 sendUrgentData 设置什么好呢??

情况二: 客户端的程序无法改动时,又该如何应对呢??

当然这个问题的终结解决方案并不在本文的讨论范围之类,这个很明显就是 mina 的一个 BUG ,不知道 2.04 的版本有没有解决这个问题,但从官网所贴的问题列表来看,这个 BUG 应该是依旧存在的,奈何笔者 E 文比较烂,看看资料还可以,要我动手写,并将以上内容用 E 文表达出来,确实是没有那个勇气的,希望能看到这一段的童鞋能代劳一、二,督促官方早日改好这个 BUG ,咱也算是为开源软件尽了一份力不是? ^_^

有的童鞋看到这里可能会说笔者是不是太啰嗦了,几句话可以解决的事情讲这么多,这里我想强调一下的是,本文 的核心内容 主要讲的还是使用心得, 而 不是使用方法,在这里之所以把 解决 过程讲得这么详细,还是希望达成二点目的。

1 、笔者希望通过对过程详细的描述,来提醒大家。做咱们这一行的不可能不遇到问题,但遇到问题首要的是先缩小范围,再确定范围,实际确定不下来的,不妨跟同行讨论一番,如果还确定不下来,咱就暂时放一放,等脑子静下来以后,把思路跳出来,反向追踪,说不定问题的表象并不是问题发生的缘原呢?(这话有点拗口,不做解释。。。。   -_-!! )在本文中就是一个很明显的例子,明明是调度机表现出来的 CPU 占用过高,但导致这一现象发生的却是在通讯机上了。

2 、再次推荐 JRMC, 这东西真的很好用。独乐乐不如众乐乐~~   什么?你已经知道了?知道了,你怎么不告诉我?你真是坏啊。。。。。。哦 ~ 漏说了一句 , 这个工具是 Oracle JRockit JDK 自带的,据说这个 JDK 的效率能比普通 JDK 的效率高上 2%-10% 非常适合在生产环境下使用。 什么??你还是知道了?? 你,你,你。。。。。。(吐血 ing.... )

当然了,群众的力量是无穷的,大家嫌我太罗嗦的话,下面的内容我就简单一点吧。



注三: 所谓的 native 类型的方法,是 JAVA 中的一种特殊方法,凡是标注这个关键字的方法,其本身并没有任何实现代码,最终的运行都是通过虚拟机调用 OS 的底层的方法来运行的。
Concurrent 包下的一些类

这里主要推荐二个集合类,分别是 ConcurrentLinkedQueue , ConcurrentHashMap 这二个类的使用基本上不需要你考虑是不是多线程的编程,也不需要用锁,可以大幅提高并发量过大时的对像存取,至于实现机制劳驾大家自己去 GOOGLE 一把吧。
结尾语

文章到这里基本上算是结束了,而项目最终的并发量从 2680 提升至 3.5W, 客户要求的是 2W 的并发量,所以后面更高的并发没有继续再测,至本文截稿为止,最新的消息是项目的一期工作已经顺利通过了国家级机构的评测。但实质上本文关于 MINA 框架还有很多东西没有涉及到,比如说与 Spring 的结合,对 Jmx 的支持,自定义协议详细举例等等,但我想这点小困难应该不会妨碍大家对 mina 的学习热情吧!感兴趣的童鞋不妨把它当成一个课后作业来做做吧 !!  ^_^
0 请登录后投票
   发表时间:2011-06-29  
恩.心得不错.
我以前也碰到一个问题.那就是messageReceived中
iosession.write(respMsg);

iosession.close(false);//等socket发送完毕再关闭socket连接

如果运行的数据小,一切正常,如果一旦运行大的数据,就出现丢包问题.
经过试验发现是
iosession.close(false);//等socket发送完毕再关闭socket连接

并不是MINA官方说的,等socket发送完毕再关闭socket连接,而且会出现发送中关闭socket.
iosession.close(true);为立即关闭socket..晕晕的
0 请登录后投票
   发表时间:2011-06-29  
用带外数据来测试连接保活,我还是第一次见到,一般都是定义个应用层的心跳协议来做,没必要用带外数据搞。
0 请登录后投票
   发表时间:2011-07-01  
dennis_zane 写道
用带外数据来测试连接保活,我还是第一次见到,一般都是定义个应用层的心跳协议来做,没必要用带外数据搞。

对,在应用层实现心跳协议...如果客户端也是java的话,好像MINA本身就有一个心跳Filter
0 请登录后投票
   发表时间:2011-07-07  
saturn 写道
恩.心得不错.
我以前也碰到一个问题.那就是messageReceived中
iosession.write(respMsg);

iosession.close(false);//等socket发送完毕再关闭socket连接

如果运行的数据小,一切正常,如果一旦运行大的数据,就出现丢包问题.
经过试验发现是
iosession.close(false);//等socket发送完毕再关闭socket连接

并不是MINA官方说的,等socket发送完毕再关闭socket连接,而且会出现发送中关闭socket.
iosession.close(true);为立即关闭socket..晕晕的



你的写法会不会有问题呢? iosession.close这个方法你使用的时候请注意,因为有多线程的关系,很可能线程一还在使用的时候,线程二就把他关了。这样当然会导致掉包了。所以如果你想做到发送完毕后,再关闭socket,请确认你的多线程的使用方面没有问题。而且我记得有资料上面说iosession.close在MINA框架中并不是真的关闭.不知道我是不是记错了,请指正。
0 请登录后投票
   发表时间:2011-07-07   最后修改:2011-07-07
saturn 写道
dennis_zane 写道
用带外数据来测试连接保活,我还是第一次见到,一般都是定义个应用层的心跳协议来做,没必要用带外数据搞。

对,在应用层实现心跳协议...如果客户端也是java的话,好像MINA本身就有一个心跳Filter



这个怪我没有说清楚,因为篇幅有限,所以对项目的描述不是非常清析。请注意看项目的结构图,其中终端与通信机的连接已经是通过心跳机制来完成“测试连接活动”了。而通信机与调度机之间,在当初设计的时候,他与通信机的连接,有二个,一个是用来传输有用的数据帧的,还有一个是用来传输终端状态的。而终端状态的改变(即上下线)非常频繁。考虑到性能问题。所以这二个连接都没有用心跳协议,而是用sendUrgentData 来测试连接是否通畅。再说了,为了保证重要数据的不丢失,我的做法如下:
1.将所有数据入栈
2.传输前出栈,赋值给变量A
3.测试连接是否通畅
4.连接通畅、发送A报文
5.连接不通畅,将A再次压入栈内,尝试再次连接,直到重新连接

所以说 “用带外数据来测试连接保活”这个应该很常见的。因为一般的socket项目中sendUrgentData方法本身就是用来测试连接,不过大家习惯性的写0或1,但我这里出于需要改为-16而已。
如果所有的连接保活都是必须通过心跳来完成,我想 sendUrgentData这个方法也没出现的必要了,再说一个心跳过去,再回一个确认帧,这不也是一种资源浪费吗?
所以个人认为如果在客户端和服务端都可控,并且对sendUrgentData方法支持的话,“连接保活”直接就用sendUrgentData方法就可以了。 心跳机制只是适用性好而已,基本上能适应任何环境(不需要管客户端或服务端是否为同一个人所写,也不需要管sendUrgentData是否被支持),就性能和使用方便度上来讲,优势并不明显。
所以在合适的环境里使用sendUrgentData是要优于心跳机制的。

另外,
引用

对,在应用层实现心跳协议...如果客户端也是java的话,好像MINA本身就有一个心跳Filter

这个心跳filter能否给提示一下?学习时间太紧了,API从来就没细读过。
0 请登录后投票
   发表时间:2011-07-08  
我在IO编程里还真没有用过带外数据,看帖之后专门写了个简单的程序。
在Java里,我没有找到专门用于接收紧急数据的API,如果要接收只能sock.setOOBInline(true);,但和正常数据混在一起的方式真的不能接受。
程序的客户端用BIO发送10个字节的正常数据,然后发送1个紧急数据,sleep5秒后再发10个字节的正常数据。
服务器端有3中模式:BIO、JavaNIO阻塞、JavaNIO非阻塞。只有在JavaNIO非阻塞模式在setOOBInline(false)的时候出现了你在文章中介绍到得异常情况,Selector.select方法总是立即返回,并产生OP_READ事件,但SocketChannel读不到任何数据。在随后的5秒,Selector就这样循环了一百多万次,直到5秒后客户端的后继数据到来。
如果我的测试代码没有问题的话,你冤枉MINA了,这是JavaNIO的问题。

import java.io.OutputStream;
import java.net.Socket;


public class Client
{
    public static void main(String[] args) throws Exception
    {
        Socket sock = new Socket("localhost",Server.PORT);
        sock.setOOBInline(true);
        OutputStream out = sock.getOutputStream();
        for(int i=0;i<10;i++) out.write(i);
        sock.sendUrgentData(0x7f);
        Thread.sleep(5000);
        for(int i=0;i<10;i++) out.write(i);
        sock.close();
    }

}



import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;

public class NIOServer extends Thread
{
    final SocketChannel sock;
    
    NIOServer(SocketChannel sock)
    {
        super();
        this.sock = sock;
    }

    public static void main(String[] args) throws Exception
    {
        ServerSocketChannel srv = ServerSocketChannel.open();
        srv.socket().bind(new InetSocketAddress(Server.PORT));
        for(;;)
        {
            SocketChannel sock = srv.accept();
            new NIOServer(sock).start();
        }
    }
    public void run()
    {
        this.noneBlockingMode();
    }
    public void noneBlockingMode()
    {
        Selector selector = null;
        try
        {
            selector = Selector.open();
            sock.configureBlocking(false);
            sock.socket().setOOBInline(false);
            sock.register(selector,SelectionKey.OP_READ);
            for(int i=0;;i++)
            {
                int r = selector.select();
                //System.out.println(i + "\tselect returned " + r);
                for(SelectionKey key : selector.selectedKeys())
                {
                    //System.out.println("selected ops = " + key.interestOps());
                }
                selector.selectedKeys().clear();
                ByteBuffer buf = ByteBuffer.allocate(1);
                r = sock.read(buf);
                //System.out.println("socket read returned " + r);
                if(r<0)
                    break;
                if(r>0)
                {
                    buf.flip();
                    System.out.println(i + "\treceived byte value " + buf.get());
                }
            }
        }
        catch(Exception ex)
        {
            
        }
        try
        {
            if(selector != null)
                selector.close();
        }
        catch(Exception _)
        {
            
        }
        try
        {
            sock.close();
        }
        catch(Exception _)
        {
            
        }
    }
    public void blockingMode()
    {
        try
        {
            System.out.println("recv data at " + sock);
            sock.socket().setOOBInline(false);
            for(int i=0;;i++)
            {
                ByteBuffer buf = ByteBuffer.allocate(1);
                int r = sock.read(buf);
                if(r<0)
                    break;
                buf.flip();
                System.out.println(buf.get());
            }
        }
        catch(Exception ex)
        {
            ex.printStackTrace();
        }
        try
        {
            sock.close();
        }
        catch(Exception _)
        {
            
        }
    }
}

0 请登录后投票
   发表时间:2011-07-08  
另外请教楼主,用MINA管理两万多个连接,用了多少个Selector?有没有做过在实际运行环境当中针对不同Selector数量在性能方面的比较?
0 请登录后投票
   发表时间:2011-07-08  
请问一下LZ, 3.5W并发是单台服务器,还是多台服务器? 
单台服务器并发大概多少?
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics