Linux下I/O多路轉(zhuǎn)接之select fd_set

fd_set

你終于還是來了坐求,能看到這個標(biāo)題進來的,我想晌梨,你一定是和我遇到了一樣的問題桥嗤,一樣的疑惑,接下來幾個小時仔蝌,我一定竭盡全力泛领,寫出我想說的,希望也正是你所需要的:

關(guān)于Linux下I/O多路轉(zhuǎn)接之select掌逛,我不想太多的解釋师逸,用較少的文章引出今天我要說的問題:fd_set...自我感覺,這個東西,是理解select的關(guān)鍵篓像。

一动知、關(guān)于select函數(shù):

以上只是截屏,以保證本人說的是真話员辩,下面解釋:

? ? ? ? 系統(tǒng)提供select函數(shù)來實現(xiàn)多路復(fù)用輸入/輸出模型盒粮。select系統(tǒng)調(diào)用是用來讓我們的程序監(jiān)視多個文件句柄的狀態(tài)變化的。

程序會停在select這里等待奠滑,直到被監(jiān)視的文件句柄有一個或 多個發(fā)生了狀態(tài)改變丹皱。關(guān)于文件句柄,其實就是一個整數(shù)宋税,我們最熟悉的句柄是0摊崭、1、2三 個杰赛,

0是標(biāo)準(zhǔn)輸入呢簸,1是標(biāo)準(zhǔn)輸出,2是標(biāo)準(zhǔn)錯誤輸出乏屯。0根时、1、2是整數(shù)表示的辰晕,對應(yīng)的FILE * 結(jié)構(gòu)的表示就是stdin蛤迎、stdout、stderr含友。

1.參數(shù)nfds是需要監(jiān)視的最大的文件描述符值+1替裆;

2.rdset,wrset,exset分別對應(yīng)于需要檢測的可讀文件描述符的集合,可寫文件描述符的集合及異常文件描述符的集合唱较。

3.struct timeval結(jié)構(gòu)用于描述一段時間長度扎唾,如果在這個時間內(nèi),需要監(jiān)視的描述符沒有事件發(fā)生則函數(shù)返回南缓,返回值為0胸遇。

下面的宏提供了處理這三種描述詞組的方式:

1. FD_CLR(inr fd,fd_set* set);用來清除描述詞組set中相關(guān)fd 的位汉形。

2. FD_ISSET(int fd,fd_set *set)后雷;用來測試描述詞組set中相關(guān)fd 的位是否為真 苞慢。

3.FD_SET(int fd,fd_set*set);用來設(shè)置描述詞組set中相關(guān)fd的位 。

4.FD_ZERO(fd_set *set)置森;用來清除描述詞組set的全部位 參數(shù)timeout為結(jié)構(gòu)timeval愕难,用來設(shè)置select()的等待時間丰捷,其結(jié)構(gòu)定義如下:

結(jié)構(gòu)體成員兩個篡石,第一個單位是秒,第二個單位是微妙 ,作用是時間為兩個之和罐呼;

更多關(guān)于select的應(yīng)用鞠柄,咱移駕看這位大神:http://blog.sina.com.cn/s/blog_5c8d13830100pwaf.html? ? ? 關(guān)于select的使用,理解了也就好弄了嫉柴,關(guān)于應(yīng)用厌杜,后附代碼:

下面才是我想說的東西:

二、fd_set:

1>>fd_set是什么:

? ? ? ? select()機制中提供一fd_set的數(shù)據(jù)結(jié)構(gòu)计螺,可以理解為一個集合夯尽,實際上是一個位圖,每一個特定位來標(biāo)志相應(yīng)大小文件描述符登馒,這個集合中存放的是文件描述符匙握,即就是文件句柄(不管是socket句柄,還是其他文件或命名管道或設(shè)備句柄)建立聯(lián)系谊娇,建立聯(lián)系的工作由程序員完成肺孤,當(dāng)調(diào)用select()時,由內(nèi)核根據(jù)IO狀態(tài)修改fd_set的內(nèi)容济欢,由此來通知執(zhí)行了select()的進程哪一socket或文件可讀。Unix下任何設(shè)備小渊、管道法褥、FIFO等都是文件形式,全部包括在內(nèi)酬屉,所以毫無疑問一個socket就是一個文件半等,socket句柄就是一個文件描述符。fd_set集合可以通過一些宏由人為來操作呐萨,程序員通過操作4類宏杀饵,來完成最fd_set的操作,在上文已經(jīng)提及谬擦。

2>>fe_set怎么表示:

其中readfds切距、writefds等都是fd_set類型,其中的每一位都表示一個fd,即文件描述符惨远。

3>>fd_set用法:

過去谜悟,一個fd_set通常只能包含<32的fd(文件描述字),因為fd_set其實只用了一個32位矢量來表示fd北秽;現(xiàn)在,UNIX系統(tǒng)通常會在頭文件中定義常量FD_SETSIZE葡幸,它是數(shù)據(jù)類型fd_set的描述字?jǐn)?shù)量,其值通常是1024贺氓,這樣就能表示<1024的fd蔚叨。根據(jù)fd_set的位矢量實現(xiàn),我們可以重新理解操作fd_set的四個宏:

fd_set set;

FD_ZERO(&set);? ? ? /*將set的所有位置0,如set在內(nèi)存中占8位則將set置為00000000*/

FD_SET(0, &set);? ? /* 將set的第0位置1蔑水,如set原來是00000000邢锯,則現(xiàn)在變?yōu)?0000000,這樣fd==1的文件描述字就被加進set中了 */

FD_CLR(4, &set);? ? /*將set的第4位置0肤粱,如set原來是10001000弹囚,則現(xiàn)在變?yōu)?0000000,這樣fd==4的文件描述字就被從set中清除了 */

FD_ISSET(5, &set);? /* 測試set的第5位是否為1领曼,如果set原來是10000100鸥鹉,則返回非零,表明fd==5的文件描述字在set中庶骄;否則返回0*/

我們在回到原函數(shù):select

int select(int nfds, fd_set *readset, fd_set *writeset毁渗,fd_set* exceptset, struct timeval *timeout);

功能:測試指定的fd可讀、可寫单刁、有異常條件待處理灸异。? ?

參數(shù):

1.nfds? ?

需要檢查的文件描述字個數(shù)(即檢查到fd_set的第幾位),數(shù)值應(yīng)該比三組fd_set中所含的最大fd值更大羔飞,一般設(shè)為三組fd_set中所含的最大fd值加1(如在上邊例子中readset,writeset,exceptset中所含最大的fd為5肺樟,則nfds=6,因為fd是從0開始的)逻淌。設(shè)這個值是為提高效率么伯,使函數(shù)不必檢查fd_set的所有1024位。

readset? :用來檢查可讀性的一組文件描述字卡儒。

writeset :用來檢查可寫性的一組文件描述字田柔。

exceptset :用來檢查是否有異常條件出現(xiàn)的文件描述字。(注:錯誤不包括在異常條件之內(nèi))

timeout:有三種可能:

1.? timeout=NULL(阻塞:直到有一個fd位被置為1函數(shù)才返回)

2.? timeout所指向的結(jié)構(gòu)設(shè)為非零時間(等待固定時間:有一個fd位被置為1或者時間耗盡骨望,函數(shù)均返回)

3.? timeout所指向的結(jié)構(gòu)硬爆,時間設(shè)為0(非阻塞:函數(shù)檢查完每個fd后立即返回)

返回值:

? ?

1.當(dāng)監(jiān)視的相應(yīng)的文件描述符集中滿足條件時,比如說讀文件描述符集中有數(shù)據(jù)到來時擎鸠,內(nèi)核(I/O)根據(jù)狀態(tài)修改文件描述符集缀磕,并返回一個大于0的數(shù)。

2.當(dāng)沒有滿足條件的文件描述符糠亩,且設(shè)置的timeval監(jiān)控時間超時時虐骑,select函數(shù)會返回一個為0的值。

3.當(dāng)select返回負值時赎线,發(fā)生錯誤廷没。

備注:

三組fd_set均將某些fd位置0,只有那些可讀垂寥,可寫以及有異常條件待處理的fd位仍然為1颠黎。

使用select函數(shù)的過程一般是:

先調(diào)用宏FD_ZERO將指定的fd_set清零另锋,然后調(diào)用宏FD_SET將需要測試的fd加入fd_set,接著調(diào)用函數(shù)select測試fd_set中的所有fd狭归,最后用宏FD_ISSET檢查某個fd在函數(shù)select調(diào)用后夭坪,相應(yīng)位是否仍然為1。

以下是一個測試單個文件描述字可讀性的例子:

? ?

基于select實現(xiàn)的網(wǎng)絡(luò)服務(wù)器和客戶端:

server.c

#include<stdio.h>?

#include<sys/types.h>?

#include<sys/socket.h>?

#include<unistd.h>?

#include<stdlib.h>?

#include<errno.h>?

#include<arpa/inet.h>?

#include<netinet/in.h>?

#include<string.h>?

#include<signal.h>?

#include<sys/wait.h>?

/*

*網(wǎng)絡(luò)服務(wù)器过椎,select參與調(diào)度

* */

//ERR_EXIT(M)是一個錯誤退出宏

#define ERR_EXIT(m) \?

? ? do { \?

? ? ? ? perror(m); \?

? ? ? ? exit(EXIT_FAILURE); \?

? ? } while (0)?

?

?

int main(void)?

{? ?

? ? signal(SIGPIPE, SIG_IGN);

? ? //1.創(chuàng)建套接字

? ? int listenfd;? ? ? ? ? ? ? ? //被動套接字(文件描述符)室梅,即只可以accept, 監(jiān)聽套接字?

? ? if ((listenfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)? ?

? ? ? ? ERR_EXIT("socket error"); //調(diào)用上邊的宏

?

? ? struct sockaddr_in servaddr;

? ? //memset(&servaddr, 0, sizeof(servaddr));?

? ? //三個結(jié)構(gòu)體成員

? ? //設(shè)置本地IP 和端口

? ? servaddr.sin_family = AF_INET;?

? ? servaddr.sin_port = htons(8080);?

? ? servaddr.sin_addr.s_addr = htonl(INADDR_ANY);?

? ? /* servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); */?

? ? /* inet_aton("127.0.0.1", &servaddr.sin_addr); */?

? ? ?

? ? //2.設(shè)置套接字屬性

? ? int on = 1;?

? ? if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)?

? ? ? ? ERR_EXIT("setsockopt error");?

? ?

? ? //3.綁定

? ? if (bind(listenfd, (struct sockaddr*)&servaddr,sizeof(servaddr)) < 0)?

? ? ? ? ERR_EXIT("bind error");?

?

? ? //4.監(jiān)聽

? ? if (listen(listenfd, SOMAXCONN) < 0) //listen應(yīng)在socket和bind之后,而在accept之前?

? ? ? ? ERR_EXIT("listen error");?

? ? ?

? ? struct sockaddr_in peeraddr; //傳出參數(shù)?

? ? socklen_t peerlen = sizeof(peeraddr); //傳入傳出參數(shù)疚宇,必須有初始值?

? ? ?

? ? int conn; // 已連接套接字(變?yōu)橹鲃犹捉幼滞鍪螅纯梢灾鲃觕onnect)?

? ? int i;?

? ? int client[FD_SETSIZE];?

? ? int maxi = 0; // client數(shù)組中最大不空閑位置的下標(biāo)?

? ? for (i = 0; i < FD_SETSIZE; i++)?

? ? ? ? client[i] = -1;?

?

? ? int nready;?

? ? int maxfd = listenfd;?

? ? fd_set rset;?

? ? fd_set allset;?

? ? FD_ZERO(&rset);?

? ? FD_ZERO(&allset);?

? ? FD_SET(listenfd, &allset);?

?

? ? while (1) {?

? ? ? ? rset = allset;?

? ? ? ? nready = select(maxfd + 1, &rset, NULL, NULL, NULL);?

? ? ? ? if (nready == -1) {?

? ? ? ? ? ? if (errno == EINTR)?

? ? ? ? ? ? ? ? continue;?

? ? ? ? ? ? ERR_EXIT("select error");?

? ? ? ? }?

?

? ? ? ? if (nready == 0)?

? ? ? ? ? ? continue;?

?

? ? ? ? if (FD_ISSET(listenfd, &rset)) {?

? ? ? ? ?

? ? ? ? ? ? conn = accept(listenfd, (struct sockaddr*)&peeraddr, &peerlen);? //accept不再阻塞?

? ? ? ? ? ? if (conn == -1)?

? ? ? ? ? ? ? ? ERR_EXIT("accept error");?

? ? ? ? ? ? ?

? ? ? ? ? ? for (i = 0; i < FD_SETSIZE; i++) {?

? ? ? ? ? ? ? ? if (client[i] < 0) {?

? ? ? ? ? ? ? ? ? ? client[i] = conn;?

? ? ? ? ? ? ? ? ? ? if (i > maxi)?

? ? ? ? ? ? ? ? ? ? ? ? maxi = i;?

? ? ? ? ? ? ? ? ? ? break;?

? ? ? ? ? ? ? ? }?

? ? ? ? ? ? }?

? ? ? ? ? ? ?

? ? ? ? ? ? if (i == FD_SETSIZE) {?

? ? ? ? ? ? ? ? fprintf(stderr, "too many clients\n");?

? ? ? ? ? ? ? ? exit(EXIT_FAILURE);?

? ? ? ? ? ? }?

?

? ? ? ? ? ? printf("recv connect ip=%s port=%d\n", inet_ntoa(peeraddr.sin_addr),?

? ? ? ? ? ? ? ? ntohs(peeraddr.sin_port));?

?

? ? ? ? ? ? FD_SET(conn, &allset);?

? ? ? ? ? ? if (conn > maxfd)?

? ? ? ? ? ? ? ? maxfd = conn;?

?

? ? ? ? ? ? if (--nready <= 0)?

? ? ? ? ? ? ? ? continue;?

? ? ? ? }?

?

? ? ? ? for (i = 0; i <= maxi; i++) {?

? ? ? ? ? ? conn = client[i];?

? ? ? ? ? ? if (conn == -1)?

? ? ? ? ? ? ? ? continue;?

?

? ? ? ? ? ? if (FD_ISSET(conn, &rset)) {?

? ? ? ? ? ? ? ? ?

? ? ? ? ? ? ? ? char recvbuf[1024] = {0};?

? ? ? ? ? ? ? ? int ret = read(conn, recvbuf, 1024);?

? ? ? ? ? ? ? ? if (ret == -1)?

? ? ? ? ? ? ? ? ? ? ERR_EXIT("readline error");?

? ? ? ? ? ? ? ? else if (ret? == 0) { //客戶端關(guān)閉?

? ? ? ? ? ? ? ? ? ? printf("client close \n");?

? ? ? ? ? ? ? ? ? ? FD_CLR(conn, &allset);?

? ? ? ? ? ? ? ? ? ? client[i] = -1;?

? ? ? ? ? ? ? ? ? ? close(conn);?

? ? ? ? ? ? ? ? }?

? ? ? ? ?

? ? ? ? ? ? ? ? fputs(recvbuf, stdout);?

? ? ? ? ? ? ? ? write(conn, recvbuf, strlen(recvbuf));?

? ? ? ? ? ? ? ? ?

? ? ? ? ? ? ? ? if (--nready <= 0)?

? ? ? ? ? ? ? ? ? ? break;?

? ? ? ? ? ? }?

? ? ? ? }?

? ? }? ? ? ?

? ? return 0;?

}

client.c

#include<stdio.h>?

#include<sys/types.h>?

#include<sys/socket.h>?

#include<unistd.h>?

#include<stdlib.h>?

#include<errno.h>?

#include<arpa/inet.h>?

#include<netinet/in.h>?

#include<string.h>?

?

?

#define ERR_EXIT(m) \?

? ? do { \?

? ? ? ? perror(m); \?

? ? ? ? exit(EXIT_FAILURE); \?

? ? } while (0)?

?

?

?

?

int main(void)?

{?

? ? int sock;?

? ? if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)?

? ? ? ? //? listenfd = socket(AF_INET, SOCK_STREAM, 0)?

? ? ? ? ERR_EXIT("socket error");?

?

?

? ? struct sockaddr_in servaddr;?

? ? memset(&servaddr, 0, sizeof(servaddr));?

? ? servaddr.sin_family = AF_INET;?

? ? servaddr.sin_port = htons(8080);?

? ? servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");?

? ? /* inet_aton("127.0.0.1", &servaddr.sin_addr); */?

?

? ? if (connect(sock, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)?

? ? ? ? ERR_EXIT("connect error");?

? ? struct sockaddr_in localaddr;?

? ? char cli_ip[20];?

? ? socklen_t local_len = sizeof(localaddr);?

? ? memset(&localaddr, 0, sizeof(localaddr));?

? ? if( getsockname(sock,(struct sockaddr *)&localaddr,&local_len) != 0 )?

? ? ? ? ERR_EXIT("getsockname error");?

? ? inet_ntop(AF_INET, &localaddr.sin_addr, cli_ip, sizeof(cli_ip));?

? ? printf("host %s:%d\n", cli_ip, ntohs(localaddr.sin_port));?

?

? ? fd_set rset;?

? ? FD_ZERO(&rset);?

? ? int nready;?

? ? int maxfd;?

? ? int fd_stdin = fileno(stdin); //?

? ? if (fd_stdin > sock)?

? ? ? ? maxfd = fd_stdin;?

? ? else?

? ? ? ? maxfd = sock;?

? ? char sendbuf[1024] = {0};?

? ? char recvbuf[1024] = {0};?

? ? ?

? ? while (1)?

? ? {?

?

? ? ? ? FD_SET(fd_stdin, &rset);?

? ? ? ? FD_SET(sock, &rset);?

? ? ? ? nready = select(maxfd + 1, &rset, NULL, NULL, NULL); //select返回表示檢測到可讀事件?

? ? ? ? if (nready == -1)?

? ? ? ? ? ? ERR_EXIT("select error");?

?

? ? ? ? if (nready == 0)?

? ? ? ? ? ? continue;?

?

? ? ? ? if (FD_ISSET(sock, &rset))?

? ? ? ? {?

?

? ? ? ? ? ? int ret = read(sock, recvbuf, sizeof(recvbuf));?

? ? ? ? ? ? if (ret == -1)?

? ? ? ? ? ? ? ? ERR_EXIT("read error");?

? ? ? ? ? ? else if (ret? == 0)? //服務(wù)器關(guān)閉?

? ? ? ? ? ? {?

? ? ? ? ? ? ? ? printf("server close\n");?

? ? ? ? ? ? ? ? break;?

? ? ? ? ? ? }?

?

? ? ? ? ? ? fputs(recvbuf, stdout);?

? ? ? ? ? ? memset(recvbuf, 0, sizeof(recvbuf));?

? ? ? ? }?

?

? ? ? ? if (FD_ISSET(fd_stdin, &rset))?

? ? ? ? {?

?

? ? ? ? ? ? if (fgets(sendbuf, sizeof(sendbuf), stdin) == NULL)?

? ? ? ? ? ? ? ? break;?

?

? ? ? ? ? ? write(sock, sendbuf, strlen(sendbuf));?

? ? ? ? ? ? memset(sendbuf, 0, sizeof(sendbuf));?

? ? ? ? }?

? ? }?

?

? ? close(sock);?

? ? return 0;?

}?

賜教!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末敷待,一起剝皮案震驚了整個濱河市间涵,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌榜揖,老刑警劉巖勾哩,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異举哟,居然都是意外死亡思劳,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門妨猩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來敢艰,“玉大人,你說我怎么就攤上這事册赛。” “怎么了震嫉?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵森瘪,是天一觀的道長。 經(jīng)常有香客問我票堵,道長扼睬,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任悴势,我火速辦了婚禮窗宇,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘特纤。我一直安慰自己军俊,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布捧存。 她就那樣靜靜地躺著粪躬,像睡著了一般担败。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上镰官,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天提前,我揣著相機與錄音,去河邊找鬼泳唠。 笑死狈网,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的笨腥。 我是一名探鬼主播拓哺,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼扇雕!你這毒婦竟也來了拓售?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤镶奉,失蹤者是張志新(化名)和其女友劉穎础淤,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體哨苛,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡鸽凶,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了建峭。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片玻侥。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖亿蒸,靈堂內(nèi)的尸體忽然破棺而出凑兰,到底是詐尸還是另有隱情,我是刑警寧澤边锁,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布姑食,位于F島的核電站,受9級特大地震影響茅坛,放射性物質(zhì)發(fā)生泄漏音半。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一贡蓖、第九天 我趴在偏房一處隱蔽的房頂上張望曹鸠。 院中可真熱鬧,春花似錦斥铺、人聲如沸彻桃。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽叛薯。三九已至浑吟,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間耗溜,已是汗流浹背组力。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留抖拴,地道東北人燎字。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像阿宅,于是被迫代替她去往敵國和親候衍。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,976評論 2 355

推薦閱讀更多精彩內(nèi)容