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函数的使用和解释的更多相关文章

  1. socket 通信之select

    对于socket 通信,大家很多都用的单线程通信.同时只能监听一个端口,只能响应一个服务,select的方式可以解决多个socket 被连接的问题.一次可以分配多个资源,只要一个连接便可以进行通信.在 ...

  2. Socket通信中AF_INET 和 AF_UNIX域的区别

    转载:http://blog.csdn.net/sandware/article/details/40923491 1.  AF_INET域socket通信过程 典型的TCP/IP四层模型的通信过程. ...

  3. 转:linux中select()函数分析

    源地址:http://blog.csdn.net/zi_jin/article/details/4214359 Select在Socket编程中还是比较重要的,可是对于初学Socket的人来说都不太爱 ...

  4. 【Java TCP/IP Socket】TCP Socket通信中由read返回值造成的的死锁问题(含代码)(转)

    书上示例 在第一章<基本套接字>中,作者给出了一个TCP Socket通信的例子——反馈服务器,即服务器端直接把从客户端接收到的数据原原本本地反馈回去. 书上客户端代码如下: 1 2 3 ...

  5. epoll在socket通信中的应用

    当服务器需要服务多个客户时,需要使用并发通信,实现并发通信有以下几种方法: 1.在服务器中fork子进程来为每个客户服务  具体可参考http://www.cnblogs.com/ggjucheng/ ...

  6. [转]SOCKET通信中TCP、UDP数据包大小的确定

    TCP.UDP数据包大小的确定 UDP和TCP协议利用端口号实现多项应用同时发送和接收数据.数据通过源端口发送出去,通过目标端口接收.有的网络应用只能使用预留或注册的静态端口:而另外一些网络应用则可以 ...

  7. socket学习笔记——select函数的使用(windows)

    #include <stdio.h> #include <stdlib.h> #include <string.h> #include <winsock2.h ...

  8. Socket通信中的 BeginReceive与EndReceive

    BeginReceive 与endReceive 必须成对出现,如果BeginReceive没有及时调用endReceive,可能会出现数据被从buffer中读取二次,如果在下面这行代码下面加入别的代 ...

  9. IO创建Socket通信中慎用BufferReader中的readLine()

    在编写Socket的Demo的时候,在Server中使用BufferReader获取从客服端发送过来的内容 package cn.lonecloud.socket; import cn.loneclo ...

随机推荐

  1. Linux中访问Apache报403错误处理方法

    1.先说本地环境,脱离基础环境的解决策略都是耍流氓 (1)Linux:CentOS7.5 (2)Apache: Apache/2.4.6 (CentOS) (3)Apache的安装路径在 /usr/l ...

  2. AutomaticReferenceCounting.html#runtime-support

    https://clang.llvm.org/docs/AutomaticReferenceCounting.html#runtime-support Runtime support This sec ...

  3. jQuery-4.动画篇---jQuery核心

    jQuery中each方法的应用 jQuery中有个很重要的核心方法each,大部分jQuery方法在内部都会调用each,其主要的原因的就是jQuery的实例是一个元素合集 如下:找到所有的div, ...

  4. python 8

    一.文件操作初识 1. path 文件路径 F:\文件.txt encoding 编码方式 utf-8, gbk ... mode 操作方式 只读,只写,读写,写读,追加... f1 = open(r ...

  5. 计算机原码、补码、反码与java移位运算符(<</>>/>>>)

    一.机器值和真值 1.机器值 一个数在计算机中的二进制表示形式,  叫做这个数的机器数.机器数是带符号的,在计算机用一个数的最高位存放符号, 正数为0, 负数为1. 比如,十进制中的数 +3 ,计算机 ...

  6. 通过sql查找指定字段存在哪些表中

    select * from INFORMATION_SCHEMA.columns where COLUMN_NAME Like '%order_type%';

  7. 基于Linux-3.9.4的mykernel实验环境的极简内核分析

    382 + 原创作品转载请注明出处 + https://github.com/mengning/linuxkernel/ 一.实验环境 win10 -> VMware -> Ubuntu1 ...

  8. Redis管道功能

    Redis管道,Redis存储用户浏览数据 当频繁的存储获取Redis数据库中的数据时,可以使用Redis的pipeline(管道)功能,将多个相互没有依赖关系的读写操作,如:下一步执行的Redis操 ...

  9. 关于使用jQuery操作dom时的一点发现

    <body> <ul> <li>list item 1</li> <li>list item 2</li> <li> ...

  10. web Function函数

    javascript中函数定义 js中函数一般定义如下: function functionName(arg0,arg1,arg2,...,argN) { statements;}其中function ...