Linux下I/O多路轉接之select --fd_set

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。

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

     

基於select實現的網絡服務器和客戶端:

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;  
}  
賜教!
相關文章
相關標籤/搜索