为了解决网络IO中的问题,学者们提出了4种网络IO模型:①阻塞IO模型;②非阻塞IO模型;③多路IO复用模型;④异步IO模型。
1.阻塞IO模型
在Linux中,默认情况下所有的socket都是阻塞的,阻塞和非阻塞的概念描述的是用户线程调用内核IO操作的方式:阻塞是指IO操作需要彻底完成后才返回到用户空间;而非阻塞是指IO操作被调用后立即返回给用户一个状态值,不需要等到IO操作彻底完成。典型
2.非阻塞IO模型
当用户进程发出read操作时,如果内核中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个错误。从用户进程角度讲,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果。当用户进程判断结果是一个错误时,它就知道数据还没有准备好,于是它可以再次发送read操作。一旦内核中的数据准备好了,并且又再次收到了用户进程的系统调用,那么它马上就将数据复制到了用户内存中,然后返回正确的返回值。
使用如下函数可以将fd换成非阻塞状态:
fcntl( fd, F_SETFL, O_NONBLOCK );
3.多路复用型IO模型
多路IO复用,有时也称为事件驱动IO。它的基本原理就是有个函数(如select)会不断地轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。
4.异步IO模型
用户进程发起read操作之后,立刻就可以开始去做其他的事;而另一方面,从内核的角度,当它收到一个异步的read请求操作之后,首先会立刻返回,所以不会对用户进程产生任何阻塞。然后,内核会等待数据准备完成
重点介绍多路复用IO模型中的三种函数,select,poll和epoll函数。
1.select函数
select函数原型如下:
int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval*timeout);
(1)参数maxfdp是一个整数值,是指集合中所有文件描述符的范围,其值为所有文件描述符的最大值加1。
(2)参数timeout是select的超时时间,这个参数至关重要,它可以使select处于3种状态:若传入参数timeout=NULL,那么select将处于阻塞态,直到监视到文件描述符集合中某个描述符变化为止;如果将参数timeout=0,则select将处于非阻塞状态,不管文件是否变化,有变化返回一个正值,无变化返回0;如果将参数timeout设置为大于0,即select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,超时返回0,有变化返回一个正值。
(3)readfds,writefds和errorfds表示分别指向fd_set 描述符集合的指针,分别监视描述符集合中描述符的读,写和异常变化。
fd_set 结构可以理解为一个fd的集合,其宏定义的控制函数如下所示:
fd_set set;
FD_ZERO(&set); /*将set清零*/
FD_SET(fd, &set); /*将fd加入set */
FD_CLR(fd, &set); /*将fd从set中清除*/
FD_ISSET;/*如果fd在set中则真,函数返回时,在set中的为变化描述符*/
例子程序:
使用select循环读取键盘输入:
#include "main.h"
int main(){
int keyboard;
int ret,i;
char c;
fd_set readfd;
struct timeval timeout;
//只读,非阻塞的方式打开
keyboard = open("/dev/tty",O_RDONLY | O_NONBLOCK);
assert(keyboard>0);
while(1){
timeout.tv_sec=1;
timeout.tv_usec=0;
FD_ZERO(&readfd);
FD_SET(keyboard,&readfd);
ret=select(keyboard+1,&readfd,NULL,NULL,&timeout);
if(FD_ISSET(keyboard,&readfd)) {
i=read(keyboard,&c,1);
if('n'==c)
continue;
printf(