epoll使用詳解(精髓)

轉(zhuǎn)自:https://blog.csdn.net/ljx0305/article/details/4065058

epoll - I/O event notification facility

在linux的網(wǎng)絡(luò)編程中,很長的時(shí)間都在使用select來做事件觸發(fā)纲酗。在linux新的內(nèi)核中币励,有了一種替換它的機(jī)制乡洼,就是epoll。
相比于select缰贝,epoll最大的好處在于它不會隨著監(jiān)聽fd數(shù)目的增長而降低效率。因?yàn)樵趦?nèi)核中的select實(shí)現(xiàn)中,它是采用輪詢來處理的纺且,輪詢的fd數(shù)目越多,自然耗時(shí)越多稍浆。并且载碌,在linux/posix_types.h頭文件有這樣的聲明:

define __FD_SETSIZE 1024

表示select最多同時(shí)監(jiān)聽1024個(gè)fd,當(dāng)然衅枫,可以通過修改頭文件再重編譯內(nèi)核來擴(kuò)大這個(gè)數(shù)目嫁艇,但這似乎并不治本。

epoll的接口非常簡單弦撩,一共就三個(gè)函數(shù):
1. int epoll_create(int size);
創(chuàng)建一個(gè)epoll的句柄步咪,size用來告訴內(nèi)核這個(gè)監(jiān)聽的數(shù)目一共有多大。這個(gè)參數(shù)不同于select()中的第一個(gè)參數(shù)益楼,給出最大監(jiān)聽的fd+1的值猾漫。需要注意的是,當(dāng)創(chuàng)建好epoll句柄后感凤,它就是會占用一個(gè)fd值悯周,在linux下如果查看/proc/進(jìn)程id/fd/,是能夠看到這個(gè)fd的陪竿,所以在使用完epoll后禽翼,必須調(diào)用close()關(guān)閉,否則可能導(dǎo)致fd被耗盡族跛。

2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll的事件注冊函數(shù)闰挡,它不同與select()是在監(jiān)聽事件時(shí)告訴內(nèi)核要監(jiān)聽什么類型的事件,而是在這里先注冊要監(jiān)聽的事件類型庸蔼。第一個(gè)參數(shù)是epoll_create()的返回值解总,第二個(gè)參數(shù)表示動(dòng)作,用三個(gè)宏來表示:
EPOLL_CTL_ADD:注冊新的fd到epfd中姐仅;
EPOLL_CTL_MOD:修改已經(jīng)注冊的fd的監(jiān)聽事件花枫;
EPOLL_CTL_DEL:從epfd中刪除一個(gè)fd刻盐;
第三個(gè)參數(shù)是需要監(jiān)聽的fd,第四個(gè)參數(shù)是告訴內(nèi)核需要監(jiān)聽什么事劳翰,struct epoll_event結(jié)構(gòu)如下:

typedef union epoll_data {
    void *ptr;
    int fd;
    __uint32_t u32;
    __uint64_t u64;
} epoll_data_t;

struct epoll_event {
    __uint32_t events; /* Epoll events */
    epoll_data_t data; /* User data variable */
};

events可以是以下幾個(gè)宏的集合:
EPOLLIN :表示對應(yīng)的文件描述符可以讀(包括對端SOCKET正常關(guān)閉)敦锌;
EPOLLOUT:表示對應(yīng)的文件描述符可以寫;
EPOLLPRI:表示對應(yīng)的文件描述符有緊急的數(shù)據(jù)可讀(這里應(yīng)該表示有帶外數(shù)據(jù)到來)佳簸;
EPOLLERR:表示對應(yīng)的文件描述符發(fā)生錯(cuò)誤乙墙;
EPOLLHUP:表示對應(yīng)的文件描述符被掛斷;
EPOLLET: 將EPOLL設(shè)為邊緣觸發(fā)(Edge Triggered)模式生均,這是相對于水平觸發(fā)(Level Triggered)來說的听想。
EPOLLONESHOT:只監(jiān)聽一次事件,當(dāng)監(jiān)聽完這次事件之后马胧,如果還需要繼續(xù)監(jiān)聽這個(gè)socket的話汉买,需要再次把這個(gè)socket加入到EPOLL隊(duì)列里

3. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
等待事件的產(chǎn)生,類似于select()調(diào)用佩脊。參數(shù)events用來從內(nèi)核得到事件的集合蛙粘,maxevents告之內(nèi)核這個(gè)events有多大,這個(gè) maxevents的值不能大于創(chuàng)建epoll_create()時(shí)的size威彰,參數(shù)timeout是超時(shí)時(shí)間(毫秒出牧,0會立即返回,-1將不確定歇盼,也有說法說是永久阻塞)舔痕。該函數(shù)返回需要處理的事件數(shù)目,如返回0表示已超時(shí)旺遮。

4赵讯、關(guān)于ET、LT兩種工作模式:
可以得出這樣的結(jié)論:
ET模式僅當(dāng)狀態(tài)發(fā)生變化的時(shí)候才獲得通知,這里所謂的狀態(tài)的變化并不包括緩沖區(qū)中還有未處理的數(shù)據(jù),也就是說,如果要采用ET模式,需要一直read/write直到出錯(cuò)為止,很多人反映為什么采用ET模式只接收了一部分?jǐn)?shù)據(jù)就再也得不到通知了,大多因?yàn)檫@樣;而LT模式是只要有數(shù)據(jù)沒有處理就會一直通知下去的.

那么究竟如何來使用epoll呢耿眉?其實(shí)非常簡單边翼。
通過在包含一個(gè)頭文件#include <sys/epoll.h> 以及幾個(gè)簡單的API將可以大大的提高你的網(wǎng)絡(luò)服務(wù)器的支持人數(shù)。

首先通過create_epoll(int maxfds)來創(chuàng)建一個(gè)epoll的句柄鸣剪,其中maxfds為你epoll所支持的最大句柄數(shù)组底。這個(gè)函數(shù)會返回一個(gè)新的epoll句柄,之后的所有操作將通過這個(gè)句柄來進(jìn)行操作筐骇。在用完之后债鸡,記得用close()來關(guān)閉這個(gè)創(chuàng)建出來的epoll句柄。

之后在你的網(wǎng)絡(luò)主循環(huán)里面铛纬,每一幀的調(diào)用epoll_wait(int epfd, epoll_event events, int max events, int timeout)來查詢所有的網(wǎng)絡(luò)接口厌均,看哪一個(gè)可以讀,哪一個(gè)可以寫了告唆」妆祝基本的語法為:
nfds = epoll_wait(kdpfd, events, maxevents, -1);
其中kdpfd為用epoll_create創(chuàng)建之后的句柄晶密,events是一個(gè)epoll_event*的指針,當(dāng)epoll_wait這個(gè)函數(shù)操作成功之后模她,epoll_events里面將儲存所有的讀寫事件稻艰。max_events是當(dāng)前需要監(jiān)聽的所有socket句柄數(shù)。最后一個(gè)timeout是 epoll_wait的超時(shí)侈净,為0的時(shí)候表示馬上返回尊勿,為-1的時(shí)候表示一直等下去,直到有事件范圍畜侦,為任意正整數(shù)的時(shí)候表示等這么長的時(shí)間(單位:毫秒)元扔,如果一直沒有事件,則返回夏伊。一般如果網(wǎng)絡(luò)主循環(huán)是單獨(dú)的線程的話摇展,可以用-1來等吻氧,這樣可以保證一些效率溺忧,如果是和主邏輯在同一個(gè)線程的話,則可以用0來保證主循環(huán)的效率盯孙。

epoll_wait范圍之后應(yīng)該是一個(gè)循環(huán)鲁森,遍利所有的事件。

幾乎所有的epoll程序都使用下面的框架:

    for( ; ; )
    {
        nfds = epoll_wait(epfd,events,20,500);
        for(i=0;i<nfds;++i)
        {
            if(events[i].data.fd==listenfd) //有新的連接
            {
                connfd = accept(listenfd,(sockaddr *)&clientaddr, &clilen); //accept這個(gè)連接
                ev.data.fd=connfd;
                ev.events=EPOLLIN|EPOLLET;
                epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev); //將新的fd添加到epoll的監(jiān)聽隊(duì)列中
            }
            else if( events[i].events&EPOLLIN ) //接收到數(shù)據(jù)振惰,讀socket
            {
                n = read(sockfd, line, MAXLINE)) < 0    //讀
                ev.data.ptr = md;     //md為自定義類型歌溉,添加數(shù)據(jù)
                ev.events=EPOLLOUT|EPOLLET;
                epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);//修改標(biāo)識符,等待下一個(gè)循環(huán)時(shí)發(fā)送數(shù)據(jù)骑晶,異步處理的精髓
            }
            else if(events[i].events&EPOLLOUT) //有數(shù)據(jù)待發(fā)送痛垛,寫socket
            {
                struct myepoll_data* md = (myepoll_data*)events[i].data.ptr;    //取數(shù)據(jù)
                sockfd = md->fd;
                send( sockfd, md->ptr, strlen((char*)md->ptr), 0 );        //發(fā)送數(shù)據(jù)
                ev.data.fd=sockfd;
                ev.events=EPOLLIN|EPOLLET;
                epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev); //修改標(biāo)識符,等待下一個(gè)循環(huán)時(shí)接收數(shù)據(jù)
            }
            else
            {
                //其他的處理
            }
        }
    }

下面給出一個(gè)完整的服務(wù)器端例子:

#include <iostream>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>

using namespace std;

#define MAXLINE 5
#define OPEN_MAX 100
#define LISTENQ 20
#define SERV_PORT 5000
#define INFTIM 1000

void setnonblocking(int sock)
{
    int opts;
    opts=fcntl(sock,F_GETFL);
    if(opts<0)
    {
        perror("fcntl(sock,GETFL)");
        exit(1);
    }
    opts = opts|O_NONBLOCK;
    if(fcntl(sock,F_SETFL,opts)<0)
    {
        perror("fcntl(sock,SETFL,opts)");
        exit(1);
    }
}

int main(int argc, char* argv[])
{
    int i, maxi, listenfd, connfd, sockfd,epfd,nfds, portnumber;
    ssize_t n;
    char line[MAXLINE];
    socklen_t clilen;

    if ( 2 == argc )
    {
        if( (portnumber = atoi(argv[1])) < 0 )
        {
            fprintf(stderr,"Usage:%s portnumber/a/n",argv[0]);
            return 1;
        }
    }
    else
    {
        fprintf(stderr,"Usage:%s portnumber/a/n",argv[0]);
        return 1;
    }

    //聲明epoll_event結(jié)構(gòu)體的變量,ev用于注冊事件,數(shù)組用于回傳要處理的事件 
    struct epoll_event ev,events[20];
    //生成用于處理accept的epoll專用的文件描述符 
    epfd=epoll_create(256);
    struct sockaddr_in clientaddr;
    struct sockaddr_in serveraddr;
    listenfd = socket(AF_INET, SOCK_STREAM, 0);
    //把socket設(shè)置為非阻塞方式 
    //setnonblocking(listenfd); 
    //設(shè)置與要處理的事件相關(guān)的文件描述符 
    ev.data.fd=listenfd;
    //設(shè)置要處理的事件類型 
    ev.events=EPOLLIN|EPOLLET;
    //ev.events=EPOLLIN; 
    //注冊epoll事件 
    epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);
    bzero(&serveraddr, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    char *local_addr="127.0.0.1";
    inet_aton(local_addr,&(serveraddr.sin_addr));//htons(portnumber); 
    serveraddr.sin_port=htons(portnumber);
    bind(listenfd,(sockaddr *)&serveraddr, sizeof(serveraddr));
    listen(listenfd, LISTENQ);
    maxi = 0;
    for ( ; ; ) {
        //等待epoll事件的發(fā)生 
        nfds=epoll_wait(epfd,events,20,500);
        //處理所發(fā)生的所有事件 
        for(i=0;i<nfds;++i)
        {
            if(events[i].data.fd==listenfd)//如果新監(jiān)測到一個(gè)SOCKET用戶連接到了綁定的SOCKET端口桶蛔,建立新的連接匙头。 
            {
                connfd = accept(listenfd,(sockaddr *)&clientaddr, &clilen);
                if(connfd<0){
                    perror("connfd<0");
                    exit(1);
                }
                //setnonblocking(connfd); 
                char *str = inet_ntoa(clientaddr.sin_addr);
                cout << "accapt a connection from " << str << endl;
                //設(shè)置用于讀操作的文件描述符 
                ev.data.fd=connfd;
                //設(shè)置用于注測的讀操作事件 
                ev.events=EPOLLIN|EPOLLET;
                //ev.events=EPOLLIN; 
                //注冊ev 
                epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev);
            }
            else if(events[i].events&EPOLLIN)//如果是已經(jīng)連接的用戶,并且收到數(shù)據(jù)仔雷,那么進(jìn)行讀入蹂析。 
            {
                cout << "EPOLLIN" << endl;
                if ( (sockfd = events[i].data.fd) < 0)
                    continue;
                if ( (n = read(sockfd, line, MAXLINE)) < 0) {
                    if (errno == ECONNRESET) {
                        close(sockfd);
                        events[i].data.fd = -1;
                    } else
                        std::cout<<"readline error"<<std::endl;
                } else if (n == 0) {
                    close(sockfd);
                    events[i].data.fd = -1;
                }
                line[n] = '/0';
                cout << "read " << line << endl;
                //設(shè)置用于寫操作的文件描述符 
                ev.data.fd=sockfd;
                //設(shè)置用于注測的寫操作事件 
                ev.events=EPOLLOUT|EPOLLET;
                //修改sockfd上要處理的事件為EPOLLOUT 
                epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev); 
            }
            else if(events[i].events&EPOLLOUT) // 如果有數(shù)據(jù)發(fā)送 
            {
                sockfd = events[i].data.fd;
                write(sockfd, line, n);
                //設(shè)置用于讀操作的文件描述符 
                ev.data.fd=sockfd;
                //設(shè)置用于注測的讀操作事件 
                ev.events=EPOLLIN|EPOLLET;
                //修改sockfd上要處理的事件為EPOLIN 
                epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);
            }
        }
    }
    return 0;
}

客戶端直接連接到這個(gè)服務(wù)器就好了。碟婆。

引用:http://blog.chinaunix.net/u/16292/showart_1844376.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末电抚,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子竖共,更是在濱河造成了極大的恐慌蝙叛,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,817評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件公给,死亡現(xiàn)場離奇詭異借帘,居然都是意外死亡锻煌,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評論 3 385
  • 文/潘曉璐 我一進(jìn)店門姻蚓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來宋梧,“玉大人,你說我怎么就攤上這事狰挡∥媪洌” “怎么了?”我有些...
    開封第一講書人閱讀 157,354評論 0 348
  • 文/不壞的土叔 我叫張陵加叁,是天一觀的道長倦沧。 經(jīng)常有香客問我,道長它匕,這世上最難降的妖魔是什么展融? 我笑而不...
    開封第一講書人閱讀 56,498評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮豫柬,結(jié)果婚禮上告希,老公的妹妹穿的比我還像新娘。我一直安慰自己烧给,他們只是感情好燕偶,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,600評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著础嫡,像睡著了一般指么。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上榴鼎,一...
    開封第一講書人閱讀 49,829評論 1 290
  • 那天伯诬,我揣著相機(jī)與錄音,去河邊找鬼巫财。 笑死盗似,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的翁涤。 我是一名探鬼主播桥言,決...
    沈念sama閱讀 38,979評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼葵礼!你這毒婦竟也來了号阿?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,722評論 0 266
  • 序言:老撾萬榮一對情侶失蹤鸳粉,失蹤者是張志新(化名)和其女友劉穎扔涧,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,189評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡枯夜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,519評論 2 327
  • 正文 我和宋清朗相戀三年弯汰,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片湖雹。...
    茶點(diǎn)故事閱讀 38,654評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡咏闪,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出摔吏,到底是詐尸還是另有隱情鸽嫂,我是刑警寧澤,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布征讲,位于F島的核電站据某,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏诗箍。R本人自食惡果不足惜癣籽,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,940評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望滤祖。 院中可真熱鬧筷狼,春花似錦、人聲如沸氨距。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,762評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽俏让。三九已至,卻和暖如春茬暇,著一層夾襖步出監(jiān)牢的瞬間首昔,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,993評論 1 266
  • 我被黑心中介騙來泰國打工糙俗, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留勒奇,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,382評論 2 360
  • 正文 我出身青樓巧骚,卻偏偏與公主長得像赊颠,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子劈彪,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,543評論 2 349

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