IO多路復用之select全面總結(必看篇)

轉載:http://www.jb51.net/article/101057.htm

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全面總結(必看篇)全部內容了,希望大家多多支持腳本之家~


本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/384351.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/384351.shtml
英文地址,請注明出處:http://en.pswp.cn/news/384351.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

leetcode(189) 旋轉數組

**給定一個數組&#xff0c;將數組中的元素向右移動 k 個位置&#xff0c;其中 k 是非負數。 進階&#xff1a; 盡可能想出更多的解決方案&#xff0c;至少有三種不同的方法可以解決這個問題。 你可以使用空間復雜度為 O(1) 的 原地 算法解決這個問題嗎&#xff1f; 示例 1: …

I/O 多路復用之select

轉載&#xff1a;http://blog.csdn.net/u012432778/article/details/47347133 概述 Linux提供了三種 I/O 多路復用方案&#xff1a;select&#xff0c;poll和epoll。在這一篇博客里先討論select, poll 在將下一篇中介紹&#xff0c;epoll是Linux特有的高級解決方案&#xff0c;…

leetcode(283)移動零

283. 移動零 給定一個數組 nums&#xff0c;編寫一個函數將所有 0 移動到數組的末尾&#xff0c;同時保持非零元素的相對順序。 示例: 輸入: [0,1,0,3,12] 輸出: [1,3,12,0,0] 說明: 必須在原數組上操作&#xff0c;不能拷貝額外的數組。 盡量減少操作次數。 方法一&#xff1…

exec函數族實例解析

轉載&#xff1a;http://www.cnblogs.com/blankqdb/archive/2012/08/23/2652386.html fork()函數通過系統調用創建一個與原來進程(父進程)幾乎完全相同的進程(子進程是父進程的副本&#xff0c;它將獲得父進程數據空間、堆、棧等資源的副本。注意&#xff0c;子進程持有的是上述…

leetcode(167)兩數之和 II - 輸入有序數組

兩數之和 II - 輸入有序數組 給定一個已按照 升序排列 的整數數組 numbers &#xff0c;請你從數組中找出兩個數滿足相加之和等于目標數 target 。 函數應該以長度為 2 的整數數組的形式返回這兩個數的下標值。numbers 的下標 從 1 開始計數 &#xff0c;所以答案數組應當滿足 …

常量指針與指針常量的區別(轉帖)

轉載&#xff1a;http://www.cnblogs.com/witty/archive/2012/04/06/2435311.html 三個名詞雖然非常繞嘴&#xff0c;不過說的非常準確。用中國話的語義分析就可以很方便地把三個概念區分開。 一) 常量指針。 常量是形容詞&#xff0c;指針是名詞&#xff0c;以指針為中心的一個…

c/c++錯題總結

1.類名 對象名 默認調用“對象名()”這個構造函數&#xff0c;在棧內存中存在對象名&#xff0c;在堆內存中存在實際對象&#xff1b; 2.類名 對象名(一個或以上個參數) 默認調用相應的構造函數&#xff0c;在棧內存中存在對象名&#xff0c;在堆內存中也是存在實際對象的&a…

智能指針學習筆記

轉載&#xff1a;http://www.cnblogs.com/wuchanming/p/4411878.html 1. 介紹 本文介紹智能指針的使用。智能指針是c 中管理資源的一種方式&#xff0c;用智能指針管理資源&#xff0c;不必擔心資源泄露&#xff0c;將c 程序員 從指針和內存管理中解脫出來&#xff0c;再者&…

c++程序編譯過程

c程序編譯分成四個過程&#xff1a;編譯預處理&#xff0c;編譯&#xff0c;匯編&#xff0c;鏈接 編譯預處理&#xff1a;處理以#為開頭 編譯&#xff1a;將.cpp文件翻譯成.s匯編文件 匯編&#xff1a;將.s匯編文件翻譯成機器指令.o文件 鏈接&#xff1a;匯編生產的目標文件.o…

仿函數(函數對象)

轉載&#xff1a;http://www.cnblogs.com/wuchanming/p/4411867.html 本文乃作者學習《C標準程序庫》的學習筆記&#xff0c;首先介紹了仿函數&#xff08;函數對象&#xff09;和函數適配器&#xff08;配接器&#xff09;的概念&#xff0c;然后列出STL中所有的仿函數&#x…

C++ template —— 動多態與靜多態(六)

轉載&#xff1a;http://www.cnblogs.com/yyxt/p/5157517.html 前面的幾篇博文介紹了模板的基礎知識&#xff0c;并且也深入的講解了模板的特性。接下來的博文中&#xff0c;將會針對模板與設計進行相關的介紹。 ------------------------------------------------------------…

變量之間的區別

全局變量、局部變量、靜態全局變量、靜態局部變量的區別 c變量根據定義具有不同的生命周期&#xff0c;會有不同的作用域&#xff0c;主要有六個作用域&#xff1a;全局作用域&#xff0c;局部作用域&#xff0c;文件作用域&#xff0c;類作用域&#xff0c;語句作用域&#xf…

計算機的網絡體系以及參考模型

計算機的網絡體系以及參考模型一、OSI七層模型二、TCP/IP參考模型三、TCP/IP 五層參考模型四、OSI 模型和 TCP/IP 模型異同比較五、OSI 和 TCP/IP 協議之間的對應關系六、為什么 TCP/IP 去除了表示層和會話層&#xff1f;七、數據如何在各層之間傳輸&#xff08;數據的封裝過程…

C++ 模板詳解(二)

轉載&#xff1a;http://www.cnblogs.com/gw811/archive/2012/10/25/2736224.html 四、類模板的默認模板類型形參 1、可以為類模板的類型形參提供默認值&#xff0c;但不能為函數模板的類型形參提供默認值。函數模板和類模板都可以為模板的非類型形參提供默認值。 2、類模板的類…

c++類對象的創建方式

對象創建限制在堆或棧 c類對象的創建方式對象創建限制在堆或棧C 中的類的對象的建立模式如何將類限制在堆上呢&#xff1f;C 中的類的對象的建立模式 C 中的類的對象的建立模式分為兩張&#xff1a;靜態建立&#xff0c;動態建立 靜態建立&#xff1a;由編譯器為對象在棧空間…

C++ 模板詳解(一)

轉載&#xff1a;http://www.cnblogs.com/gw811/archive/2012/10/25/2738929.html C模板 模板是C支持參數化多態的工具&#xff0c;使用模板可以使用戶為類或者函數聲明一種一般模式&#xff0c;使得類中的某些數據成員或者成員函數的參數、返回值取得任意類型。 模板是一種對類…

劍指Offer09. 用兩個棧實現隊列

class CQueue { public:stack<int> stack1,stack2;CQueue() {//初始化棧while(!stack1.empty()){stack1.pop();}while(!stack2.empty()){stack2.pop();}}void appendTail(int value) {stack1.push(value);}int deleteHead() {if(stack2.empty()){while(!stack1.empty()){…

rk3588 之啟動

目錄 uboot版本配置修改編譯 linux版本配置修改編譯 啟動sd卡啟動制作spi 燒錄 參考 uboot 版本 v2024.01-rc2 https://github.com/u-boot/u-boot https://github.com/rockchip-linux/rkbin 配置修改 使用這兩個配置即可&#xff1a; orangepi-5-plus-rk3588_defconfig r…

C++引用詳解

轉載&#xff1a;http://www.cnblogs.com/gw811/archive/2012/10/20/2732687.html 引用的概念 引用&#xff1a;就是某一變量&#xff08;目標&#xff09;的一個別名&#xff0c;對引用的操作與對變量直接操作完全一樣。 引用的聲明方法&#xff1a;類型標識符 &引用名目標…

劍指Offer03.數組中重復的數字

找出數組中重復的數字。 在一個長度為 n 的數組 nums 里的所有數字都在 0&#xff5e;n-1 的范圍內。數組中某些數字是重復的&#xff0c;但不知道有幾個數字重復了&#xff0c;也不知道每個數字重復了幾次。請找出數組中任意一個重復的數字。 示例 1&#xff1a; 輸入&…