5种常见的IO模型及IO复用
Posted 胖虎code
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了5种常见的IO模型及IO复用相关的知识,希望对你有一定的参考价值。
背景
在Redis的客户端连接时处理网络连接的模型为多路I/O复用,这个概念咋一看这么熟悉,哦,原来我之前面试某家公司时,吃了这个亏,哎,多少有些可惜。
前言
在了解网络模型之前,对于一个程序和操作系统,我们还要准备个知识:用户空间(application)和内核空间(kernel)。用户空间是和其他进程共享,也就是进程的私有空间PCB,而进程无法访问内核空间,需要将数据从内核空间复制到用户空间,进程才能读取。
5种IO类型
本文讨论的是Linux下的网络IO,一般的分为五种:
阻塞IO (Blocking IO)
非阻塞IO (nonBlocking IO)
多路IO (multiplexing)
信号驱动 (signal driven)
异步IO (Asynchronous IO)
以及介绍多路IO中的select、poll、和epoll。
例1:小明在客厅看电视,然后厨房在烧水,你等着水煮泡面。
阻塞IO
什么是阻塞IO,简言之,阻塞罢了,在例1中,阻塞IO就是,你一直等在厨房,而不能去看电视。在程序中,监听一个端口,当没有数据返回,就一直等在那,不能继续下面的操作。如下图:
图中,程序调用recvfrom,从内核中获取数据,而内核一直没准备好,进程就一直阻塞于此,直到数据准备好datagram ready,复制数据到用户区,然后由具体的执行命令读取数据。
在程序中,我们应对阻塞IO的解决办法,一般都线程池连接池等,具体是单个线程负责一个连接,但是对于并发连比较大,以上就不是用了;
非阻塞IO
对于例1,小明的做法是,一边在客厅看电视,每隔一段时间去厨房看看。在程序中,就需要进程不断系统调用,去查看内核是否准备好数据,一旦准备好,执行上面阻塞IO的操作。
在Linux 中可以利用fcntl()方法将的套接字socket默认从阻塞改成非阻塞。
在非阻塞下, recvfrom返回值为:
0,表示接收到的字节数;
=0,表示连接已断开;
==-1,且errno等于EAGAIN,表示内核还没准备好数据;
==-1,且errno不等于EAGAIN,表示调用出错。
多路IO复用
在例1的基础上,小明同时烧多壶水,而且基于水壶的定制的通知机制,等到某壶水烧好了,发出独特的叫声,这样小明就可以具体处理哪一壶水。
以上是epoll的多路IO复用模型,而对于select、poll水壶就没有定制的铃声,就需要去挨个查看。
以select为例:
程序是阻塞于select的,等到数据准备好了【将数据复制到了用户区】,然后挨个轮询,找出对应的文件描述符,处理相应的读写。
对于select、poll、epoll各自的优缺点呢?
select
#include <sys/select.h>
#include <sys/time.h>
int select (int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
/* Returns: positive count of ready descriptors, 0 on timeout, –1 on error */
读写需要将文件描述符从用户区拷贝到内核区,开销大
支持的客户端文件描述符有限。
poll
#include <sys/poll.h>
int poll (struct pollfd *fdarray, unsigned long nfds, int timeout);
/* Returns: count of ready descriptors, 0 on timeout, –1 on error */
和select类似,不同的是,基于链表来存储的,但也需要去轮询。
epoll
#include <sys/epoll.h>
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
epoll是线程安全的,而select、poll不是的;
epoll内部使用mmap()共享了内核和用户的部分空间,减少了来回复制;
epoll基于事件驱动,先制造一个句柄,再注册套接字,最后去轮询准备好了的事件,避免了整个的轮询;
信号驱动
如下图所示:
异步IO
在例1中,小明就不自己烧水了,而是“雇”其他人去烧水,自己做自己的事了。在程序中,读写完全由内核来处理。当用户发起异步的读操作,aio_read,不再阻塞任何,直接返回,等待内核准备数据,直到数据拷贝到用户内存,通知程序,操作完成。
如下图:
问题:
同步和异步:
定义:【同步】直到IO操作完成之前,IO操作造成了请求程序的阻塞;
【异步】IO操作不会造成任何的阻塞。
以5中IO操作为具体例子,如图:
可以看到:
阻塞IO一直被阻塞,直到有数据;
非阻塞IO不被阻塞,直到有数据可读,程序执行具体的读写逻辑操作,此处被阻塞;
多路复用IO阻塞于epoll_wait,直到有数据,读写处再次被阻塞;
信号驱动,当内核出过来SIGIO后,执行读写逻辑操作处被阻塞;
异步IO完全交由内核,自己不做任何处理,不阻塞。
阻塞和非阻塞
同上。
以上是关于5种常见的IO模型及IO复用的主要内容,如果未能解决你的问题,请参考以下文章