select函数的作用:
select()在SOCKET编程中还是比较重要的,可是对于初学SOCKET的人来说都不太爱用select()写程序,他们只是习惯写诸如 conncet()、accept()、recv()或recvfrom这样的阻塞程序(所谓阻塞方式block,顾名思义,就是进程或是线程执行到这些函数时必须等待某个事件发生,如果事件没有发生,进程或线程就被阻塞,函数不能立即返回)。可是使用select()就可以完成非阻塞(所谓非阻塞方式non-block,就是进程或线程执行此函数时不必非要等待事件的发生,一旦执行肯定返回,以返回值的不同来反映函数的执行情况。如果事件发生则与阻塞方式相同,若事件没有发生则返回一个代码来告知事件未发生,而进程或线程继续执行,所以效率高)方式工作的程序,它能够监视我们需要监视的文件描述符的变化情况——读写或是异常。
select函数格式:
select()函数的格式(所说的是Unix系统下的Berkeley Socket编程,和Windows下的有区别,一会儿说明):
Unix系统下解释:
int select(int maxfdp, fd_set* readfds, fd_set* writefds, fd_set* errorfds, struct timeval* timeout);
先说明两个结构体:
第一:struct fd_set可以理解为一个集合,这个集合中存放的是文件描述符(file descriptor),即文件句柄,这可以是我们所说的普通意义的文件,当然Unix下任何设备、管道、FIFO等都是文件形式,全部包括在内,所以,毫无疑问,一个socket就是一个文件,socket句柄就是一个文件描述符。fd_set集合可以通过一些宏由人为来操作,比如清空集合:FD_ZERO(fd_set*),将一个给定的文件描述符加入集合之中FD_SET(int, fd_set*),将一个给定的文件描述符从集合中删除FD_CLR(int, fd_set*),检查集合中指定的文件描述符是否可以读写FD_ISSET(int, fd_set*)。一会儿举例说明。
第二:struct timeval是一个大家常用的结构,用来代表时间值,有两个成员,一个是秒数,另一个毫秒数。
具体解释select的参数:
int maxfdp是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1,不能错!在Windows中这个参数值无所谓,可以设置不正确。
fd_set* readfds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的读变化的,即我们关心是否可以从这些文件中读取数据了,如果这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读,如果没有可读的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的读变化。
fd_set* writefds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的写变化的,即我们关心是否可以向这些文件中写入数据了,如果这个集合中有一个文件可写,select就会返回一个大于0的值,表示有文件可写,如果没有可写的文件,则根据timeout再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的写变化。
fe_set* errorfds同上面两个参数的意图,用来监视文件错误异常。
struct timeval* timeout是select的超时时间,这个参数至关重要,它可以使select处于三种状态。
第一:若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;
第二:若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;
第三:timeout的值大于0,这就是等待的超时时间,即select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值同上述。
select函数返回值:
负值:select错误
正值:某些文件可读写或出错
0:等待超时,没有可读写或错误的文件
Windows平台下解释:
1,函数原型:
int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, const struct timeval* timeout);
2,参数:
writefds: (可选)指针,指向一组等待可写性检查的套接口;
exceptfds:(可选)指针,指向一组等待错误检查的套接口;
timeout: 本函数最多等待时间,对阻塞操作则为NULL。
(1)select()调用返回处于就绪状态并且已经包含在fd_set结构中的描述字总数;
(2)如果超时则返回0;
(3)否则的话,返回SOCKET_ERROR错误,应用程序可通过WSAGetLastError()获取相应错误代码。
看下源码:
#ifndef FD_SETSIZE #define FD_SETSIZE 64 #endif /* FD_SETSIZE */ typedef struct fd_set { u_int fd_count; /* how many are SET? */ SOCKET fd_array[FD_SETSIZE]; /* an array of SOCKETs */ } fd_set; extern int PASCAL FAR __WSAFDIsSet(SOCKET, fd_set FAR *); #define FD_CLR(fd, set) do { / u_int __i; / for (__i = 0; __i < ((fd_set FAR *)(set))->;fd_count ; __i++) { / if (((fd_set FAR *)(set))->;fd_array[__i] == fd) { / while (__i < ((fd_set FAR *)(set))->;fd_count-1) { / ((fd_set FAR *)(set))->;fd_array[__i] = / ((fd_set FAR *)(set))->;fd_array[__i+1]; / __i++; / } / ((fd_set FAR *)(set))->;fd_count--; / break; / } / } / } while(0) #define FD_SET(fd, set) do { / u_int __i; / for (__i = 0; __i < ((fd_set FAR *)(set))->;fd_count; __i++) { / if (((fd_set FAR *)(set))->;fd_array[__i] == (fd)) { / break; / } / } / if (__i == ((fd_set FAR *)(set))->;fd_count) { / if (((fd_set FAR *)(set))->;fd_count < FD_SETSIZE) { / ((fd_set FAR *)(set))->;fd_array[__i] = (fd); / ((fd_set FAR *)(set))->;fd_count++; / } / } / } while(0) #define FD_ZERO(set) (((fd_set FAR *)(set))->;fd_count=0) #define FD_ISSET(fd, set) __WSAFDIsSet((SOCKET)(fd), (fd_set FAR *)(set)) typedef int32_t __fd_mask; #define _NFDBITS (sizeof(__fd_mask) * 8) /* 8 bits per byte */ #define __howmany(x,y) (((x)+((y)-1))/(y)) #ifndef _FD_SET # define _FD_SET typedef struct __fd_set { long fds_bits[__howmany(FD_SETSIZE, (sizeof(long) * 8))]; } fd_set; # ifndef _KERNEL # ifdef __cplusplus extern "C" { # endif /* __cplusplus */ #ifdef _INCLUDE_HPUX_SOURCE # define FD_SET(n,p) (((__fd_mask *)((p)->;fds_bits))[(n)/_NFDBITS] |= (1 << ((n) % _NFDBITS))) # define FD_CLR(n,p) (((__fd_mask *)((p)->;fds_bits))[(n)/_NFDBITS] &= ~(1 << ((n) % _NFDBITS))) # define FD_ISSET(n,p) (((__fd_mask *)((p)->;fds_bits))[(n)/_NFDBITS] & (1 << ((n) % _NFDBITS))) # define FD_ZERO(p) memset((void *)(p), (int) 0, sizeof(*(p))) #else # define FD_SET(n,p) (__fd_set1(n, p)) # define FD_CLR(n,p) (__fd_clr(n, p)) # define FD_ISSET(n,p) (__fd_isset(n, p)) # define FD_ZERO(p) memset((void *)(p), (int) 0, sizeof(fd_set))
本函数用于确定一个或多个套接口的状态。对每一个套接口,调用者可查询它的可读性、可写性及错误状态信息。用fd_set结构来表示一组等待检查的套接口。在调用返回时,这个结构存有满足一定条件的套接口组的子集,并且select()返回满足条件的套接口的数目。有一组宏可用于对fd_set的操作,这些宏与Berkeley Unix软件中的兼容,但内部的表达是完全不同的。
readfds参数标识等待可读性检查的套接口。如果该套接口正处于监听listen()状态,则若有连接请求到达,该套接口便被标识为可读,这样一个accept()调用保证可以无阻塞完成。对其他套接口而言,可读性意味着有排队数据供读取。或者对于SOCK_STREAM类型套接口来说,相对于该套接口的虚套接口已关闭,于是recv()或recvfrom()操作均能无阻塞完成。如果虚电路被“优雅地”中止,则recv()不读取数据立即返回;如果虚电路被强制复位,则recv()将以WSAECONNRESET错误立即返回。如果SO_OOBINLINE选项被设置,则将检查带外数据是否存在(参见setsockopt())。
writefds参数标识等待可写性检查的套接口。如果一个套接口正在connect()连接(非阻塞),可写性意味着连接顺利建立。如果套接口并未处于connect()调用中,可写性意味着send()和sendto()调用将无阻塞完成。〔但并未指出这个保证在多长时间内有效,特别是在多线程环境中〕。
exceptfds参数标识等待带外数据存在性或意味错误条件检查的套接口。请注意如果设置了SO_OOBINLINE选项为假FALSE,则只能用这种方法来检查带外数据的存在与否。对于SO_STREAM类型套接口,远端造成的连接中止和KEEPALIVE错误都将被作为意味出错。如果套接口正在进行连接connect()(非阻塞方式),则连接试图的失败将会表现在exceptfds参数中。
如果对readfds、writefds或exceptfds中任一个组类不感兴趣,可将它置为空NULL。
在winsock2.h头文件中共定义了四个宏来操作描述字集。FD_SETSIZE变量用于确定一个集合中最多有多少描述字(FD_SETSIZE缺省值为64,可在包含winsock.h前用#define FD_SETSIZE来改变该值)。对于内部表示,fd_set被表示成一个套接口的队列,最后一个有效元素的后续元素为INVAL_SOCKET。宏为:
FD_CLR(s,*set): 从集合set中删除描述字s。
FD_ISSET(s,*set): 若s为集合中一员,非零;否则为零。
FD_SET(s,*set): 向集合添加描述字s。
FD_ZERO(*set): 将set初始化为空集NULL。
timeout参数控制select()完成的时间。若timeout参数为空指针,则select()将一直阻塞到有一个描述字满足条件。否则的话,timeout指向一个timeval结构,其中指定了select()调用在返回前等待多长时间。如果timeval为{0,0},则select()立即返回,这可用于探询所选套接口的状态。如果处于这种状态,则select()调用可认为是非阻塞的,且一切适用于非阻塞调用的假设都适用于它。
#define MAXIMUM_WAIT_OBJECTS 1024 //要等待的对象数