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 C/C++之IO多路复用(select)
这篇文章主要介绍了TCP的三次握手和四次挥手过程,TCP与UDP的区别,以及如何使用select函数实现IO多路复用,包括服务器监听多个客户端连接和简单聊天室场景的应用示例。
103 0
|
6天前
|
Linux
【Linux】System V信号量详解以及semget()、semctl()和semop()函数讲解
System V信号量的概念及其在Linux中的使用,包括 `semget()`、`semctl()`和 `semop()`函数的具体使用方法。通过实际代码示例,演示了如何创建、初始化和使用信号量进行进程间同步。掌握这些知识,可以有效解决多进程编程中的同步问题,提高程序的可靠性和稳定性。
45 19
|
8天前
|
Linux Android开发 开发者
linux m、mm、mmm函数和make的区别
通过理解和合理使用这些命令,可以更高效地进行项目构建和管理,特别是在复杂的 Android 开发环境中。
39 18
|
16天前
|
存储 监控 Linux
嵌入式Linux系统编程 — 5.3 times、clock函数获取进程时间
在嵌入式Linux系统编程中,`times`和 `clock`函数是获取进程时间的两个重要工具。`times`函数提供了更详细的进程和子进程时间信息,而 `clock`函数则提供了更简单的处理器时间获取方法。根据具体需求选择合适的函数,可以更有效地进行性能分析和资源管理。通过本文的介绍,希望能帮助您更好地理解和使用这两个函数,提高嵌入式系统编程的效率和效果。
79 13
|
4月前
|
Linux Shell
Linux系统编程:掌握popen函数的使用
记得在使用完 `popen`打开的流后,总是使用 `pclose`来正确关闭它,并回收资源。这种做法符合良好的编程习惯,有助于保持程序的健壮性和稳定性。
171 6
|
4月前
|
Linux Shell
Linux系统编程:掌握popen函数的使用
记得在使用完 `popen`打开的流后,总是使用 `pclose`来正确关闭它,并回收资源。这种做法符合良好的编程习惯,有助于保持程序的健壮性和稳定性。
188 3
|
4月前
|
Linux
在Linux内核中根据函数指针输出函数名称
在Linux内核中根据函数指针输出函数名称
|
5月前
|
Linux PHP
Linux CentOS 宝塔 Suhosin禁用php5.6版本eval函数详细图文教程
【8月更文挑战第27天】本文介绍两种禁用PHP执行的方法:使用`PHP_diseval_extension`禁用和通过`suhosin`禁用。由于`suhosin`不支持PHP8,仅适用于PHP7及以下版本,若服务器安装了PHP5.6,则需对应安装`suhosin-0.9.38`版本。文章提供了详细的安装步骤,并强调了宝塔环境下与普通环境下的PHP路径差异。安装完成后,在`php.ini`中添加`suhosin.so`扩展并设置`executor.disable_eval = on`以禁用执行功能。最后通过测试代码验证是否成功禁用,并重启`php-fpm`服务生效。
66 2
|
5月前
|
Shell Linux C语言
Linux0.11 execve函数(六)
Linux0.11 execve函数(六)
93 1
|
5月前
|
Linux
Linux0.11 文件打开open函数(五)
Linux0.11 文件打开open函数(五)
58 0