- IO的操作也就是应用程序从TCP缓冲区中读取数据的时候。
- 网络I/O的本质是socket的读取,socket在linux中被抽象为流,I/O可以理解为对流的操作。对于一次I/O访问,数据会先被拷贝到操作系统的内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。所以说当一个read操作发生时,他会经历两个阶段:
第一阶段:等待数据准备(Waiting for the data to be ready)
第二阶段:将数据从内核拷贝到进程中(Copy the data from the kernel to the process)
对于socket流而言:
第一步:通常涉及等待网络上的数据分组到达,然后被复制到内核的某个缓冲区
第二步:把数据从内核缓冲区复制到进程缓冲区
1.阻塞IO(BIO)
(1)什么是阻塞I/O
阻塞IO就是当应用程序向TCP缓冲区发起读取数据申请时,在内核数据没有准备好之前,应用程序会一致处于等待数据的状态,直到内核把数据准备好交给应用程序才结束。
**术语描述:**在应用程序调用recvfrom读取数据时,其系统调用直到数据包到达别并且被复制到应用缓冲区中或者发生错误时才返回,此期间一致处于等待,进程从调用直到返回这段时间被阻塞的成为阻塞IO。
(2)阻塞I/O流程
- 第一步:应用程序向内核发起recvfrom读取数据
- 第二步:准备数据报(应用进程阻塞)
- 第三步:将数据从内核复制到应用空间
- 第四步:复制完成后,返回成功提示
2.非阻塞I/O(NIO)
(1)什么是非阻塞I/O
非阻塞I/O就是当应用程序发起读取数据时,如果内核没有准备好数据报,会返回给应用程序,不会让应用程序一致等待,但是应用程序要时不时去尝试调用,当数据包准备好时,将数据从内核复制到用户空间,这个过程也是同步的,阻塞的。
**术语描述:**非阻塞I/O是在应用调用recvfrom读取数据时,如果缓冲区中没有数据的话,就会直接返回一个EWOULDBLOCK错误,不会让应用一致等待。在没有数据时会即刻返回错误标识,那也意味着如果应用要读取数据就需要不断的调用recvfrom请求,直到读取到它要的数据为止。
(2)非阻塞I/O流程
- 第一步:应用进程向内核发起recvfrom读取数据
- 第二步:没有数据报准备好,即刻返回EWOULDBLOCK错误码
- 第三步:应用程序向内核再次发起recvfrom读取数据
- 第四步:已有数据报就从内核拷贝到用户空间,否则还是返回错误码
3.I/O多路复用
(1)什么时I/O多路复用
I/O多路复用的思路就是系统提供了一种函数可以同时监控多个网络请求的操作,这个函数就是我们常说的select、poll、epoll函数,有了这个函数后,应用线程通过调用select函数就可以同时监控多个网络请求,select函数监控的网络请求中只要有任何一个数据状态准备就绪了,select函数就会返回可读状态,这时询问线程再去通知处理数据的线程,对应线程此时再发起recvfrom请求去读取数据。
**术语描述:**进程通过将一个或者多个网络请求传递给select,阻塞在select操作之上,select帮我们侦测多个网络请求是否准备就绪,当有网络请求准备就绪时,select返回数据可读状态,应用程序在调用recvfrom读取数据。
(2)I/O多路复用流程
- 第一步:进程发起网络请求到select函数调用进行阻塞
- 第二步:select函数调用内核获取数据报
- 第三步:select函数监控的网络请求中只要有任何一个数据状态准备就绪了,select函数就会返回可读状态
- 第四步:询问线程再去通知处理数据的线程,对应线程在次发起recvfrom请求去读取数据
4.信号驱动I/O
(1)什么是信号驱动I/O
信号驱动I/O不是循环请求询问的方式去监控数据就绪状态,而是调用sigaction时候建立一个SIGIO的信号联系,当内核数据准备好之后在通过SISGIO信号通知线程数据准备好后的可读状态,当线程收到可读状态的信号后,此时在向内核发起recvfrom读取数据的请求,因为信号驱动I/O的模型下应用线程在发出信号监控后即可返回,不会阻塞,所以这样的方式下,一个应用线程也可以控制多个网络请求。
**术语描述:**首先开启套接字信号驱动I/O功能,并通过系统调用sigaction执行一个信号处理函数,此时请求即可返回,当数据准备就绪时,就生成对应进程的SIGIO信号,通过信号回调通知应用线程调用recvfrom来读取数据。
(2)信号驱动I/O流程
- 第一步:进程建立SIGIO的信号处理程序调用sigaction,然后返回
- 第二步:内核准备好数据递交SIGIO给信号处理程序
- 第三步:应用程序收到信号后,调用数据拷贝,复制完成返回数据报
5.异步I/O(AIO)
(1)什么是异步I/O
异步I/O应用只需要向内核发送一个read请求,告诉内核他要读取数据后即刻返回,内核收到请求后会建立一个信号联系,但数据准备就绪,内核会主动把数据从内核复制到用户空间,等所有操作都完成之后,内核会发起一个通知告诉应用,处理数据报完成。
**术语描述:**应用告知内核启动某个操作,并让内核在整个操作完成之后,通知应用,这种模型与信号驱动的主要区别在于信号驱动I/O是由内核通知我们何时开始下一个I/O,而异步I/O模型是由内核通知我们操作什么时候完成。
(2)异步I/O流程
- 第一步:异步I/O应用只需要向内核发送一个read请求,告诉内核他要读取数据后即刻返回
- 第二步:内核收到请求后会建立一个信号联系,但数据准备就绪
- 第三步:内核会主动把数据从内核复制到用户空间,等所有操作都完成之后,内核会发起一个通知告诉应用,处理数据报完成
6.IO多路复用的select-poll-epoll区别
select | poll | epoll | |
---|---|---|---|
查找模式 | 遍历 | 遍历 | 回调 |
底层结构 | bitmap | 链表 | 红黑树 |
最大值 | x86架构32位系统最大1024 x64架构64位系统最大2048 | 无限制 | 无限制,1G内存大概支持10万个句柄fd |
IO效率 | 每次调用,线性遍历,时间复杂度O(n) | 每次调用,线性遍历,时间复杂度O(n) | 当fd就绪,系统注册的回调函数就会被调用,将就绪fd放到列表里,时间复杂度O(1) |
备注 | 每次调用,需要把全部的fd从用户空间拷贝到内核空间。 几乎在所有的平台上支持,跨平台支持性好 | 每次调用,需要把全部的fd从用户空间拷贝到内核空间 | 在Linux2.6版本内核新增,用户态拷贝到内核态只需要一次,使用事件通知,通过epoll_ctl( ) 函数注册fd,一旦该fd就绪,内核就会采用callback的回调机制来激活对应的fd |