Linux下I/O多路转接之select --fd_set
fd_set
你終于還是來了,能看到這個標題進來的,我想,你一定是和我遇到了一樣的問題,一樣的疑惑,接下來幾個小時,我一定竭盡全力,寫出我想說的,希望也正是你所需要的:
關于Linux下I/O多路轉接之select,我不想太多的解釋,用較少的文章引出今天我要說的問題:fd_set...自我感覺,這個東西,是理解select的關鍵。
一、關于select函數:
以上只是截屏,以保證本人說的是真話,下面解釋:
? ? ? ? 系統提供select函數來實現多路復用輸入/輸出模型。select系統調用是用來讓我們的程序監視多個文件句柄的狀態變化的。
程序會停在select這里等待,直到被監視的文件句柄有一個或 多個發生了狀態改變。關于文件句柄,其實就是一個整數,我們最熟悉的句柄是0、1、2三 個,
0是標準輸入,1是標準輸出,2是標準錯誤輸出。0、1、2是整數表示的,對應的FILE * 結構的表示就是stdin、stdout、stderr。?
1.參數nfds是需要監視的最大的文件描述符值+1;?
2.rdset,wrset,exset分別對應于需要檢測的可讀文件描述符的集合,可寫文件描述符的集合及異常文件描述符的集合。?
3.struct timeval結構用于描述一段時間長度,如果在這個時間內,需要監視的描述符沒有事件發生則函數返回,返回值為0。?
下面的宏提供了處理這三種描述詞組的方式:
1. FD_CLR(inr fd,fd_set* set);用來清除描述詞組set中相關fd 的位。
2. FD_ISSET(int fd,fd_set *set);用來測試描述詞組set中相關fd 的位是否為真 。
3.FD_SET(int fd,fd_set*set);用來設置描述詞組set中相關fd的位 。
4.FD_ZERO(fd_set *set);用來清除描述詞組set的全部位 參數timeout為結構timeval,用來設置select()的等待時間,其結構定義如下:
結構體成員兩個,第一個單位是秒,第二個單位是微妙 ,作用是時間為兩個之和;?
更多關于select的應用,咱移駕看這位大神:http://blog.sina.com.cn/s/blog_5c8d13830100pwaf.html? ? ? 關于select的使用,理解了也就好弄了,關于應用,后附代碼:
下面才是我想說的東西:
二、fd_set:
1>>fd_set是什么:
? ? ? ? ?select()機制中提供一fd_set的數據結構,可以理解為一個集合,實際上是一個位圖,每一個特定位來標志相應大小文件描述符,這個集合中存放的是文件描述符,即就是文件句柄(不管是socket句柄,還是其他文件或命名管道或設備句柄)建立聯系,建立聯系的工作由程序員完成,當調用select()時,由內核根據IO狀態修改fd_set的內容,由此來通知執行了select()的進程哪一socket或文件可讀。Unix下任何設備、管道、FIFO等都是文件形式,全部包括在內,所以毫無疑問一個socket就是一個文件,socket句柄就是一個文件描述符。fd_set集合可以通過一些宏由人為來操作,程序員通過操作4類宏,來完成最fd_set的操作,在上文已經提及。
2>>fe_set怎么表示:
其中readfds、writefds等都是fd_set類型,其中的每一位都表示一個fd,即文件描述符。
3>>fd_set用法:
過去,一個fd_set通常只能包含<32的fd(文件描述字),因為fd_set其實只用了一個32位矢量來表示fd;現在,UNIX系統通常會在頭文件中定義常量FD_SETSIZE,它是數據類型fd_set的描述字數量,其值通常是1024,這樣就能表示<1024的fd。根據fd_set的位矢量實現,我們可以重新理解操作fd_set的四個宏:?
?fd_set set;
FD_ZERO(&set);????? /*將set的所有位置0,如set在內存中占8位則將set置為00000000*/
FD_SET(0, &set);??? /* 將set的第0位置1,如set原來是00000000,則現在變為10000000,這樣fd==1的文件描述字就被加進set中了 */
FD_CLR(4, &set);??? /*將set的第4位置0,如set原來是10001000,則現在變為10000000,這樣fd==4的文件描述字就被從set中清除了 */?
FD_ISSET(5, &set);? /* 測試set的第5位是否為1,如果set原來是10000100,則返回非零,表明fd==5的文件描述字在set中;否則返回0*/
我們在回到原函數:select
?int select(int nfds, fd_set *readset, fd_set *writeset,fd_set* exceptset, struct timeval *timeout);?
?功能:測試指定的fd可讀、可寫、有異常條件待處理。 ? ??
參數:
1.nfds ? ?
需要檢查的文件描述字個數(即檢查到fd_set的第幾位),數值應該比三組fd_set中所含的最大fd值更大,一般設為三組fd_set中所含的最大fd值加1(如在上邊例子中readset,writeset,exceptset中所含最大的fd為5,則nfds=6,因為fd是從0開始的)。設這個值是為提高效率,使函數不必檢查fd_set的所有1024位。
readset ?:用來檢查可讀性的一組文件描述字。
writeset :用來檢查可寫性的一組文件描述字。
exceptset :用來檢查是否有異常條件出現的文件描述字。(注:錯誤不包括在異常條件之內)
timeout:有三種可能:
1. ?timeout=NULL(阻塞:直到有一個fd位被置為1函數才返回)
2. ?timeout所指向的結構設為非零時間(等待固定時間:有一個fd位被置為1或者時間耗盡,函數均返回)
3. ?timeout所指向的結構,時間設為0(非阻塞:函數檢查完每個fd后立即返回)?
返回值:?
? ? ?
1.當監視的相應的文件描述符集中滿足條件時,比如說讀文件描述符集中有數據到來時,內核(I/O)根據狀態修改文件描述符集,并返回一個大于0的數。
2.當沒有滿足條件的文件描述符,且設置的timeval監控時間超時時,select函數會返回一個為0的值。
3.當select返回負值時,發生錯誤。
備注:
三組fd_set均將某些fd位置0,只有那些可讀,可寫以及有異常條件待處理的fd位仍然為1。
使用select函數的過程一般是:
先調用宏FD_ZERO將指定的fd_set清零,然后調用宏FD_SET將需要測試的fd加入fd_set,接著調用函數select測試fd_set中的所有fd,最后用宏FD_ISSET檢查某個fd在函數select調用后,相應位是否仍然為1。
以下是一個測試單個文件描述字可讀性的例子:
? ? ?
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> /**網絡服務器,select參與調度* *///ERR_EXIT(M)是一個錯誤退出宏 #define ERR_EXIT(m) \ do { \ perror(m); \ exit(EXIT_FAILURE); \ } while (0) int main(void) { signal(SIGPIPE, SIG_IGN);//1.創建套接字int listenfd; //被動套接字(文件描述符),即只可以accept, 監聽套接字 if ((listenfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) ERR_EXIT("socket error"); //調用上邊的宏 struct sockaddr_in servaddr;//memset(&servaddr, 0, sizeof(servaddr)); //三個結構體成員//設置本地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.設置套接字屬性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.監聽if (listen(listenfd, SOMAXCONN) < 0) //listen應在socket和bind之后,而在accept之前 ERR_EXIT("listen error"); struct sockaddr_in peeraddr; //傳出參數 socklen_t peerlen = sizeof(peeraddr); //傳入傳出參數,必須有初始值 int conn; // 已連接套接字(變為主動套接字,即可以主動connect) int i; int client[FD_SETSIZE]; int maxi = 0; // client數組中最大不空閑位置的下標 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) { //客戶端關閉 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) //服務器關閉 { 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; } 賜教!
轉載于:https://www.cnblogs.com/li-ning/p/9489947.html
總結
以上是生活随笔為你收集整理的Linux下I/O多路转接之select --fd_set的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Cramfs、JFFS2、YAFFS2的
- 下一篇: 正则表达式高级用法【原】