异步I/O:
使用select/poll函数可以实现异步I/O,系统并不主动告诉我们描述符的状态,我们需要通过select或poll主动的查询。信号提供了一种异步形式通知某种事件发生的方法,但是每个进程只有一个信号,如果要同时对几个描述符进行异步I/O,那么进程接收到信号时并不知道这个信号对应于哪个文件描述符。
阻塞与非阻塞:
阻塞方式block,是进程或是线程执行到这些函数时必须等待某个事件的发生,如果事件没有发生,进程或线程就被阻塞,函数不能立即返回。使用select就可以完成非阻塞(non-block,进程或线程执行此函数时不必非要等待事件的发生,一旦执行肯定返回,以返回值的不同来反映函数的执行情况,如果事件发生则与阻塞方式相同,若事件没有发生则返回一个代码来告知事件未发生,而进程或线程继续执行,效率较高),它能够监视我们需要监视的文件描述符的变化情况——读写或是异常。
select函数使我们可以执行I/O多路转接(多路复用)。
IO多路复用是指内核一旦发现进程指定的一个或者多个IO条件准备读取,它就通知该进程。IO多路复用适用如下场合:
(1)当客户处理多个描述字时(一般是交互式输入和网络套接口),必须使用I/O复用。
(2)当一个客户同时处理多个套接口时,而这种情况是可能的,但很少出现。
(3)如果一个TCP服务器既要处理监听套接口,又要处理已连接套接口,一般也要用到I/O复用。
(4)如果一个服务器即要处理TCP,又要处理UDP,一般要使用I/O复用。
(5)如果一个服务器要处理多个服务或多个协议,一般要使用I/O复用。
与多进程和多线程技术相比,I/O多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。
传向select函数的参数告诉内核:
1 我们所关心的描述符。
2 对于每个描述符我们关心的条件(读,写,异常)
3 希望等待多长时间(永久等待,等一段时间,不等待)
select函数返回,内核告诉我们:
1 已准备好的描述符数量。
2 哪个描述符已准备好读、写、或异常。
使用该返回值,就可以调用相应的I/O函数(read,write),并且明确知道该函数不会阻塞。
int select(int maxfdp1,fd_set* readfds,fd_set* writefds,fd_set *exceptfds,struct timeval* tvptr);
第1个参数maxfdp1:说明最大描述符数(经常256或1024),对大部分程序来说太大,可以设置为最高描述符加1。
第2,3,4个参数readfds,writefds,exceptfds:指向描述符集的指针,每个描述符集放在一个fd_set数据类型中。如果三个指针都是空指针,则select提供了比sleep更精确的计时器。sleep等待整数秒,select可以小于1秒。
底下的宏提供了处理这三种描述词组的方式:
FD_CLR(inr fd,fd_set* set);用来清除描述词组set中相关fd的位
FD_ISSET(int fd,fd_set* set);用来测试描述词组set中相关fd的位是否为真
FD_SET(int fd,fd_set* set);用来设置描述词组set中相关fd的位
FD_ZERO(fd_set* set); 用来清除描述词组set的全部位
第5个参数tvptr: 说明等待的时间。可以是永久等待(将select置于阻塞状态)、等待一段时间精确到us(这段时间内阻塞),完全不等待(非阻塞)。
struct timeval
{
time_t tv_sec;//秒
time_t tv_usec;//微秒
};
select函数有3种返回值:
1 返回-1,表示出错,例如指定的描述符集都没准备好时捕捉到一个信号。
2 返回0,表示没有描述符准备好,指定的时间已经超过。
3 返回正值,表示已经准备好的描述符数,三个描述符集中仍旧打开的位是对应已准备好的描述符位。
所以除非返回正值,否则返回后检查描述符集都是没有意义的。
准备好的意思是:
1 对于读集中一个描述符read,不会阻塞,则它是准备好的。
2 对于写集中一个描述符write,不会阻塞,则它是准备好的。
3 对于异常条件集中的一个描述符有一个未决的异常条件,则它是准备好的。
一个描述符阻塞与否并不影响select是否阻塞,即如果希望读一个非阻塞的描述符,并且超时值设为5秒,则select最多阻塞5s。如果设为永久等待,则select阻塞直到该描述符数据准备好或者捕捉到信号。
如果在一个描述符上碰到文件结束,则select认为该描述符时可读的。调用read返回0,这是UNIX指示文件结尾的办法。
例子:
#include <sys/time.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define BUFFSIZE 1000
int main ()
{
int fd;
int ret;
char c[BUFFSIZE];
fd_set readfd;
struct timeval timeout;
fd = open("/dev/tty",O_RDONLY | O_NONBLOCK);
while(1)
{
timeout.tv_sec=3;
timeout.tv_usec=0;
FD_ZERO(&readfd);
FD_SET(fd,&readfd);
ret=select(fd+1,&readfd,NULL,NULL,&timeout);
if(ret == -1)
printf("error\n");
else if(ret){
if(FD_ISSET(fd,&readfd))
{
int n=read(fd,c,BUFFSIZE);
write(STDOUT_FILENO,c,n);
if (strcmp(c,"end")==0)
break;
}
}
else //超时
{
printf("time out\n");
continue;
}
}
}