一.概述:
系统提供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
|
1
/****************************************
2 > File Name:test.c
3 > Author:xiaoxiaohui
4 > mail:1924224891@qq.com
5 > Created Time:2016年05月23日 星期一 16时11分45秒
6 ****************************************/
7
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 }
|
执行结果:
(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
|
1
/****************************************
2 > File Name:server.c
3 > Author:xiaoxiaohui
4 > mail:1924224891@qq.com
5 > Created Time:2016年05月23日 星期一 12时18分21秒
6 ****************************************/
7
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, ×))
//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
|
1
/****************************************
2 > File Name:client.c
3 > Author:xiaoxiaohui
4 > mail:1924224891@qq.com
5 > Created Time:2016年05月23日 星期一 12时30分01秒
6 ****************************************/
7
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
3
4 server:server.c
5 gcc -o $@ $^ -g
6 client:client.c
7 gcc -o $@ $^ -g
8
9 .PHONY:clean
10 clean:
11 rm -f server client
|
执行结果:
五.总结:
select用于I/O复用,通过监听文件描述符的状态,当与文件描述符相关的资源准备就绪就返回,从而提高性能。
reads,writes, timeout都是输入/输出型参数,所以要在while循环内设置它们的状态。