socket通信中select函数的使用和解释

Posted ricks

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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平台下解释:

 

1,函数原型:

   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 { / 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))
技术分享图片

 

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()调用可认为是非阻塞的,且一切适用于非阻塞调用的假设都适用于它。

 

5,错误代码:

 

    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,它表示要等待的对象数。重定义后,程序在现场运行正常。



以上是关于socket通信中select函数的使用和解释的主要内容,如果未能解决你的问题,请参考以下文章

Python 使用socket实现一对多通信

《c语言》中select函数的作用是啥?

Linux 高级IO

Linux 高级IO

面试android的时候面试官提问"对网络通信协议熟悉吗?对socket和http了解吗?能解释下吗?",跪求帮助

socket通信中 ,服务器如何获取连接的客户端的主机名。求函数代码