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;?
}?
賜教!