电竞比分网-中国电竞赛事及体育赛事平台

分享

I/O多路復(fù)用詳解(一)

 zhouADNjj 2014-04-29

一、五種I/O模型

1、阻塞I/O模型

     最流行的I/O模型是阻塞I/O模型,缺省情形下,所有套接口都是阻塞的。我們以數(shù)據(jù)報(bào)套接口為例來講解此模型(我們使用UDP而不是TCP作為例子的原因在于就UDP而言,數(shù)據(jù)準(zhǔn)備好讀取的概念比較簡單:要么整個(gè)數(shù)據(jù)報(bào)已經(jīng)收到,要么還沒有。然而對于TCP來說,諸如套接口低潮標(biāo)記等額外變量開始活動,導(dǎo)致這個(gè)概念變得復(fù)雜)。

     進(jìn)程調(diào)用recvfrom,其系統(tǒng)調(diào)用直到數(shù)據(jù)報(bào)到達(dá)且被拷貝到應(yīng)用進(jìn)程的緩沖區(qū)中或者發(fā)生錯(cuò)誤才返回,期間一直在等待。我們就說進(jìn)程在從調(diào)用recvfrom開始到它返回的整段時(shí)間內(nèi)是被阻塞的。

2、非阻塞I/O模型

      進(jìn)程把一個(gè)套接口設(shè)置成非阻塞是在通知內(nèi)核:當(dāng)所請求的I/O操作非得把本進(jìn)程投入睡眠才能完成時(shí),不要把本進(jìn)程投入睡眠,而是返回一個(gè)錯(cuò)誤。也就是說當(dāng)數(shù)據(jù)沒有到達(dá)時(shí)并不等待,而是以一個(gè)錯(cuò)誤返回。

3、I/O復(fù)用模型

     調(diào)用select或poll,在這兩個(gè)系統(tǒng)調(diào)用中的某一個(gè)上阻塞,而不是阻塞于真正I/O系統(tǒng)調(diào)用。 阻塞于select調(diào)用,等待數(shù)據(jù)報(bào)套接口可讀。當(dāng)select返回套接口可讀條件時(shí),調(diào)用recevfrom將數(shù)據(jù)報(bào)拷貝到應(yīng)用緩沖區(qū)中。

4、信號驅(qū)動I/O模型

     首先開啟套接口信號驅(qū)動I/O功能, 并通過系統(tǒng)調(diào)用sigaction安裝一個(gè)信號處理函數(shù)(此系統(tǒng)調(diào)用立即返回,進(jìn)程繼續(xù)工作,它是非阻塞的)。當(dāng)數(shù)據(jù)報(bào)準(zhǔn)備好被讀時(shí),就為該進(jìn)程生成一個(gè)SIGIO信號。隨即可以在信號處理程序中調(diào)用recvfrom來讀數(shù)據(jù)報(bào),井通知主循環(huán)數(shù)據(jù)已準(zhǔn)備好被處理中。也可以通知主循環(huán),讓它來讀數(shù)據(jù)報(bào)。

5、異步I/O模型

     告知內(nèi)核啟動某個(gè)操作,并讓內(nèi)核在整個(gè)操作完成后(包括將數(shù)據(jù)從內(nèi)核拷貝到用戶自己的緩沖區(qū))通知我們。這種模型與信號驅(qū)動模型的主要區(qū)別是:
           信號驅(qū)動I/O:由內(nèi)核通知我們何時(shí)可以啟動一個(gè)I/O操作,
           異步I/O模型:由內(nèi)核通知我們I/O操作何時(shí)完成。

 

二、I/O復(fù)用的典型應(yīng)用場合:

 1、當(dāng)客戶處理多個(gè)描述字(通常是交互式輸入和網(wǎng)絡(luò)套接口)時(shí),必須使用I/O復(fù)用。

 2、如果一個(gè)服務(wù)器要處理多個(gè)服務(wù)或者多個(gè)協(xié)議(例如既要處理TCP,又要處理UDP),一般就要使用I/O復(fù)用。

 

三、支持I/O復(fù)用的系統(tǒng)調(diào)用

     目前支持I/O復(fù)用的系統(tǒng)調(diào)用有select、pselect、poll、epoll:

1、select函數(shù)

     該函數(shù)允許進(jìn)程指示內(nèi)核等待多個(gè)事件中的任何一個(gè)發(fā)生,并僅在有一個(gè)或多個(gè)事件發(fā)生或經(jīng)歷一段指定的時(shí)間后才喚醒它。

格式為:

#include <sys/select.h>
#include <sys/time.h>

int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout);
          
         返回:就緒描述字的正數(shù)目,0-超時(shí),-1-出錯(cuò)


我們從該函數(shù)的最后一個(gè)參數(shù)開始介紹,它告知內(nèi)核等待所指定描述字中的任何一個(gè)就緒可花多少時(shí)間。其timeval結(jié)構(gòu)用于指定這段時(shí)間的秒數(shù)和微秒數(shù)。

         struct timeval{

                   long tv_sec;   //seconds

                   long tv_usec;  //microseconds

       };

這個(gè)參數(shù)有三種可能:

(1)永遠(yuǎn)等待下去:僅在有一個(gè)描述字準(zhǔn)備好I/O時(shí)才返回。為此,我們把該參數(shù)設(shè)置為空指針。

(2)等待一段固定時(shí)間:在有一個(gè)描述字準(zhǔn)備好I/O時(shí)返回,但是不超過由該參數(shù)所指向的timeval結(jié)構(gòu)中指定的秒數(shù)和微秒數(shù)。

(3)根本不等待:檢查描述字后立即返回,這稱為輪詢。為此,該參數(shù)必須指向一個(gè)timeval結(jié)構(gòu),而且其中的定時(shí)器值必須為0。

    中間的三個(gè)參數(shù)readset、writeset和exceptset指定我們要讓內(nèi)核測試讀、寫和異常條件的描述字。如果我們對某一個(gè)的條件不感興趣,就可以把它設(shè)為空指針。struct fd_set可以理解為一個(gè)集合,這個(gè)集合中存放的是文件描述符,可通過以下四個(gè)宏進(jìn)行設(shè)置:

          void FD_ZERO(fd_set *fdset);           //清空集合

          void FD_SET(int fd, fd_set *fdset);   //將一個(gè)給定的文件描述符加入集合之中

          void FD_CLR(int fd, fd_set *fdset);   //將一個(gè)給定的文件描述符從集合中刪除

          int FD_ISSET(int fd, fd_set *fdset);   // 檢查集合中指定的文件描述符是否可以讀寫 ?

目前支持的異常條件只有兩個(gè):

(1)某個(gè)套接口的帶外數(shù)據(jù)的到達(dá)。

(2)某個(gè)已置為分組方式的偽終端存在可從其主端讀取的控制狀態(tài)信息。

    第一個(gè)參數(shù)maxfdp1指定待測試的描述字個(gè)數(shù),它的值是待測試的最大描述字加1(因此我們把該參數(shù)命名為maxfdp1),描述字0、1、2...maxfdp1-1均將被測試。

 一個(gè)應(yīng)用select的例子:

/**
  *TCP回射服務(wù)器客戶端程序
  */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netdb.h>
#include <string.h>
#include <math.h>
#include <sys/select.h>
#include <sys/time.h>

#define SERVER_PORT 3333 //服務(wù)器端口號


void str_cli(FILE *fp, int sockfd)
{
    int maxfdp1, stdineof;
    fd_set rset;
    char buf[BUFSIZ];
    int n;

    stdineof = 0;
    FD_ZERO(&rset);

    while(1)
    {
        if( stdineof == 0 )
            FD_SET(fileno(fp),&rset);
        FD_SET(sockfd, &rset);

        maxfdp1 = ((fileno(fp) > sockfd) ? fileno(fp) : sockfd) + 1;

        select(maxfdp1, &rset, NULL, NULL, NULL);

        if( FD_ISSET(sockfd, &rset) )
        {
            if( (n = read(sockfd, buf, BUFSIZ)) == 0 )
                if( stdineof == 1 )
                    return;
                else
                    perror("server terminated prematurely");
            write(fileno(stdout), buf, n);
        }

        if( FD_ISSET(fileno(fp), &rset))
        {
            if( (n = read(fileno(fp), buf, BUFSIZ)) == 0 )
            {
                stdineof = 1;
                shutdown(sockfd, SHUT_WR);
                FD_CLR(fileno(fp), &rset);
                continue;
            }
            write(sockfd, buf, n);
        }
    }
}

int main(int argc, char *argv[])
{
    int sockfd[5];
    struct sockaddr_in servaddr;
    struct hostent *hp;
    char buf[BUFSIZ];

    if( argc != 2 )
    {
        printf("Please input %s <hostname>\n", argv[0]);
        exit(1);
    }
    
    int i;
    for(i = 0; i < 5; ++i)
    {

        //創(chuàng)建socket

        if( (sockfd[i] = socket(AF_INET, SOCK_STREAM,0)) < 0 )
        {
            printf("Create socket error!\n");
            exit(1);
        }

        //設(shè)置服務(wù)器地址結(jié)構(gòu)

        bzero(&servaddr, sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        if( (hp = gethostbyname(argv[1])) != NULL )
        {
            bcopy(hp->h_addr, (struct sockaddr*)&servaddr.sin_addr, hp->h_length);
        }
        else if(inet_aton(argv[1], &servaddr.sin_addr) < 0 )
        {
            printf("Input Server IP error!\n");
            exit(1);
        }
        servaddr.sin_port = htons(SERVER_PORT);

        //連接服務(wù)器

        if( connect(sockfd[i],(struct sockaddr*)&servaddr, sizeof(servaddr)) < 0 )
        {
            printf("Connect server failure!\n");
            exit(1);
        }
    }
    str_cli(stdin, sockfd[0]);

    exit(0);
}


    本站是提供個(gè)人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多