linux epoll詳解及使用方法概述
一、什麼是epoll
於Linux 2.5.44首度登場的epoll是Linux核心的可擴充套件I/O事件通知機制。它設計目的只在取代既有POSIX select(2)與poll(2)系統函式,讓需要大量操作檔案描述符的程式得以發揮更優異的效能(舉例來說:舊有的系統函式所花費的時間複雜度為O(n),epoll則耗時O(1))。---維基百科
簡要概括其主要特性:
1、 和select poll一樣,一種I/O多路複用技術;
2、 只關注活躍的連線,而無需遍歷所有的描述符集合;
3、 可以處理大量的連結請求(最大可以到系統可以開啟的檔案數目 /proc/sys/fs/file-max)
二、和select、poll的比較
相比於select,epoll最大的好處在於它不會隨著監聽fd數目的增長而降低效率。因為在核心中的select實現中,它是採用輪詢來處理的,輪詢的fd數目越多,自然耗時越多。並且,在linux/posix_types.h標頭檔案有這樣的宣告:
#define __FD_SETSIZE 1024
表示select最多同時監聽1024個fd,當然,可以通過修改標頭檔案再重編譯核心來擴大這個數目,但這似乎並不治本。
而poll基本上效率和select是相同的。
Epoll沒有最大併發連線的限制,上限是最大可以開啟檔案的數目,這個數字一般遠大於2048, 一般來說這個數目和系統記憶體關係很大,具體數目可以cat/proc/sys/fs/file-max察看。Epoll
三、epoll高效的原因
對於select模型當有I/O事件到來時,select通知應用程式有事件到了快去處理,而應用程式必須輪詢所有的FD集合,測試每個FD是否有事件發生,並處理事件;程式碼像下面這樣:
int res = select(maxfd+1, &readfds, NULL, NULL, 120); if(res > 0) { for(int i = 0; i < MAX_CONNECTION; i++) { if(FD_ISSET(allConnection[i],&readfds)) { handleEvent(allConnection[i]); } } } // if(res == 0) handle timeout, res < 0 handle error
Epoll不僅會告訴應用程式有I/0事件到來,還會告訴應用程式相關的資訊,這些資訊是應用程式填充的,因此根據這些資訊應用程式就能直接定位到事件,而不必遍歷整個FD集合。
int res = epoll_wait(epfd, events, 20, 120);
for(int i = 0; i < res;i++)
{
handleEvent(events[n]);
}
比如當伺服器處理200個連線時,它將為需要寫入或讀取資料的每個連線提供服務,然後需要等待有更多的工作要做。當他在等待的時候,200個連線中的任意一個連線接收到資料,服務將被中斷。
使用select時,核心會將程序新增到200個等待list中,每個連線一個,要做到這一點,需要一個“thunk”來將程序附加到等待list中。當程序最終醒來的時候,需要從所有200個等待list中移除,所有的thunk需要被釋放。
相比於epoll,epoll的socket自己有一個等待list,該過程只需要使用一個等待list,只使用一個”thunk”。當程序被喚醒時,它只需要從一個等待列表中刪除,只有一個thunk需要被清除,使用epoll時,epoll套接字本身attach到200個連線中的每一個,但是這僅需要在最初的時候做一次,當各個連線被刪除時,它也僅需銷燬一次。相比而言,每個呼叫select的阻塞操作必須將程序新增到正在監視的每個套接字的每個等待佇列中。
對於select,最大的開銷在於檢測沒有活動的socket是否開始有活動了,對於epoll,不需要檢測那些沒有活動的socket,因為當活動發生時,它將會通知epoll的socket,每次呼叫select檢視是否有活動時,select會輪詢各個socket,而epoll是讓活動的套接字自己通知程序。
四、epoll的使用
1、建立epoll
/**
* @param size 告訴核心監聽的數目
*
* @returns 返回一個epoll控制代碼(即一個檔案描述符)
*/
int epoll_create(int size);
建立一個epoll的控制代碼,size用來告訴核心這個監聽的數目一共有多大。其實是申請一個核心空間,用來存放你想關注的socketfd上是否發生以及發生了什麼事件。需要注意的是,當建立好epoll控制代碼後,它就是會佔用一個fd值,在linux下如果檢視/proc/程序id/fd/,是能夠看到這個fd的,所以在使用完epoll後,必須呼叫close()關閉,否則可能導致fd被耗盡。
2、控制epoll
/**
* @param epfd 用epoll_create所建立的epoll控制代碼
* @param op 表示對epoll監控描述符控制的動作
*
* EPOLL_CTL_ADD(註冊新的fd到epfd)
* EPOLL_CTL_MOD(修改已經註冊的fd的監聽事件)
* EPOLL_CTL_DEL(epfd刪除一個fd)
*
* @param fd 需要監聽的檔案描述符
* @param event 告訴核心需要監聽的事件
*
* @returns 成功返回0,失敗返回-1, errno檢視錯誤資訊
*/
int epoll_ctl(int epfd, int op, int fd,
struct epoll_event *event);
struct epoll_event {
__uint32_t events; /* epoll 事件 */
epoll_data_t data; /* 使用者傳遞的資料 */
}
/*
* events : {EPOLLIN, EPOLLOUT, EPOLLPRI,
EPOLLHUP, EPOLLET, EPOLLONESHOT}
*/
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event new_event;
new_event.events = EPOLLIN | EPOLLOUT;
new_event.data.fd = 5;
epoll_ctl(epfd, EPOLL_CTL_ADD, 5, &new_event);
epoll的事件註冊函式,它不同與select()是在監聽事件時告訴核心要監聽什麼型別的事件,而是在這裡先註冊要監聽的事件型別。
3、等待epoll
/**
*
* @param epfd 用epoll_create所建立的epoll控制代碼
* @param event 從核心得到的事件集合
* @param maxevents 告知核心這個events有多大,
* 注意: 值 不能大於建立epoll_create()時的size.
* @param timeout 超時時間
* -1: 永久阻塞
* 0: 立即返回,非阻塞
* >0: 指定微秒
*
* @returns 成功: 有多少檔案描述符就緒,時間到時返回0
* 失敗: -1, errno 檢視錯誤
*/
int epoll_wait(int epfd, struct epoll_event *event,
int maxevents, int timeout);
struct epoll_event my_event[1000];
int event_cnt = epoll_wait(epfd, my_event, 1000, -1);
等待IO事件的發生,類似於select()呼叫。
4、epoll程式設計框架
//建立 epoll
int epfd = epoll_crete(1000);
//將 listen_fd 新增進 epoll 中
epoll_ctl(epfd, EPOLL_CTL_ADD, listen_fd, &listen_event);
while (1) {
//阻塞等待 epoll 中 的fd 觸發
int active_cnt = epoll_wait(epfd, events, 1000, -1);
for (i = 0; i < active_cnt; i++) {
if (evnets[i].data.fd == listen_fd) {
//accept. 並且將新accept 的fd 加進epoll中.
} else if (events[i].events & EPOLLIN) {
//對此fd 進行讀操作
} else if (events[i].events & EPOLLOUT) {
//對此fd 進行寫操作
}
}
}
參考:
http://blog.csdn.net/sparkliang/article/details/4770655#
http://stackoverflow.com/questions/17355593/why-is-epoll-faster-than-select/17355702#17355702
http://blog.csdn.net/ljx0305/article/details/4065058