NIO的一坑一惑小记
前言
不知不觉,已那么长时间没有更新东西了,说来真是汗颜啊。(主要是最近在技术上豁然开朗的感觉越来越少了-_-|||)
最近一直在学习Linux相关的东西。又一次接触到了I/O复用模型(select/poll/epoll),由于好久没在用NIO写过代码了,今天就小试写个例子,以巩固下对I/O复用模型的理解。这不,遇到了一个坑,也产生了一点疑惑。^_^。
一坑
简单描述:Selector的select方法返回的key集合中有一个SelectionKey是可读的,但是调用与此SelectionKey关联的channel的read方法,总是返回读取长度是-1。既然返回-1,可以说明tcp链接已经断开。在下次调用select方法不应再返回这个SelectionKey,也不应该此SelectionKey是可读状态的。但事实并非如此:
public class NIOMain { public static void main(String[] args) throws Exception {
Selector selector = Selector.open();
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
serverChannel.socket().bind(new InetSocketAddress(9000), 10);
serverChannel.register(selector, SelectionKey.OP_ACCEPT); doSelect(selector); } public static void doSelect(Selector selector)throws Exception{
while (true) {
int srt=selector.select();
if(srt<=0){
continue;
}
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> iter = keys.iterator(); while(iter.hasNext()){
SelectionKey key = iter.next();
if(key.isAcceptable()){
ServerSocketChannel sChannel= (ServerSocketChannel) key.channel();
SocketChannel cChannel = sChannel.accept();
cChannel.configureBlocking(false);
cChannel.register(selector, SelectionKey.OP_READ);
}else if(key.isReadable()){
SocketChannel cChannel = (SocketChannel) key.channel();
ByteBuffer bb = ByteBuffer.allocate(1024);
int len =cChannel.read(bb);
bb.flip();
if(bb.hasArray() && len>0){
System.out.println("from client "+":"+ new String(bb.array(),0,len));
int newInterestOps = key.interestOps();
newInterestOps |= SelectionKey.OP_WRITE;
key.interestOps(newInterestOps);
}else if(len==-1){
System.out.println("no data");//在这里不能忘记关闭channel
} bb.clear();
}
iter.remove();
}
}
} }
运行此代码,然后在浏览器里输入127.0.0.1:9000,回车。结果是控制台里首先打印出http协议的信息。然后就是死循环打印no data。原因可想而知,浏览器在发起http请求后,一定时间没有得到服务器端的相应,便会断开tcp链接。此时channel的read方法就会返回-1。坑的是,链接都已经断开了,Selector还能将它select出来,并且一直是可读状态。这就导致了一直死循环打印no data。如果这种事情发生在生产环境,后果真是不堪设想啊。
解决方式虽然比较简单,但却不能疏忽遗漏。当channel的read方法返回-1时。调用channel的close方法关闭channel。上边代码就是在打印no data的地方添加一行:cChannel.close()。这样channel对应的SelectionKey也就不会再被select出来了。也就不再发生死循环了。
一惑
NIO编程中我一直有一个疑惑或者说不确定,就是什么时候调用channel的write方法将数据返回给客户端。
在网上看到的一些例子代码中无非两种。
- 直接返回---服务器端读取到客户端发过来的数据后,直接调用channel的write方法将数据返回给客户端。
- 注册Writable事件,可写事件发生后再返回---服务器读取到客户端发来的数据后,然后将channel注册到selector对Writable感兴趣。当可写后,再调用channel.write写数据。但这个方式一定得注意:当写完数据后,一定取消对Writable事件的感兴趣。否则服务器又得忙到崩溃。
这两个方式似乎都可以工作,跑一些例子也都没发现什么问题。但是心里总是感觉有一点不够明确不够开朗(可能就是因为对系统底层的实现不够明确的原因)。Java有一些成熟的开源的NIO框架,比如netty、mina。何不去看看他们是如何处理的呢?好,接下来就看看mina的实现方式。(我这里看的是mina2.0.2版本)
接下来是我追踪到AbstractPollingIoProcessor的flushNow方法的代码
由于篇幅就不贴上writeBuffer方法的全部代码,其关键调用:,writeBuffer方法也是将write方法返回的localWrittenBytes返回。接下来让我们抓紧看看write方法的实现吧。并看看到底返回的是什么东西
抛开其他的细节不管,咱们先看看如何实现向客户返回数据的,mina直接从session中拿到关联的SocketChannel,然后直接调用SocketChannel的write方法写数据到客户端,并将write写出去数据的长度记录下来。
让我们返回到最开始flushNow方法:
可以看到,当channel写出去的数据长度大于零,并且buff里还有数据要写时。调用了setInterestedInWrite方法,通过方法名也知道是在注册对写事件感兴趣是吧,看下代码明确下吧
没错,确实是在注册对写事件感兴趣。在flushNow方法后边还有一个对localWrittenBytes等于零的判断:
通过源代码里的注释,就知道,当localWrittenBytes等于零时,也就是调用channel的write没有写出任何数据,此时就是内核的Buufer满了,是不可写状态。所以这里也调用setInterestedInWrite方法注册可写感兴趣,以待可写事件发生后再发送数据到客户端。
总结一下mina的实现就是:读取到客户端请求的数据后,就调用channel的write方法向客户返回数据,如果channel的write方法没有把所要返回的数据全部发送完,就注册对可写感兴趣,以待下次可写事件触发时再继续发送。
就写到这吧,有啥说的不清楚,说的不准确的地方,还望高手不吝指教(*^__^*) ……
NIO的一坑一惑小记的更多相关文章
- Ubuntu 16.04 安装Mysql 5.7 踩坑小记
title:Ubuntu 16.04 安装Mysql 5.7 踩坑小记 date: 2018.02.03 安装mysql sudo apt-get install mysql-server mysql ...
- 支付宝使用流程和踩坑小记(附Demo)
# 支付宝使用整理 html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym, ...
- springcloud采坑--Zuul上传文件报java.nio.charset.IllegalCharsetNameException: UTF-8;boundary=sqgzzmMxl1UPdIp0IAYnQgUIAr9yNewVAzKIX
报错日志: 2018-12-17 10:01:19,688 ERROR [io.undertow.request] (default task-3) UT005023: Exception handl ...
- MySql 踩坑小记
MySql 踩坑一时爽,一直踩啊一直爽... 以下记录刚踩的三个坑,emmm... 首先是远程机子上创建表错误(踩第一个坑),于是将本地机器 MySql 版本回退至和远程一致(踩第二个坑),最后在 ...
- Guava Lists.transform踩坑小记<转>
1.问题提出 1.前段时间在项目中用到Lists.transform返回的List,在对该list修改后发现修改并没有反映在结果里,研究源码后发现问题还挺大.下面通过单步调试的结果来查看Guava L ...
- async语法升级踩坑小记
从今年过完年回来,三月份开始,就一直在做重构相关的事情. 就在今天刚刚上线了最新一次的重构代码,希望高峰期安好,接近半年的Node.js代码重构. 包含从callback+async.waterfal ...
- Codeforces VP/补题小记 (持续填坑)
Codeforces VP/补题小记 1149 C. Tree Generator 给你一棵树的括号序列,每次交换两个括号,维护每次交换之后的直径. 考虑括号序列维护树的路径信息和,是将左括号看做 ...
- HTTP访问控制(CORS)踩坑小记
前几天在帮后端排查一个cors的问题的时候发现的一些小坑特此记录 ** cors的本质是出于安全原因,浏览器限制从脚本内发起的跨源HTTP请求. 例如,XMLHttpRequest和FetchAPI遵 ...
- dubbo 2.7应用级服务发现踩坑小记
本文已收录 https://github.com/lkxiaolou/lkxiaolou 欢迎star. 背景 本文记录最近一位读者反馈的dubbo 2.7.x中应用级服务发现的问题,关于dubbo应 ...
随机推荐
- Python学习笔记1——Python基础
一. 数据类型和变量 整数:十六进制用0x前缀和0-9,a-f表示 浮点数:小数,科学计数法:10用e代替:整数和浮点数在计算机内部存储的方式是不同的,整数运算永远是精确的(包括除法),浮点数运算则可 ...
- \(\S1\) 描述性统计
在认识客观世界的过程中,统计学的思想和方法经常起着不可替代的作用.在许多工程及自然科学的专业领域中,包括可靠性分析.质量控制.生物信息.脑科学.心理分析.经济分析.金融风险管理.社会科学推断.行为科学 ...
- MyRocks简介
RocksDB是facebook基于LevelDB实现的,目前为facebook内部大量业务提供服务.经过facebook大量工作,将RocksDB为MySQL的一个存储引擎移植到MySQL,称之为M ...
- 关于实现一个基于文件持久化的EventStore的核心构思
大家知道enode框架的架构是基于ddd+event sourcing的思想.我们持久化的不是聚合根的最新状态,而是聚合根产生的领域事件.最近我在思考如何实现一个基于文件的eventstore.目标有 ...
- NoSQL:从关系型数据库到非关系型数据库
关系型数据库 所谓关系型数据库,,就是指采用了关系模型来组织数据的数据库. 什么是关系模型,简单说,关系模型就是二维表格模型,而一个关系型数据库就是由二维表及其之间的联系所组成的一个数据组织. 关系模 ...
- Java虚拟机9:Java类加载机制
前言 我们知道我们写的程序经过编译后成为了.class文件,.class文件中描述了类的各种信息,最终都需要加载到虚拟机之后才能运行和使用.而虚拟机如何加载这些.class文件?.class文件的信息 ...
- 人人都是 DBA(XI)I/O 信息收集脚本汇编
什么?有个 SQL 执行了 8 秒! 哪里出了问题?臣妾不知道啊,得找 DBA 啊. DBA 人呢?离职了!!擦!!! 程序员在无处寻求帮助时,就得想办法自救,努力让自己变成 "伪 DBA& ...
- Selenium自动化测试问题
org.openqa.selenium.firefox.NotConnectedException: Unable to connect to host 127.0.0.1 on port 7055 ...
- 分享一个U3D在Runtime显示碰撞盒的插件
有些时候,我们需要在Game视图显示碰撞盒,比如格斗游戏我要开发碰撞配置的工具,我经常需要看到碰撞盒,今天找了一下,没有发现合适的插件,我还花5美金买了一个插件,结果也只是在scene视图显示,这里我 ...
- Amazon Dynamo论文学习
Dynamo是一个key-value数据存储系统,去中心化.高可扩展.高可用,使用一致性哈希来分区和备份数据,使用数据版本化来实现一致性. 核心技术 CAP:一致性.可用性.扩展性 一致性哈希:切分数 ...