(转)nio 连网和异步 I/O
连网和异步 I/O
概述
连网是学习异步 I/O 的很好基础,而异步 I/O 对于在 Java 语言中执行任何输入/输出过程的人来说,无疑都是必须具备的知识。NIO 中的连网与 NIO 中的其他任何操作没有什么不同 ― 它依赖通道和缓冲区,而您通常使用 InputStream 和 OutputStream 来获得通道。
本节首先介绍异步 I/O 的基础 ― 它是什么以及它不是什么,然后转向更实用的、程序性的例子。
异步 I/O
异步 I/O 是一种 没有阻塞地 读写数据的方法。通常,在代码进行 read() 调用时,代码会阻塞直至有可供读取的数据。同样, write() 调用将会阻塞直至数据能够写入。
另一方面,异步 I/O 调用不会阻塞。相反,您将注册对特定 I/O 事件的兴趣 ― 可读的数据的到达、新的套接字连接,等等,而在发生这样的事件时,系统将会告诉您。
异步 I/O 的一个优势在于,它允许您同时根据大量的输入和输出执行 I/O。同步程序常常要求助于轮询,或者创建许许多多的线程以处理大量的连接。使用异步 I/O,您可以监听任何数量的通道上的事件,不用轮询,也不用额外的线程。
我们将通过研究一个名为 MultiPortEcho.java 的例子程序来查看异步 I/O 的实际应用。这个程序就像传统的 echo server,它接受网络连接并向它们回响它们可能发送的数据。不过它有一个附加的特性,就是它能同时监听多个端口,并处理来自所有这些端口的连接。并且它只在单个线程中完成所有这些工作。
Selectors
本节的阐述对应于 MultiPortEcho 的源代码中的 go() 方法的实现,因此应该看一下源代码,以便对所发生的事情有个更全面的了解。
异步 I/O 中的核心对象名为 Selector。Selector 就是您注册对各种 I/O 事件的兴趣的地方,而且当那些事件发生时,就是这个对象告诉您所发生的事件。
所以,我们需要做的第一件事就是创建一个 Selector:
Selector selector = Selector.open();
然后,我们将对不同的通道对象调用 register() 方法,以便注册我们对这些对象中发生的 I/O 事件的兴趣。register() 的第一个参数总是这个 Selector。
打开一个 ServerSocketChannel
为了接收连接,我们需要一个 ServerSocketChannel。事实上,我们要监听的每一个端口都需要有一个 ServerSocketChannel 。对于每一个端口,我们打开一个 ServerSocketChannel,如下所示:
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.configureBlocking( false ); ServerSocket ss = ssc.socket();
InetSocketAddress address = new InetSocketAddress( ports[i] );
ss.bind( address );
第一行创建一个新的 ServerSocketChannel ,最后三行将它绑定到给定的端口。第二行将 ServerSocketChannel 设置为 非阻塞的 。我们必须对每一个要使用的套接字通道调用这个方法,否则异步 I/O 就不能工作。
选择键
下一步是将新打开的 ServerSocketChannels 注册到 Selector上。为此我们使用 ServerSocketChannel.register() 方法,如下所示:
SelectionKey key = ssc.register( selector, SelectionKey.OP_ACCEPT );
register() 的第一个参数总是这个 Selector。第二个参数是 OP_ACCEPT,这里它指定我们想要监听 accept 事件,也就是在新的连接建立时所发生的事件。这是适用于 ServerSocketChannel 的唯一事件类型。
请注意对 register() 的调用的返回值。 SelectionKey 代表这个通道在此 Selector 上的这个注册。当某个 Selector 通知您某个传入事件时,它是通过提供对应于该事件的 SelectionKey 来进行的。SelectionKey 还可以用于取消通道的注册。
内部循环
现在已经注册了我们对一些 I/O 事件的兴趣,下面将进入主循环。使用 Selectors 的几乎每个程序都像下面这样使用内部循环:
int num = selector.select(); Set selectedKeys = selector.selectedKeys();
Iterator it = selectedKeys.iterator(); while (it.hasNext()) {
SelectionKey key = (SelectionKey)it.next();
// ... deal with I/O event ...
}
首先,我们调用 Selector 的 select() 方法。这个方法会阻塞,直到至少有一个已注册的事件发生。当一个或者更多的事件发生时, select() 方法将返回所发生的事件的数量。
接下来,我们调用 Selector 的 selectedKeys() 方法,它返回发生了事件的 SelectionKey 对象的一个 集合 。
我们通过迭代 SelectionKeys 并依次处理每个 SelectionKey 来处理事件。对于每一个 SelectionKey,您必须确定发生的是什么 I/O 事件,以及这个事件影响哪些 I/O 对象。
监听新连接
程序执行到这里,我们仅注册了 ServerSocketChannel,并且仅注册它们“接收”事件。为确认这一点,我们对 SelectionKey 调用 readyOps() 方法,并检查发生了什么类型的事件:
if ((key.readyOps() & SelectionKey.OP_ACCEPT)
== SelectionKey.OP_ACCEPT) { // Accept the new connection
// ...
}
可以肯定地说, readOps() 方法告诉我们该事件是新的连接。
接受新的连接
因为我们知道这个服务器套接字上有一个传入连接在等待,所以可以安全地接受它;也就是说,不用担心 accept() 操作会阻塞:
ServerSocketChannel ssc = (ServerSocketChannel)key.channel();
SocketChannel sc = ssc.accept();
下一步是将新连接的 SocketChannel 配置为非阻塞的。而且由于接受这个连接的目的是为了读取来自套接字的数据,所以我们还必须将 SocketChannel 注册到 Selector上,如下所示:
sc.configureBlocking( false );
SelectionKey newKey = sc.register( selector, SelectionKey.OP_READ );
注意我们使用 register() 的 OP_READ 参数,将 SocketChannel 注册用于 读取 而不是 接受 新连接。
删除处理过的 SelectionKey
在处理 SelectionKey 之后,我们几乎可以返回主循环了。但是我们必须首先将处理过的 SelectionKey 从选定的键集合中删除。如果我们没有删除处理过的键,那么它仍然会在主集合中以一个激活的键出现,这会导致我们尝试再次处理它。我们调用迭代器的 remove() 方法来删除处理过的 SelectionKey:
it.remove();
现在我们可以返回主循环并接受从一个套接字中传入的数据(或者一个传入的 I/O 事件)了。
传入的 I/O
当来自一个套接字的数据到达时,它会触发一个 I/O 事件。这会导致在主循环中调用 Selector.select(),并返回一个或者多个 I/O 事件。这一次, SelectionKey 将被标记为 OP_READ 事件,如下所示:
} else if ((key.readyOps() & SelectionKey.OP_READ)
== SelectionKey.OP_READ) {
// Read the data
SocketChannel sc = (SocketChannel)key.channel();
// ...
}
与以前一样,我们取得发生 I/O 事件的通道并处理它。在本例中,由于这是一个 echo server,我们只希望从套接字中读取数据并马上将它发送回去。关于这个过程的细节,请参见 参考资料 中的源代码 (MultiPortEcho.java)。
回到主循环
每次返回主循环,我们都要调用 select 的 Selector()方法,并取得一组 SelectionKey。每个键代表一个 I/O 事件。我们处理事件,从选定的键集中删除 SelectionKey,然后返回主循环的顶部。
这个程序有点过于简单,因为它的目的只是展示异步 I/O 所涉及的技术。在现实的应用程序中,您需要通过将通道从 Selector 中删除来处理关闭的通道。而且您可能要使用多个线程。这个程序可以仅使用一个线程,因为它只是一个演示,但是在现实场景中,创建一个线程池来负责 I/O 事件处理中的耗时部分会更有意义。
https://www.ibm.com/developerworks/cn/education/java/j-nio/j-nio.html
(转)nio 连网和异步 I/O的更多相关文章
- IO复用,AIO,BIO,NIO,同步,异步,阻塞和非阻塞 区别参考
参考https://www.cnblogs.com/aspirant/p/6877350.html?utm_source=itdadao&utm_medium=referral IO复用,AI ...
- 拿搬东西来解释udp tcpip bio nio aio aio异步
[群主]雷欧纳德简单理解 tcpip是有通信确认的面对面通信 有打招呼的过程 有建立通道的过程 有保持通道的确认 有具体传输udp是看到对面的人好像在对面等你 就往对面扔东西[群主]雷欧 ...
- IO复用,AIO,BIO,NIO,同步,异步,阻塞和非阻塞 区别(百度)
如果面试问到IO操作,这篇文章提到的问题,基本是必问,百度的面试官问我三个问题 (1)什么是NIO(Non-blocked IO),AIO,BIO (2) java IO 与 NIO(New IO)的 ...
- (转)IO复用,AIO,BIO,NIO,同步,异步,阻塞和非阻塞 区别
本文来自:https://www.cnblogs.com/aspirant/p/6877350.html?utm_source=itdadao&utm_medium=referral,非常感谢 ...
- IO复用,AIO,BIO,NIO,同步,异步,阻塞和非阻塞 区别
一.什么是socket?什么是I/O操作? 我们都知道unix(like)世界里,一切皆文件,而文件是什么呢?文件就是一串二进制流而已,不管socket,还是FIFO.管道.终端,对我们来说,一切都是 ...
- NIO学习:异步IO实例
工作模式: 客户端代码: package demos.nio.socketChannel; import java.io.ByteArrayOutputStream; import java.io.I ...
- JAVA 中BIO,NIO,AIO的理解以及 同步 异步 阻塞 非阻塞
在高性能的IO体系设计中,有几个名词概念常常会使我们感到迷惑不解.具体如下: 序号 问题 1 什么是同步? 2 什么是异步? 3 什么是阻塞? 4 什么是非阻塞? 5 什么是同步阻塞? 6 什么是同步 ...
- 关于同步,异步,阻塞,非阻塞,IOCP/epoll,select/poll,AIO ,NIO ,BIO的总结
相关资料 IO基本概念 Linux环境 同步异步阻塞非阻塞 同步与异步 阻塞与非阻塞 IO模型Reference Link 阻塞IO模型 非阻塞IO模型 IO复用模型 信号驱动异步IO模型 异步IO模 ...
- JDK中关于BIO,NIO,AIO,同步,异步介绍
在理解什么是BIO,NIO,AIO之前,我们首先需要了解什么是同步,异步,阻塞,非阻塞.假如我们现在要去银行取钱: 同步 : 自己亲自出马持银行卡到银行取钱(使用同步IO时,Java自己处理IO读写) ...
随机推荐
- Bzoj2818 Gcd(莫比乌斯反演)
题面 题意都在题目里面了 题解 你可以把题意看成这个东西 $$ \sum_{i=1}^n\sum_{j=1}^m\mathbf f(gcd(i,j)) $$ 其中$\mathbf f(n)$为$是否是 ...
- [BZOJ1444]有趣的游戏(AC自动机+矩阵乘法)
n个等长字符串,机器会随机输出一个字符串(每个字母出现的概率为p[i]),问每个字符串第一个出现的概率是多少. 显然建出AC自动机,套路地f[i][j]表示i时刻位于节点j的概率. 构建转移矩阵,当i ...
- BZOJ 4883 [Lydsy2017年5月月赛]棋盘上的守卫(最小生成环套树森林)
[题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=4883 [题目大意] 在一个n*m的棋盘上要放置若干个守卫. 对于n行来说,每行必须恰好 ...
- BZOJ 1202 [HNOI2005]狡猾的商人(并查集)
[题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=1202 [题目大意] 给出一些区间和的数值,问是否存在矛盾 [题解] 用并查集维护前缀和 ...
- 【贪心】AtCoder Grand Contest 018 B - Sports Festival
假设我们一开始选取所有的运动项目,然后每一轮将当前选择人数最多的运动项目从我们当前的项目集合中删除,尝试更新答案.容易发现只有这样答案才可能变优,如果不动当前选取人数最多的项目,答案就不可能变优. 我 ...
- 基于socket的udp传输,socketserver模块,进程
基于UDP的套接字 udp是无连接的,先启动哪一端都不会报错 socket.SOCK_DGRAM 数据报协议 udp不会发送空数据,什么都不输入直接发送也会有报头发过去 服务端 import sock ...
- [NOIp2016提高组]组合数问题
题目大意: 给定n,m和k,对于所有的0<=i<=n,0<=j<=min(i,m)有多少对(i,j)满足C(j,i)是k的倍数. 思路: 先预处理出组合数,再预处理一下能整除个 ...
- Codeforces Round #343 (Div. 2) C. Famil Door and Brackets dp
C. Famil Door and Brackets 题目连接: http://www.codeforces.com/contest/629/problem/C Description As Fami ...
- (转)Linux下数据段的区别(数据段、代码段、堆栈段、BSS段)
进程(执行的程序)会占用一定数量的内存,它或是用来存放从磁盘载入的程序代码,或是存放取自用户输入的数据等等.不过进程对这些内存的管理方式因内存用途 不一而不尽相同,有些内存是事先静态分配和统一回收的, ...
- 从connect到express01-connect
介绍 Connect是一个node中间件框架.每个中间件在http处理过程中通过改写request, response的数据.状态,实现了特定的功能. 根据中间件在整个http处理流程的位置,将中间件 ...