Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1823586
  • 博文数量: 317
  • 博客积分: 1557
  • 博客等级: 上尉
  • 技术积分: 1208
  • 用 户 组: 普通用户
  • 注册时间: 2008-02-26 23:38
个人简介

如果想出发,就不要等到明天!

文章分类

全部博文(317)

文章存档

2016年(1)

2015年(41)

2014年(152)

2013年(114)

2012年(4)

2011年(1)

2009年(4)

分类: LINUX

2015-03-24 07:38:09

原理:

select函数会等待,直到描述符句柄中有可用资源(可读、可写、异常)时返回,返回值是可用资源(可读/可写/异常等)描述符的个数(>0),0代表超时,-1代表错误。具体到内核大致是:当应用程序调用select() 函数, 内核就会相应调用 poll_wait(), 把当前进程添加到相应设备的等待队列上,然后将该应用程序进程设置为睡眠状态。直到该设备上的数据可以获取,然后调用wake_up()唤醒该应用程序进程。select每次轮训都会遍历所有描述符句柄。


函数接口:

 int select(int max_fd,fd_set * readfds,fd_set * writefds,fd_set * exceptfds,struct timeval * timeout); 

 max_fd   :fd+1

 readfds  :可读描述符句柄

 writefds :可写描述符句柄

 exceptfds:异常描述符句柄

 timeout  :超时时间 

 select函数的参数将告诉内核:

(1)我们所关心的对应描述符句柄

(2)对于每个描述符我们所关心的条件,是否可读,是否可写或是否异常

(3)希望等待多长时间,struct timeval * timeout

struct timeval{      

        long tv_sec;   /*秒 */

        long tv_usec;  /*微秒 */   

    }

timeout == NULL  等待无限长的时间;

timeout->tv_sec == 0 &&timeout->tv_usec == 0不等待,直接返回;

timeout->tv_sec !=0 ||timeout->tv_usec!= 0 等待指定的时间,超时后返回0,表示在一定时间内没有可用资源的描述符。

一个描述符句柄保存在fd_set类型中,fd_set类型变量每一位代表了一个描述符。我们也可以认为它只是一个由很多二进制位构成的数组。

fd_set类型变量是一下几个宏来控制它:

#include   

int FD_ZERO(int fd, fd_set *fdset);   --将一个fd_set类型变量所有位设置为0

int FD_CLR(int fd, fd_set *fdset);    --将fd_set类型变量中对应fd删除

int FD_SET(int fd, fd_set *fd_set);   --将fd添加到fd_set类型变量中,进而将fd_set类型变量的对应的位置位

int FD_ISSET(int fd, fd_set *fdset);  --fd是否在fd_set类型变量中,对应为1说明资源可用


select函数执行结果:执行成功则返回文件描述词状态已改变的个数,如果返回0代表在描述词状态改变前已超过timeout时间,没有返回;当有错误发生时则返回-1,错误原因存于errno,此时参数readfds,writefds,exceptfds和timeout的值变成不可预测。错误

值可能为:

EBADF 文件描述词为无效的或该文件已关闭

EINTR 此调用被信号所中断

EINVAL 参数n 为负值。

ENOMEM 核心内存不足


从http://blog.csdn.net/lingfengtengfei/article/details/12392449中摘取“理解select模型:”

理解select模型:

理解select模型的关键在于理解fd_set,为说明方便,取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模型的特点:

(1)可监控的文件描述符个数取决与sizeof(fd_set)的值。我这边服务器上sizeof(fd_set)=512,每bit表示一个文件描述符,则我服务器上支持的最大文件描述符是512*8=4096。据说可调,另有说虽然可调,但调整上限受于编译内核时的变量值。

(2)将fd加入select监控集的同时,还要再使用一个数据结构array保存放到select监控集中的fd,一是用于再select返回后,array作为源数据和fd_set进行FD_ISSET判断。二是select返回后会把以前加入的但并无事件发生的fd清空,则每次开始 select前都要重新从array取得fd逐一加入(FD_ZERO最先),扫描array的同时取得fd最大值maxfd,用于select的第一个参数。

(3)可见select模型必须在select前循环array(加fd,取maxfd),select返回后循环array(FD_ISSET判断是否有时间发生)。


下面具体举个简单例子。

功能实现:

        服务器回复从客户端接收到的数据-->回显数据


点击(此处)折叠或打开

  1. #include<stdio.h>
  2.  
  3. #include<stdlib.h>
  4.  
  5. #include<unistd.h>
  6.  
  7. #include<errno.h>
  8.  
  9. #include<netdb.h>
  10.  
  11. #include<sys/types.h>
  12.  
  13. #include<sys/socket.h>
  14.  
  15. #include<netinet/in.h>
  16.  
  17. #include<arpa/inet.h>
  18.  
  19. #include<netdb.h>
  20.  
  21. #include<sys/time.h>
  22.  
  23. #include<string.h>
  24.  
  25. #include<sys/select.h>
  26.  
  27. #include<pthread.h>
  28.  
  29.  
  30.  
  31.  
  32. /*************************
  33. *用于返回最大文件描述值
  34. *
  35. *************************/
  36. int max_fd(int a[], int num)
  37. {
  38.     int max = -1;
  39.     int i = 0;
  40.     for(i = 0; i < num; i++)
  41.     {
  42.         if(a[i] > max)
  43.         {
  44.             max = a[i];
  45.  
  46.         }
  47.     }
  48.  
  49.     return max;
  50. }
  51.  
  52. int main(int argc, char *argv[])
  53. {
  54.     int sockfd = 0;
  55.     int confd = 0;
  56.     int i = 0, j = 0;
  57.     fd_set fd_read[2];
  58.     int clifd[FD_SETSIZE]; /*存放监听以及已与客户连接的套接字*/
  59.     int fd_count = 0; /*已经连接的套接字个数*/
  60.     struct sockaddr_in seradd, cliadd;
  61.     int fd_ret = 0;
  62.     socklen_t cli_len = sizeof(cliadd);
  63.     char readbuf[1024];
  64.     char writebuf[1024];
  65.     /*create sock;*/
  66.     sockfd = socket(AF_INET, SOCK_STREAM, 0); /*此套接字是阻塞的*/
  67.     if(sockfd < 0)
  68.     {
  69.         perror("sock create fail !!");
  70.         exit(-1);
  71.     }
  72.  
  73.  
  74.     seradd.sin_family = AF_INET;
  75.     seradd.sin_port = htons(8080);
  76.     seradd.sin_addr.s_addr = htonl(INADDR_ANY);
  77.  
  78.     /*bind*/
  79.     if(-1 == (bind(sockfd, (struct sockaddr *)&seradd, sizeof(struct sockaddr))))
  80.     {
  81.         perror("bind error");
  82.         exit(-1);
  83.     }
  84.  
  85.     /*listen*/
  86.     if(-1 == (listen(sockfd, 5)))
  87.     {
  88.         perror("listen error");
  89.         exit(-1);
  90.     }
  91.  
  92.     //memset(&fd_read[0],0,sizeof(fd_read[0]));
  93.  
  94.     FD_ZERO(&fd_read[0]); /*清空fd_read[0]所有位*/
  95.     FD_SET(sockfd, &fd_read[0]); /*将sockfd添加到fd_read[0]描述集中,也就是说将sockfd对应的fd_read[0]位中置位*/
  96.     for(i = 0; i < FD_SETSIZE; i++)
  97.     {
  98.         clifd[i] = -1;
  99.     }
  100.  
  101.     clifd[0] = sockfd;
  102.     printf("--ser start work---\n");
  103.     while(1)
  104.     {
  105.         FD_ZERO(&fd_read[1]);
  106.         fd_read[1] = fd_read[0]; /*每次select后,没有达到条件的描述将被清空,所以每次都需要重新赋值*/
  107.         /*进程将阻塞在select函数处,直到在整个队列中有读描述符可用为止,个人理解--内核检测整个队列,然后将可用的描述符返回(一个或多个,一个没有时将阻塞)*/
  108.         fd_ret = select(max_fd(clifd, FD_SETSIZE) + 1, &fd_read[1], NULL, NULL, NULL); /*我们只关心读描述符集*/
  109.  
  110.         if(fd_ret < 0)
  111.         {
  112.             perror("select error");
  113.         }
  114.         else if(fd_ret > 0)
  115.         {
  116.             /*是监听套接字可读*/
  117.             if(FD_ISSET(sockfd, &fd_read[1]) && (fd_count < FD_SETSIZE - 1))
  118.             {
  119.  
  120.                 confd = accept(sockfd, (struct sockaddr *)&cliadd, &cli_len); /*获取与客户端连接套接字*/
  121.                 if(-1 == confd)
  122.                 {
  123.                     perror("confd error");
  124.                 }
  125.  
  126.                 for(i = 1; i < FD_SETSIZE; i++)
  127.                 {
  128.                     if(clifd[i] == -1)
  129.                     {
  130.                         clifd[i] = confd; /*将获得新连接套接字放到clifd数组中*/
  131.                         FD_SET(confd, &fd_read[0]); /*将获得新连接套接字添加到读描述集中*/
  132.                         fd_count++;
  133.                         break;
  134.                     }
  135.                 }
  136.             }
  137.  
  138.             /*连接套接字可读*/
  139.             for(j = 1; j < FD_SETSIZE; j++)
  140.             {
  141.                 if(FD_ISSET(clifd[j], &fd_read[1]))
  142.                 {
  143.                     /*从clifd[i]套接字中读取数据*/
  144.                     if(read(clifd[j], readbuf, sizeof(readbuf)) <= 0)
  145.                     {
  146.                         perror("read data error");
  147.                         FD_CLR(clifd[j], &fd_read[0]); /*将clifd[j]描述从读描述符集中删除*/
  148.                         close(clifd[j]); /*关闭该套接字*/
  149.                         clifd[j] = -1;
  150.                         fd_count--;
  151.                         continue;
  152.  
  153.                     }
  154.  
  155.                     strcpy(writebuf, readbuf);
  156.                     printf("read data:%s\n", readbuf);
  157.                     if(write(clifd[j], writebuf, sizeof(writebuf)) <= 0)
  158.                     {
  159.                         perror("write data error");
  160.                         FD_CLR(clifd[j], &fd_read[0]);
  161.                         close(clifd[j]);
  162.                         clifd[j] = -1;
  163.                         fd_count--;
  164.                         continue;
  165.                     }
  166.                 }
  167.             }
  168.         }
  169.  
  170.  
  171.     }
  172.  
  173. }

注意:在使用select函数时,请注意一下几点

      1,select第一个参数是最大描述加1;

  2,描述集中在每次select后都要重新赋值,select函数会把不符合条件的对应位清空;

  3,如果有时间参数,在超时后,时间参数都必须重新赋值,否则会一直超时。



参考博客:

http://blog.chinaunix.net/uid-21275705-id-224351.html

http://blog.csdn.net/lingfengtengfei/article/details/12392449


阅读(1326) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~