select 模型
具体的模型名称是:多路复用输入/输出模型
直白的解释:比如说你的设备同时接收来自网络的两路数据,这时你可以建两个socket来接收,用select函数就可以同时监控两个socket的情况,完成两个socket的接收,这就是所谓“多路复用接收”。参考:这里
这里边的FD指的是file descriptor(文件描述符)
系统调用:
#include<sys/select.h> // select
#include<sys/time.h> // struct timeval
int select (int nfds , fd_set* readfds , fd_set * writefds , fd_set * exceptfds , struct timeval * timeout );
select监听文件描述符(也就是文件的句柄)的属性事件,属性包括:可读、可写、和异常。它可以是阻塞也可以是非阻塞的,监听的时候,一般使用while(1)循环。对于结构体fd_set 稍后了解。监听的文件描述符,不局限于网络编程里边的套接字文件描述符,Unix下任何设备、管道、FIFO等都是文件形式,全部包括在内,所以毫无疑问一个socket就是一个文件,socket句柄就是一个文件描述符。
参数介绍:
nfds:
是需要监视的最大的文件描述符值+1,在使用select之前,要记得找到那几个监听的句柄的最大值。
可读:
比如A、B两端通信,A向B发送套接字消息,但是B不知道A何时发,那么就用select监听可读事件,因为可读也就意味着有数据来了。
可写:
而对于可写情况,多数网络编程的时候,不需要使用,因为网络缓冲区一般都很大。例子
异常:
指的是异常条件出现的文件描述符,错误并不包括在里边。
阻塞和非阻塞:
用第五个参数来控制:
1. timeout=NULL (阻塞:直到有一个fd位被置为1函数才返回)
2. timeout所指向的结构设为非零时间(等待固定时间:有一个fd位被置为1或者时间耗尽,函数均返回)
3. timeout所指向的结构,时间设为0 (非阻塞:函数检查完每个fd后立即返回)
返回值:
小于0(-1):表示select函数出现错误,或者因为停止信号而停止,想要得到真正原因,可以通过errno得到,具体参考:这里
等于0:超时
大于0:发生状态变化的句柄的数量
比较好的例子:
特殊情况比如对方通过一个socket句柄发来了紧急数据。如果我们程序里只想检测某个socket是否有数据可读,我们可以这样: fd_set rdfds; /* 先申明一个 fd_set 集合来保存我们要检测的 socket句柄 */ struct timeval tv; /* 申明一个时间变量来保存时间 */ int ret; /* 保存返回值 */ FD_ZERO(&rdfds); /* 用select函数之前先把集合清零 */ FD_SET(socket, &rdfds); /* 把要检测的句柄socket加入到集合里 */ tv.tv_sec = 1; tv.tv_usec = 500; /* 设置select等待的最大时间为1秒加500毫秒 */ ret = select(socket + 1, &rdfds, NULL, NULL, &tv); /* 检测我们上面设置到集合rdfds里的句柄是否有可读信息 */ if(ret < 0) perror("select");/* 这说明select函数出错 */ else if(ret == 0) printf("超时 "); /* 说明在我们设定的时间值1秒加500毫秒的时间内,socket的状态没有发生变化 */ else { /* 说明等待时间还未到1秒加500毫秒,socket的状态发生了变化 */ printf("ret=%d ", ret); /* ret这个返回值记录了发生状态变化的句柄的数目,由于我们只监视了socket这一个句柄,所以这里一定ret=1,如果同时有多个句柄发生变化返回的就是句柄的总和了 */ /* 这里我们就应该从socket这个句柄里读取数据了,因为select函数已经告诉我们这个句柄里有数据可读 */ if(FD_ISSET(socket, &rdfds)) { /* 先判断一下socket这外被监视的句柄是否真的变成可读的了 */ /* 读取socket句柄里的数据 */ recv(...); //之前可能还会加ioctl的控制 } }
这里边有一点需要格外注意,先要循环读取的时候,要每次进入循环都重新使用FD_ZERO清空集合并用FD_SET将句柄重新设置,因为事件发生后,内核会将文件描述符集合修改。
《Linuc高性能服务器编程》的第九章9.1.3节;里边的一个例子:
对于select整体了解了,了解一下参数里边的类型fd_set :
select()机制中提供一fd_set的数据结构,实际上是一long类型的数组,每一个数组元素都能与一打开的文件句柄(不管是socket句柄,还是其他文件或命名管道或设备句柄)建立联系,建立联系的工作由程序员完成,当调用select()时,由内核根据IO状态修改fd_set的内容,由此来通知执行了select()的进程哪一socket或文件发生了可读或可写事件。
fd_set定义为:
typedefstruct { /* XPG4.2 requires this member name. Otherwise avoid the name from the global namespace. */ #ifdef __USE_XOPEN __fd_mask fds_bits[__FD_SETSIZE / __NFDBITS]; # define __FDS_BITS(set) ((set)->fds_bits) #else __fd_mask __fds_bits[__FD_SETSIZE / __NFDBITS]; # define __FDS_BITS(set) ((set)->__fds_bits) #endif } fd_set;
几个操作它的红定义:
FD_ZERO(&set); /*将set清零使集合中不含任何fd*/
FD_SET(fd, &set); /*将fd加入set集合*/
FD_CLR(fd, &set); /*将fd从set集合中清除*/
FD_ISSET(fd, &set); /*在调用select()函数后,用FD_ISSET来检测fd在fdset集合中的状态是否变化返回整型,当检测到fd状态发生变化时返回真,否则,返回假(0)*/
以上式子中的fd为socket句柄。
#include <stdio.h> #include <sys/select.h> #include <unistd.h> intmain(intargc,char**argv){ fd_set fdset; FD_ZERO (&fdset); /*清空集合中所有的元素*/ FD_SET(STDOUT_FILENO,&fdset); /*设置stdout,使集合中包含stdout*/ if(FD_ISSET(STDOUT_FILENO,&fdset)!=0) /*测试stdout是否包含在集合中*/ printf("stdout has been set\n"); else printf("stdout has not been set\n"); FD_CLR(STDOUT_FILENO,&fdset); /*从位向量中清除stdout*/ if(FD_ISSET(STDOUT_FILENO,&fdset)!=0) /*再次测试*/ printf("stdout has been set\n"); else printf("stdout has not been set\n"); return0; }
原文:http://blog.csdn.net/lileiyang12/article/details/22478789