epoll的提高--工作模式

  • 水平觸發(fā)模式 -- 默認(rèn)就是這種模式(如上一篇所寫(xiě))
  • 邊沿阻塞觸發(fā)模式
  • 邊沿非阻塞工作模式 -- 效率最高
先來(lái)個(gè)需求吧:

針對(duì)一個(gè)客戶端(進(jìn)程間管道通信)對(duì)應(yīng)一個(gè)服務(wù)器來(lái)說(shuō)
如果客戶端發(fā)送的信息有100字節(jié), 而服務(wù)器每次接收只接收50字節(jié), 那么剩下的50字節(jié)怎么處理?

分析:
  1. 默認(rèn)執(zhí)行流程: 對(duì)應(yīng)的緩沖區(qū)存放了發(fā)送來(lái)的100字節(jié),系統(tǒng)epoll監(jiān)聽(tīng)到了對(duì)應(yīng)的文件描述符的變化, 此時(shí)服務(wù)端去讀數(shù)據(jù), 但是只讀了50字節(jié), 那么緩沖區(qū)中就還留有50字節(jié)
    此時(shí)有兩種說(shuō)法: 事實(shí)是第二種
    1.系統(tǒng)為提高效率, 不會(huì)再去調(diào)用epoll_wait函數(shù), 那么50字節(jié)數(shù)據(jù)就只能等下次客戶端發(fā)送信息的時(shí)候接收(為了提高效率, 減少該函數(shù)的調(diào)用次數(shù))
    2.系統(tǒng)會(huì)再次調(diào)用epoll_wait函數(shù), 將數(shù)據(jù)讀出來(lái).(后面會(huì)附帶上代碼)
  2. 邊沿阻塞觸發(fā)模式: 會(huì)想上述的第1種情況那樣, 但是會(huì)導(dǎo)致緩沖區(qū)里每次都?xì)埩魯?shù)據(jù), 并且越來(lái)越多...
  3. 邊沿非阻塞(O_NONBLOCK)觸發(fā)模式: 該效率最高, 主要是因?yàn)閷⒖蛻舳藢?duì)應(yīng)的那個(gè)文件描述符即緩沖區(qū)(管道)設(shè)置成非阻塞模式, 此時(shí)接受(讀取)信息的時(shí)候就需要循環(huán)去讀取, 當(dāng)read/recv返回值為0時(shí)表示讀取完畢.再加上邊沿模式(只調(diào)用一次epoll_wait函數(shù))所以效率高
    設(shè)置非阻塞:
    1.open的時(shí)候設(shè)置參數(shù);
    2.fcntl設(shè)置
//文件打開(kāi)之后修改文件屬性 先獲勝設(shè)置的屬性 flags
//獲取flags:
 int flags = fcntl(fd, F_GETFL); 
//設(shè)置flags:
flags = flags | O_NONBLOCK;
fcntl(fd, F_SETFL, flags);

對(duì)應(yīng)的三種模式的代碼:

  1. 利用管道-父子進(jìn)程之間通信實(shí)現(xiàn)前兩種模式:
    切換在代碼中注釋的地方, 輸出的格式如上文描述那樣, 這里模擬的是10字節(jié)和5字節(jié)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <error.h>
int main(int argc, char *argv[]) {
    char buf[10];

    //使用管道實(shí)現(xiàn),管道創(chuàng)建需要一個(gè)數(shù)組,存放的一個(gè)對(duì)應(yīng)讀,一個(gè)對(duì)應(yīng)寫(xiě)
    int pfd[2];
    //創(chuàng)建匿名管道
    pipe(pfd);

    //創(chuàng)建子進(jìn)程
    pid_t pid = fork();

    if(pid == 0) { //子進(jìn)程
        //不需要讀操作,關(guān)閉讀的文件描述符,確保管道單項(xiàng)傳輸數(shù)據(jù)
        close(pfd[0]);
        while(1) {
            int i = 0;
            for(i = 0; i < 10/2; i++) {
                buf[i] = 'a';
            }
            buf[i-1] = '\n';
            for(; i < 10; i++) {
                buf[i] = 'b';
            }
            buf[i-1] = '\n';
            //此時(shí)數(shù)組中存放的是aaaa\nbbbb\n
            //一次發(fā)送10字節(jié)
            write(pfd[1], buf, sizeof(buf));
            sleep(3);
        }
        close(pfd[1]);
    } else if(pid > 0) {//父進(jìn)程

        close(pfd[1]);
        //創(chuàng)建epoll模型,指向根節(jié)點(diǎn),句柄
        int efd = epoll_create(10);
        //將要監(jiān)聽(tīng)的掛載到根節(jié)點(diǎn)上
        struct epoll_event event;
        //設(shè)置邊沿觸發(fā)如下:
        event.events = EPOLLIN | EPOLLET;
        /*
         * //默認(rèn)就是水平觸發(fā)
         * event.events = EPOLLIN;
         */
        event.data.fd = pfd[0];//寫(xiě)端
        epoll_ctl(efd, EPOLL_CTL_ADD, pfd[0], &event);

        struct epoll_event resevents[10];
        char readbuf[5];
        while(1) {
            int res = epoll_wait(efd, resevents, 10, -1);
            printf("res:%d\n", res);
            if(resevents[0].data.fd == pfd[0]) {
                int len = read(pfd[0], readbuf, 5);//一次讀5字節(jié)
                write(STDOUT_FILENO, readbuf, len);
            }
        }
        close(pfd[0]);
        close(efd);
    } else {
        perror("fork error");
        exit(1);
    }
    return 0;
}
  1. 利用c/s模型實(shí)現(xiàn)的邊沿阻塞觸發(fā), 這里做的是只對(duì)應(yīng)一個(gè)客戶端進(jìn)行監(jiān)聽(tīng)
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <unistd.h>
#define MAXLINE 10
#define SERV_PORT 9000
int main(void) {
    struct sockaddr_in servaddr, cliaddr;
    socklen_t cliaddr_len;
    int listenfd, connfd;
    char buf[MAXLINE];
    char str[INET_ADDRSTRLEN];

    listenfd = socket(AF_INET, SOCK_STREAM, 0);

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(SERV_PORT);

    bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

    listen(listenfd, 20);

    struct epoll_event event;
    struct epoll_event resevent[10];
    int res, len;

    printf("Accepting connections ...\n");

    cliaddr_len = sizeof(cliaddr);
    connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
    printf("received from %s at PORT %d\n",
            inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
            ntohs(cliaddr.sin_port));

    // 創(chuàng)建紅黑樹(shù)根節(jié)點(diǎn)
    int efd = epoll_create(10);
    // 檢測(cè)的事件設(shè)置
#if 0
    /* ET 邊沿觸發(fā) */
    event.events = EPOLLIN | EPOLLET;     
#else
    /* 默認(rèn) LT 水平觸發(fā) */
    event.events = EPOLLIN;                 
#endif
    // 需要檢測(cè)的文件描述符
    event.data.fd = connfd;
    epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &event);

    while (1) {
        res = epoll_wait(efd, resevent, 10, -1);

        printf("========res %d\n", res);
        if (resevent[0].data.fd == connfd) {
            len = read(connfd, buf, MAXLINE/2);        
            write(STDOUT_FILENO, buf, len);
        }
    }
    return 0;
}
  1. 同2一樣只對(duì)客戶端進(jìn)行監(jiān)聽(tīng), 不過(guò)這里要注意的是不能再根據(jù)read的返回值去判斷是客戶端斷開(kāi)了連接還是讀取失敗還是讀到的內(nèi)容, 需要另外的思路去設(shè)計(jì)程序, 比如利用data里面的void *指針來(lái)做, 存放一個(gè)時(shí)間, 如果長(zhǎng)時(shí)間沒(méi)有聯(lián)系, 則斷開(kāi)連接...或者利用心跳包
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <fcntl.h>
#define MAXLINE 10
#define SERV_PORT 8000
int main(void) {
    struct sockaddr_in servaddr, cliaddr;
    socklen_t cliaddr_len;
    int listenfd, connfd;
    char buf[MAXLINE];
    char str[INET_ADDRSTRLEN];

    listenfd = socket(AF_INET, SOCK_STREAM, 0);

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(SERV_PORT);

    bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

    listen(listenfd, 20);

    ///////////////////////////////////////////////////////////////////////
    struct epoll_event event;
    struct epoll_event resevent[10];

    int efd = epoll_create(10);

    //event.events = EPOLLIN;
    printf("Accepting connections ...\n");
    cliaddr_len = sizeof(cliaddr);
    connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
    printf("received from %s at PORT %d\n",
            inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
            ntohs(cliaddr.sin_port));

    /* 修改connfd為非阻塞讀 */
    int flag = fcntl(connfd, F_GETFL);          
    flag |= O_NONBLOCK;
    fcntl(connfd, F_SETFL, flag);

    /* ET 邊沿觸發(fā),默認(rèn)是水平觸發(fā) */
    event.events = EPOLLIN | EPOLLET;     
    event.data.fd = connfd;
    //將connfd加入監(jiān)聽(tīng)紅黑樹(shù)
    epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &event);      

    while (1) {
        int len = 0;
        printf("epoll_wait begin\n");
        //最多10個(gè), 阻塞監(jiān)聽(tīng)
        int res = epoll_wait(efd, resevent, 10, -1);        
        printf("epoll_wait end res %d\n", res);

        if (resevent[0].data.fd == connfd) {
            // 非阻塞讀, 輪詢
            // epoll_wait 觸發(fā)一次,剩余數(shù)據(jù)循環(huán)讀取
            while ((len = read(connfd, buf, MAXLINE/2)) >0 ) {
                write(STDOUT_FILENO, buf, len);
            }
        }
    }
    return 0;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容