I/O復(fù)用三 : epoll

epoll_create
#include <sys/epoll.h> 

int epoll_create(int size);

返回值:
  success:返回一個非0 的未使用過的最小的文件描述符
  error:-1 errno被設(shè)置

參數(shù) size 從 Linux 2.6.8 以后就不再使用杖刷,但是必須設(shè)置一個大于 0 的值。

需要注意的是宛官,當(dāng)創(chuàng)建好epoll句柄后媒咳,它就是會占用一個fd值迹缀,在linux下查看/proc/進程id/fd/,是能夠看到這個fd的( eg: ls /proc/$(ps -aux | grep './main' | awk 'NR==1 { print $2 }')/fd )蜜徽,所以在使用完 epoll 后祝懂,必須調(diào)用close()關(guān)閉,否則可能導(dǎo)致 fd 被耗盡拘鞋。

  • epoll_create1
    int epoll_create1(int flags);
    
    - flags:
     - 如果這個參數(shù)是0砚蓬,這個函數(shù)等價于epoll_create(0)
     - EPOLL_CLOEXEC:這是這個參數(shù)唯一的有效值,如果這個參數(shù)設(shè)置為這個盆色。
       那么當(dāng)進程替換映像的時候會關(guān)閉這個文件描述符灰蛙,這樣新的映像中就無法對這個文件描述符操作,
       適用于多進程編程+映像替換的環(huán)境里
    
epoll_ctl
#include <sys/epoll.h> 

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

epoll的事件注冊函數(shù)
第一個參數(shù)是 epoll_create() 的創(chuàng)建的 epoll 實例隔躲。
第二個參數(shù)表示動作摩梧,用3個宏表示:

EPOLL_CTL_ADD:注冊新的fd到epfd中
EPOLL_CTL_MOD:修改已經(jīng)注冊的fd的監(jiān)聽事件
EPOLL_CTL_DEL:從epfd中刪除一個fd

第三個參數(shù)是需要監(jiān)聽的fd
第四個參數(shù)是內(nèi)核需要監(jiān)聽什么事件,struct epoll_event 結(jié)構(gòu)如下:

#include <sys/epoll.h> 

struct epoll_event {
      __uint32_t events;
      epoll_data_t data;
}

typedef union epoll_data {
    void        *ptr;
    int          fd;
    __uint32_t   u32;
    __uint64_t   u64;
} epoll_data_t;
  • EPOLLIN
    表示對應(yīng)的文件描述符可以讀(包括對端SOCKET正常關(guān)閉)
  • EPOLLOUT
    表示對應(yīng)的文件描述符可以寫
  • EPOLLPRI
    表示對應(yīng)的文件描述符有緊急的數(shù)據(jù)可讀(這里應(yīng)該表示有帶外數(shù)據(jù)到來)
  • EPOLLERR
    表示對應(yīng)的文件描述符發(fā)生錯誤
  • EPOLLHUP
    表示對應(yīng)的文件描述符被掛斷
  • EPOLLET
    將EPOLL設(shè)為邊緣觸發(fā)(Edge Triggered)模式宣旱,這是相對于水平觸發(fā)(Level Triggered)來說的
epoll_wait
#include <sys/epoll.h> 

int epoll_wait(int epfd, struct epoll_event *event, int maxevents, int timeout);

等待事件的產(chǎn)生仅父。

參數(shù) events 用來從內(nèi)核得到事件的集合。
參數(shù) maxevents 告之內(nèi)核這個events有多大(數(shù)組成員的個數(shù))浑吟。
參數(shù) timeout 是超時時間笙纤,單位毫秒(0會立即返回,-1將是永久阻塞)组力。

該函數(shù)返回需要處理的事件數(shù)目省容,返回的事件集合在 events 數(shù)組中,如返回 0 表示已超時燎字。

  • maxevents 參數(shù)設(shè)置多少合適腥椒?
    muduo 中的思路是動態(tài)擴張:開始是 n 個阿宅,當(dāng)發(fā)現(xiàn)有事件的 fd 數(shù)量已經(jīng)到達 n 個后,將 struct epoll_event 數(shù)量調(diào)整成 2n 個寞酿,下次如果還不夠家夺,則變成 4n 個,以此類推伐弹。
    // 初始化代碼  
    std::vector<struct epoll_event> events_(16);  
    
    // 線程循環(huán)里面的代碼  
    while (m_bExit)  
    {  
        int numEvents = ::epoll_wait(epollfd_, &*events_.begin(), static_cast<int>(events_.size()), 1);  
        if (numEvents > 0)  
        {  
            if (static_cast<size_t>(numEvents) == events_.size())  
            {  
                events_.resize(events_.size() * 2);  
            }  
        }  
    }
    
  • timeout 超時時間設(shè)置多少合適拉馋?
    muduo 中epoll_wait 的超時事件設(shè)置為 1 毫秒。
代碼示例

服務(wù)器

#include "lib/common.h"

#define MAXEVENTS 128

char rot13_char(char c) 
{
    if ((c >= 'a' && c <= 'm') || (c >= 'A' && c <= 'M'))
        return c + 13;
    else if ((c >= 'n' && c <= 'z') || (c >= 'N' && c <= 'Z'))
        return c - 13;
    else
        return c;
}

int main(int argc, char **argv) 
{
    int listen_fd, socket_fd;
    int n, i;
    int efd;
    struct epoll_event event;
    struct epoll_event *events;

    listen_fd = tcp_nonblocking_server_listen(SERV_PORT);

    efd = epoll_create1(0);
    if (efd == -1) {
        error(1, errno, "epoll create failed");
    }

    event.data.fd = listen_fd;
    event.events = EPOLLIN | EPOLLET;
    if (epoll_ctl(efd, EPOLL_CTL_ADD, listen_fd, &event) == -1) {
        error(1, errno, "epoll_ctl add listen fd failed");
    }

    /* Buffer where events are returned */
    events = calloc(MAXEVENTS, sizeof(event));

    while (1) {
        n = epoll_wait(efd, events, MAXEVENTS, -1);
        printf("epoll_wait wakeup\n");
        for (i = 0; i < n; i++) {
            if ((events[i].events & EPOLLERR) ||
                (events[i].events & EPOLLHUP) ||
                (!(events[i].events & EPOLLIN))) {
                fprintf(stderr, "epoll error\n");
                close(events[i].data.fd);
                continue;
            } else if (listen_fd == events[i].data.fd) {
                struct sockaddr_storage ss;
                socklen_t slen = sizeof(ss);
                int fd = accept(listen_fd, (struct sockaddr *) &ss, &slen);
                if (fd < 0) {
                    error(1, errno, "accept failed");
                } else {
                    make_nonblocking(fd);
                    event.data.fd = fd;
                    event.events = EPOLLIN | EPOLLET; //edge-triggered
                    if (epoll_ctl(efd, EPOLL_CTL_ADD, fd, &event) == -1) {
                        error(1, errno, "epoll_ctl add connection fd failed");
                    }
                }
                continue;
            } else {
                socket_fd = events[i].data.fd;
                printf("get event on socket fd == %d \n", socket_fd);
                while (1) {
                    char buf[512];
                    if ((n = read(socket_fd, buf, sizeof(buf))) < 0) {
                        if (errno != EAGAIN) {
                            error(1, errno, "read error");
                            close(socket_fd);
                        }
                        break;
                    } else if (n == 0) {
                        close(socket_fd);
                        break;
                    } else {
                        for (i = 0; i < n; ++i) {
                            buf[i] = rot13_char(buf[i]);
                        }
                        if (write(socket_fd, buf, n) < 0) {
                            error(1, errno, "write error");
                        }
                    }
                }
            }
        }
    }

    free(events);
    close(listen_fd);
}
LT和ET 工作模式

epoll對文件描述符的操作有2種模式:LT和ET惨好。

  • LT模式(水平觸發(fā)):
    LT(水平觸發(fā))是默認的工作模式煌茴,并且同時支持阻塞和非阻塞socket。
    對于采用LT工作模式的文件描述符日川,當(dāng)epoll_wait檢測到其上有時間發(fā)生并將此事件通知應(yīng)用程序后蔓腐,應(yīng)用程序可以不立即處理該事件,這樣龄句,當(dāng)應(yīng)用程序下一次調(diào)用epoll_wait時回论,epoll_wait還會再次相應(yīng)應(yīng)用程序通告此事件,直到該事件被處理分歇。

  • ET模式(邊沿觸發(fā)):
    ET是一種高效的模式傀蓉,只支持非阻塞socket。
    對于采用ET模式的文件描述符职抡,當(dāng)epoll_wait檢測到其上有事件發(fā)生并將此時間通知應(yīng)用程序以后葬燎,應(yīng)用程序可以不立即處理該事件,但是后續(xù)的epoll_wait將不再向應(yīng)用程序通知這一事件缚甩。

    • 為什么將ET稱為高效的工作方式了谱净?
      因為ET不用多次觸發(fā),減少了每次epoll_wait可能需要返回的fd數(shù)量擅威,在并發(fā)event數(shù)量極多的情況下能夠加快epoll_wait的處理速度壕探。
      注意 epoll 工作在 ET模式的時候,必須使用非阻塞套接口郊丛,以避免由于一個文件句柄的 阻塞讀/阻塞寫 操作把處理多個文件描述符的任務(wù)餓死浩蓉。

如果對于一個非阻塞 socket,如果使用 epoll 邊緣模式去檢測數(shù)據(jù)是否可讀宾袜,觸發(fā)可讀事件以后捻艳,一定要一次性把 socket 上的數(shù)據(jù)收取干凈才行,也就是一定要循環(huán)調(diào)用 recv 函數(shù)直到 recv 出錯庆猫,錯誤碼是 EWOULDBLOCK 或者 EAGAIN 认轨。

如果使用水平模式,則不用月培,你可以根據(jù)業(yè)務(wù)一次性收取固定的字節(jié)數(shù)嘁字,或者收完為止恩急。邊緣模式下收取數(shù)據(jù)的代碼示例如下:


參考資料
[1]《UNIX 網(wǎng)絡(luò)編程》3th [美] W.Richard Stevens,Bill Fenner纪蜒,Andrew M. Rudoff
[2] http://www.cnblogs.com/ajianbeyourself/p/5859989.html
[3] https://blog.csdn.net/hnlyyk/article/details/50946194

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末衷恭,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子纯续,更是在濱河造成了極大的恐慌随珠,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,029評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件猬错,死亡現(xiàn)場離奇詭異窗看,居然都是意外死亡,警方通過查閱死者的電腦和手機倦炒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,395評論 3 385
  • 文/潘曉璐 我一進店門显沈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人逢唤,你說我怎么就攤上這事拉讯。” “怎么了鳖藕?”我有些...
    開封第一講書人閱讀 157,570評論 0 348
  • 文/不壞的土叔 我叫張陵魔慷,是天一觀的道長。 經(jīng)常有香客問我吊奢,道長盖彭,這世上最難降的妖魔是什么纹烹? 我笑而不...
    開封第一講書人閱讀 56,535評論 1 284
  • 正文 為了忘掉前任页滚,我火速辦了婚禮,結(jié)果婚禮上铺呵,老公的妹妹穿的比我還像新娘裹驰。我一直安慰自己,他們只是感情好片挂,可當(dāng)我...
    茶點故事閱讀 65,650評論 6 386
  • 文/花漫 我一把揭開白布幻林。 她就那樣靜靜地躺著,像睡著了一般音念。 火紅的嫁衣襯著肌膚如雪沪饺。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,850評論 1 290
  • 那天闷愤,我揣著相機與錄音整葡,去河邊找鬼。 笑死讥脐,一個胖子當(dāng)著我的面吹牛遭居,可吹牛的內(nèi)容都是我干的啼器。 我是一名探鬼主播,決...
    沈念sama閱讀 39,006評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼俱萍,長吁一口氣:“原來是場噩夢啊……” “哼端壳!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起枪蘑,我...
    開封第一講書人閱讀 37,747評論 0 268
  • 序言:老撾萬榮一對情侶失蹤损谦,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后腥寇,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體成翩,經(jīng)...
    沈念sama閱讀 44,207評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,536評論 2 327
  • 正文 我和宋清朗相戀三年赦役,在試婚紗的時候發(fā)現(xiàn)自己被綠了麻敌。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,683評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡掂摔,死狀恐怖术羔,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情乙漓,我是刑警寧澤级历,帶...
    沈念sama閱讀 34,342評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站叭披,受9級特大地震影響寥殖,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜涩蜘,卻給世界環(huán)境...
    茶點故事閱讀 39,964評論 3 315
  • 文/蒙蒙 一嚼贡、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧同诫,春花似錦粤策、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,772評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至霹俺,卻和暖如春柔吼,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背丙唧。 一陣腳步聲響...
    開封第一講書人閱讀 32,004評論 1 266
  • 我被黑心中介騙來泰國打工愈魏, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 46,401評論 2 360
  • 正文 我出身青樓蝌戒,卻偏偏與公主長得像串塑,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子北苟,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,566評論 2 349

推薦閱讀更多精彩內(nèi)容