NIO中几个非常重要的技术点
参考:http://ifeve.com/selectors/
参考:https://www.ibm.com/developerworks/cn/education/java/j-nio/j-nio.html
netty的NioEventLoop类的实现也是类似
这些都是在实践中踩过雷的,今天某应用再次踩雷,把遇到的几个雷都收集一下,给后来者参考。
1.即使是accept事件,没有真正的read和write,Channel也要关闭,否则unix domain socket会被泄漏(WINDOWS更可怕),因为NIO的每个
Channel上都有两个FD用来监听事件(接收和发送走不同的FD)。
2.cancel事件导致CPU占用100%,http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6403933
其原因就是调用key.cancel()时底层在下一次seelect前并没有真正的取消。导致等待select事件返回却又没有返回我们注册的key.这个事件不断地
循环触发,CPU一直处理返回 key为0的select()调用。解决方法有两种,一是在key.cancel()后立即selectNow();但是如果是多线程并发操作,有
可能这两行语句中间线程被切换,使得key.cancel()后没有立即执行 selectNow().这在多Selector情况下是可能的。另一种就是jetty处理方式,如果
select()返回0且连续几次出现这样的情况(有事件触发返回,却不是返回我们注册的KEY),就将有效的key重新注册到一个新的selector上。其实
glassfish在处理多次次次次write返回为0的情况时也是这种策略。
示例代码:(真实的项目中)
int selectTimeout = connectionConfig.getSelectTimeout();
int allProcessMaxTime = connectionConfig.getAllProcessMaxTime();
//selector在实现时有bug,epool底层可能会发送一个错误的信号导致select方法提前返回,但没有
//返回注册的事件,而且不断循环造成CPU100%
int slelectZeroCount = 0;
int maxZeroCount = 20;
int fixed = 0; while (selector.isOpen() && selector.keys().size() != 0 && allProcessMaxTime > 0) {
long start = System.currentTimeMillis();
// 查询看是否有已经准备好的通道,指定超时时间
int count = selector.select(selectTimeout); if (count == 0) {
slelectZeroCount++;
} else {
slelectZeroCount = 0;
//保证是连续的count==0时才将slelectZeroCount++,如果其中有一次返回注册事件测已经正常
}
if (slelectZeroCount > maxZeroCount && fixed == 0) {
//没有尝试修复动作,则先进行修复干预
for (SelectionKey key : selector.keys()) {
if (key.isValid() && key.interestOps() == 0) {
key.cancel();
}
}
fixed = 1;
} else if (slelectZeroCount > maxZeroCount && fixed == 1) {
//如果已经干预过仍然连续返回0,注意如果不返回0的话slelectZeroCount就被置0.
//重新获取一个selector,将当前事件重新注册到新的selector上。并销毁当前selector
Selector newSelector = this.getSelector();
this.changeSelector(selector, newSelector);
selector = newSelector;
}
//对channel进行正常处理
重新注册的代码:
private synchronized void changeSelector(Selector oldSelector, Selector newSelector) {
for (SelectionKey key : oldSelector.keys()) {
if (!key.isValid() || key.interestOps() == 0) {
continue;
}
Object att = key.attachment();
try {
if (att == null) {
key.channel().register(newSelector, key.interestOps());
} else {
key.channel().register(newSelector, key.interestOps(), att);
}
} catch (ClosedChannelException e) {
SocketChannel sc = (SocketChannel) key.channel();
sc.close();
}
}
try {
oldSelector.close();
} catch (IOException e) {
logger.error(e.getMessage());
} }
同样对于网络状态不好时,连续写操作返回0的处理:
private void flushData(Selector selector, SocketChannel socketChannel, ByteBuffer byteBuffer)
throws IOException { int count = 0;
int maxCount = 20; while (byteBuffer.hasRemaining()) {
int len = socketChannel.write(byteBuffer);
if (len < 0) {
throw new EOFException("write channel is closed.");
}
// 如果不对len==0(即当前网络不可用)的情况处理,则while(byteBuffer.hasRemaining())可能一直
// 循环下去而消耗大量的CPU.
if (len == 0) {
count++;
} else {
count = 0;
}
if (count > maxCount) {
throw new IOException("can't connect to target.");
}
} }
我自己写的代码:
package com.eshore.ismp.hbinterface.crm; import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext; import com.eshore.ismp.hbinterface.service.BizCommonService; /**
* @author mercy
*接收CRM优惠工单信息
*/
public class CrmServerTest {
private static final Logger logger = LoggerFactory.getLogger(CrmServerTest.class);
private Selector selector=null;
private ServerSocketChannel serverSocketChannel=null;
private int port=10003;
//private Charset charset=Charset.forName("GBK");//返回一个字符类型对象
public CrmServerTest() throws IOException{
serverSocketChannel=ServerSocketChannel.open();
serverSocketChannel.socket().setReuseAddress(true);
serverSocketChannel.configureBlocking(false);//设置无阻塞模式
serverSocketChannel.socket().bind(new InetSocketAddress(port));
logger.info("服务已启动...");
}
public void start(BizCommonService bizCommonService) throws IOException{
buildSelector();
this.service(bizCommonService);
}
public void buildSelector()throws IOException{
logger.info("构建selector");
selector=Selector.open();
//创建Selector对象
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
}
//重建selector
public void rebuildSelector(){
logger.info("重建selector");
Selector oldSelector=selector;
if (oldSelector == null) {
return;
}
try {
selector=Selector.open();
} catch (IOException e1) {
e1.printStackTrace();
}
int nChannels = 0;
for (SelectionKey key: oldSelector.keys()) {
//返回key的附加对象
Object a = key.attachment();
try{
if (!key.isValid() || key.channel().keyFor(selector) != null) {
continue;
}
int interestOps = key.interestOps();
key.cancel();
key.channel().register(selector, interestOps, a);
//就不再更新通道的selectKey
nChannels ++;
} catch (Exception e) {
logger.warn("Failed to re-register a Channel to the new Selector.", e);
}
}
try {
// time to close the old selector as everything else is registered to the new one
oldSelector.close();
} catch (Throwable t) {
if (logger.isWarnEnabled()) {
logger.warn("Failed to close the old Selector.", t);
}
}
logger.info("Migrated " + nChannels + " channel(s) to the new Selector.");
}
public void service(BizCommonService bizCommonService) throws IOException{
int start=0;
while(true){
if(selector.select()==0){
start++;
logger.info("continue....");
continue;
}
if(start==5000){
start=0;
logger.info("重建selector");
rebuildSelector();
}
Set readyKeys=selector.selectedKeys();
Iterator it=readyKeys.iterator();
while(it.hasNext()){
SelectionKey key=null;
try{
key=(SelectionKey)it.next();
it.remove();//删除集合中的key
if(key.isAcceptable()){//是否可以接收客户端的socket连接
ServerSocketChannel ssc=(ServerSocketChannel)key.channel();
SocketChannel socketChannel=ssc.accept();
//logger.info("接收到的客户端连接,来自:"+socketChannel.socket().getInetAddress()+":"+socketChannel.socket().getPort());
socketChannel.configureBlocking(false);//设置无阻塞模式
ByteBuffer buffer=ByteBuffer.allocate(6000);//创建一个ByteBuffer对象用于存放数据(数据存放缓冲区)
socketChannel.register(selector,SelectionKey.OP_READ|SelectionKey.OP_WRITE,buffer);//注册事件,Selector会监控事件是否发生
}
if(key.isReadable()){//key的channel是否可读
receive(key);
}
if(key.isWritable()){//key的channel是否可写
send(key,bizCommonService);
}
}catch(IOException e){
logger.info(" crm exception....");
e.printStackTrace();
try{
if(key!=null){
key.cancel();
key.channel().close();
selector.selectNow();
}
}catch(Exception ex){
ex.printStackTrace();
}
}
}
} }
/**
* @param key
* @throws IOException
* @author mercy
* 根据读取的数据处理完返回给客户端
*/
public void send(SelectionKey key,BizCommonService bizCommonService) throws IOException{
ByteBuffer buffer=(ByteBuffer) key.attachment();
SocketChannel socketChannel=(SocketChannel) key.channel();
buffer.flip();
String data=decode(buffer);//解码客户端发过来的数据
if(data.length()==0){
return ;
}
String outputData=data;//.substring(0, data.indexOf("\n")+1);
//logger.info("客户端发送的数据:"+outputData+",length:"+outputData.length());
//String reply="FFFF02141433570200012400050301IBSS01662 001023CS0214143357*0189086510002001100301420170214143500004003099005007success";
//ByteBuffer outputBuffer=encode("echo:"+reply);//返回给客户端的数据
boolean result = bizCommonService.sendOperToCacheAysn(String.valueOf(outputData));
//创建响应报文
String res = bizCommonService.createResponseStr(String.valueOf(outputData),result);
ByteBuffer outputBuffer=encode(res);//返回给客户端的数据
while(outputBuffer.hasRemaining()){
//System.out.println("=="+decode(outputBuffer));
socketChannel.write(outputBuffer);
}
ByteBuffer temp=encode(outputData);
buffer.position(temp.limit());
buffer.compact();
if(outputData.length()==0){
key.cancel();
socketChannel.close();
logger.info("关闭与某客户端的连接");
}
}
/**
* @param key
* @throws IOException
* @author mercy
* 读取客户端发来的数据
*/
public void receive(SelectionKey key) throws IOException{
ByteBuffer buffer=(ByteBuffer) key.attachment();
SocketChannel socketChannel=(SocketChannel) key.channel();
ByteBuffer readBuffer=ByteBuffer.allocate(6000);//创建自定义内存的buffer(存放读到的数据)
socketChannel.read(readBuffer);
readBuffer.flip();
buffer.limit(buffer.capacity());//设置buffer的极限为buffer的容量
buffer.put(readBuffer);//复制到缓存区
}
public String decode(ByteBuffer buffer){//解码
CharBuffer charBuffer=Charset.forName("GBK").decode(buffer);
return charBuffer.toString(); }
public ByteBuffer encode(String str){//编码
return Charset.forName("GBK").encode(str);
}
public static void main(String[] args) throws IOException {
final Logger log = LoggerFactory.getLogger(CrmServerTest.class);
try{
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
new String[] { "applicationContext.xml" });
context.start();
BizCommonService bizCommonService = (BizCommonService) context.getBean("bizCommonService");
new CrmServerTest().start(bizCommonService);
}catch(Exception e){
log.error("start agent interface server error:",e);
System.exit(-1);
}
}
}
一般来说
while(true){
if(selector.select()==0){
start++;
logger.info("continue....");
continue;
}
没有
selector.select()>0
好,因为万一 selector.select() 不阻塞了返回0用while(true)会反复执行 if(selector.select()==0) 这一段
NIO中几个非常重要的技术点的更多相关文章
- NIO中的ZeroCopy
前文提到网络IO可以使用多路复用技术,而文件IO无法使用多路复用,但是文件IO可以通过减少底层数据拷贝的次数来提升性能,而这个减少底层数据拷贝次数的技术,就叫做ZeroCopy. 操作系统层面的Zer ...
- Java I/O(3):NIO中的Buffer
您好,我是湘王,这是我的博客园,欢迎您来,欢迎您再来- 之前在调用Channel的代码中,使用了一个名叫ByteBuffer类,它是Buffer的子类.这个叫Buffer的类是专门用来解决高速设备与低 ...
- Java I/O(4):AIO和NIO中的Selector
您好,我是湘王,这是我的博客园,欢迎您来,欢迎您再来- 在Java NIO的三大核心中,除了Channel和Buffer,剩下的就是Selector了.有的地方叫它选择器,也有叫多路复用器的(比如Ne ...
- JAVA NIO中的Channels和Buffers
前言 Channels和Buffers是JAVA NIO里面比较重要的两个概念,NIO正是基于Channels和Buffers进行数据操作,且数据总是从Channels读取到Buffers,或者从Bu ...
- 分享MYSQL中的各种高可用技术(源自姜承尧大牛)
分享MYSQL中的各种高可用技术(源自姜承尧大牛) 图片和资料来源于MYSQL大牛姜承尧老师(MYSQL技术内幕作者) 姜承尧: 网易杭州研究院 技术经理 主导INNOSQL的开发 mysql高可用各 ...
- NIO中Selector分析
NIO中,使用Selector.select()方法来侦听是否有数据可以读/写,服务端开始执行时,如果没有客户端,这里的语句将进行阻塞,等待下面三个情况出现,才会进行后续的方法之行,这里是重点 ...
- [翻译]比较ADO.NET中的不同数据访问技术(Performance Comparison:Data Access Techniques)
Performance Comparison: Data Access Techniques Priya DhawanMicrosoft Developer Network January 2002 ...
- 在C#中实现Python的分片技术
在C#中实现Python的分片技术 前言 之前在学习Python的时候发现Python中的分片技术超好玩的,本人也是正则表达式热爱狂,平时用C#比较多,所以决定把Python中的分片技术在C#中实现, ...
- Java NIO中核心组成和IO区别
1.Java NIO核心组件 Java NIO中有很多类和组件,包括Channel,Buffer 和 Selector 构成了核心的API.其它组件如Pipe和FileLock是与三个核心组件共同使用 ...
随机推荐
- sed n/N使用说明
sed的语法格式: sed [option] {sed-command} {input-file} sed在正常情况下,将处理的行读入模式空间(pattern space),脚本中的“sed-comm ...
- MFS排错
[root@Nginx_Master mfs]# /app/server/mfs/sbin/mfsmaster start working directory: /app/server/mfs/var ...
- Ubuntu下安装RabbbitVCS(图形化svn管理工具)- Ubuntu也有TortoiseSVN
在Windows下用惯了TortoiseSVN这只小乌龟,到了Ubuntu下很不习惯命令行的SVN,于是经过一番寻找安装了RabbitVCS这款SVN图形化前端工具(官方网站:http://rabbi ...
- FreeRtos——移植
现在准备的简单程序LED灯的工程目录中增加freertos文件夹: 在 source目录下的portable目录下只留下下面的文件夹: 为什么呢? 把对应文件移植在工程中之后,添加头文件路径如下图: ...
- getAttribute()方法
http://www.imooc.com/code/1587 getAttribute()方法 通过元素节点的属性名称获取属性的值. 语法: elementNode.getAttribute(name ...
- 基于html5和jquery的篮球跳动游戏
今天给大家分享一款基于html5和jquery的篮球跳动游戏.这款实例和之前分享的HTML5重力感应小球冲撞动画类似.用鼠标拖动篮球,篮球在页面上跳动,可在演示中看下效果.效果图如下: 在线预览 ...
- MapReduce原理<转>
江湖传说永流传:谷歌技术有"三宝",GFS.MapReduce和大表(BigTable)! 谷歌在03到06年间连续发表了三篇很有影响力的文章,分别是03年SOSP的GFS,04年 ...
- PHP——注册页面,审核页面,登录页面:加Session和Cookie
实现效果: 用户注册信息,管理员核对信息审核通过后,可实现注册的用户名和密码的成功登陆,利用session和cookie获取用户信息并且不能跳过登录页面直接进入主页面 1.Session存储在服务器可 ...
- 示例 - 如何在Console应用程序中应用SpiderStudio生成的DLL?
以前面生成的XML/JSON互转DLL为例, 我们写一个Console Appliction来做这件事情, 步骤如下: 1. 创建Console Project 2. 引入www.utilities_ ...
- MySQL数据库运维的五大指标
如何评价一个公司数据库运维水平的高低?用什么来进行横向与纵向对比?自动化平台建设的目标是什么?必须有相应的指标体系来指导,此指标体系必须满足以下条件: • 可以用数字来测算和衡量 • 最终指标,而不是 ...