收藏 分销(赏)

C--网络编程Select函数用法详解.doc

上传人:仙人****88 文档编号:9313383 上传时间:2025-03-21 格式:DOC 页数:10 大小:41.50KB 下载积分:10 金币
下载 相关 举报
C--网络编程Select函数用法详解.doc_第1页
第1页 / 共10页
C--网络编程Select函数用法详解.doc_第2页
第2页 / 共10页


点击查看更多>>
资源描述
Socket-Select 2008-09-14 11:31 Select在Socket编程中还是比较重要的,可是对于初学Socket的人来说都不太爱用Select写程序,他们只是习惯写诸如 connect、accept、recv或recvfrom这样的阻塞程序(所谓阻塞方式block,顾名思义,就是进程或是线程执行到这些函数时必须等 待某个事件的发生,如果事件没有发生,进程或线程就被阻塞,函数不能立即返回)。       可是使用Select就可以完成非阻塞(所谓非阻塞方式non-block,就是进程或线程执行此函数时不必非要等待事件的发生,一旦执行肯定返回,以返 回值的不同来反映函数的执行情况,如果事件发生则与阻塞方式相同,若事件没有发生则返回一个代码来告知事件未发生,而进程或线程继续执行,所以效率较高) 方式工作的程序,它能够监视我们需要监视的文件描述符的变化情况——读写或是异常。下面详细介绍一下! Select的函数格式: 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值,表示不关心任何文件的写变化。 fd_set *errorfds同上面两个参数的意图,用来监视文件错误异常。 struct timeval* timeout是select的超时时间,这个参数至关重要,它可以使select处于三种状态,第一,若将NULL以形参传入,即不传入时间结构,就是 将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;第二,若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数, 不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;第三,timeout的值大于0,这就是等待的超时时间,即 select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值同上述。 返回值: 负值:select错误 正值:某些文件可读写或出错 0:等待超时,没有可读写或错误的文件 在有了select后可以写出像样的网络程序来!举个简单的例子,就是从网络上接受数据写入一个文件中。 例子: main() { int sock; FILE *fp; struct fd_set fds; struct timeval timeout={3,0}; //select等待3秒,3秒轮询,要非阻塞就置0 char buffer[256]={0}; //256字节的接收缓冲区 /* 假定已经建立UDP连接,具体过程不写,简单,当然TCP也同理,主机ip和port都已经给定,要写的文件已经打开         sock=socket(...);         bind(...);          fp=fopen(...); */ while(1)    {       FD_ZERO(&fds); //每次循环都要清空集合,否则不能检测描述符变化       FD_SET(sock,&fds); //添加描述符       FD_SET(fp,&fds); //同上       maxfdp=sock>fp?sock+1:fp+1; //描述符最大值加1       switch(select(maxfdp,&fds,&fds,NULL,&timeout)) //select使用        {        case -1: exit(-1);break; //select错误,退出程序        case 0:break; //再次轮询        default:         if(FD_ISSET(sock,&fds)) //测试sock是否可读,即是否网络上有数据          {           recvfrom(sock,buffer,256,.....);//接受网络数据           if(FD_ISSET(fp,&fds)) //测试文件是否可写             fwrite(fp,buffer...);//写入文件           //buffer清空;          } // end if break;        } // end switch    } //end while } //end main 由于采用 select 机制, 因此当没有字符可读时, 程序处于阻塞状态,最小程度的占用CPU 资源, 在同一台机器上执行一个 server 和若干个client 时, 系统负载只有 0.1 左右, 而采用原来的非阻塞通信方法, 只运行一个 server, 系统负载就可以达到 1.5 左右. 因此我们推荐使用 select socket select()用法 一、winsock中 #include <winsock.h> 原型 int select( int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, const struct timeval* timeout ); nfds:本参数忽略,仅起到兼容作用。    readfds:(可选)指针,指向一组等待可读性检查的套接口。    writefds:(可选)指针,指向一组等待可写性检查的套接口。    exceptfds:(可选)指针,指向一组等待错误检查的套接口。    timeout:select()最多等待时间,对阻塞操作则为NULL。 注释:    本函数用于确定一个或多个套接口的状态。对每一个套接口,调用者可查询它的可读性、可写性及错误状态信息。用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。    在winsock.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()调用可认为是非阻塞的,且一切适用于非阻塞调用的假设都 适用于它。举例来说,阻塞钩子函数不应被调用,且WINDOWS套接口实现不应yield。 返回值:    select()调用返回处于就绪状态并且已经包含在fd_set结构中的描述字总数;如果超时则返回0;否则的话,返回SOCKET_ERROR错误,应用程序可通过WSAGetLastError()获取相应错误代码。 错误代码:    WSANOTINITIALISED:在使用此API之前应首先成功地调用WSAStartup()。    WSAENETDOWN:WINDOWS套接口实现检测到网络子系统失效。    WSAEINVAL:超时时间值非法。    WSAEINTR:通过一个WSACancelBlockingCall()来取消一个(阻塞的)调用。    WSAEINPROGRESS:一个阻塞的WINDOWS套接口调用正在运行中。    WSAENOTSOCK:描述字集合中包含有非套接口的元素。 范例 : sock= socket(AF_INET,SOCK_STREAM,0); struct sockaddr_in addr;      //告诉sock 应该再什么地方licence memset(&addr,0,sizeof(addr)); addr.sin_family=AF_INET; addr.sin_port=htons(11111);   //端口啦 addr.sin_addr.s_addr=htonl(INADDR_ANY);        //在本机的所有ip上开始监听 bind (sock,(sockaddr *)&addr,sizeof(addr));//bind.... listen(sock,5);                   //最大5个队列 SOCKET socka;                    //这个用来接受一个连接 fd_set rfd;                     // 描述符集 这个将用来测试有没有一个可用的连接 struct timeval timeout; FD_ZERO(&rfd);                     //总是这样先清空一个描述符集 timeout.tv_sec=60;                //等下select用到这个 timeout.tv_usec=0; u_long ul=1; ioctlsocket(sock,FIONBIO,&ul);    //用非阻塞的连接 //现在开始用select FD_SET(sock,&rfd);    //把sock放入要测试的描述符集 就是说把sock放入了rfd里面 这样下一步调用select对rfd进行测试的时候就会测试sock了(因为我们将sock放入的rdf) 一个描述符集可以包含多个被测试的描述符, if(select(sock+1,&rfd,0,0, &timeout)==0) { //这个大括号接上面的,返回0那么就超过了timeout预定的时间 //处理.... } if(FD_ISSET(sock,&rfd)) {      //有一个描述符准备好了 socka=accept(sock,0,0);     //一个用来测试读 一个用来测试写 FD_ZERO(&rfd); FD_ZERO(&wfd); FD_SET(socka,&rfd);//把socka放入读描述符集 FD_SET(sockb,&rfd);//把sockb放入读描述符集 FD_SET(socka,&wfd);把socka放入写描述符集 FD_SET(sockb,&wfd);把sockb放入写描述符集 if(SOCKET_ERROR!=select(0,&rfd,&wfd,0,0))      //测试这两个描述符集,永不超时 其中rfd只用来测试读 wfd只用来测试写 {      //没有错误 if(FD_ISSET(socka,&rfd))    //socka可读 {...} if(FD_ISSET(sockb,&rfd)   //sockb可读 {...} if(FD_ISSET(socka,&wfd) //socka 可写 {...} if(FD_ISSET(sockb,&wfd) //sockb可写 {...} } 二、linux c中 select(I/O多工机制) 表头文件 #i nclude<sys/time.h> #i nclude<sys/types.h> #i nclude<unistd.h> 定义函数 int select(int n,fd_set * readfds,fd_set * writefds,fd_set * exceptfds,struct timeval * timeout); 函数说明 select()用来等待文件描述词状态的改变。参数n代表最大的文件描述词加1,参数readfds、writefds 和exceptfds 称为描述词组,是用来回传该描述词的读,写或例外的状况。底下的宏提供了处理这三种描述词组的方式: FD_CLR(inr fd,fd_set* set);用来清除描述词组set中相关fd 的位 FD_ISSET(int fd,fd_set *set);用来测试描述词组set中相关fd 的位是否为真 FD_SET(int fd,fd_set*set);用来设置描述词组set中相关fd的位 FD_ZERO(fd_set *set); 用来清除描述词组set的全部位 参数 timeout为结构timeval,用来设置select()的等待时间,其结构定义如下 struct timeval { time_t tv_sec; time_t tv_usec; }; 返回值 如果参数timeout设为NULL则表示select()没有timeout。 错误代码 执行成功则返回文件描述词状态已改变的个数,如果返回0代表在描述词状态改变前已超过timeout时间,当有错误发生时则返回-1,错误原因存于errno,此时参数readfds,writefds,exceptfds和timeout的值变成不可预测。 EBADF 文件描述词为无效的或该文件已关闭 EINTR 此调用被信号所中断 EINVAL 参数n 为负值。 ENOMEM 核心内存不足 范例 常见的程序片段:fs_set readset; FD_ZERO(&readset); FD_SET(fd,&readset); select(fd+1,&readset,NULL,NULL,NULL); if(FD_ISSET(fd,readset){……} 下面是linux环境下select的一个简单用法 #i nclude <sys/time.h> #i nclude <stdio.h> #i nclude <sys/types.h> #i nclude <sys/stat.h> #i nclude <fcntl.h> #i nclude <assert.h> int main () { int keyboard; int ret,i; char c; fd_set readfd; struct timeval timeout; keyboard = open("/dev/tty",O_RDONLY | O_NONBLOCK); assert(keyboard>0); while(1)     { timeout.tv_sec=1; timeout.tv_usec=0; FD_ZERO(&readfd); FD_SET(keyboard,&readfd); ret=select(keyboard+1,&readfd,NULL,NULL,&timeout); if(FD_ISSET(keyboard,&readfd))     {       i=read(keyboard,&c,1);           if('\n'==c)           continue;       printf("hehethe input is %c\n",c);             if ('q'==c)       break;       } } }
展开阅读全文

开通  VIP会员、SVIP会员  优惠大
下载10份以上建议开通VIP会员
下载20份以上建议开通SVIP会员


开通VIP      成为共赢上传

当前位置:首页 > 教育专区 > 小学其他

移动网页_全站_页脚广告1

关于我们      便捷服务       自信AI       AI导航        抽奖活动

©2010-2026 宁波自信网络信息技术有限公司  版权所有

客服电话:0574-28810668  投诉电话:18658249818

gongan.png浙公网安备33021202000488号   

icp.png浙ICP备2021020529号-1  |  浙B2-20240490  

关注我们 :微信公众号    抖音    微博    LOFTER 

客服