Linux下I/O多路转接之select --fd_set

http://blog.csdn.net/li_ning_/article/details/52165993

fd_set

你终于还是来了,能看到这个标题进来的,我想,你一定是和我遇到了一样的问题,一样的疑惑,接下来几个小时,我一定竭尽全力,写出我想说的,希望也正是你所需要的:

关于Linux下I/O多路转接之select,我不想太多的解释,用较少的文章引出今天我要说的问题:fd_set...自我感觉,这个东西,是理解select的关键。

一、关于select函数:


以上只是截屏,以保证本人说的是真话,下面解释:

        系统提供select函数来实现多路复用输入/输出模型。select系统调用是用来让我们的程序监视多个文件句柄的状态变化的。

程序会停在select这里等待,直到被监视的文件句柄有一个或 多个发生了状态改变。关于文件句柄,其实就是一个整数,我们最熟悉的句柄是0、1、2三 个,

0是标准输入,1是标准输出,2是标准错误输出。0、1、2是整数表示的,对应的FILE * 结构的表示就是stdin、stdout、stderr。 

1.参数nfds是需要监视的最大的文件描述符值+1; 

2.rdset,wrset,exset分别对应于需要检测的可读文件描述符的集合,可写文件描述符的集合及异常文件描述符的集合。 

3.struct timeval结构用于描述一段时间长度,如果在这个时间内,需要监视的描述符没有事件发生则函数返回,返回值为0。 

下面的宏提供了处理这三种描述词组的方式:

1. FD_CLR(inr fd,fd_set* set);用来清除描述词组set中相关fd 的位。

2. FD_ISSET(int fd,fd_set *set);用来测试描述词组set中相关fd 的位是否为真 。

3.FD_SET(int fd,fd_set*set);用来设置描述词组set中相关fd的位 。

4.FD_ZERO(fd_set *set);用来清除描述词组set的全部位 参数timeout为结构timeval,用来设置select()的等待时间,其结构定义如下:

结构体成员两个,第一个单位是秒,第二个单位是微妙 ,作用是时间为两个之和; 

更多关于select的应用,咱移驾看这位大神:http://blog.sina.com.cn/s/blog_5c8d13830100pwaf.html      关于select的使用,理解了也就好弄了,关于应用,后附代码:

下面才是我想说的东西:

二、fd_set:

1>>fd_set是什么:

         select()机制中提供一fd_set的数据结构,可以理解为一个集合,实际上是一个位图,每一个特定位来标志相应大小文件描述符,这个集合中存放的是文件描述符,即就是文件句柄(不管是socket句柄,还是其他文件或命名管道或设备句柄)建立联系,建立联系的工作由程序员完成,当调用select()时,由内核根据IO状态修改fd_set的内容,由此来通知执行了select()的进程哪一socket或文件可读。Unix下任何设备、管道、FIFO等都是文件形式,全部包括在内,所以毫无疑问一个socket就是一个文件,socket句柄就是一个文件描述符。fd_set集合可以通过一些宏由人为来操作,程序员通过操作4类宏,来完成最fd_set的操作,在上文已经提及。

2>>fe_set怎么表示:


其中readfds、writefds等都是fd_set类型,其中的每一位都表示一个fd,即文件描述符。

3>>fd_set用法:

过去,一个fd_set通常只能包含<32的fd(文件描述字),因为fd_set其实只用了一个32位矢量来表示fd;现在,UNIX系统通常会在头文件中定义常量FD_SETSIZE,它是数据类型fd_set的描述字数量,其值通常是1024,这样就能表示<1024的fd。根据fd_set的位矢量实现,我们可以重新理解操作fd_set的四个宏: 

 fd_set set;

FD_ZERO(&set);      /*将set的所有位置0,如set在内存中占8位则将set置为00000000*/

FD_SET(0, &set);    /* 将set的第0位置1,如set原来是00000000,则现在变为10000000,这样fd==1的文件描述字就被加进set中了 */

FD_CLR(4, &set);    /*将set的第4位置0,如set原来是10001000,则现在变为10000000,这样fd==4的文件描述字就被从set中清除了 */ 

FD_ISSET(5, &set);  /* 测试set的第5位是否为1,如果set原来是10000100,则返回非零,表明fd==5的文件描述字在set中;否则返回0*/

我们在回到原函数:select

 int select(int nfds, fd_set *readset, fd_set *writeset,fd_set* exceptset, struct timeval *timeout); 

 功能:测试指定的fd可读、可写、有异常条件待处理。     

参数:

1.nfds    

需要检查的文件描述字个数(即检查到fd_set的第几位),数值应该比三组fd_set中所含的最大fd值更大,一般设为三组fd_set中所含的最大fd值加1(如在上边例子中readset,writeset,exceptset中所含最大的fd为5,则nfds=6,因为fd是从0开始的)。设这个值是为提高效率,使函数不必检查fd_set的所有1024位。

readset  :用来检查可读性的一组文件描述字。

writeset :用来检查可写性的一组文件描述字。

exceptset :用来检查是否有异常条件出现的文件描述字。(注:错误不包括在异常条件之内)

timeout:有三种可能:

1.  timeout=NULL(阻塞:直到有一个fd位被置为1函数才返回)

2.  timeout所指向的结构设为非零时间(等待固定时间:有一个fd位被置为1或者时间耗尽,函数均返回)

3.  timeout所指向的结构,时间设为0(非阻塞:函数检查完每个fd后立即返回) 

返回值: 

     

1.当监视的相应的文件描述符集中满足条件时,比如说读文件描述符集中有数据到来时,内核(I/O)根据状态修改文件描述符集,并返回一个大于0的数。

2.当没有满足条件的文件描述符,且设置的timeval监控时间超时时,select函数会返回一个为0的值。

3.当select返回负值时,发生错误。

备注:

三组fd_set均将某些fd位置0,只有那些可读,可写以及有异常条件待处理的fd位仍然为1。

使用select函数的过程一般是:

先调用宏FD_ZERO将指定的fd_set清零,然后调用宏FD_SET将需要测试的fd加入fd_set,接着调用函数select测试fd_set中的所有fd,最后用宏FD_ISSET检查某个fd在函数select调用后,相应位是否仍然为1。

以下是一个测试单个文件描述字可读性的例子:

     

基于select实现的网络服务器和客户端:

server.c

[cpp] view plain copy
  1. #include<stdio.h>    
  2. #include<sys/types.h>    
  3. #include<sys/socket.h>    
  4. #include<unistd.h>    
  5. #include<stdlib.h>    
  6. #include<errno.h>    
  7. #include<arpa/inet.h>    
  8. #include<netinet/in.h>    
  9. #include<string.h>    
  10. #include<signal.h>    
  11. #include<sys/wait.h>    
  12.   
  13. /* 
  14.  *网络服务器,select参与调度 
  15.  * */  
  16.   
  17. //ERR_EXIT(M)是一个错误退出宏  
  18. #define ERR_EXIT(m) \    
  19.     do { \    
  20.         perror(m); \    
  21.         exit(EXIT_FAILURE); \    
  22.     } while (0)    
  23.     
  24.     
  25. int main(void)    
  26. {      
  27.     signal(SIGPIPE, SIG_IGN);  
  28.   
  29.     //1.创建套接字  
  30.     int listenfd;                 //被动套接字(文件描述符),即只可以accept, 监听套接字    
  31.     if ((listenfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)      
  32.         ERR_EXIT("socket error"); //调用上边的宏   
  33.     
  34.     struct sockaddr_in servaddr;  
  35.   
  36.     //memset(&servaddr, 0, sizeof(servaddr));    
  37.     //三个结构体成员  
  38.     //设置本地IP 和端口  
  39.     servaddr.sin_family = AF_INET;    
  40.     servaddr.sin_port = htons(8080);    
  41.     servaddr.sin_addr.s_addr = htonl(INADDR_ANY);     
  42.     /* servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); */    
  43.     /* inet_aton("127.0.0.1", &servaddr.sin_addr); */    
  44.         
  45.     //2.设置套接字属性  
  46.     int on = 1;    
  47.     if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)    
  48.         ERR_EXIT("setsockopt error");    
  49.       
  50.     //3.绑定  
  51.     if (bind(listenfd, (struct sockaddr*)&servaddr,sizeof(servaddr)) < 0)    
  52.         ERR_EXIT("bind error");    
  53.     
  54.     //4.监听  
  55.     if (listen(listenfd, SOMAXCONN) < 0) //listen应在socket和bind之后,而在accept之前    
  56.         ERR_EXIT("listen error");    
  57.         
  58.     struct sockaddr_in peeraddr; //传出参数    
  59.     socklen_t peerlen = sizeof(peeraddr); //传入传出参数,必须有初始值    
  60.         
  61.     int conn; // 已连接套接字(变为主动套接字,即可以主动connect)    
  62.     int i;    
  63.     int client[FD_SETSIZE];    
  64.     int maxi = 0; // client数组中最大不空闲位置的下标    
  65.     for (i = 0; i < FD_SETSIZE; i++)    
  66.         client[i] = -1;    
  67.     
  68.     int nready;    
  69.     int maxfd = listenfd;    
  70.     fd_set rset;    
  71.     fd_set allset;    
  72.     FD_ZERO(&rset);    
  73.     FD_ZERO(&allset);    
  74.     FD_SET(listenfd, &allset);    
  75.     
  76.     while (1) {    
  77.         rset = allset;    
  78.         nready = select(maxfd + 1, &rset, NULL, NULL, NULL);    
  79.         if (nready == -1) {    
  80.             if (errno == EINTR)    
  81.                 continue;    
  82.             ERR_EXIT("select error");    
  83.         }    
  84.     
  85.         if (nready == 0)    
  86.             continue;    
  87.     
  88.         if (FD_ISSET(listenfd, &rset)) {    
  89.             
  90.             conn = accept(listenfd, (struct sockaddr*)&peeraddr, &peerlen);  //accept不再阻塞    
  91.             if (conn == -1)    
  92.                 ERR_EXIT("accept error");    
  93.                 
  94.             for (i = 0; i < FD_SETSIZE; i++) {    
  95.                 if (client[i] < 0) {    
  96.                     client[i] = conn;    
  97.                     if (i > maxi)    
  98.                         maxi = i;    
  99.                     break;    
  100.                 }     
  101.             }    
  102.                 
  103.             if (i == FD_SETSIZE) {    
  104.                 fprintf(stderr, "too many clients\n");    
  105.                 exit(EXIT_FAILURE);    
  106.             }    
  107.     
  108.             printf("recv connect ip=%s port=%d\n", inet_ntoa(peeraddr.sin_addr),    
  109.                 ntohs(peeraddr.sin_port));    
  110.     
  111.             FD_SET(conn, &allset);    
  112.             if (conn > maxfd)    
  113.                 maxfd = conn;    
  114.     
  115.             if (--nready <= 0)    
  116.                 continue;    
  117.         }    
  118.     
  119.         for (i = 0; i <= maxi; i++) {    
  120.             conn = client[i];    
  121.             if (conn == -1)    
  122.                 continue;    
  123.     
  124.             if (FD_ISSET(conn, &rset)) {    
  125.                     
  126.                 char recvbuf[1024] = {0};    
  127.                 int ret = read(conn, recvbuf, 1024);    
  128.                 if (ret == -1)    
  129.                     ERR_EXIT("readline error");    
  130.                 else if (ret  == 0) { //客户端关闭     
  131.                     printf("client close \n");    
  132.                     FD_CLR(conn, &allset);    
  133.                     client[i] = -1;    
  134.                     close(conn);    
  135.                 }    
  136.             
  137.                 fputs(recvbuf, stdout);    
  138.                 write(conn, recvbuf, strlen(recvbuf));    
  139.                     
  140.                 if (--nready <= 0)    
  141.                     break;     
  142.             }    
  143.         }    
  144.     }           
  145.     return 0;    
  146. }   
client.c

[cpp] view plain copy
  1. #include<stdio.h>    
  2. #include<sys/types.h>    
  3. #include<sys/socket.h>    
  4. #include<unistd.h>    
  5. #include<stdlib.h>    
  6. #include<errno.h>    
  7. #include<arpa/inet.h>    
  8. #include<netinet/in.h>    
  9. #include<string.h>    
  10.     
  11.     
  12. #define ERR_EXIT(m) \    
  13.     do { \    
  14.         perror(m); \    
  15.         exit(EXIT_FAILURE); \    
  16.     } while (0)    
  17.     
  18.     
  19.     
  20.     
  21. int main(void)    
  22. {    
  23.     int sock;    
  24.     if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)    
  25.         //  listenfd = socket(AF_INET, SOCK_STREAM, 0)    
  26.         ERR_EXIT("socket error");    
  27.     
  28.     
  29.     struct sockaddr_in servaddr;    
  30.     memset(&servaddr, 0, sizeof(servaddr));    
  31.     servaddr.sin_family = AF_INET;    
  32.     servaddr.sin_port = htons(8080);    
  33.     servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");    
  34.     /* inet_aton("127.0.0.1", &servaddr.sin_addr); */    
  35.     
  36.     if (connect(sock, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)    
  37.         ERR_EXIT("connect error");    
  38.     struct sockaddr_in localaddr;    
  39.     char cli_ip[20];    
  40.     socklen_t local_len = sizeof(localaddr);    
  41.     memset(&localaddr, 0, sizeof(localaddr));    
  42.     if( getsockname(sock,(struct sockaddr *)&localaddr,&local_len) != 0 )    
  43.         ERR_EXIT("getsockname error");    
  44.     inet_ntop(AF_INET, &localaddr.sin_addr, cli_ip, sizeof(cli_ip));    
  45.     printf("host %s:%d\n", cli_ip, ntohs(localaddr.sin_port));     
  46.     
  47.     fd_set rset;    
  48.     FD_ZERO(&rset);    
  49.     int nready;    
  50.     int maxfd;    
  51.     int fd_stdin = fileno(stdin); //    
  52.     if (fd_stdin > sock)    
  53.         maxfd = fd_stdin;    
  54.     else    
  55.         maxfd = sock;    
  56.     char sendbuf[1024] = {0};    
  57.     char recvbuf[1024] = {0};    
  58.         
  59.     while (1)    
  60.     {    
  61.     
  62.         FD_SET(fd_stdin, &rset);    
  63.         FD_SET(sock, &rset);    
  64.         nready = select(maxfd + 1, &rset, NULL, NULL, NULL); //select返回表示检测到可读事件    
  65.         if (nready == -1)    
  66.             ERR_EXIT("select error");    
  67.     
  68.         if (nready == 0)    
  69.             continue;    
  70.     
  71.         if (FD_ISSET(sock, &rset))    
  72.         {    
  73.     
  74.             int ret = read(sock, recvbuf, sizeof(recvbuf));     
  75.             if (ret == -1)    
  76.                 ERR_EXIT("read error");    
  77.             else if (ret  == 0)   //服务器关闭    
  78.             {    
  79.                 printf("server close\n");    
  80.                 break;    
  81.             }    
  82.     
  83.             fputs(recvbuf, stdout);    
  84.             memset(recvbuf, 0, sizeof(recvbuf));    
  85.         }    
  86.     
  87.         if (FD_ISSET(fd_stdin, &rset))    
  88.         {    
  89.     
  90.             if (fgets(sendbuf, sizeof(sendbuf), stdin) == NULL)    
  91.                 break;    
  92.     
  93.             write(sock, sendbuf, strlen(sendbuf));    
  94.             memset(sendbuf, 0, sizeof(sendbuf));    
  95.         }    
  96.     }    
  97.     
  98.     close(sock);    
  99.     return 0;    
  100. }    
赐教!


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/384101.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

I/O多路转接之poll 函数

http://blog.csdn.net/li_ning_/article/details/52167224 poll 一、poll()函数&#xff1a; 这个函数是某些Unix系统提供的用于执行与select()函数同等功能的函数&#xff0c;自认为poll和select大同小异&#xff0c;下面是这个函数的声明&#xff1a; [cpp] view plaincopy …

链表相关笔试面试题

1.判断两个链表是否相交 两个链表是否相交可分为以下几种情况     &#xff08;1&#xff09;两个链表都不带环&#xff0c;此时两个链表所对应的最后一个节点是相等的     &#xff08;2&#xff09;两个链表一个带环&#xff0c;一个不带环&#xff0c;两个链表一定…

Linux经典问题—五哲学家就餐问题

http://m.blog.csdn.net/aspenstars/article/details/70149038 一、问题介绍 由Dijkstra提出并解决的哲学家进餐问题(The Dinning Philosophers Problem)是典型的同步问题。该问题是描述有五个哲学家共用一张圆桌&#xff0c;分别坐在周围的五张椅子上&#xff0c;在圆桌上有五…

修改之前的myshell使之支持输入输出重定向

1.open函数     这个函数是打开一个文件&#xff08;文件名叫pathname),以 flag 权限打开&#xff0c;flag 包括了以下几种 O_RDONLY&#xff08;只读&#xff09;, O_WRONLY&#xff08;只写&#xff09;, O_RDWR&#xff08;读写&#xff09;&#xff0c;当文件打开成…

链表相关的算法题大汇总 — 数据结构之链表奇思妙想

http://blog.csdn.net/lanxuezaipiao/article/details/22100021基本函数&#xff08;具体代码实现见后面&#xff09; 1&#xff0c;构造节点 //定义节点类型 struct Node { int value; Node*next; }; 2&#xff0c;分配节点 //之所以要分配节点原因是需要在分配函数中…

匿名管道

1.进程通信的目的 (1) 数据传输: 一个进程需要将它的数据传输给另一个进程     (2) 资源共享: 多个进程之间共享同样的资源     (3) 通知事件: 一个进程需要向另一个或一组进程发送消息, 通知它们发生了什么事情 2.管道 管道是一种进程之间通信的一种方式, 我们把从…

将信号量代码生成静态库以及动态库

1.信号量相关代码生成静态库 2.信号量相关代码生成动态库

C++11 标准新特性:Defaulted 和 Deleted 函数

https://www.ibm.com/developerworks/cn/aix/library/1212_lufang_c11new/index.html Defaulted 函数 背景问题 C 的类有四类特殊成员函数&#xff0c;它们分别是&#xff1a;默认构造函数、析构函数、拷贝构造函数以及拷贝赋值运算符。这些类的特殊成员函数负责创建、初始化、…

顺序表实现栈相关操作

1.栈的相关概念 栈是一种特殊的线性表, 其中只允许在固定的一端进行插入和删除元素.进行数据插入和删除的一端叫做栈顶, 另一端成为栈底. 不含任何元素的栈称为空栈, 栈又称为先进先出的线性表. 2. 顺序栈的结构 3. 顺序栈的具体操作 (1). 数据结构 typedef char SeqStackTyp…

双向带环带头结点的链表实现栈

1. 数据结构 利用带头结点带环的结点实现栈的相关操作.因此, 每一个结点包括了一个前驱, 一个后继, 还有一个数据成员 typedef char DLinkStackType;typedef struct DLinkStack {DLinkStackType data;struct DLinkStack* next;struct DLinkStack* prev; }DLinkStack;2. 初始化…

C++11 标准新特性:委派构造函数

https://www.ibm.com/developerworks/cn/rational/1508_chenjing_c11/index.html陈 晶2015 年 8 月 11 日发布WeiboGoogle用电子邮件发送本页面 1本文首先介绍了在委派构造函数提出之前类成员构造所面临的问题&#xff0c;再结合实例介绍了委派构造函数的用法&#xff0c;并说明…

顺序表实现队列

一. 队列相关概念 队列是只允许在一段进行插入元素, 在另一端进行删除元素的线性表,即只允许对队列进行尾插,头删的操作.队列具有先进先出, 后进后出的特性.          1.初始化 void SeqQueInit(SeqQue* q) {if(q NULL){return;//非法输入}q -> head 0;q -> …

链表实现队列

上篇博客是用顺序表实现队列, 现在用双向带头结点带环链表实现对队列的出队列, 入队列, 取队首元素, 以及销毁队列的相关操作 1.初始化链表 void DLinkQueInit(DLinkQue** q) {if(q NULL){return;//非法输入}if(*q NULL){return;//非法输入带头结点的链表至少有一个傀儡结点…

HDU - 1796——容斥原理+二进制枚举

【题目描述】 Now you get a number N, and a M-integers set, you should find out how many integers which are small than N, that they can divided exactly by any integers in the set. For example, N12, and M-integer set is {2,3}, so there is another set {2,3,4,…

信号的基本概念以及信号的产生

一. 信号产生的场景 1. 用户输入命令, 在shell 启动一个前台进程      2. 当用户按一下 Ctrl C 的时候,从键盘产生一个硬件中断      3. 此时CPU 正在执行这个进程的带代码, 则该进程的执行代码暂停执行, CPU 从用户态切换到内核态处理该硬件中断.      4. 中断…

信号的阻塞

一. 阻塞信号 1.信号的相关概念     (1) 递达: 实际执行信号的处理动作称为信号的递达     (2) 未决: 信号从产生到递达之间的过程叫做信号的未决     (3) 阻塞: 进程可以选择阻塞某个信号, 被阻塞的信号产生时将保持在未决状态, 直到进程解除该信号的屏蔽, 才…

信号的捕捉以及SIGCHLD信号

一. 信号的捕捉定义 用户提供一个处理函数, 要求内核在处理信号时必须切换到用户态,执行这个函数, 这种方式就叫做信号的捕捉 二. 图解信号的捕捉过程 1. 由上图可以看出,当处理信号的执行动作时用户自定义的时候,此时就需返回该函数去调用该函数, 这就叫做信号的捕捉. 以前我…

最小栈的实现

所谓最小栈, 就是当前栈顶元素最小, 我们可以这样做, 每次在入栈之前, 将待入栈元素与栈顶元素相比, 每次现将待入栈的元素先入栈, 再将带入栈的元素和较小的元素入栈, 这样就可以保证每次栈顶元素是最小元素. 在出栈的时候规定每次出栈两个元素,这样就可以保证在出栈之后栈顶元…

用C++实现单链表的创建、逆置和输出 的两种方法

http://blog.csdn.net/lfeng_coding/article/details/47300563 题目描述&#xff1a;在已知单链表头节点的情况下&#xff0c;设计算法逆置单链表并输出 方法一&#xff1a;采用首先将头节点指向空&#xff0c;让其变为尾节点&#xff0c;然后利用中间节点 p、q 将其后的节点一…

两个栈实现一个队列

利用两个栈实现一个队列思路是这样的. 首先这个队列包含两个栈, 然后一个栈用来入队列, 一个栈用来出队列 typedef struct QueBy2Stack {SeqStack input;SeqStack output; }QueBy2Stack; 1. 初始化 void QueBy2StackInit(QueBy2Stack* stack) {if(stack NULL){return;//非法…