select函數(shù)實(shí)現(xiàn)高性能IO多路訪問的關(guān)鍵示例深入解析
一.select函數(shù)原型
select
函數(shù)的原型如下:
#include <sys/select.h> int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
該函數(shù)接受五個(gè)參數(shù):
nfds
:需要監(jiān)聽的文件描述符的最大編號(hào)加 1。在監(jiān)聽范圍內(nèi)的文件描述符包括從 0 到nfds-1
。這個(gè)參數(shù)的值應(yīng)該是監(jiān)聽的所有文件描述符中的最大值加 1。readfds
:一個(gè)指向fd_set
結(jié)構(gòu)體的指針,用于指定需要監(jiān)聽可讀事件的文件描述符。writefds
:一個(gè)指向fd_set
結(jié)構(gòu)體的指針,用于指定需要監(jiān)聽可寫事件的文件描述符。exceptfds
:一個(gè)指向fd_set
結(jié)構(gòu)體的指針,用于指定需要監(jiān)聽異常事件的文件描述符。timeout
:一個(gè)指向struct timeval
結(jié)構(gòu)體的指針,用于設(shè)置select
的超時(shí)時(shí)間。如果設(shè)置為NULL
,則select
將會(huì)阻塞直到有事件發(fā)生。如果設(shè)置為一個(gè)指向struct timeval
結(jié)構(gòu)體的指針,則select
會(huì)等待指定的時(shí)間,超時(shí)后返回。
fd_set
是一個(gè)文件描述符集合數(shù)據(jù)類型,用于存儲(chǔ)需要監(jiān)聽事件的文件描述符。
struct timeval
是一個(gè)時(shí)間結(jié)構(gòu)體,用于指定超時(shí)時(shí)間。它包含了兩個(gè)成員:tv_sec
表示秒數(shù),tv_usec
表示微秒數(shù)。
select
函數(shù)返回就緒文件描述符的總數(shù),如果出錯(cuò)則返回 -1。通過檢查 readfds
、writefds
和 exceptfds
來確定哪些文件描述符已經(jīng)就緒。
注意:在使用 select
函數(shù)之前,需要使用 FD_ZERO
和 FD_SET
宏來初始化和設(shè)置文件描述符集合。
二.select相關(guān)宏函數(shù)
在使用 select
函數(shù)進(jìn)行文件描述符集合操作時(shí),常用的宏函數(shù)有以下幾個(gè):
FD_ZERO
:用于將文件描述符集合清空,將集合中所有位都設(shè)置為 0。
void FD_ZERO(fd_set *set);
FD_SET
:用于將指定的文件描述符加入到集合中。
void FD_SET(int fd, fd_set *set);
FD_CLR
:用于將指定的文件描述符從集合中移除。
void FD_CLR(int fd, fd_set *set);
FD_ISSET
:用于檢查指定的文件描述符是否在集合中已經(jīng)設(shè)置。
int FD_ISSET(int fd, fd_set *set);
這些宏函數(shù)可以用于操作文件描述符集合,例如初始化集合、添加文件描述符、移除文件描述符以及檢查集合中是否已經(jīng)設(shè)置了特定的文件描述符。
需要注意的是,fd_set
是一個(gè)位圖類型的結(jié)構(gòu),用于存儲(chǔ)文件描述符集合的狀態(tài)。宏函數(shù)配合使用可以方便地操作這個(gè)位圖,以設(shè)置和判斷文件描述符的狀態(tài)。
另外,需要包含 <sys/select.h>
頭文件來使用這些宏函數(shù)。
三.select特點(diǎn)
select
函數(shù)是一種多路 I/O 復(fù)用機(jī)制,具有以下特點(diǎn):
多路復(fù)用:
select
允許在一個(gè)事件循環(huán)中同時(shí)監(jiān)聽多個(gè)文件描述符(比如套接字、管道等),并在這些文件描述符就緒時(shí)通知應(yīng)用程序。平臺(tái)兼容性:
select
是一種標(biāo)準(zhǔn)的系統(tǒng)調(diào)用,幾乎在所有的 POSIX 操作系統(tǒng)上都有支持,因此具有良好的跨平臺(tái)性。阻塞和非阻塞模式:
select
支持兩種調(diào)用方式。在阻塞模式下,調(diào)用select
會(huì)一直等待,直到至少一個(gè)文件描述符變得可讀或可寫。而在非阻塞模式下,調(diào)用select
會(huì)立即返回,不管是否有文件描述符就緒。超時(shí)功能:
select
允許指定一個(gè)超時(shí)時(shí)間,在等待文件描述符就緒時(shí)設(shè)定一個(gè)最長(zhǎng)等待時(shí)間。一旦超過設(shè)定的超時(shí)時(shí)間,select
將返回,這樣可以避免無限期地阻塞程序。可讀性檢查:
select
對(duì)于讀操作可用于檢查一個(gè)文件描述符是否有數(shù)據(jù)可以讀取。可寫性檢查:
select
對(duì)于寫操作可用于檢查一個(gè)文件描述符是否可以寫入數(shù)據(jù)而不會(huì)造成阻塞。異常條件檢查:
select
還可以檢查是否有異常條件發(fā)生,例如套接字的帶外數(shù)據(jù)。
然而,值得注意的是,select
在高并發(fā)情況下可能性能較差,因?yàn)樵诿恳淮握{(diào)用 select
時(shí),都需要遍歷所有的文件描述符。在這種情況下,使用更高效的 I/O 復(fù)用機(jī)制,如 poll
或 epoll
,可能是更好的選擇。
四.select機(jī)制
select
是一種多路 I/O 復(fù)用機(jī)制,用于同時(shí)監(jiān)聽多個(gè)文件描述符的可讀、可寫和異常事件。它的機(jī)制如下:
調(diào)用
select
函數(shù)時(shí),將需要監(jiān)聽的文件描述符通過參數(shù)傳遞給select
函數(shù),同時(shí)設(shè)置超時(shí)時(shí)間(可選)。select
函數(shù)會(huì)阻塞程序運(yùn)行,直到以下三種情況之一發(fā)生:- 有一個(gè)或多個(gè)文件描述符可以讀取(被置于可讀狀態(tài))。
- 有一個(gè)或多個(gè)文件描述符可以寫入(被置于可寫狀態(tài))。
- 有一個(gè)或多個(gè)文件描述符發(fā)生了異常。
當(dāng)有一個(gè)或多個(gè)文件描述符就緒時(shí),
select
函數(shù)返回,程序可以繼續(xù)往下執(zhí)行。此時(shí),通過檢查文件描述符集合的狀態(tài)(readfds
、writefds
和exceptfds
),可以確定哪些文件描述符已經(jīng)就緒。根據(jù)文件描述符的狀態(tài),進(jìn)行相應(yīng)的讀取、寫入或處理異常的操作。
重復(fù)以上步驟,循環(huán)使用
select
函數(shù)來監(jiān)聽文件描述符,以實(shí)現(xiàn)事件驅(qū)動(dòng)的編程模式。
select
的特點(diǎn)是可以同時(shí)監(jiān)聽多個(gè)文件描述符,在有事件發(fā)生時(shí)返回,并指示哪些文件描述符已經(jīng)就緒,使得程序能夠及時(shí)處理相關(guān)操作。它適用于需要同時(shí)處理多個(gè) I/O 事件的情況,并且具有良好的跨平臺(tái)性。然而,在高并發(fā)情況下性能可能較差,因?yàn)槊看握{(diào)用 select
都需要遍歷所有的文件描述符。在這種情況下,可以考慮使用其他更高效的 I/O 復(fù)用機(jī)制,如 poll
或 epoll
。
五.select編程步驟
使用 select
函數(shù)進(jìn)行 I/O 復(fù)用時(shí),通常包含以下步驟:
準(zhǔn)備文件描述符集合:創(chuàng)建并初始化需要監(jiān)聽的文件描述符集合,例如使用
fd_set
類型的變量,并使用FD_ZERO
宏將其清空,然后使用FD_SET
宏將需要監(jiān)聽的文件描述符添加到集合中。設(shè)置超時(shí)時(shí)間:可選擇是否設(shè)置超時(shí)時(shí)間。如果需要設(shè)置超時(shí)時(shí)間,在
struct timeval
結(jié)構(gòu)體中設(shè)置合適的值,然后將該結(jié)構(gòu)體的指針作為select
函數(shù)的最后一個(gè)參數(shù)傳遞給函數(shù)。調(diào)用
select
函數(shù):將文件描述符集合作為參數(shù)傳遞給select
函數(shù),并傳遞其他必要的參數(shù),如最大文件描述符編號(hào)加 1(nfds
)以及超時(shí)時(shí)間(可選)。調(diào)用select
函數(shù)后,程序?qū)⒃谠摵瘮?shù)處阻塞等待。檢查就緒文件描述符:當(dāng)
select
函數(shù)返回時(shí),需要檢查文件描述符集合的狀態(tài),以確定哪些文件描述符已經(jīng)就緒??梢允褂?nbsp;FD_ISSET
宏來檢查特定的文件描述符是否已經(jīng)設(shè)置。處理就緒文件描述符:根據(jù)文件描述符的狀態(tài)(可讀、可寫或異常),進(jìn)行相應(yīng)的處理操作。例如,如果一個(gè)文件描述符可讀,可以使用
read
函數(shù)讀取數(shù)據(jù);如果一個(gè)文件描述符可寫,可以使用write
函數(shù)寫入數(shù)據(jù);如果一個(gè)文件描述符發(fā)生異常,可以進(jìn)行相應(yīng)的錯(cuò)誤處理。重復(fù)步驟 3-5:根據(jù)需要,可以使用循環(huán)來多次調(diào)用
select
函數(shù)以處理更多的事件。這樣可以實(shí)現(xiàn)事件驅(qū)動(dòng)的編程模式,不斷監(jiān)聽和處理多個(gè)文件描述符的事件。
需要注意的是,在每次循環(huán)中調(diào)用 select
函數(shù)之前需要重新設(shè)置文件描述符集合和超時(shí)時(shí)間,以反映當(dāng)前需要監(jiān)聽的文件描述符集合和超時(shí)時(shí)間的變化。
六.自定義數(shù)組提高效率
要提高 select
函數(shù)的效率,可以考慮使用自定義的數(shù)組來替代文件描述符集合,以便更有效地管理和操作需要監(jiān)聽的文件描述符。
以下是使用自定義數(shù)組提高 select
函數(shù)效率的一般步驟:
- 創(chuàng)建自定義數(shù)組:根據(jù)需要監(jiān)聽的文件描述符數(shù)量,創(chuàng)建一個(gè)數(shù)組來存儲(chǔ)這些文件描述符的信息??梢允褂谜麛?shù)型數(shù)組、結(jié)構(gòu)體數(shù)組等來表示每個(gè)文件描述符的狀態(tài)和其他相關(guān)信息。
- 初始化自定義數(shù)組:在程序開始時(shí),初始化自定義數(shù)組,設(shè)置每個(gè)文件描述符的初始狀態(tài)和其他必要的信息。
- 更新自定義數(shù)組:在程序中需要做出更改時(shí),通過修改自定義數(shù)組中的相應(yīng)元素來反映文件描述符的狀態(tài)變化。
- 使用自定義數(shù)組代替
select
函數(shù)的文件描述符集合:在調(diào)用select
函數(shù)之前,將自定義數(shù)組轉(zhuǎn)換為對(duì)應(yīng)的文件描述符集合,例如使用FD_ZERO
和FD_SET
宏來設(shè)置相應(yīng)的文件描述符集合。 - 調(diào)用
select
函數(shù):使用轉(zhuǎn)換后的文件描述符集合作為參數(shù)調(diào)用select
函數(shù),并傳遞其他必要的參數(shù),如最大文件描述符編號(hào)加 1(nfds
)以及超時(shí)時(shí)間(可選)。 - 檢查就緒文件描述符:當(dāng)
select
函數(shù)返回時(shí),檢查文件描述符集合的狀態(tài)以確定哪些文件描述符已經(jīng)就緒??梢酝ㄟ^遍歷自定義數(shù)組,根據(jù)其中的狀態(tài)信息來確定哪些文件描述符已經(jīng)就緒。 - 處理就緒文件描述符:根據(jù)文件描述符的狀態(tài),進(jìn)行相應(yīng)的處理操作,如讀取、寫入或處理異常等。
- 重復(fù)步驟 3-7:根據(jù)需要,可以使用循環(huán)來多次調(diào)用
select
函數(shù)以處理更多的事件。
使用自定義數(shù)組可以更靈活地管理文件描述符,并且可以根據(jù)程序需求進(jìn)行優(yōu)化,以提高效率。但請(qǐng)注意,當(dāng)需要監(jiān)聽的文件描述符數(shù)量過大時(shí),自定義數(shù)組的使用可能會(huì)導(dǎo)致額外的內(nèi)存開銷。因此,在性能和內(nèi)存消耗之間需要進(jìn)行權(quán)衡。
七.編程實(shí)戰(zhàn)
練習(xí):
利用select實(shí)現(xiàn)一個(gè)高并發(fā)服務(wù)器,當(dāng)服務(wù)連接成功后,客戶端發(fā)送小寫字母的字符串,服務(wù)器端發(fā)送其大寫形式。要求自定義一個(gè)數(shù)組,用于提高程序效率。
server.c
#include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netinet/ip.h> #include <arpa/inet.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> #include <sys/wait.h> #include <sys/select.h> #include <sys/time.h> #include <unistd.h> int main(int argc, char const *argv[]) { //1.socket創(chuàng)建套接字 int socked = socket(AF_INET, SOCK_STREAM, 0); if (socked < 0) { perror("socket is err"); return -1; } //2.bind綁定服務(wù)器ip地址和端口號(hào) struct sockaddr_in saddr, caddr; saddr.sin_family = AF_INET; saddr.sin_port = htons(atoi(argv[1])); saddr.sin_addr.s_addr = inet_addr("0.0.0.0"); int len = sizeof(caddr); int ret = bind(socked, (struct sockaddr *)(&saddr), sizeof(saddr)); if (ret < 0) { perror("bind is err"); return -1; } //3.listen設(shè)置同時(shí)最大鏈接數(shù) ret = listen(socked, 5); if (ret < 0) { perror("listen is err"); return -1; } //4.select前置工作 fd_set readfds, tempfds; //創(chuàng)建表 FD_ZERO(&readfds); //清空表 FD_SET(socked, &readfds); //添加文件描述符 FD_SET(0, &readfds); int maxfd = socked, size = 0, maxi = -1, client[1024] = {0}; char buf[1024] = {0}; //初始化數(shù)組為-1 for (int i = 0; i < 1024; ++i) client[i] = -1; while (1) { tempfds = readfds; int ret = select(maxfd + 1, &tempfds, NULL, NULL, NULL); if (ret < 0) { perror("select is err"); break; } if (FD_ISSET(socked, &tempfds)) { int fd = accept(socked, (struct sockaddr *)(&caddr), &len); printf("port:%d ip: %s\n", ntohs(caddr.sin_port), inet_ntoa(caddr.sin_addr)); //文件描述符加入數(shù)組中 int i; for (i = 0; i < 1024; ++i) { if (client[i] < 0) { client[i] = fd; break; } } if (i == 1024) { printf("too many clients\n"); exit(-1); } if (i > maxi) maxi = i; FD_SET(fd, &readfds); if (maxfd < fd) maxfd = fd; if (--ret == 0) continue; } for (int i = 0; i <= maxi; ++i) { if (client[i] < 0) continue; if (FD_ISSET(client[i], &tempfds)) { int flage = recv(client[i], buf, sizeof(buf), 0); if (flage < 0) { perror("recv is err"); } else if (flage == 0) { printf("ip:%s is close\n", inet_ntoa(caddr.sin_addr)); close(i); FD_CLR(i, &readfds); client[i] = -1; } else { size = strlen(buf); for (int i = 0; i < size; ++i) { if (buf[i] >= 'a' && buf[i] <= 'z') buf[i] = buf[i] + ('A' - 'a'); else buf[i] = buf[i] + ('a' - 'A'); } printf("%s\n", buf); send(client[i], buf, sizeof(buf), 0); } if (--ret == 0) break; } } } close(socked); return 0; }
client.c
#include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netinet/ip.h> #include <arpa/inet.h> #include <string.h> #include <stdlib.h> #include <unistd.h> int main(int argc, char const *argv[]) { //1.socket建立文件描述符 int fd = socket(AF_INET, SOCK_STREAM, 0); if (fd < 0) { perror("socket is err"); } //2.connect連接服務(wù)器 struct sockaddr_in saddr; saddr.sin_family = AF_INET; saddr.sin_port = htons(atoi(argv[2])); saddr.sin_addr.s_addr = inet_addr(argv[1]); int flage = connect(fd, (struct sockaddr *)(&saddr), sizeof(saddr)); if (flage < 0) { perror("connect is err"); } //3.服務(wù)器端不斷發(fā)送數(shù)據(jù),接受服務(wù)器轉(zhuǎn)化后的數(shù)據(jù) char buf[1024] = {0}; while (1) { //memset(buf,0,sizeof(buf)); fgets(buf, sizeof(buf), stdin); if (strncmp(buf,"quit#",5)==0) { break; } if (buf[strlen(buf) - 1] == '\n') buf[strlen(buf) - 1] = '\0'; send(fd, buf, sizeof(buf), 0); flage = recv(fd, buf, sizeof(buf), 0); if (flage < 0) { perror("recv is err"); } else { fprintf(stdout, "%s\n", buf); } } close(fd); return 0; }
以上就是select函數(shù)實(shí)現(xiàn)高性能IO多路訪問的關(guān)鍵示例深入解析的詳細(xì)內(nèi)容,更多關(guān)于select函數(shù)IO多路訪問的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C語言植物大戰(zhàn)數(shù)據(jù)結(jié)構(gòu)快速排序圖文示例
這篇文章主要為大家介紹了C語言植物大戰(zhàn)數(shù)據(jù)結(jié)構(gòu)快速排序圖文示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05C語言變長(zhǎng)數(shù)組 struct中char data[0]的用法詳解
下面小編就為大家?guī)硪黄狢語言變長(zhǎng)數(shù)組 struct中char data[0]的用法詳解。小編覺得挺不錯(cuò)的現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-01-01C++中成員函數(shù)和友元函數(shù)的使用及區(qū)別詳解
大家好,本篇文章主要講的是C++中成員函數(shù)和友元函數(shù)的使用及區(qū)別詳解,感興趣的同學(xué)趕快來看一看吧,對(duì)你有幫助的話記得收藏一下2022-01-01C++中std::stringstream多類型數(shù)據(jù)拼接和提取用法小結(jié)
本文主要介紹了C++中std::stringstream多類型數(shù)據(jù)拼接和提取用法小結(jié),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-09-09MFC實(shí)現(xiàn)對(duì)話框編輯控件上拖拽文件
這篇文章主要為大家詳細(xì)介紹了MFC實(shí)現(xiàn)對(duì)話框編輯控件上拖拽文件,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-06-06