fd_set
html
你終於仍是來了,能看到這個標題進來的,我想,你必定是和我遇到了同樣的問題,同樣的疑惑,接下來幾個小時,我必定不遺餘力,寫出我想說的,但願也正是你所須要的:程序員
關於Linux下I/O多路轉接之select,我不想太多的解釋,用較少的文章引出今天我要說的問題:fd_set...自我感受,這個東西,是理解select的關鍵。數組
1、關於select函數:服務器
以上只是截屏,以保證本人說的是真話,下面解釋:網絡
系統提供select函數來實現多路複用輸入/輸出模型。select系統調用是用來讓咱們的程序監視多個文件句柄的狀態變化的。數據結構
程序會停在select這裏等待,直到被監視的文件句柄有一個或 多個發生了狀態改變。關於文件句柄,其實就是一個整數,咱們最熟悉的句柄是0、一、2三 個,socket
0是標準輸入,1是標準輸出,2是標準錯誤輸出。0、一、2是整數表示的,對應的FILE * 結構的表示就是stdin、stdout、stderr。 函數
1.參數nfds是須要監視的最大的文件描述符值+1; 測試
2.rdset,wrset,exset分別對應於須要檢測的可讀文件描述符的集合,可寫文件描述符的集合及異常文件描述符的集合。 spa
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的使用,理解了也就好弄了,關於應用,後附代碼:
下面纔是我想說的東西:
2、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; }賜教!