所谓文件描述符,就是你用来表示输入输出对象的整数,例如打开的文件以及网络通信用的套接字等。文件描述符的数量也是有限的,在Linux默认下,一个进程所能打开的文件描述符最大是1024个。
用单线程来处理多个连接的时候,像等待套接字收到数据的过程,这种阻塞是致命的,为了避免阻塞,我们必须在读取数据前先检查文件描述符中的输入是否已经达到并可用。
在unix中,对于多个文件描述符,可以使用一个叫做select的系统调用来监视他们是否处于可读可写的状态。select系统调用将要监视的文件描述符存入一个fd_set的结构体,并设定一个超时时间,对他们的状态进行监视。当指定的文件描述符可读、可写、发生异常等状态或者超时时,该调用系统会返回。之后检查fa_set,将可以得知在制定的文件描述符中,发生了怎样的状态变化。
select系统调用所能监视的文件描述符数量是有上限的,此外在调用select时,作为参数的fd_set会重新修改,它只会留存符合条件的文件描述符,所有再次监听需要重新改写,并且select调用中只要有符合条件的就会返回,而返回的参数只有一个整数n,根据n大于0,小于0,等于0来判断所有fd的事件,而没有单独返回具体事件fd的信息,因此还要全部遍历来检查是那些fd。
为了避免上述问题,一般尽量不使用select,使用epoll等或者非阻塞IO,但是如果不通过select系统调用实现对多个文件描述符的监视,则各种操作系统没有统一的方法。例如Linux使用epoll,freebsd使用kqueue。
epoll功能由epoll_creat和epoll_ctl和epoll_wait三个系统调用组成。
epoll_creat:创建监视描述符
epoll_ctl:将监视描述符进行注册
epoll_wait:通过其进行实际的监视
和select相比,epoll的优点:
要监视的fd数量没有限制
无需每次都要进行初始化
返回产生事件的fd信息,保存在epoll_event数组中,无需遍历所有的,而select没有开辟空间,只是在原来的fd集合中将不符合条件的删去
//http://blog.csdn.net/xs574924427/article/details/17921637
#define NSOCKS 2
int sock[NSOCKS], maxfd;
fd_set readfds;
struct timeval tv;
int i, n;
FD_ZERO(&readfds);
for(i=0; i<NSOCKS; i++) {
FD_SET(sock[i], &readfds);
}
tv.tv_sec = 2;
tv.tv_usec = 0;
/*
第一个参数为三个集合中最大的文件描述符+1
第二个参数为可读的文件描述符集合
第三个参数为可写的集合
第四个参数为异常(不包括错误)的集合
*/
n = select(maxfd + 1, &readfds, NULL, NULL, &tv);
if(n < 0) {
perror(NULL);
exit(0);
}
if(n == 0) {
puts("timeout");
exit(0);
}
else {
for(i = 0;i < NSOCKS; i++) {
/*
检查是否在这个集合里面,select将更新这个集合,把其中不可读的套节字去掉,
只保留符合条件的套节字在这个集合里面
一般while中,所以,每次都要重新初始化,将需要检测的加入其中
*/
if(FD_ISSET(socket[i], &readfds)) {
do_something(sock[i]);
}
}
}