socket通信中select函数的使⽤和解释
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平台下解释:
int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, const struct timeval*    timeout);
2,参数:
nfds:本参数忽略,仅起到兼容作⽤,设为0即可;
readfds:(可选)指针,指向⼀组等待可读性检查的套接⼝;
writefds:(可选)指针,指向⼀组等待可写性检查的套接⼝;
exceptfds:(可选)指针,指向⼀组等待错误检查的套接⼝;
timeout:本函数最多等待时间,对阻塞操作则为NULL。
3,返回值:
(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 { /
socket通信在哪一层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
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))
4,注释:
本函数⽤于确定⼀个或多个套接⼝的状态。对每⼀个套接⼝,调⽤者可查询它的可读性、可写性及错误状态信息。⽤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()调⽤可认为是⾮阻塞的,且⼀切适⽤于⾮阻塞调⽤的假设都适⽤于它。
WSANOTINITIALISED:在使⽤此API之前应⾸先成功地调⽤WSAStartup()。
WSAENETDOWN:      WINDOWS套接⼝实现检测到⽹络⼦系统失效。
WSAEINVAL:超时时间值⾮法。
WSAEINTR:通过⼀个WSACancelBlockingCall()来取消⼀个(阻塞的)调⽤。
WSAEINPROGRESS:⼀个阻塞的WINDOWS套接⼝调⽤正在运⾏中。
WSAENOTSOCK:描述字集合中包含有⾮套接⼝的元素。
6,如何处理
上⾯在说明FD_SETSIZE时,winsock2.h中定义FD_SETSIZE的⼤⼩为64,这样就对readfds、writefds、exceptfds的socket句柄数进⾏了限制。在实际应⽤中可以使⽤端⼝分组或者重新定义FD_SETSIZE的⽅式进⾏解决。在stdAfx.h最末⾏添加如下定义:
#define FD_SETSIZE 1024                  //socket句柄数
#define MAXIMUM_WAIT_OBJECTS    1024    //要等待的对象数
要注意的是我们还重定义了要另⼀个宏MAXIMUM_WAIT_OBJECTS,它表⽰要等待的对象数。重定义后,程序在现场运⾏正常。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。