多线程高性能 7x24小时 Java NewIO 程序 小结

本文介绍了作者使用Java NIO、多线程和TimerTask实现的一个7x24小时稳定运行的SocketTCP通讯系统,用于与C++采集服务器交换DVB相关数据。系统具有高可用性和自恢复能力,遇到异常时能自动重连。总结了在实现过程中需要注意的关键点,包括资源关闭、线程管理、ByteBuffer的使用等。
摘要由CSDN通过智能技术生成

//2008-04-29 09:02

最近一年以来,我主要完成了一个多线程高性能的7x24小时基于SocketTCP通讯的系统. 这一个月在最后发布前调试,感慨颇多.

首先介绍下需求:

就是我们这边和一台采集电视PSI/SI的服务器(C++实现)相连,通过自定义的协议来进行通讯(通讯建立在TCP Socket的连接上),根据请求的消息返回给我们DVB相关的表,如NIT,PAT,PMT,AIT,SDT,EIT等,我们进行数据分析打包提供给上层web应用程序使用. 我们的程序运行在tomcat底下,tomcat在启动时开启我们的线程.由于另一个部门的采集服务器十分不稳定,经常出现死机,重启,消息头错误,无消息反应,突然中断连接等问题,他们部门的人又找不到问题原因所在,所以我们这边一定要保证我们程序的稳定性,重连性,出现错误的自恢复性等,也就是说,只要我的程序一开,就不能死掉,出现任何问题都能自动校正,超级强壮.

技术方面:

我的程序使用的JDK1.4.x版本,使用了nio的非阻塞模式读写ByteBuffer,Timer和TimerTask来定时发送消息,Thread,同步,Observer/Observable等技术.

具体实现简介:

1.总体宏观上有一个线程(名称为:Portal),是程序的入口,实现observer接口,如果发现observable的update()参数为ServerSuddenlyShutdownException(继承自RuntimeException)异常消息,则停止线程EveryTime,等待一段时间后重新启动线程EveryTime.本个线程永不死,除非用户关闭.

2.重连的程序线程(名称为:EveryTime),每次线程Portal发出开启消息,都会创建一个这个线程的新实例,这个线程负责检测服务器是否能成功连接,如果服务器能正确连接,则发送我们需要的频点xml列表(列表中标明需要的频点信息和频点PSI/SI表的配置情况),并且会对每一个频点打开一个新的线程Frequency. 线程Portal发出关闭消息时,则负责将它所开启的线程Frequency们关闭.

3.每个频点的线程(名称为:Frequency),这个线程会按照xml配置文件里对PSI/SI的配置来进行请求数据,并且把数据保存在一个静态的DvbDataHolder结构相应的频点信息里. 由于PSI/SI里面的各个表,会定期变化,所以在一定时间后要重复对其请求确认是否数据有变化(使用Timer和TimerTask来实现会很简单方便).

4.线程Freqnecy负责实例Timer定期发送TimerTask中的请求消息;并且启动一个新线程ReceivedAnalyzer对收到的消息进行处理. 创建一个继承TimerTask的基础类,将Observable放入其中,线程ReceivedAnalyzer中也创建一个Observable,这样可以通过Obersable向上层提供反馈.

5.当最底层的socketchannel在读写中出现IOException时,则一般出现了Socket的异常情况,在我们的程序中,多数为采集服务器莫名其妙的挂掉了.则程序向上层发出notify()事件,提供ServerSuddenlyShutdownException异常. 主线程会停止下属线程对服务器进行重新连接.

实现简单来说就是这样,实际还有很多细节不多说打字费事.  

 

------------ 你好, 我是美丽的分割线. ------------

 

经验总结(最值得看的部分):

1.一定要记住在finally中关闭stream和channel和selector. 虽然在windows下测试没有问题,但往往一放到linux下,就会出现错误,叫做Too many open file 或者其他的异常名称.

2. 如果某些线程的存在就是为了启动后不再去管理它(除了关闭时),让他自动顽强无论什么情况都要正确运行下去,那么在线程中初始化资源,则一定要这样初始化,谁也不能料到现实中会发生什么事情:

public void run()

  while( !this.isInitReady && this.isRunning) 

  {   

    //初始化必要非常重要的资源 (如socketChannel连接等)

    this.isInitReady = true; 

  } 

  while(this.isRunning) 

  {   

    //去做你要做的事情吧 

  }

}

3.ByteBuffer非常好用,尤其是mark和reset,如果需要来回读写,则省去了byte[]这类的下标示符来回计算.

4.TCP协议的socket流读取数据,如果已知数据长度,并且数据很长,则也许数据不会一次到达完毕(或许是网络不太好的原因,也或许是数据分了很多包传输重组需要时间),需要多次读取:

ByteBuffer contentBuffer = ByteBuffer.allocate(dataLength);

while(contentBuffer.hasRemaining())

    socketchannel.read(contentBuffer);

}

写数据也是如此:

contentBuffer.flip();

while(contentBuffer.hasRemaining())

    socketchannel.write(contentBuffer);

}

5. 如果对socketchannel的读写在同一个线程中(我的程序不是这样的),则最好不要注册写状态SelectionKey.OP_WRITE,因为它几乎是时时可行的,如果这样做了以后,你会发现,在while循环里,写状态每次都返回准备完毕,占用大量CPU. 只用注册读状态即可:SocketChannel.register(selector, SelectionKey.OP_READ).

6.在java nio包中, socketChannel是线程安全的,可多线程使用. ByteBuffer是非线程安全的,注意存取.

 

finally,简单写完啦,暂时这么多,语句不知道通顺不通顺. 哈哈哈.

//10:59

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值