epoll使用詳解

在Linux網(wǎng)絡(luò)編程當(dāng)中,很長時間都是使用select來做事件的觸發(fā),而在新的linux內(nèi)核當(dāng)中,有一種替換他的機(jī)制,就是epoll()//#include <sys/epoll.h>。
相對于select,epoll的好處就是更加靈活,沒有描述符的限制,且不會隨著監(jiān)聽fd數(shù)目的增長而降低效率。因為在內(nèi)核中的select的實現(xiàn)使用的是輪詢機(jī)制,輪詢的fd數(shù)目越多,耗時就越多,并且在linux/posix_types.h頭文件中有這樣的聲明:#define __FD_SETSIZE 1024 當(dāng)然可以通過修改頭文件,再重新編譯內(nèi)核來擴(kuò)大這個數(shù)目,但是這似乎并不治本。epoll使用一個文件描述符管理多個文件描述符,將用戶關(guān)系的文件描述符的事件存放到內(nèi)核的一個事件表當(dāng)中,這樣在用戶空間和內(nèi)核空間的copy只需要一次。

輪詢(polling)

  • 是一種CPU決策如何提供周邊設(shè)備的服務(wù)方式,又稱為程序控制輸入輸出(programmed I/O)。輪詢由CPU定時發(fā)出詢問,依序詢問每一個周邊設(shè)備是否需要其服務(wù),有需要即給予服務(wù),服務(wù)結(jié)束后再詢問下一個周邊,接著不斷地周而復(fù)始。輪詢法容易實現(xiàn),但是效率偏低。

epoll的接口非常簡單,一共就三個函數(shù)

  1. int epoll_create(int size); 創(chuàng)建一個epoll的句柄,size用來告訴內(nèi)核這個監(jiān)聽的數(shù)目一共有多大(即你的epoll所支持的最大句柄數(shù)注意和select()的最大描述符值+1相區(qū)別)。這個參數(shù)不同于select中的第一個參數(shù)給出最大監(jiān)聽描述符的fd+1的值。需要注意的是,當(dāng)創(chuàng)建好epoll句柄以后,它就是會占用一個fd值,在linux的/proc/進(jìn)程ID/fd/下使能夠看到這個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)聽事件時告訴內(nèi)核要監(jiān)聽什么類型的事件不同,epoll在這里先注冊要監(jiān)聽的事件類型(讀、寫、異常)。
  1. 第一個參數(shù)是epoll_create()的返回值。
  2. 第二個參數(shù)表示動作。使用三個宏表示:EPOLL_CTL_ADD:注冊新的fd到epfd中;EPOLL_CTL_MOD:修改已經(jīng)注冊的fd的監(jiān)聽事件;EPOLL_CTL_DEL:從epfd中刪除某個fd。
  3. 第三個參數(shù)是需要監(jiān)聽的fd。
  4. 第四個參數(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 event; epoll_data_t data; }events可以是以下的幾個宏的集合:EPOLLIN:表示對應(yīng)的文件描述符可讀(包括對端socket正常關(guān)閉)。EPOLLOUT:表示對應(yīng)的文件描述符可寫;EPOLLPRI:表示對應(yīng)的文件描述符有緊急的可讀數(shù)據(jù)(此處應(yīng)該表示有帶外數(shù)據(jù)到來);
    EPOLLERR:表示對應(yīng)的描述符發(fā)生錯誤;EPOLLHUP:標(biāo)識對應(yīng)的文件描述符被掛斷;EPOLLET:將EPOLL設(shè)置為邊緣觸發(fā)(Edge Triggered)模式,相對于水平觸發(fā)。EPOLLONESHOT:只監(jiān)聽一次事件,當(dāng)監(jiān)聽完這次事件之后,若還需要繼續(xù)監(jiān)聽這個socket的話,需要再次把這個socket加入到EPOLL隊列當(dāng)中。
  1. *int epoll_wait(int epfd,struct epoll_event events,int maxevents,int timeout);等待事件的發(fā)生,類似于select()調(diào)用,參數(shù)events用來從內(nèi)核得到事件的集合,maxevents告訴內(nèi)核這個events有多大,這個maxevents的值不能大于創(chuàng)建epoll_create()時的size,參數(shù)timeout是超時的時間(0:立即返回,-1:永久阻塞)。函數(shù)返回需要處理的事件的數(shù)目。若返回0則表示已超時。

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

首先通過create_epoll(int maxfds)來創(chuàng)建一個epoll的句柄,其中maxfds為你epoll所支持的最大句柄數(shù)。這個函數(shù)會返回一個新的epoll句柄,之后的所有操作將通過這個句柄來進(jìn)行操作。在用完之后,記得用close()來關(guān)閉這個創(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ò)接口,看哪一個可以讀,哪一個可以寫了。基本的語法為:
nfds = epoll_wait(kdpfd, events, maxevents, -1);
其中kdpfd為用epoll_create創(chuàng)建之后的句柄,events是一個epoll_event*的指針,當(dāng)epoll_wait這個函數(shù)操作成功之后,epoll_events里面將儲存所有的讀寫事件。max_events是當(dāng)前需要監(jiān)聽的所有socket句柄數(shù)。最后一個timeout是 epoll_wait的超時,為0的時候表示馬上返回,為-1的時候表示一直等下去,直到有事件范圍,為任意正整數(shù)的時候表示等這么長的時間,如果一直沒有事件,則范圍。一般如果網(wǎng)絡(luò)主循環(huán)是單獨的線程的話,可以用-1來等,這樣可以保證一些效率,如果是和主邏輯在同一個線程的話,則可以用0來保證主循環(huán)的效率。

epoll_wait范圍之后應(yīng)該是一個循環(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這個連接
                ev.data.fd=connfd;
                ev.events=EPOLLIN|EPOLLET;
                epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev); //將新的fd添加到epoll的監(jiān)聽隊列中
            }
            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)識符,等待下一個循環(huán)時發(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)識符,等待下一個循環(huán)時接收數(shù)據(jù)
            }
            else
            {
                //其他的處理
            }
        }
    }

關(guān)于ET、LT兩種工作模式
epoll對文件描述符的操作有兩種模式:LT(level trigger)和ET(edge trigger)。LT模式是默認(rèn)模式,LT模式與ET模式的區(qū)別如下:
  LT模式:當(dāng)epoll_wait檢測到描述符事件發(fā)生并將此事件通知應(yīng)用程序,應(yīng)用程序可以不立即處理該事件。下次調(diào)用epoll_wait時,會再次響應(yīng)應(yīng)用程序并通知此事件。
  ET模式:當(dāng)epoll_wait檢測到描述符事件發(fā)生并將此事件通知應(yīng)用程序,應(yīng)用程序必須立即處理該事件。如果不處理,下次調(diào)用epoll_wait時,不會再次響應(yīng)應(yīng)用程序并通知此事件。
  ET模式在很大程度上減少了epoll事件被重復(fù)觸發(fā)的次數(shù),因此效率要比LT模式高。epoll工作在ET模式的時候,必須使用非阻塞套接口,以避免由于一個文件句柄的阻塞讀/阻塞寫操作把處理多個文件描述符的任務(wù)餓死。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,983評論 6 537
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,772評論 3 422
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,947評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,201評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,960評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,350評論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,406評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,549評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,104評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,914評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,089評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,647評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,340評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,753評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,007評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,834評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,106評論 2 375

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