Select()在编程中的使用
来源:互联网 发布:收入支出软件 编辑:程序博客网 时间:2024/05/17 03:01
最近接触到了网络编程,感觉select这个函数还是蛮实用的,也可以让代码显得高端。下面就先来总结下从各位大神汲取的知识,再次消化下。
使用select()理由
可以使程序进行非阻塞的读写,从而能大大提高程序的执行效率。
比如在原本应该recv阻塞之前,先加个select来判断是否有数据到来,如果有再进行接收,如果没有则直接跳过。
select函数原型及参数分析
select原型
int select(int maxfdp,
fd_set *readfds,
fd_set *writefds,
fd_set *errorfds,
struct timeval*timeout);
select参数分析
首先同前辈们一样先分析一下select参数中的fd_set和timeval结构体,进而更快理解select中各参数的作用。
fd_set(结构体)
fd_set是一个文件描述符集合,也可以是一个句柄的集合。
套用一个经常听的概念,即Unix下一切皆文件,就可以知道select的作用会很大,管道,设备,FIFO等都是文件,当然Socket也是文件,而且一般select与Socket搭配会用的更多。
而对于fd_set的赋值,我们可以通过宏来进行该结构体的相应操作,如:
FD_SET( int, fd_set* ): 将一个给定的文件描述符加入集合;
FD_ZERO( fd_set* ):清空集合,这里需要注意,对文件描述符集合进行设置之前都必须对该文件描述符进行FD_ZERO的操作,即初始化的操作。如果不清空,会导致结果不可知;(具体原因稍后讲到)
FD_CLR( int, fd_set* ):将一个给定的文件描述符从集合中删除;
FD_ISSET( int, fd_set* ):检查集合中的文件描述符是否可读写。
timeval(结构体)
其代表一个时间值。有秒数与毫秒数两个变量,结构体如下:
struct timeval {
time_t tv_sec; /* 秒数 */
suseconds_t tv_usec; /* 毫秒数 */
};
FD_SET等相关宏的阐述
既然fd_set是结构体,那到底是由哪些变量组成的呢,为什么每次设置赋值前需要清空呢,这些宏到底对fd_set干了什么,又是怎么干的呢?下面将对这些问题进行分析解决。
首先我们先在程序中通过sizeof()打印出fd_set的大小为128字节,怎么会是128字节,先放着后续说。
其次了解到fd_set中是每一位表示一个文件描述符。
最后一个字节8位。
所以综上所述,fd_set一般最大能保存128(字节) * 8个文件描述符,也就是1024个文件描述符。这个值在Unix中通常会用头文件”sys/select.h”中的FD_SETSIZE来定义,即FD_SETSIZE的值为1024。
下面引用自一篇博客的说明进行上面宏的问题进行解答,博客地址如下:http://blog.sina.com.cn/s/blog_a43aa27401015kt9.html
为说明方便,取fd_set中的一个1字节进行说明,上面说到fd_set中的每一bit可以对应一个文件描述符fd。那么1字节长的fd_set最大可以对应8个fd。
(1)执行fd_set set; FD_ZERO(&set);则set用位表示是0000,0000。
(2)若fd=5,执行FD_SET(fd,&set);后set变为0001,0000(第5位置为1)
(3)若再加入fd=2,fd=1,则set变为0001,0011
(4)执行select(6,&set,0,0,0)阻塞等待
(5)若fd=1,fd=2上都发生可读事件,则select返回,此时set变为0000,0011。注意:没有事件发生的fd=5被清空。
这里可以看出,在每次select之后,没有事件发生的那一位会被清0。同时描述符集合能放多少个,取决于sizeof(fd_set)的值,这也限制了集合的使用数量,那么有什么方法可以突破这个限制吗?可以,下面引用上面博客的一段话进行解释:
(1)可监控的文件描述符个数取决与sizeof(fd_set)的值。 (2)可以有效突破select可监控的文件描述符上限。
(3)将fd加入select监控集的同时,还要再使用一个数据结构array保存放到select监控集中的fd,一是用于在select返回后,array作为源数据和fd_set进行FD_ISSET判断。二是select返回后会把以前加入的但并无事件发生的fd清空,则每次开始select前都要重新从array取得fd逐一加入(FD_ZERO最先),扫描array的同时取得fd最大值maxfd,用于select的第一个参数。
(4)可见select模型必须在select前循环array(加fd,取maxfd),select返回后循环array(FD_ISSET判断是否有时间发生)。
下面进入正题,描述处理集合的相关宏的操作:
FD_SET(int x, fd_set* pset):
指在pset描述集中加入一个新的描述符x;
FD_ZERO(fd_set* pset):
指清空pset描述集,还是得啰嗦一遍,就是在每次select后,原先没有事件发生的描述位会被清0,所以请在每次执行前进行FD_ZERO;
FD_CLR(int x, fd_set* pset):
指在描述集中清除第x位;
FD_ISSET(int x, fd_set* pset):
指判断描述集中的第x位是否发生可读写事件。
好了讲了这么多回归正题,接下来讲解select中的各个参数的使用。
select参数说明
一、maxfdp:
为填入描述集总最大的值加一,需要注意这里是最大值加1,不是个数加一;
二、readfds:
所要监听的读描述集,得到是否有该描述集中的各描述符有读的变化,有则返回大于0,没有则根据超时时间进行返回,异常错误返回负值。如果传入NULL的话则表示不关心读变化;
三、writefds:
所要监听的写描述集,得到释放有该描述集中的各个描述符的写变化,有则返回大于0,没有则根据超时时间timeout进行返回,异常错误返回负值,传入NULL表示不关心写变化;
四、errorfds:
所要监听的错误描述集,同上,表示关系文件的错误异常;
五、timeout:
超时时间,这里需要注意的是,三种不同timeout的值会产生三中不同的阻塞情况,具体如下所示:
非阻塞:timeout结构体的秒数和毫秒数均为0,表示select函数不进行阻塞,即非阻塞,当程序执行到select函数时,不管其是否有读写事件或者错误事件发生,都立即返回,返回值当有事件发生时大于0,无事件发生则立即返回0;
超时阻塞:当timeout结构体中秒数、毫秒数存在值时,表示select函数会进行阻塞,阻塞的时间为timeout设置的时间,当期间有事件发生时即返回大于0,超时时则返回0;
阻塞:当timeou结构体参数设置为NULL时,表示select函数只有在有事件发生时才会返回大于0,否则一直阻塞。
select返回值
负值:select错误;
大于0的值:描述集中有事件发生变化;
等于0:select函数超时,没有事件发生。
举例
下面引用http://blog.csdn.net/piaojun_pj/article/details/5991968的一个例子来进行使用说明:
①读取键盘输入值,超时间隔2.5秒,输出用户输入的字符个数。
#include <sys/types.h> #include <sys/time.h> #include <stdio.h> #include <fcntl.h> #include <sys/ioctl.h> #include <unistd.h> int main() { char buffer[128]; int result, nread; fd_set inputs, testfds; struct timeval timeout; FD_ZERO(&inputs);//用select函数之前先把集合清零 FD_SET(0,&inputs);//把要检测的句柄——标准输入(0),加入到集合里。 while(1) { testfds = inputs; timeout.tv_sec = 2; timeout.tv_usec = 500000; result = select(FD_SETSIZE, &testfds, (fd_set *)0, (fd_set *)0, &timeout); switch(result) { case 0: printf("timeout/n"); break; case -1: perror("select"); exit(1); default: if(FD_ISSET(0,&testfds)) { ioctl(0,FIONREAD,&nread);//取得从键盘输入字符的个数,包括回车。 if(nread == 0) { printf("keyboard done/n"); exit(0); } nread = read(0,buffer,nread); buffer[nread] = 0; printf("read %d from keyboard: %s", nread, buffer); } break; } } return 0;}
②利用select而不是fork来解决socket中的多客户问题。
//服务器端:#include <sys/types.h> #include <sys/socket.h> #include <stdio.h> #include <netinet/in.h> #include <sys/time.h> #include <sys/ioctl.h> #include <unistd.h> int main() { int server_sockfd, client_sockfd; int server_len, client_len; struct sockaddr_in server_address; struct sockaddr_in client_address; int result; fd_set readfds, testfds; server_sockfd = socket(AF_INET, SOCK_STREAM, 0);//建立服务器端socket server_address.sin_family = AF_INET; server_address.sin_addr.s_addr = htonl(INADDR_ANY); server_address.sin_port = htons(9734); server_len = sizeof(server_address); bind(server_sockfd, (struct sockaddr *)&server_address, server_len); listen(server_sockfd, 5); FD_ZERO(&readfds); FD_SET(server_sockfd, &readfds);//将服务器端socket加入到集合中 while(1) { char ch; int fd; int nread; testfds = readfds; printf("server waiting/n"); /*无限期阻塞,并测试文件描述符变动 */ result = select(FD_SETSIZE, &testfds, (fd_set *)0,(fd_set *)0, (struct timeval *) 0); if(result < 1) { perror("server5"); exit(1); } /*扫描所有的文件描述符*/ for(fd = 0; fd < FD_SETSIZE; fd++) { /*找到相关文件描述符*/ if(FD_ISSET(fd,&testfds)) { /*判断是否为服务器套接字,是则表示为客户请求连接。*/ if(fd == server_sockfd) { client_len = sizeof(client_address); client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_address, &client_len); FD_SET(client_sockfd, &readfds);//将客户端socket加入到集合中 printf("adding client on fd %d/n", client_sockfd); } /*客户端socket中有数据请求时*/ else { ioctl(fd, FIONREAD, &nread);//取得数据量交给nread /*客户数据请求完毕,关闭套接字,从集合中清除相应描述符 */ if(nread == 0) { close(fd); FD_CLR(fd, &readfds); printf("removing client on fd %d/n", fd); } /*处理客户数据请求*/ else { read(fd, &ch, 1); sleep(5); printf("serving client on fd %d/n", fd); ch++; write(fd, &ch, 1); } } } } } }
//客户端#include <sys/types.h> #include <sys/socket.h> #include <stdio.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> int main() { int client_sockfd; int len; struct sockaddr_in address;//服务器端网络地址结构体 int result; char ch = 'A'; client_sockfd = socket(AF_INET, SOCK_STREAM, 0);//建立客户端socket address.sin_family = AF_INET; address.sin_addr.s_addr = inet_addr(“127.0.0.1”); address.sin_port = 9734; len = sizeof(address); result = connect(client_sockfd, (struct sockaddr *)&address, len); if(result == -1) { perror("oops: client2"); exit(1); } write(client_sockfd, &ch, 1); read(client_sockfd, &ch, 1); printf("char from server = %c/n", ch); close(client_sockfd); zexit(0); }
- Select()在编程中的使用
- Select在Socket编程中的应用
- Select在Socket编程中的应用
- Select在Socket编程中的使用方法
- 串口设置,select和 signal 在linux编程中的使用实例
- Select Top在不同数据库中的使用
- select在socket中的使用示例
- 关于select 函数在 Linux C Socket编程中的应用
- 递归在编程中的使用
- 如何在SQL Server中的SELECT TOP 中使用变量
- Select Top在不同数据库中的使用用法
- 如何在SQL Server中的SELECT TOP 中使用变量
- struts2中s:select标签在freemarker中的使用
- 如何在 SQL Server 中的 SELECT TOP 中 使用 变量
- 如何在 SQL Server 中的 SELECT TOP 中 使用 变量
- 如何在SQL Server中的SELECT TOP 中使用变量
- {网络编程}Multiplexing——Select在网络编程中的简单运用
- Socket编程中的select多路复用
- cocos2d-x CCScrollView和CCTableView的使用
- 和为S的两个数字
- CSDN发表博客等操作点击按钮没反应
- gcc-5.1.0编译笔记
- 左旋转字符串
- Select()在编程中的使用
- java demo
- contextLoaderListener和dispatcherServlet初始化上下文的区别
- Android ListView的优化
- cocos2dx命令行编译打包安卓apk ndk_root not defined
- 【BZOJ3036】绿豆蛙的归宿 概率DP
- VTK图像像素拾取
- Js_Js_js
- Spark Release 1.4.0