1、基本概念
io多路復用是指內核一旦發現進程指定的一個或者多個io條件準備讀取,它就通知該進程。io多路復用適用如下場合:
(1)當客戶處理多個描述字時(一般是交互式輸入和網絡套接口),必須使用i/o復用。
(2)當一個客戶同時處理多個套接口時,而這種情況是可能的,但很少出現。
(3)如果一個tcp服務器既要處理監聽套接口,又要處理已連接套接口,一般也要用到i/o復用。
(4)如果一個服務器即要處理tcp,又要處理udp,一般要使用i/o復用。
(5)如果一個服務器要處理多個服務或多個協議,一般要使用i/o復用。
與多進程和多線程技術相比,i/o多路復用技術的最大優勢是系統開銷小,系統不必創建進程/線程,也不必維護這些進程/線程,從而大大減小了系統的開銷。
2、select函數
該函數準許進程指示內核等待多個事件中的任何一個發送,并只在有一個或多個事件發生或經歷一段指定的時間后才喚醒。函數原型如下:
#include <sys/select.h>
#include <sys/time.h>
int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout)
返回值:就緒描述符的數目,超時返回0,出錯返回-1
函數參數介紹如下:
(1)第一個參數maxfdp1指定待測試的描述字個數,它的值是待測試的最大描述字加1(因此把該參數命名為maxfdp1),描述字0、1、2...maxfdp1-1均將被測試。
因為文件描述符是從0開始的。
(2)中間的三個參數readset、writeset和exceptset指定我們要讓內核測試讀、寫和異常條件的描述字。如果對某一個的條件不感興趣,就可以把它設為空指針。struct fd_set可以理解為一個集合,這個集合中存放的是文件描述符,可通過以下四個宏進行設置:
void fd_zero(fd_set *fdset); //清空集合
void fd_set(int fd, fd_set *fdset); //將一個給定的文件描述符加入集合之中
void fd_clr(int fd, fd_set *fdset); //將一個給定的文件描述符從集合中刪除
int fd_isset(int fd, fd_set *fdset); // 檢查集合中指定的文件描述符是否可以讀寫
(3)timeout告知內核等待所指定描述字中的任何一個就緒可花多少時間。其timeval結構用于指定這段時間的秒數和微秒數。
1
2
3
4
5
6
7
|
struct timeval{ long tv_sec; //seconds long tv_usec; //microseconds }; |
這個參數有三種可能:
(1)永遠等待下去:僅在有一個描述字準備好i/o時才返回。為此,把該參數設置為空指針null。
(2)等待一段固定時間:在有一個描述字準備好i/o時返回,但是不超過由該參數所指向的timeval結構中指定的秒數和微秒數。
(3)根本不等待:檢查描述字后立即返回,這稱為輪詢。為此,該參數必須指向一個timeval結構,而且其中的定時器值必須為0。
原理圖:
3、測試程序
寫一個tcp回射程序,程序的功能是:客戶端向服務器發送信息,服務器接收并原樣發送給客戶端,客戶端顯示出接收到的信息。
服務端程序如下:
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
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
|
#include < stdio.h > #include < stdlib.h > #include < string.h > #include < errno.h > #include < netinet /in.h> #include < sys /socket.h> #include < sys /select.h> #include < sys /types.h> #include < netinet /in.h> #include < arpa /inet.h> #include < unistd.h > #include < assert.h > #define ipaddr "127.0.0.1" #define port 8787 #define maxline 1024 #define listenq 5 #define size 10 typedef struct server_context_st { int cli_cnt; /*客戶端個數*/ int clifds[size]; /*客戶端的個數*/ fd_set allfds; /*句柄集合*/ int maxfd; /*句柄最大值*/ } server_context_st; static server_context_st *s_srv_ctx = null; /*=========================================================================== * ==========================================================================*/ static int create_server_proc(const char* ip,int port) { int fd; struct sockaddr_in servaddr; fd = socket(af_inet, sock_stream,0); if (fd == -1) { fprintf(stderr, "create socket fail,erron:%d,reason:%s\n", errno, strerror(errno)); return -1; } /*一個端口釋放后會等待兩分鐘之后才能再被使用,so_reuseaddr是讓端口釋放后立即就可以被再次使用。*/ int reuse = 1; if (setsockopt(fd, sol_socket, so_reuseaddr, &reuse, sizeof(reuse)) == -1) { return -1; } bzero(&servaddr,sizeof(servaddr)); servaddr.sin_family = af_inet; inet_pton(af_inet,ip,&servaddr.sin_addr); servaddr.sin_port = htons(port); if (bind(fd,(struct sockaddr*)&servaddr,sizeof(servaddr)) == -1) { perror("bind error: "); return -1; } listen(fd,listenq); return fd; } static int accept_client_proc(int srvfd) { struct sockaddr_in cliaddr; socklen_t cliaddrlen; cliaddrlen = sizeof(cliaddr); int clifd = -1; printf("accpet clint proc is called.\n"); accept: clifd = accept(srvfd,(struct sockaddr*)&cliaddr,&cliaddrlen); if (clifd == -1) { if (errno == eintr) { goto accept; } else { fprintf(stderr, "accept fail,error:%s\n", strerror(errno)); return -1; } } fprintf(stdout, "accept a new client: %s:%d\n", inet_ntoa(cliaddr.sin_addr),cliaddr.sin_port); //將新的連接描述符添加到數組中 int i = 0; for (i = 0; i < size ; i++) { if (s_srv_ctx->clifds[i] < 0 ) { s_srv_ctx->clifds[i] = clifd; s_srv_ctx->cli_cnt++; break; } } if (i == size) { fprintf(stderr,"too many clients.\n"); return -1; } 101 } static int handle_client_msg(int fd, char *buf) { assert(buf); printf("recv buf is :%s\n", buf); write(fd, buf, strlen(buf) +1); return 0; } static void recv_client_msg(fd_set *readfds) { int i = 0, n = 0; int clifd; char buf[maxline] = {0}; for (i = 0;i <= s_srv_ctx->cli_cnt;i++) { clifd = s_srv_ctx->clifds[i]; if (clifd < 0 ) { continue; } /*判斷客戶端套接字是否有數據*/ if (fd_isset(clifd, readfds)) { //接收客戶端發送的信息 n = read (clifd, buf, maxline); if (n <= 0) { /*n==0表示讀取完成,客戶都關閉套接字*/ fd_clr(clifd, &s_srv_ctx->allfds); close(clifd); s_srv_ctx->clifds[i] = -1; continue; } handle_client_msg(clifd, buf); } } } static void handle_client_proc(int srvfd) { int clifd = -1; int retval = 0; fd_set *readfds = &s_srv_ctx->allfds; struct timeval tv; int i = 0; while (1) { /*每次調用select前都要重新設置文件描述符和時間,因為事件發生后,文件描述符和時間都被內核修改啦*/ fd_zero(readfds); /*添加監聽套接字*/ fd_set(srvfd, readfds); s_srv_ctx->maxfd = srvfd; tv.tv_sec = 30; tv.tv_usec = 0; /*添加客戶端套接字*/ for (i = 0; i < s_srv_ctx- >cli_cnt; i++) { clifd = s_srv_ctx->clifds[i]; /*去除無效的客戶端句柄*/ if (clifd != -1) { fd_set(clifd, readfds); } s_srv_ctx->maxfd = (clifd > s_srv_ctx->maxfd ? clifd : s_srv_ctx->maxfd); } /*開始輪詢接收處理服務端和客戶端套接字*/ retval = select(s_srv_ctx->maxfd + 1, readfds, null, null, &tv); if (retval == -1) { fprintf(stderr, "select error:%s.\n", strerror(errno)); return; } if (retval == 0) { fprintf(stdout, "select is timeout.\n"); continue; } if (fd_isset(srvfd, readfds)) { /*監聽客戶端請求*/ accept_client_proc(srvfd); } else { /*接受處理客戶端消息*/ recv_client_msg(readfds); } } } static void server_uninit() { if (s_srv_ctx) { free(s_srv_ctx); s_srv_ctx = null; } } static int server_init() { s_srv_ctx = (server_context_st *)malloc(sizeof(server_context_st)); if (s_srv_ctx == null) { return -1; } memset(s_srv_ctx, 0, sizeof(server_context_st)); int i = 0; for (;i < size ; i++) { s_srv_ctx->clifds[i] = -1; } return 0; } int main(int argc,char *argv[]) { int srvfd; /*初始化服務端context*/ if (server_init() < 0) { return -1; } /*創建服務,開始監聽客戶端請求*/ srvfd = create_server_proc(ipaddr, port); if (srvfd < 0) { fprintf(stderr, "socket create or bind fail.\n"); goto err; } /*開始接收并處理客戶端請求*/ handle_client_proc(srvfd); server_uninit(); return 0; err: server_uninit(); return -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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
|
#include < netinet /in.h> #include < sys /socket.h> #include < stdio.h > #include < string.h > #include < stdlib.h > #include < sys /select.h> #include < time.h > #include < unistd.h > #include < sys /types.h> #include < errno.h > #define maxline 1024 #define ipaddress "127.0.0.1" #define serv_port 8787 #define max(a,b) (a > b) ? a : b static void handle_recv_msg(int sockfd, char *buf) { printf("client recv msg is:%s\n", buf); sleep(5); write(sockfd, buf, strlen(buf) +1); } static void handle_connection(int sockfd) { char sendline[maxline],recvline[maxline]; int maxfdp,stdineof; fd_set readfds; int n; struct timeval tv; int retval = 0; while (1) { fd_zero(&readfds); fd_set(sockfd,&readfds); maxfdp = sockfd; tv.tv_sec = 5; tv.tv_usec = 0; retval = select(maxfdp+1,&readfds,null,null,&tv); if (retval == -1) { return ; } if (retval == 0) { printf("client timeout.\n"); continue; } if (fd_isset(sockfd, &readfds)) { n = read(sockfd,recvline,maxline); if (n <= 0) { fprintf(stderr,"client: server is closed.\n"); close(sockfd); fd_clr(sockfd,&readfds); return; } handle_recv_msg(sockfd, recvline); } } } int main(int argc,char *argv[]) { int sockfd; struct sockaddr_in servaddr; sockfd = socket(af_inet,sock_stream,0); bzero(&servaddr,sizeof(servaddr)); servaddr.sin_family = af_inet; servaddr.sin_port = htons(serv_port); inet_pton(af_inet,ipaddress,&servaddr.sin_addr); int retval = 0; retval = connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr)); if (retval < 0) { fprintf(stderr, "connect fail,error:%s\n", strerror(errno)); return -1; } printf("client send to server .\n"); write(sockfd, "hello server", 32); handle_connection(sockfd); return 0; } |
4、程序結果
啟動服務程序,執行三個個客戶程序進行測試,結果如下圖所示:
以上就是小編為大家帶來的io多路復用之select全面總結(必看篇)全部內容了,希望大家多多支持服務器之家~