使用select函数也才一年多时间,也许对select的理解还不是很深刻,所以不敢称详解,我只能简单的描述一下自己对select函数功能的理解和调用方式,我想这些对于初次使用select函数的人来说够用了,如有说得不恰当的地方,还请大家批评指正。
首先我们需要解答一些疑惑。为什么要使用select函数?
比如你有一个服务器程序,维护着N多个TCP连接,你如何去判断这些TCP连接上有数据传送过来了呢?最傻瓜的办法就是隔一定时间去循环读一次所有TCP连接对应的文件描述符,如果read返回正值则说明有数据过来。这样做未免也太傻瓜太浪费CPU了一点,无法做到及时的检测到结果,还好,select函数可以帮我们自动检测指定的文件描述符是否有数据可读,即已经建立好的连接那端有新的数据过来或者有新的尚未建立好的连接请求过来。
再比如你想编写一个非阻塞的连接程序,即你非阻塞的调用connect函数去连接另一台服务器,让系统去帮我们维护connect的返回结果,以便我们在connect尝试的这段时间可以执行其他任务,但是我们该如何去捕捉已经被系统所接管的connect函数的返回结果呢?傻瓜的办法就是隔一定时间调用getsockopt函数检测一下指定文件描述符是否异常,如果正常则连接成功,否则连接失败。这样做的缺点同样是浪费很多无用的定时检测消耗,而且还无法保证能够即使检测到connect函数是否连接成功,还好,select函数可以帮我们自动检测指定指定文件描述符是否有数据可写,即TCP三次握手是否已经建立完毕,即connect函数是否连接成功。
如此等等,简而言之,如果你需要监听一组文件描述符(套接字)是否可读、可写或者出错,你可以使用select函数来轻松实现。
简单了解了select的功能以后,那么到底怎样调用select函数呢?
1、依赖头文件“types.h”或者”sys/select.h”
select函数是在系统库”sys/select.h”中声明的,其实在系统库“types.h”也include了一下“sys/select.h”,所以,我们在调用select函数之前务必加上头文件”sys/select.h”或者“types.h”。
2、依赖结构体“struct timeval”和“fd_set”
select有五个参数,三种类型,分别是int、struct timeval *和fd_set*,fd_set在”sys/select.h”定义,struct timeval在“time.h”中定义,所以也别忘了把头文件“time.h”加上。
3、需要配合宏变量FD_ZERO(fdsetp)、FD_SET(fd, fdsetp)、FD_ISSET(fd, fdsetp)和FD_CLR(fd, fdsetp)一起调用
其实这四个宏变量的调用方式类似于我们的普通函数,功能也很类似。看看下面的两个宏变量,你应该就理解了吧。
#define min(a,b) ((a) < (b) ? (a) : (b)) #define max(a,b) ((a) > (b) ? (a) : (b))
(1)宏变量FD_ZERO(fdsetp)用来清空整个fd_set结构体对象,形象地说就是fd_set结构体对象用来存储一组你关心的文件描述符(套接字),FD_ZERO负责清空该对象中已有的文件描述符(套接字)。注意:fdsetp需要传入fd_set结构体对象的指针。
(2)宏变量FD_SET(fd, fdsetp)用来将你关心的文件描述符(套接字)fd存入fd_set结构体对象fdsetp中去。注意:fdsetp需要传入fd_set结构体对象的指针。
(3)宏变量FD_ISSET(fd, fdsetp)用来判断你指定的文件描述符(套接字)fd是否满足可读、可写或者错误条件,如果满足则返回非0值,否则返回0。注意:fdsetp需要传入fd_set结构体对象的指针。
(4)宏变量FD_CLR(fd, fdsetp)用来将你指定的文件描述符(套接字)fd从指定fd_set对象fdsetp中清除,即表示不再关心此fd。注意:fdsetp需要传入fd_set结构体对象的指针。
通常情况下,你关心文件描述符(套接字)的几种状态就定义几个fe_set对象,比如我既关心可读属性又关心可写属性,还关心错误属性,那么我就需要分别定义如下三个fd_set对象。
fd_set readfds; fd_set writefds; fd_set errorfds;
由此看来,你关心几种状态,也得相对应的调用几次上面四个宏变量。
4、int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
第一个参数:nfds是你关心的所有文件描述符中最大者的值加一;第二、三、四个参数:分别传入可读、可写和错误集合fd_set对象的指针&readfds、&writefds和&errorfds,如果不关心某种状态,就将某种状态对应的参数传入NULL值即可;第五个参数:timeout传入select的阻塞时间,如果传入NULL则select函数会一直阻塞直到有文件描述符可读、可写或者错误。
最后,给出一个整体的调用实例如下。
#include <stdio.h> #include <types.h> #include <time.h> #define SLEEP_TIME 10 #define MAX_SESSION_UP 100 int main(void) { int i; int fd_num; int accept_fd_up[MAX_SESSION_UP] = {0}; fd_set readfds; struct timeval select_timeval; //you can add some task here, so the fd in accept_fd_up maybe changed here while (1) { FD_ZERO(&readfds); for (i=0; i<MAX_SESSION_UP; i++) { if (accept_fd_up[i] > max_sock) max_sock = accept_fd_up[i]; if (accept_fd_up[i] > 0) FD_SET(accept_fd_up[i], &readfds); } fd_num = select(max_sock+1, &readfds, NULL, NULL, &select_timeval); if (fd_num < 0) { perror("select"); continue; } else if (0 == fd_num) { printf("select timeout...\n"); //resert the select_time after timeout select_timeval.tv_sec = SLEEP_TIME; select_timeval.tv_usec = 0; //you can add some task here, so the task could work a time every SLEEP_TIME //the fd in accept_fd_up maybe changed here continue; } for (i=0; i<MAX_SESSION_UP; i++) { if (FD_ISSET(accept_fd_up[i], &readfds)) { printf("do case in do_fd_up: %d,%d", accept_fd_up[i], i); //you can add some task here to deal with the change of the fd //the fd in accept_fd_up also maybe changed here } } } }
该实例仅仅只是一个框架而已,它假设有一组大小为MAX_SESSION_UP的int类型数组存储了一堆文件描述符,其中凡是大于0的则默认为我们关心的文件描述符,需要select监管它,小于等于0的则被忽视。
除非注明,文章均为CppLive 编程在线原创,转载请注明出处,谢谢。
纯技术问题,还真不懂呢。
你的网站运营理念很棒,祝你成功吧!~