目录
一. 同步与阻塞
1.1 同步阻塞
1.2 同步非阻塞
1.3 异步阻塞
1.4 异步非阻塞
1.5 I/O多路
二.多路复用的技术
2.1 UNIX I/O Models
2.1.1 blocking I/O
2.1.2 nonblocking I/O
2.1.3 I/O Multiplexing Model
2.1.4 SIGIO
2.1.5 asynchronous I/O
2.2 IO多路复用
2.2.1 从同步阻塞到同步非阻塞
2.2.2 select
2.2.3 poll
2.2.4 epoll
REF
一. 同步与阻塞
同步是针对调用者的操作行为来说的,阻塞是针对这个行为所使用的接口来说的。
例如你是某场竞赛的主考官,需要监考考生A、B、C、D、E五位考生现场答题。你的操作就是走到每位考生面前去收试卷,而每位考生的答题情况(例如是否答完试卷)是不同的,有的可能在你走到的时候很快就交卷了,有的可能需要很长时间才能完成。
1.1 同步阻塞
到了收卷的时间,你要依次去收A,B,C,D,E考生的试卷。假说收到C考生的时候,他还未能答完试卷且他想答完题了再交给你,你又必须得等C交卷了才能去收D和E考生的试卷,那么这时候就是同步阻塞的。
对应Java中BIO(Block IO)。
1.2 同步非阻塞
到了收卷的时间,你要依次去收A,B,C,D,E考生的试卷。假说收到C考生的时候,他还未能答完试卷,你跳过C直接去收 D、E 的试卷,那么这时候就是同步非阻塞的。
1.3 异步阻塞
暂时没听说有这种场景。
1.4 异步非阻塞
你收卷的时候不用刻意去等某个学生交卷,学生交卷又迅速。
1.5 I/O多路
select/poll:考生做完了试卷,大喊了一声“我要交卷”,但是你不知道谁喊的,这时候你需要一个个地去询问,这个就是select/poll。
epoll:考生做完了试卷,大喊了一声“我要交卷”而且还举手了,你直接去收卷。
二.多路复用的技术
2.1 UNIX I/O Models
https://masterraghu.com/subjects/np/introduction/unix_network_programming_v1.3/ch06lev1sec2.html
-
blocking I/O:阻塞式I/O -- BIO
-
nonblocking I/O:非阻塞式I/O -- NIO,AIO(AIO是在BIO的包里)
-
I/O multiplexing (select and poll):I/O复用
-
signal driven I/O (SIGIO):信号驱动式I/O
-
asynchronous I/O (the POSIX aio_functions):异步I/O --AIO
UNIX I/O Models | 阻塞/非阻塞 | 对应JAVA IO说明 |
blocking I/O | 阻塞 | BIO |
nonblocking I/O | 非阻塞 | NIO |
I/O multiplexing | 阻塞 | AIO和NIO的底层都是用epoll,这是JDK又进行了一层封装使之成为了非阻塞式的。 |
asynchronous I/O | 非阻塞 | AIO |
SIGIO | 非阻塞 |
在介绍I/O models前,先对Socket的读取操作做简单说明,通常来说包括如下两个操作:
1. wait for data:等待数据从网络中到达,当数据到达后就会将器复制到内核中的某个缓冲区;
2. copy data from kernel to user:把数据从内核缓冲区复制到应用进程的缓冲区,这个过程虽然阻塞的,但是这个内存的拷贝是及其快的。
2.1.1 blocking I/O
由上图可知,应用进程从调用recvfrom()到它返回的这整段时间内都是阻塞的,主要阻塞在wait for data这个过程。当应用进程有返回值(return OK)的时候,应用进程已经读完数据了。
当然也可能因系统调用被信号终端导致调用发生错误。
2.1.2 nonblocking I/O
由上图可知,应用进程调用recvfrom的时候没有数据可返回则立即返回EWOULDBLOCK,而不是一直等着。
2.1.3 I/O Multiplexing Model
IO多路复用也是阻塞式的,应用进程阻塞在select调用,等待数据报套接字变为可读后,应用进程才会立即调用recvfrom读取数据。
从这里看I/O多路复用还不如BIO,因为它比BIO多一次select的系统调用,BIO只有一次recvfrom的系统调用。从后面的IO多路复用的进一步描述可知select的优势在于可以等待多个fd描述符就绪。
2.1.4 SIGIO
非阻塞式IO。应用进程通过sigaction的系统调用告知内核在数据就绪发送SIGIO信号来通知下,这个sigaction调用完就立马返回了。等内核数据就绪后,内核在通过信号告知应用进程来取数据。
2.1.5 asynchronous I/O
AIO和SIGIO一样也是非阻塞的,与SIGIO的差别在于:SIGIO是由内核通知应用进程什么时候去启动recvfrom这个IO操作,而AIO是由内核通知应用进程I/O操作何时完成(即这时候数据已经从内核拷贝到了用户态),可以仔细对比下两张图。
2.2 IO多路复用
以TCP socket通信为例来介绍从"BIO"到"I/O多路复用"的引进过程。
2.2.1 从同步阻塞到同步非阻塞
如上示例为"单线程+BIO"。由于accept()和read()都会阻塞,所以当client1在与server交互的过程中,client2就会被阻塞住。例如client1在connect()连接握手耗时或者是client1一直在write发数据到server,这时候client2就会一直阻塞等待。
Q:那么是否可以通过多线程来解决多client被阻塞的问题呢?
A:可以但不完全可以。因为多线程可能会存在线程浪费,线程调度也是个麻烦事。例如来一个client连接就建立一个线程,如果有1000个client就得创建1000个线程,但是实际上可能只有两三个client和server端在通信,这时候就会浪费很多线程资源。
那么我们再来看看,不阻塞会怎样呢?
不阻塞accept(),client1调用connect()的时候client2也可以调用,当他们connect()成功了就将socket fd放到fd_list集合里,后面再去轮询这个list集合,通过系统调用去读每个fd看是否有数据到达(在上面“UNIX I/O Models”中介绍过socket数据读取的两个主要过程)。
这种不阻塞的方式虽然能解决“单个 socket 阻塞影响其他socket的问题”,但是不断的遍历,不断的进行系统调用是会有一定的开销的,特别是在没有数据到来却一直在进行系统调用的时候,这种方式的缺点表现地尤为明显。
如何优雅的解决呢?这时候就引入了I/O多路复用。
2.2.2 select
(TODO)
2.2.3 poll
(TODO)
2.2.4 epoll
(TODO)
REF
1.《UNIX Network Programming Volume 1, Third Edition: The Sockets Networking API》
I/O Models章节