linux下select函数详解及实例

简介:


一.概述:

系统提供select函数来实现I/O复用输入/输出模型。select系统调用是用来让我们的程序监视多个文件句柄的状态变化的。程序会停在select这里等待,直到被监视的文件句柄中有一个或多个发生生了状态改变。




二.select函数:

以下为man文本中的解释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  /* According to POSIX.1-2001 */
        #include <sys/select.h>
 
        /* According to earlier standards */
        #include <sys/ time .h>
        #include <sys/types.h>
        #include <unistd.h>
 
        int  select( int  nfds, fd_set *readfds, fd_set *writefds,
                   fd_set *exceptfds,  struct  timeval *timeout);
 
        void  FD_CLR( int  fd, fd_set *set);
        int   FD_ISSET( int  fd, fd_set *set);
        void  FD_SET( int  fd, fd_set *set);
        void  FD_ZERO(fd_set *set);

nfds参数:需要监视的文件描述符集中最大的文件描述符 + 1;

readfds:输入/输出型参数,需要监视的可读文件描述符集合。

rwritefds:输入/输出型参数,需要监视的可写文件描述符集合。

exceptds:输入/输出型参数,需要监视的异常文件描述符集合。(一般为NULL)

timeout参数:输入/输出型参数,

NULL:则表示select()没有timeout,select将一直被阻塞,直到某个文件描述符上发生了事件。
0:仅检测描述符集合的状态,然后立即返回,并不等待外部事件的发生。
特定的时间值:如果在指定的时间段里没有事件发生,select将超时返回。

返回值:执行成功则返回文件描述符集状态已改变的个数:

                如果文件描述符集中没有满足条件的,并且时间超出了timeout,则返回0;

                出差返回-1,此时参数readfds,writefds,exceptfds和timeout的值变成不可预测。并置相应的错误码:

EBADF :文件描述词为无效的或该文件已关闭
EINTR: 此调用被信号所中断
EINVAL: 参数n 为负值。
ENOMEM :核心内存不足



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

struct timeval结构体:一个常用的结构,用来代表时间值,有两个成员,一个是秒数,另一个是毫秒数

1
2
3
4
5
struct  timeval  
{  
     long  tv_sec;     //second  
     long  tv_usec;    //microsecond  
};

下面的宏提供了处理这三种描述符集的方式:
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的全部位




三.fd_set理解:

理解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被清空

PS:readfds,writefds,exceptfds,timeout都是输入/输出型参数,输入时,是你自己设置的值,输出时是改变后的值。




四.相关代码:

(1).监控标准输入输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
  /****************************************                                                                                                 
   2     > File Name:test.c
   3     > Author:xiaoxiaohui
   4     > mail:1924224891@qq.com
   5     > Created Time:2016年05月23日 星期一 16时11分45秒
   6 ****************************************/
  
   8 #include<stdio.h>
   9 #include<stdlib.h>
  10 #include<sys/types.h>
  11 #include<sys/ time .h>
  12 #include<unistd.h>
  13 #include<string.h>
  14 
  15  const  int  LEN = 1024;
  16  int  fds[2];           //只监测标准输入与输出这两个文件描述符
  17  int  main()
  18 {
  19 
  20      int  std_in = 0;
  21      int  std_out = 1;
  22      int  fds_max = 1;
  23     fd_set reads, writes;
  24      struct  timeval timeout;
  25 
  26     fds[0] = std_in;
  27     fds[1] = std_out;
  28 
  29      while (1)
  30     {
  31         FD_ZERO(&reads);
  32         FD_ZERO(&writes);
  33         FD_SET(std_in, &reads);           //标准输入关注的是读事件
  34         FD_SET(std_out, &writes);        //标准输出关注的是写事件
  35         timeout.tv_sec = 5;
  36         timeout.tv_usec = 0;
  37          switch ( select(fds_max + 1, &reads, &writes, NULL, &timeout))
  38         {
  39              case  0:
  40                  printf ( "select time out ......\n" );
  41                  break ;
  42              case  -1:
  43                  perror ( "select" );
  44                  break ;
  45              default :
  46                  if (FD_ISSET(fds[0], &reads))        //可以从标准输入中读
  47                 {
  48                      char  buf[LEN];
  49                      memset (buf,  '\0' , LEN);
  50                      gets (buf);
  51                      printf ( "echo: %s\n" , buf);      
  52 
  53                      if ( strncmp (buf,  "quit" , 4) == 0)
  54                     {
  55                          exit (0);
  56                     }
  57                 }
  58                  if (FD_ISSET(fds[1], &writes))
  59                 {
  60                      char * buf =  "write is ready.......\n" ;
  61                      printf ( "%s" , buf);
  62                     sleep(5);
  63                 }
  64                  break ;
  65         }
  66     }
  67 
  68 
  69 }

执行结果:

wKiom1dEKCKwKpafAAAo2P7Eie0227.png


(2).多路复用的TCP套接字编程:

server.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
/****************************************                                                                                                 
   2     > File Name:server.c
   3     > Author:xiaoxiaohui
   4     > mail:1924224891@qq.com
   5     > Created Time:2016年05月23日 星期一 12时18分21秒
   6 ****************************************/
  
   8 #include<stdio.h>
   9 #include<stdlib.h>
  10 #include<sys/types.h>
  11 #include<sys/socket.h>
  12 #include<netinet/in.h>
  13 #include<arpa/inet.h>
  14 #include<string.h>
  15 #include<sys/ time .h>
  16 #include<unistd.h>
  17 
  18 #define LEN 1024
  19  const  int  PORT = 8080;
  20  struct  sockaddr_in client;
  21  struct  sockaddr_in local;
  22  int  listenSock;
  23  int  linkSock = -1;
  24  int  fds[64];
  25  int  size_client =  sizeof (client);
  26 
  27  int  ListenSock()
  28 {
  29     listenSock = socket(AF_INET, SOCK_STREAM, 0);
  30      if (listenSock < 0)
  31     {
  32          perror ( "socket" );
  33          exit (1);
  34     }
  35 
  36     local.sin_family = AF_INET;
  37     local.sin_addr.s_addr = htonl(INADDR_ANY);
  38     local.sin_port = htons(PORT);
  39 
  40      if  ( bind(listenSock, ( struct  sockaddr*)&local,  sizeof (local)) < 0)
  41     {
  42          perror ( "bind" );
  43          exit (2);
  44     }
  45 
  46      if ( listen(listenSock, 5) < 0)
  47     {
  48          perror ( "listen" );
  49          exit (3);
  50     }
  51      return  listenSock;
  52 }
  53 
  54  int  main()
  55 {
  56      listenSock = ListenSock();        //进入监听状态
  57  
  58       char  buf[LEN];
  59       memset (buf,  '\0' , LEN);
  60       while (1)
  61      {
  62          fd_set reads, writes;
  63           int  fds_max;        //fds中最大的一个文件描述符
  64  
  65           int  i = 0;
  66           int  fds_num =  sizeof (fds)/ sizeof (fds[0]);
  67           for (; i < fds_num; i++)        //初始化fds
  68          {
  69              fds[i] = -1;
  70          }
  71  
  72          fds[0] = listenSock;
  73          fds_max = fds[0];
  74           struct  timeval times;
  75  
  76           while (1)                                                                                                                         
  77          {
  78              FD_ZERO(&reads);                  //每次循环都要初始化,因为reads与writes即是是输入型参数,也是输出型参数
  79              FD_ZERO(&writes);
  80              FD_SET(listenSock, &reads);      //listenSock只关心读事件
  81              times.tv_sec = 10;
  82              times.tv_usec = 0;
  83               struct  timeval times;
  84               for (i = 1; i < fds_num; i++ )    //在select之前把所有的文件描述符都设置读事件
  85              {
  86                   if (fds[i] > 0)
  87                  {
  88                      FD_SET(fds[i], &reads);   //所有的socket都要关心读事件
  89  
  90                       if (fds[i] > fds_max)
  91                      {
  92                          fds_max = fds[i];
  93                      }
  94                  }
  95              }
  96  
  97               switch ( select(fds_max + 1, &reads, &writes, NULL, &times))      //select函数返回已就绪的文件描述符的个数
  98              {
  99                   case  0:
100                       printf ( "time out....!\n" );
101                       break ;
102                   case  -1:
103                       perror ( "select" );
104                       break ;
105                   default :
106                       for (i = 0; i < fds_num; i++)
107                      {
108                           if (fds[i] == listenSock && FD_ISSET(fds[i], &reads))   //如果为listenSock并且已经就绪  则可以accept客户端了       
109                          {
110                              linkSock = accept(listenSock, ( struct  sockaddr*)&client, &size_client);
111                               if (linkSock < 0)
112                              {
113                                   perror ( "accept" );
114                                   continue ;
115                              }
116                                                                                                                                           
117                               printf ( "a new connect is create...... the fds is %d\n" , linkSock);
118                               for (i = 0; i < fds_max; i++)    //把新创建的文件描述符放到fds中
119                              {
120                                   if (fds[i] < 0)
121                                  {
122                                      fds[i] = linkSock;                                                                                   
123                                      FD_SET(linkSock, &writes);      //设置进写事件队列中
124                                       break ;
125                                  }
126                              }
127                              
128                               if (i == fds_max - 1)
129                              {
130                                   printf ( "文件描述符集已满,请关闭一些链接,以保证系统能正常工作!\n" );
131                              }
132                          }
133                           else  if (fds[i] > 0 && FD_ISSET(fds[i], &reads))      //服务器可以读取客户端发过来的信息了
134                          {
135                               memset (buf,  '\0' , LEN);
136                               int  ret = read(fds[i], buf, LEN);
137                               if (ret < 0)    //读取错误,直接跳到下一个文件描述符
138                              {
139                                   perror ( "read" );
140                                   continue ;
141                              }
142                               else  if (ret == 0)     //客户端关闭 直接跳到下一个文件描述符
143                              {
144                                   printf ( "client is closed!\n" );
145                                   continue ;
146                              }
147                               else     //读取成功
148                              {
149                                  buf[ret] =  '\0' ;
150                                   printf ( "client# %s\n" , buf);
151                              }
152 
153                               if ( write(fds[i], buf,  strlen (buf)) < 0)     //回显给客户端
154                              {
155                                   perror ( "write" );
156                                   continue ;
157                              }
158                          }
167                      }
168 
169                       break ;
170              }
171          }
172      }
173 }


client.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
/****************************************                                                                                                 
   2     > File Name:client.c
   3     > Author:xiaoxiaohui
   4     > mail:1924224891@qq.com
   5     > Created Time:2016年05月23日 星期一 12时30分01秒
   6 ****************************************/
  
   8 #include<stdio.h>
   9 #include<stdlib.h>
  10 #include<string.h>
  11 #include<sys/types.h>
  12 #include<sys/socket.h>
  13 #include<netinet/in.h>
  14 #include<arpa/inet.h>
  15 #include<sys/ time .h>
  16 #include<unistd.h>
  17 
  18 #define LEN 1024
  19  const  int  PORT = 8080;
  20  const  char * IP =  "127.0.0.1" ;
  21  struct  sockaddr_in server;
  22  int  clientSock;
  23  char  buf[LEN];
  24 
  25  int  main()
  26 {
  27     clientSock = socket(AF_INET, SOCK_STREAM, 0);
  28      if (clientSock < 0)
  29     {
  30          perror ( "socket" );
  31          exit (1);
  32     }
  33 
  34     server.sin_family = AF_INET;
  35     server.sin_addr.s_addr = inet_addr(IP);
  36     server.sin_port = htons(PORT);
  37 
  38      if  ( connect(clientSock, ( struct  sockaddr*)&server,  sizeof (server)) < 0)
  39     {
  40          perror ( "connect" );
  41          exit (2);
  42     }
  43 
  44      while (1)
  45     {
  46          memset (buf,  '\0' , LEN);
  47          printf ( "please input: " );
  48          gets (buf);
  49         write(clientSock, buf,  strlen (buf));
  50 
  51          memset (buf,  '\0' , LEN);
  52          int  ret = read(clientSock, buf, LEN);
  53         buf[ret] =  '\0' ;
  54          printf ( "echo: %s\n" , buf);  
  55     }
  56 
  57      return  0;
  58 }


Makefile:

1
2
3
4
5
6
7
8
9
10
11
1 .PHONY:all                                                                                                                                
   2 all:server client
  
   4 server:server.c
   5     gcc -o $@ $^ -g
   6 client:client.c
   7     gcc -o $@ $^ -g
  
   9 .PHONY:clean
  10 clean:
  11     rm -f server client

执行结果:

wKiom1dEKcuxZQXHAAAm7VdhDYE800.png


wKiom1dEKdexezOHAAApQcUIkQM141.png




五.总结:

select用于I/O复用,通过监听文件描述符的状态,当与文件描述符相关的资源准备就绪就返回,从而提高性能。

reads,writes, timeout都是输入/输出型参数,所以要在while循环内设置它们的状态。










本文转自 ye小灰灰  51CTO博客,原文链接:http://blog.51cto.com/10704527/1782636,如需转载请自行联系原作者
目录
相关文章
|
3天前
|
Linux
linux中wait与waitpid函数使用场景及扩展
linux中wait与waitpid函数使用场景及扩展
|
7天前
|
存储 算法 网络协议
【探索Linux】P.26(网络编程套接字基本概念—— socket编程接口 | socket编程接口相关函数详细介绍 )
【探索Linux】P.26(网络编程套接字基本概念—— socket编程接口 | socket编程接口相关函数详细介绍 )
13 0
|
7天前
|
消息中间件 Unix Linux
【探索Linux】P.14(进程间通信 | 匿名管道 | |进程池 | pipe() 函数 | mkfifo() 函数)
【探索Linux】P.14(进程间通信 | 匿名管道 | |进程池 | pipe() 函数 | mkfifo() 函数)
21 0
|
7天前
|
Linux Shell
Linux中system函数
Linux中system函数
8 0
|
13天前
|
Linux Shell 调度
【Linux系列】fork( )函数原理与应用详解——了解【父子进程及其特性】(代码演示,画图帮助理解,思维导图,精简)(11)
【Linux系列】fork( )函数原理与应用详解——了解【父子进程及其特性】(代码演示,画图帮助理解,思维导图,精简)(11)
|
17天前
|
XML 安全 Linux
【Linux】深入探究CentOS防火墙(Firewalld):基础概念、常用命令及实例操作
【Linux】深入探究CentOS防火墙(Firewalld):基础概念、常用命令及实例操作
|
21天前
|
算法 Linux Shell
【linux进程(二)】如何创建子进程?--fork函数深度剖析
【linux进程(二)】如何创建子进程?--fork函数深度剖析
|
21天前
|
存储 Linux Windows
Linux挂载硬盘的详细步骤与实例
Linux挂载硬盘的详细步骤与实例
16 0
|
22天前
|
Linux 数据安全/隐私保护
Linux常用命令实例带注释
Linux常用命令实例带注释
34 0