本文你将看到如下内容:
-
最好的学习就是教别人。
-
NIO
的用途。
-
现代操作系统的
5
种
IO
模型。
-
JavaNIO
的实现及简单的服务器例子。
-
NodeJs
的
IO
模型。
-
计算机
IO
的硬件基础。
首先感谢
CDRD
给了我这一次分享的机会,为什么要将感谢放在最开头。因为这次我选择
java nio
这个主题是因为我自己不理解
Java nio
的内部实现原理,但是又想学。
”
最好的学习方式就是教别人
”
,所以建议大家以后想要学什么东西,可以尝试搞清楚以后,与大家分享。
当今时代是一个知识信息爆炸的时代,知识的量几乎是无限的,如果我们不带着功利心去学习知识,那么提升认知的效率将大幅度降低。那么学习
Java NIO
能有什么用呢?
答案是:帮助我们提高
IO
密集型应用的单机并发量。
何为
IO
密集型应用
?
何为
CPU
密集型应用,这个问题留给大家一起思考。在我们所用到的,或者知道的应用中,有哪些是
IO
密集型的,又有哪些是
CPU
密集型的
?
为什么
Java NIO
能够提升
IO
密集型应用的单击并发量呢?这要从操作系统的
IO
模型说起。
对于一次
IO
访问(以
read
举例),数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。所以说,当一个
read
操作发生时,它会经历两个阶段:
1.
等待数据准备
(Waiting for the data to be ready)
2.
将数据从内核拷贝到进程中
(Copying the data from the kernel to the process)
正式因为这两个阶段,
linux
系统产生了下面五种网络模式的方案。
-
阻塞
I/O
(
blocking IO
)
-
非阻塞
I/O
(
nonblocking IO
)
- I/O
多路复用(
IO multiplexing
)
-
信号驱动
I/O
(
signal driven IO
)
-
异步
I/O
(
asynchronous IO
)
1
、阻塞
I/O
(
blocking IO
)
在
linux
中,默认情况下所有的
socket
都是
blocking
,一个典型的读操作流程大概是这样:
当用户进程调用了recvfrom这个系统调用,kernel就开始了IO的第一个阶段:准备数据(对于网络IO来说,很多时候数据在一开始还没有到达。比如,还没有收到一个完整的UDP包。这个时候kernel就要等待足够的数据到来)。这个过程需要等待,也就是说数据被拷贝到操作系统内核的缓冲区中是需要一个过程的。而在用户进程这边,整个进程会被阻塞(当然,是进程自己选择的阻塞)。当kernel一直等到数据准备好了,它就会将数据从kernel中拷贝到用户内存,然后kernel返回结果,用户进程才解除block的状态,重新运行起来。
所以,blocking IO的特点就是在IO执行的两个阶段都被block了。
2
、非阻塞
I/O
(
nonblocking IO
)
linux
下,可以通过设置
socket
使其变为
non-blocking
。当对一个
non-blocking socket
执行读操作时,流程是这个样子:
当用户进程发出read操作时,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error。从用户进程角度讲,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个error时,它就知道数据还没有准备好,于是它可以再次发送read操作。一旦kernel中的数据准备好了,并且又再次收到了用户进程的systemcall,那么它马上就将数据拷贝到了用户内存,然后返回。
所以,nonblocking IO的特点是用户进程需要不断的主动询问kernel数据好了没有。
3
、
I/O
多路复用(
IO multiplexing
)
IO multiplexing
就是我们说的
select
,
poll
,
epoll
,有些地方也称这种
IO
方式为
event driven IO
。
select/epoll
的好处就在于单个
process
就可以同时处理多个网络连接的
IO
。它的基本原理就是
select
,
poll
,
epoll
这个
function
会不断的轮询所负责的所有
socket
,当某个
socket
有数据到达了,就通知用户进程。
当用户进程调用了
select
,那么整个进程会被
block
,而同时,
kernel
会
“
监视
”
所有
select
负责的
socket
,当任何一个
socket
中的数据准备好了,
select
就会返回。这个时候用户进程再调用
read
操作,将数据从
kernel
拷贝到用户进程。
所以,
I/O
多路复用的特点是通过一种机制一个进程能同时等待多个文件描述符,而这些文件描述符(套接字描述符)其中的任意一个进入读就绪状态,
select()
函数就可以返回。
这个图和
blocking IO
的图其实并没有太大的不同,事实上,还更差一些。因为这里需要使用两个
systemcall (select
和
recvfrom)
,而
blocking IO
只调用了一个
systemcall (recvfrom)
。但是,用
select
的优势在于它可以同时处理多个
connection
。
所以,如果处理的连接数不是很高的话,使用
select/epoll
的
web server
不一定比使用
multi-threading+ blocking IO
的
web server
性能更好,可能延迟还更大。
select/epoll
的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。)
在
IO multiplexing Model
中,实际中,对于每一个
socket
,一般都设置成为
non-blocking
,但是,如上图所示,整个用户的
process
其实是一直被
block
的。只不过
process
是被
select
这个函数
block
,而不是被
socket IO
给