Unix网络编程之Select函数
1. 用处
该函数允许进程知识内核等待多个时间中的任何一个发生,并只在有一个或者多个事件发生或经历一段指定的时间后才唤醒它。
2. 大致原理
select需要驱动程序的支持,驱动程序实现fops内的poll函数。select通过每个设备文件对应的poll函数提供的信息判断当前是否有资源可用(如可读或写),如果有的话则返回可用资源的文件描述符个数,没有的话则睡眠,等待有资源变为可用时再被唤醒继续执行。详细的原理请看这里
3. 函数原型
#include <sys/time.h>
#include <unistd.h>
select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout);
4. 参数
timeout
它告知内核等待所指定描述符中的任何一个就绪可花多长时间,其具有以下结构
struct timeval { time_t tv_sec; //秒 time_t tv_usec; //微秒 }; 若有就绪描述符则为其数目,若超时则为0,若出错则为-1。
该参数有以下三种可能
- 设置为空指针:永远等待下去
- 设置具体的值:等待一段固定的时间
- 两个参数均为0:根本不等待,既非阻塞
fd_set *readfds
是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的读变化的,即我们关心是否可以从这些文件中读取数据了,如果这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读,如果没有可读的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的读变化。
fd_set *writefds , fd_set *errorfds
用法同上,一个监听是否可以向这些文件中写数据,另一个用来监听文件错误异常。
int maxfdp
是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1
5.fd_set结构体
其实是一个整形数组,其中每个整数中的每一位对应一个描述符,系统提供了下面几个函数对其进行操作。
FD_SET(int fd, fd_set *fdset); //将fd加入set集合
FD_CLR(int fd, fd_set *fdset); //将fd从set集合中清除
FD_ISSET(int fd, fd_set *fdset); //检测fd是否在set集合中,不在则返回0
FD_ZERO(fd_set *fdset); //将set清零使集合中不含任何fd
6.注意事项
#include "unp.h"
void
str_cli(FILE *fp, int sockfd)
{
int maxfdp1;
fd_set rset;
char sendline[MAXLINE], recvline[MAXLINE];
FD_ZERO(&rset);
for ( ; ; ) {
FD_SET(fileno(fp), &rset);
FD_SET(sockfd, &rset);
maxfdp1 = max(fileno(fp), sockfd) + 1;
Select(maxfdp1, &rset, NULL, NULL, NULL);
if (FD_ISSET(sockfd, &rset)) { /* socket is readable */
if (Readline(sockfd, recvline, MAXLINE) == 0)
err_quit("str_cli: server terminated prematurely");
Fputs(recvline, stdout);
}
if (FD_ISSET(fileno(fp), &rset)) { /* input is readable */
if (Fgets(sendline, MAXLINE, fp) == NULL)
return; /* all done */
Writen(sockfd, sendline, strlen(sendline));
}
}
}
这个程序是unix网络教程书中P133页的程序,刚开始没搞懂它在11行设置了FD_SET(sockfd, &rset),然后又在后面14行检测是否设置了sockfd,为什么要这么做。然后查资料后发现虽然在11行会对sockfd进行设置,如果在设置之后立刻检测,其值也确实为1,但是经过13行Select之后它的值(sockfd的值)会改变,也就是说Select函数会根据实际情况来改变其中描述符的值。下面我举一个例子说明
#include "unp.h"
int mian(int argc, char **argv){
int sockfd;
sockfd = Socket(AF_INET, SOCK_STREAM, 0);
fd_set rset;
FD_ZERO(&rset); //对fd_set进行清零操作
FD_SET(sockfd, &rset); //把sockfd加到rset集合中
int isset = FD_ISSET(sockfd, &rset); //加入之后立刻检测
printf("before select, isset = %d\n", isset); //这里打印的结果为1
Select(maxfdp1, &rset, NULL, NULL, NULL); //现在进行select
isset = FD_ISSET(sockfd, &rset); //如果sockfd没有准备好的话,那么这里的打印值为0
printf("after select, isset = %d\n", isset);
return 0;
}