Linux的I/O多路複用機制之--select&poll

1. Linux下的五種I/O模型
數組

1)阻塞I/O(blocking I/O)
2)非阻塞I/O (nonblocking I/O)
3) I/O複用(select 和poll) (I/O multiplexing)
4)信號驅動I/O (signal driven I/O (SIGIO))
5)異步I/O (asynchronous I/O (the POSIX aio_functions))
瀏覽器

前四種都是同步,只有最後一種纔是異步IO。服務器

五種I/O模型的比較:

wKiom1ep3LHD-q-eAAEgjXvlnck650.jpg

2.多路複用--select網絡

系統提供select函數來實現多路複用輸入/輸出模型。select系統調用是用來讓咱們的程序監視多個文件句柄的狀態變化的。程序會停在select這裏等待,直到被監視的文件句柄有一個或多個發生了狀態改變。關於文件句柄,其實就是一個整數,咱們最熟悉的句柄是0、一、2三個,0是標準輸入,1是標準輸出,2是標準錯誤輸出。0、一、2是整數表示的,對應的FILE *結構的表示就是stdin、stdout、stderr。數據結構

select函數異步

int select(int maxfd,fd_set *rdset,fd_set *wrset, \  
           fd_set *exset,struct timeval *timeout);

參數說明:socket

參數maxfd是須要監視的最大的文件描述符值+1;rdset,wrset,exset分別對應於須要檢測的可讀文件描述符的集合,可寫文件描述符的集合及異常文件描述符的集合。struct timeval結構用於描述一段時間長度,若是在這個時間內,須要監視的描述符沒有事件發生則函數返回,返回值爲0。async

下面的宏提供了處理這三種描述詞組的方式:
FD_CLR(inr fd,fd_set* set);用來清除描述詞組set中相關fd 的位
FD_ISSET(int fd,fd_set *set);用來測試描述詞組set中相關fd 的位是否爲真
FD_SET(int fd,fd_set*set);用來設置描述詞組set中相關fd的位
FD_ZERO(fd_set *set);用來清除描述詞組set的所有位ide

參數timeout爲結構timeval,用來設置select()的等待時間,其結構定義以下:函數

struct timeval  
{  
    time_t tv_sec;//second  
    time_t tv_usec;//minisecond  
};

若是參數timeout設爲:

NULL,則表示select()沒有timeout,select將一直被阻塞,直到某個文件描述符上發生了事件。

0:僅檢測描述符集合的狀態,而後當即返回,並不等待外部事件的發生。

特定的時間值:若是在指定的時間段裏沒有事件發生,select將超時返回。

函數返回值:

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

理解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模型的特色:

  • 可監控的文件描述符個數取決與sizeof(fd_set)的值。

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

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


select缺點:

(1)每次調用select,都須要把fd集合從用戶態拷貝到內核態,這個開銷在fd不少時會很大

(2)同時每次調用select都須要在內核遍歷傳遞進來的全部fd,這個開銷在fd不少時也很大

(3)select支持的文件描述符數量過小,默認是1024


3.多路複用--poll

poll與select很是類似,不一樣之處在於,select使用三個位圖來表示三種不一樣的事件,而poll使用一個 pollfd的指針實現。

poll函數

#include <poll.h>
int poll (struct pollfd *fds, unsigned int nfds, int timeout);

參數說明:

fds:是一個struct pollfd結構類型的數組,其結構以下:

struct pollfd {    int fd; /* file descriptor */
    short events; /* requested events to watch */
    short revents; /* returned events witnessed */
    };

該結構用於存放須要檢測其狀態的Socket描述符;每當調用這個函數以後,系統不會清空這個數組,操做起來比較方便;特別是對於 socket鏈接比較多的狀況下,在必定程度上能夠提升處理的效率;這一點與select()函數不一樣,調用select()函數以後,select() 函數會清空它所檢測的socket描述符集合,致使每次調用select()以前都必須把socket描述符從新加入到待檢測的集合中;因 此,select()函數適合於只檢測一個socket描述符的狀況,而poll()函數適合於大量socket描述符的狀況;

nfds:nfds_t類型的參數,用於標記數組fds中的結構體元素的總數量;

timeout:是poll函數調用阻塞的時間,單位:毫秒;若是timeout>0那麼poll()函數會阻塞timeout所指定的毫秒時間長度以後返回。若是timeout==0,那麼 poll() 函數當即返回而不阻塞,若是timeout==INFTIM,那麼poll() 函數會一直阻塞下去,直到所檢測的socket描述符上的感興趣的事件發 生是才返回,若是感興趣的事件永遠不發生,那麼poll()就會永遠阻塞下去;

返回值:

>0:數組fds中準備好讀、寫或出錯狀態的那些socket描述符的總數量;

==0:數組fds中沒有任何socket描述符準備好讀、寫,或出錯;此時poll超時,超時時間是timeout毫秒;換句話說,若是所檢測的 socket描述符上沒有任何事件發生的話,

-1:  poll函數調用失敗,同時會自動設置全局變量errno;


poll() 函數的功能和返回值的含義與 select() 函數的功能和返回值的含義是徹底同樣的,二者之間的差異就是內部實現方式不同。


4.select實例之網絡服務器(poll實現相似)

服務器端

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include <string.h>

#define _MAX_LISTEN_ 5
#define _MAX_SIZE_ 10
#define _BUF_SIZE_ 1024

int fd_arr[_MAX_SIZE_];
int max_fd = -1;

static void init_fd_arr()
{
    int i = 0;
    for(; i < _MAX_SIZE_; ++i)
    {
        fd_arr[i] = -1;
    }
}

static int add_fd_arr(int fd)
{
    int i = 0;
    for(; i < _MAX_SIZE_; ++i)
    {
        if(fd_arr[i] == -1)
        {
            fd_arr[i] = fd;
            return 0;
        }
    }
    return 1;
}

static void remove_fd_arr(int fd)
{
    int i = 0;
    for(; i < _MAX_SIZE_; ++i)
    {
        if(fd_arr[i] == fd)
        {
            fd_arr[i] = -1;
            break;
        }
    }
}

static void reload_fd_arr(fd_set* pset)
{
    int i = 0;
    max_fd = -1;
    for(; i < _MAX_SIZE_; ++i)
    {
        if(fd_arr[i] != -1)
        {
            FD_SET(fd_arr[i], pset);
            if(fd_arr[i] > max_fd)
                max_fd = fd_arr[i];
        }
    }
}

static printf_msg(int i, const char* msg)
{
    printf("client %d # %s\n", fd_arr[i], msg);
}

void Usage(const char* proc)
{
    printf("%s usage: [ip] [port]\n", proc);
}

int startup(const char* _ip, const char* _port)
{
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if(sock < 0)
    {
        perror("socket");
        exit(1);
    }

    int opt = 1;
    if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) 
    {
        perror("setsockopt");
        exit(2);
    }  

    struct sockaddr_in local;
    local.sin_family = AF_INET;
    local.sin_port = htons(atoi(_port));
    local.sin_addr.s_addr = inet_addr(_ip);
    if(bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0)
    {
        perror("bind");
        exit(3);
    }

    if(listen(sock, _MAX_LISTEN_) < 0)
    {
        perror("listen");
        exit(4);
    }

    return sock;
}

int main(int argc, char* argv[])
{
    if(argc != 3)
    {
        Usage(argv[0]);
        return 1;
    }

    int listen_sock = startup(argv[1], argv[2]);

    init_fd_arr();
    add_fd_arr(listen_sock);

    fd_set rfds;
    FD_ZERO(&rfds);
    struct timeval tv = {5, 0};

    while(1)
    {
        reload_fd_arr(&rfds);
        int fds = select(max_fd+1, &rfds, NULL, NULL, NULL);
        switch(fds)
        {
            case -1:
                perror("select");
                exit(5);
                break;
            case 0:
                printf("time out\n");
                break;
            default:
            {
                int index = 0;
                for(; index < _MAX_SIZE_; ++index)
                {
                    if(fd_arr[index] == listen_sock && FD_ISSET(fd_arr[index], &rfds)) //new accept
                    {
                        struct sockaddr_in peer;
                        socklen_t len = sizeof(peer);
                        int new_fd = accept(listen_sock, (struct sockaddr* )&peer, &len);
                        if(new_fd < 0)
                        {
                            perror("accept");
                            exit(6);
                        }

                        printf("get a new client %d -> ip: %s port: %d\n", new_fd, inet_ntoa(peer.sin_addr), ntohs(peer.sin_port));

                        if(1 == add_fd_arr(new_fd))
                        {
                            perror("fd_arr is full\n");
                            close(new_fd);
                            exit(7);
                        }

                        continue;
                    }
                    if(fd_arr[index] != -1 && FD_ISSET(fd_arr[index], &rfds)) //new read fd
                    {
                        char buf[_BUF_SIZE_];
                        memset(buf, '\0', sizeof(buf));

                        ssize_t _s = read(fd_arr[index], buf, sizeof(buf)-1);
                        if(_s > 0)
                        {
                            buf[_s] = '\0';
                            printf_msg(index, buf);
                        }
                        else if(_s == 0) //client closed
                        {
                            printf("client %d is closed...\n", fd_arr[index]);
                            FD_CLR(fd_arr[index], &rfds);
                            close(fd_arr[index]); // must before remove!!!
                            remove_fd_arr(fd_arr[index]);
                        }
                        else
                        {}

                    }
                }
            }
            break;
        }
        
    }


    return 0;
}

客戶端

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

void Usage(const char* proc)
{
    printf("usage: %s [ip] [port]\n", proc);
}

int main(int argc, char* argv[])
{
    if(argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }
    
    int conn_sock = socket(AF_INET, SOCK_STREAM, 0);

    struct sockaddr_in conn;
    conn.sin_family = AF_INET;
    conn.sin_port = htons(atoi(argv[2]));
    conn.sin_addr.s_addr = inet_addr(argv[1]);

    if(connect(conn_sock, (const struct sockaddr*)&conn, sizeof(conn)) < 0)
    {
        perror("connect");
        exit(2);
    }

    char buf[1024];
    memset(buf, '\0', sizeof(buf));
    while(1)
    {
        printf("please enter # ");
        fflush(stdout);

        ssize_t _s = read(0, buf, sizeof(buf)-1);
        if(_s > 0)
        {
            buf[_s-1] = '\0';
            write(conn_sock, buf, strlen(buf));
        }
    }

    return 0;
}

程序演示

使用Telnet測試

wKiom1ep72OzT0KPAABiWMyW_rM485.png

使用客戶端測試

wKiom1ep79GRJksTAABQsLlwy6I790.png

使用瀏覽器測試

wKiom1ep8DDziBfaAAFbJkh6BwI061.png


j_0009.gifj_0009.gifj_0009.gifj_0009.gifj_0009.gifj_0009.gifj_0009.gifj_0009.gifj_0009.gifj_0009.gifj_0009.gifj_0009.gif

相關文章
相關標籤/搜索