I/O多路複用模型之select(一)

原理:html

select函數會等待,直到描述符句柄中有可用資源(可讀、可寫、異常)時返回,返回值是可用資源(可讀/可寫/異常等)描述符的個數(>0),0表明超時,-1表明錯誤。具體到內核大體是:當應用程序調用select() 函數, 內核就會相應調用 poll_wait(), 把當前進程添加到相應設備的等待隊列上,而後將該應用程序進程設置爲睡眠狀態。直到該設備上的數據能夠獲取,而後調用wake_up()喚醒該應用程序進程。select每次輪訓都會遍歷全部描述符句柄。java


函數接口:linux

 int select(int max_fd,fd_set * readfds,fd_set * writefds,fd_set * exceptfds,struct timeval * timeout); 數組

 max_fd   :fd+1服務器

 readfds  :可讀描述符句柄數據結構

 writefds :可寫描述符句柄socket

 exceptfds:異常描述符句柄函數

 timeout  :超時時間 ui

 select函數的參數將告訴內核:spa

(1)咱們所關心的對應描述符句柄

(2)對於每一個描述符咱們所關心的條件,是否可讀,是否可寫或是否異常

(3)但願等待多長時間,struct timeval * timeout

struct timeval{      

        long tv_sec;   /*秒 */

        long tv_usec;  /*微秒 */   

    }

timeout == NULL  等待無限長的時間;

timeout->tv_sec == 0 &&timeout->tv_usec == 0不等待,直接返回;

timeout->tv_sec !=0 ||timeout->tv_usec!= 0 等待指定的時間,超時後返回0,表示在必定時間內沒有可用資源的描述符。

一個描述符句柄保存在fd_set類型中,fd_set類型變量每一位表明了一個描述符。咱們也能夠認爲它只是一個由不少二進制位構成的數組。

fd_set類型變量是一下幾個宏來控制它:

#include <sys/select.h>   

int FD_ZERO(int fd, fd_set *fdset);   --將一個fd_set類型變量全部位設置爲0

int FD_CLR(int fd, fd_set *fdset);    --將fd_set類型變量中對應fd刪除

int FD_SET(int fd, fd_set *fd_set);   --將fd添加到fd_set類型變量中,進而將fd_set類型變量的對應的位置位

int FD_ISSET(int fd, fd_set *fdset);  --fd是否在fd_set類型變量中,對應爲1說明資源可用


select函數執行結果:執行成功則返回文件描述詞狀態已改變的個數,若是返回0表明在描述詞狀態改變前已超過timeout時間,沒有返回;當有錯誤發生時則返回-1,錯誤緣由存於errno,此時參數readfds,writefds,exceptfds和timeout的值變成不可預測。錯誤

值可能爲:

EBADF 文件描述詞爲無效的或該文件已關閉

EINTR 此調用被信號所中斷

EINVAL 參數n 爲負值。

ENOMEM 核心內存不足


從http://blog.csdn.net/lingfengtengfei/article/details/12392449中摘取「理解select模型:」

理解select模型:

理解select模型的關鍵在於理解fd_set,爲說明方便,取fd_set長度爲1字節,fd_set中的每一bit能夠對應一個文件描述符fd。則1字節長的fd_set最大能夠對應8個fd。

(1)執行fd_set set;FD_ZERO(&set);則set用位表示是0000,0000。

(2)若fd=5,執行FD_SET(fd,&set);後set變爲0001,0000(第5位置爲1)

(3)若再加入fd=2,fd=1,則set變爲0001,0011

(4)執行select(6,&set,0,0,0)阻塞等待

(5)若fd=1,fd=2上都發生可讀事件,則select返回,此時set變爲0000,0011。注意:沒有事件發生的fd=5被清空。

基於上面的討論,能夠輕鬆得出select模型的特色:

(1)可監控的文件描述符個數取決與sizeof(fd_set)的值。我這邊服務器上sizeof(fd_set)=512,每bit表示一個文件描述符,則我服務器上支持的最大文件描述符是512*8=4096。聽說可調,另有說雖然可調,但調整上限受於編譯內核時的變量值。

(2)將fd加入select監控集的同時,還要再使用一個數據結構array保存放到select監控集中的fd,一是用於再select返回後,array做爲源數據和fd_set進行FD_ISSET判斷。二是select返回後會把之前加入的但並沒有事件發生的fd清空,則每次開始 select前都要從新從array取得fd逐一加入(FD_ZERO最早),掃描array的同時取得fd最大值maxfd,用於select的第一個參數。

(3)可見select模型必須在select前循環array(加fd,取maxfd),select返回後循環array(FD_ISSET判斷是否有時間發生)。



下面具體舉個簡單例子。

功能實現:

        服務器回覆從客戶端接收到的數據-->回顯數據

#include<stdio.h>

#include<stdlib.h>

#include<unistd.h>

#include<errno.h>

#include<netdb.h>

#include<sys/types.h>

#include<sys/socket.h>

#include<netinet/in.h>

#include<arpa/inet.h>

#include<netdb.h>

#include<sys/time.h>

#include<string.h>

#include<sys/select.h>

#include<pthread.h>




/*************************
*用於返回最大文件描述值
*
*************************/
int max_fd(int a[], int num)
{
    int max = -1;
    int i = 0;
    for(i = 0; i < num; i++)
    {
        if(a[i] > max)
        {
            max = a[i];

        }
    }

    return max;
}

int main(int argc, char *argv[])
{
    int sockfd = 0;
    int confd = 0;
    int i = 0, j = 0;
    fd_set fd_read[2];
    int clifd[FD_SETSIZE]; /*存放監聽以及已與客戶鏈接的套接字*/
    int fd_count = 0; /*已經鏈接的套接字個數*/
    struct sockaddr_in seradd, cliadd;
    int fd_ret = 0;
    socklen_t cli_len = sizeof(cliadd);
    char readbuf[1024];
    char writebuf[1024];
    /*create sock;*/
    sockfd = socket(AF_INET, SOCK_STREAM, 0); /*此套接字是阻塞的*/
    if(sockfd < 0)
    {
        perror("sock create fail !!");
        exit(-1);
    }


    seradd.sin_family = AF_INET;
    seradd.sin_port = htons(8080);
    seradd.sin_addr.s_addr = htonl(INADDR_ANY);

    /*bind*/
    if(-1 == (bind(sockfd, (struct sockaddr *)&seradd, sizeof(struct sockaddr))))
    {
        perror("bind error");
        exit(-1);
    }

    /*listen*/
    if(-1 == (listen(sockfd, 5)))
    {
        perror("listen error");
        exit(-1);
    }

    //memset(&fd_read[0],0,sizeof(fd_read[0]));

    FD_ZERO(&fd_read[0]); /*清空fd_read[0]全部位*/
    FD_SET(sockfd, &fd_read[0]); /*將sockfd添加到fd_read[0]描述集中,也就是說將sockfd對應的fd_read[0]位中置位*/
    for(i = 0; i < FD_SETSIZE; i++)
    {
        clifd[i] = -1;
    }

    clifd[0] = sockfd;
    printf("--ser start work---\n");
    while(1)
    {
        FD_ZERO(&fd_read[1]);
        fd_read[1] = fd_read[0]; /*每次select後,沒有達到條件的描述將被清空,因此每次都須要從新賦值*/
        /*進程將阻塞在select函數處,直到在整個隊列中有讀描述符可用爲止,我的理解--內核檢測整個隊列,而後將可用的描述符返回(一個或多個,一個沒有時將阻塞)*/
        fd_ret = select(max_fd(clifd, FD_SETSIZE) + 1, &fd_read[1], NULL, NULL, NULL); /*咱們只關心讀描述符集*/

        if(fd_ret < 0)
        {
            perror("select error");
        }
        else if(fd_ret > 0)
        {
            /*是監聽套接字可讀*/
            if(FD_ISSET(sockfd, &fd_read[1]) && (fd_count < FD_SETSIZE - 1))
            {

                confd = accept(sockfd, (struct sockaddr *)&cliadd, &cli_len); /*獲取與客戶端鏈接套接字*/
                if(-1 == confd)
                {
                    perror("confd error");
                }

                for(i = 1; i < FD_SETSIZE; i++)
                {
                    if(clifd[i] == -1)
                    {
                        clifd[i] = confd; /*將得到新鏈接套接字放到clifd數組中*/
                        FD_SET(confd, &fd_read[0]); /*將得到新鏈接套接字添加到讀描述集中*/
                        fd_count++;
                        break;
                    }
                }
            }

            /*鏈接套接字可讀*/
            for(j = 1; j < FD_SETSIZE; j++)
            {
                if(FD_ISSET(clifd[j], &fd_read[1]))
                {
                    /*從clifd[i]套接字中讀取數據*/
                    if(read(clifd[j], readbuf, sizeof(readbuf)) <= 0)
                    {
                        perror("read data error");
                        FD_CLR(clifd[j], &fd_read[0]); /*將clifd[j]描述從讀描述符集中刪除*/
                        close(clifd[j]); /*關閉該套接字*/
                        clifd[j] = -1;
                        fd_count--;
                        continue;

                    }

                    strcpy(writebuf, readbuf);
                    printf("read data:%s\n", readbuf);
                    if(write(clifd[j], writebuf, sizeof(writebuf)) <= 0)
                    {
                        perror("write data error");
                        FD_CLR(clifd[j], &fd_read[0]);
                        close(clifd[j]);
                        clifd[j] = -1;
                        fd_count--;
                        continue;
                    }
                }
            }
        }


    }

}

注意:在使用select函數時,請注意一下幾點

      1,select第一個參數是最大描述加1;

 2,描述集中在每次select後都要從新賦值,select函數會把不符合條件的對應位清空;

 3,若是有時間參數,在超時後,時間參數都必須從新賦值,不然會一直超時。



參考博客:

http://blog.chinaunix.net/uid-21275705-id-224351.html

http://blog.csdn.net/lingfengtengfei/article/details/12392449

http://linux.chinaunix.net/techdoc/net/2009/05/03/1109887.shtml

相關文章
相關標籤/搜索