select函数的作用:

在编程的过程中,经常会遇到许多阻塞的函数,好像read和网络编程时使用的recv, recvfrom函数都是阻塞的函数,当函数不能成功执行的时候,程序就会一直阻塞在这里,无法执行下面的代码。这是就需要用到非阻塞的编程方式,使用selcet函数就可以实现非阻塞编程。
selcet函数是一个轮循函数,即当循环询问文件节点,可设置超时时间,超时时间到了就跳过代码继续往下执行。

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

select系统调用是用来让我们的程序监视多个文件句柄(file descriptor)的状态变化的。程序会停在select这里等待,直到被监视的文件句柄有某一个或多个发生了状态改变。

文件在句柄在Linux里很多,如果你man某个函数,在函数返回值部分说到成功后有一个文件句柄被创建的都是的,如man socket可以看到“On success, a file descriptor for the new socket is returned.”而man 2 open可以看到“open() and creat() return the new file descriptor”,其实文件句柄就是一个整数,看socket函数的声明就明白了:
int socket(int domain, int type, int protocol);
当然,我们最熟悉的句柄是0、1、2三个,0是标准输入,1是标准输出,2是标准错误输出。0、1、2是整数表示的,对应的FILE *结构的表示就是stdin、stdout、stderr,0就是stdin,1就是stdout,2就是stderr。
比如下面这两段代码都是从标准输入读入9个字节字符:

#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char ** argv)
{
        char buf[10] = "";
        read(0, buf, 9); /* 从标准输入 0 读入字符 */
        fprintf(stdout, "%s\n", buf); /* 向标准输出 stdout 写字符 */
        return 0;
}
/* **上面和下面的代码都可以用来从标准输入读用户输入的9个字符** */
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char ** argv)
{
        char buf[10] = "";
        fread(buf, 9, 1, stdin); /* 从标准输入 stdin 读入字符 */
        write(1, buf, strlen(buf));
        return 0;
}

继续上面说的select,就是用来监视某个或某些句柄的状态变化的。select函数原型如下:
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
函数的最后一个参数timeout显然是一个超时时间值,其类型是struct timeval *,即一个struct timeval结构的变量的指针,所以我们在程序里要申明一个struct timeval tv;然后把变量tv的地址&tv传递给select函数。struct timeval结构如下:

struct timeval {
             long    tv_sec;         /* seconds */
             long    tv_usec;        /* microseconds */
         };

第2、3、4三个参数是一样的类型: fd_set *,即我们在程序里要申明几个fd_set类型的变量,比如rdfds, wtfds, exfds,然后把这个变量的地址&rdfds, &wtfds, &exfds 传递给select函数。这三个参数都是一个句柄的集合,第一个rdfds是用来保存这样的句柄的:当句柄的状态变成可读的时系统就会告诉select函数返回,同理第二个wtfds是指有句柄状态变成可写的时系统就会告诉select函数返回,同理第三个参数exfds是特殊情况,即句柄上有特殊情况发生时系统会告诉select函数返回。特殊情况比如对方通过一个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("超时\n"); /* 说明在我们设定的时间值1秒加500毫秒的时间内,socket的状态没有发生变化 */
else { /* 说明等待时间还未到1秒加500毫秒,socket的状态发生了变化 */
    printf("ret=%d\n", ret); /* ret这个返回值记录了发生状态变化的句柄的数目,由于我们只监视了socket这一个句柄,所以这里一定ret=1,如果同时有多个句柄发生变化返回的就是句柄的总和了 */
    /* 这里我们就应该从socket这个句柄里读取数据了,因为select函数已经告诉我们这个句柄里有数据可读 */
    if(FD_ISSET(socket, &rdfds)) { /* 先判断一下socket这外被监视的句柄是否真的变成可读的了 */
        /* 读取socket句柄里的数据 */
        recv(...);
    }
}

注意select函数的第一个参数,是所有加入集合的句柄值的最大那个值还要加1。比如我们创建了3个句柄:
/************关于本文档********************************************
*filename: Linux网络编程一步一步学-select详解
*purpose: 详细说明select的用法
*wrote by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言
*date time:2007-02-03 19:40
*Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
* 但请遵循GPL
*Thanks to:Google
*Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
* 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献!
*********************************************************************/

int sa, sb, sc;
sa = socket(...); /* 分别创建3个句柄并连接到服务器上 */
connect(sa,...);
sb = socket(...);
connect(sb,...);
sc = socket(...);
connect(sc,...);

FD_SET(sa, &rdfds);/* 分别把3个句柄加入读监视集合里去 */
FD_SET(sb, &rdfds);
FD_SET(sc, &rdfds);

在使用select函数之前,一定要找到3个句柄中的最大值是哪个,我们一般定义一个变量来保存最大值,取得最大socket值如下:

int maxfd = 0;
if(sa > maxfd) maxfd = sa;
if(sb > maxfd) maxfd = sb;
if(sc > maxfd) maxfd = sc;

然后调用select函数:

ret = select(maxfd + 1, &rdfds, NULL, NULL, &tv); /* 注意是最大值还要加1 */

同样的道理,如果我们要检测用户是否按了键盘进行输入,我们就应该把标准输入0这个句柄放到select里来检测,如下:

FD_ZERO(&rdfds);
FD_SET(0, &rdfds);
tv.tv_sec = 1;
tv.tv_usec = 0;
ret = select(1, &rdfds, NULL, NULL, &tv); /* 注意是最大值还要加1 */
if(ret < 0) perror("select");/* 出错 */
else if(ret == 0) printf("超时\n"); /* 在我们设定的时间tv内,用户没有按键盘 */
else { /* 用户有按键盘,要读取用户的输入 */
    scanf("%s", buf);
}

网络编程select函数的更多相关文章

  1. UNIX网络编程——select函数的并发限制和 poll 函数应用举例

    一.用select实现的并发服务器,能达到的并发数,受两方面限制 1.一个进程能打开的最大文件描述符限制.这可以通过调整内核参数.可以通过ulimit -n来调整或者使用setrlimit函数设置,  ...

  2. c/c++ 网络编程 getaddrinfo 函数

    网络编程 getaddrinfo 函数 解析网址,返回IP地址. 例子: #include <iostream> #include <string.h> #include &l ...

  3. c/c++ 网络编程 bind函数

    网络编程 bind函数 bind的作用是确定端口号. 正常处理都是先bind,然后listen 如果不bind,直接listen,会是什么结果? 内核会自动随机分配一个端口号 例子: #include ...

  4. UNIX网络编程——fcntl函数

    fcntl函数提供了与网络编程相关的如下特性: 非阻塞式I/O.  通过使用F_SETFL命令设置O_NONBLOCK文件状态标志,我们可以把一个套接字设置为非阻塞型. 信号驱动式I/O. 通过使用F ...

  5. 网络编程——select介绍

    一.select函数简介 select一般用在socket网络编程中,在网络编程的过程中,经常会遇到许多阻塞的函数,网络编程时使用的recv, recvfrom.connect函数都是阻塞的函数,当函 ...

  6. Linux网络编程-readn函数、writen函数、readline函数实现

    readn函数功能:在网络编程的读取数据中,通常会需要用到一个读指定字节才返回的函数,linux系统调用中没有给出,需要自己封装. readn实现代码: int readn(int fd, void ...

  7. 健壮的网络编程IO函数-RIO包

    RIO包 简介 Rio包即为Robust io函数包.包中函数是对Linux基本I/O函数的封装,使其更加健壮.高效,更适用于网络编程. 分析 Rio包由rio_t结构体和系列函数组成. 首先是两个不 ...

  8. UNIX网络编程-Select模型学习

    1.相关接口介绍 1.1 select ---------------------------------------------------------------------- #include ...

  9. linux c网络编程之函数

    1. struct protoent *protocol=getprotobyname(char *p); 功能:通过协议名获取协议类型信息 解释:p为字符串指针,指向一个协议名,如icmp,stru ...

随机推荐

  1. 10 quick tips for Redis

    Redis is hot in the tech community right now. It's come a long way from being a small personal proje ...

  2. android之handler机制深入解析

    一.android中需要另开线程处理耗时.网络的任务,但是有必须要在UI线程中修改组件.这样做是为了: ①只能在UI线程中修改组件,避免了多线程造成组件显示混乱 ②不使用加锁策略是为了提高性能,因为a ...

  3. jQuery学习(二)——使用JQ完成页面定时弹出广告

    1.JQuery效果 2.步骤分析: 第一步:引入jQuery相关的文件 第二步:书写页面加载函数 第三步:在页面加载函数中,获取显示广告图片的元素. 第四步:设置定时操作(显示广告图片的函数) 第五 ...

  4. hiho 1571 - 贪心好题*

    题目链接 小Hi在帮助钢铁侠开发新的盔甲.这套新盔甲一共包含M种武器插槽,其中第i种插槽有Ci个.每个插槽最多安装一个武器模块. 小Hi一共准备了N个武器模块,编号1~N.每个武器模块都有三个参数Vi ...

  5. javascript的var声明变量和不用var声明变量在全局作用域的区别;

    在全局作用域下,使用var定义的变量不可以delete,没有var 定义的变量可以delete.也就说明隐含全局变量严格来说不是真正的变量,而是全局对象的属性,因为属性可以通过delete删除,而变量 ...

  6. springMVC 定时器配置

    1.在springMVC中加入 xmlns:task="http://www.springframework.org/schema/task" http://www.springf ...

  7. Tomcat + Mysql高并发配置优化

    1.Tomcat优化配置 (1)更改Tomcat的catalina.bat 将java变成server模式,增大jvm的内存,在文件开始位置增加 setJAVA_OPTS=-server -Xms10 ...

  8. 洛谷 P2009 跑步

    P2009 跑步 题目背景 跑步是一项有意思的运动,尤其是可以开发人的脑筋.常神牛很喜欢跑步. 题目描述 常神牛跑步的场地是一个多边形(边数≤20,每个顶点用英文大写字母表示),并且在这个多边形内部, ...

  9. mac和iphone处理视频

    今天在微信上面发现有视频打不开,也无法下载到相册 而到电脑上可以打开 搜了一下,发现格式不对,mp4有很多格式,有的是苹果支持不了的. 要下载一个转换器,我下载了“超级转霸”,然后把视频转成了ipho ...

  10. Spring的控制反转(IOC)和依赖注入(DI)具体解释

    Spring的控制反转(IOC)和依赖注入(DI)具体解释 首先介绍下(IOC)控制反转: 所谓控制反转就是应用本身不负责依赖对象的创建及维护,依赖对象的创建及维护是由外部容器负责的.这样控制器就有应 ...