网上那些同步、异步、阻塞、非阻塞概念容易弄得很混乱。
以我的理解,只要知道原理即可,没必要为了一些概念弄得很混乱。
首先,要明白IO模型的用处是:用来从IO读取、或者写入数据。
以IO读取为例,有两个阶段:
1、阶段一,等待内核准备好数据;
2、阶段二,将数据从内核区拷贝到用户区。
以下对5种IO模型分别作简单介绍。
1、最简单的方法,告诉内核需要哪些数据,然后等待,直到有了数据之后,从内核区拷贝到用户区,也就是所谓的阻塞IO。
实现方法:recvfrom
2、告诉内核需要哪些数据,不等待,过一段时间去检查下,是否有数据,有则拷贝,没有则继续等待检查,这就是非阻塞IO。
实现方法:recvfrom+nonblock
3、同时监视多个IO端口,只要有数据就处理。这就是IO多路复用。
实现方法:select 、poll或者epoll
4、告诉内核需要哪些数据,既不等待,也不轮询,等到有数据后,内核主动通知,应用程序自己去拷贝,这就是信号驱动IO。
5、和信号驱动很像,只不过内核有数据后,并将数据从内核区拷贝到用户区,再通知应用,也就是异步IO。
方法:aio_read
目前,用的最多是IO多路复用。
对IO多路复用做个简单介绍。
我们都知道IO多路复用,有三种类型,分别是select, poll, epoll
1、select
int select(int maxfdp, fd_set *readset, fd_set *writeset, fd_set *excepset, const struct timeval *timeout);
返回值:就绪描述符的数目,超时返回0,出错返回-1
缺点:
对待测试的描述符个数有限制,最大1024
每次返回后,对readset、writeset、excepset采用轮询的方式判断是否有就绪数据,效率低
每次调用select,都会将readset、writeset、excepset从用户区拷贝到内核区,同样,返回时,又会将三个数据结构从内核区拷贝到用户区,大量的空间复制,效率低
2、poll
int poll(struct pollfd *fds, unsigned int nfds, int timeout);
poll是对select的改进。
改进点:没有最大个数的限制。
select的其他缺点依然存在。
3、epoll
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存在的几个缺点分别有改进。
轮询的方式改为:wait事件返回后, 返回值是需要处理的事件数目,并且将就绪事件存放在events中,避免对非就绪fd的轮询。
大量内存复制改为:由原来一个接口改为三个接口,并只在接口epoll_ctl才将内容拷贝到内核,避免每次查询都拷贝。并且,不管是epoll_ctl的用户区拷贝到内核区,还是epoll_wait的内核区拷贝到用户区,都采用共享内存技术。
epoll还有一个优点,支持边缘触发。
所谓边缘触发,就是当描述符从未就绪变为就绪状态时,内核通过epoll告知应用,应用如果不处理,则下次内核不会告知这个变化。
网友评论