在epoll模型的講解中,這篇文章寫的非常簡單易懂,因此翻譯過來分享給大家,做爲epoll的入門。
原文地址:http://kovyrin.net/2006/04/13/epoll-asynchronous-network-programming/
一般情況下,應用“每個連接應用一個線程”的方式來實現TCP Server。但是在高負載的情況時,這種處理並不是很高效,因此需要應用其他的連接處理模型。在這篇文章中,將討論怎麼應用epoll來構建TCP Server的異步連接。
epoll是在Linux 2.6提出的新的系統調用,來代替被棄用的select(也包括poll)。select和poll的時間複雜度爲O(n),而epoll的時間複雜度爲O(1) - 這說明當被監聽的描述符不斷增加時,epoll仍然表現很好。select使用線性搜索方式,這導致它的時間複雜讀爲O(n),因此爲了提高效率epoll則在內核文件架構中應用callback機制。
epoll中另一個根本的不同是epoll可以使用邊沿觸發,區別與水平觸發。這說明當內核察覺文件描述符爲I/O的就緒狀態時你就會接受到“提示”(文件描述符可進行I/O操作的一瞬間),而不是被告知“描述符可以進行I/O操作”(只要文件描述符可以可進行I/O操作)。這樣的方式由很多小優勢:內核不必保持文件描述符的狀態,而是給用戶空間拋出異常,同時令用戶空間的程序變得更靈活(如,可以忽略總是可讀的狀態)。
應用epoll方法你需要在程序中完成以下幾個步驟:
創建epoll調用的描述符:
epfd = epoll_create(EPOLL_QUEUE_LEN);
EPOLL_QUEUE_LEN
是你想要同時管理的連接描述符的最大值。返回值epfd
將在接下來的epoll調用中使用。這個描述符可以被關閉,當在後續程序中不需要使用時。完成了第一步後,你可以通過如下調用將需要監聽的描述符添加到epoll中。
static struct epoll_event ev;
int client_sock;
...
ev.events = EPOLLIN | EPOLLPRI | EPOLLERR | EPOLLHUP;
ev.data.fd = client_sock;
int res = epoll_ctl(epfd, EPOLL_CTL_ADD, client_sock, &ev);
ev
是epoll事件配置結構體,EPOLL_CTL_ADD
- 預定義的常量以用來添加socket到epoll中。epoll_ctrl
更詳細的信息參考epoll_ctrl(2)的man頁。當client_sock
被關閉時,它將自動在epoll中被刪除。
- 當需要監聽的描述符添加到epoll中後,你的程序將空閒或者等待以對epoll中socket進行操作。
while (1) {
// wait for something to do...
int nfds = epoll_wait(epfd, events,
MAX_EPOLL_EVENTS_PER_RUN,
EPOLL_RUN_TIMEOUT);
if (nfds < 0) die("Error in epoll_wait!");
// for each ready socket
for(int i = 0; i < nfds; i++) {
int fd = events[i].data.fd;
handle_io_on_socket(fd);
}
}
下面介紹了網絡應用使用中的典型架構,這個架構在單進程和多進程中擴展性都非常好:
- Listener - 在一個線程中執行
bind()
和listen()
調用,同時等待連接接入。當有連接接入後,這個線程可以在監聽的socket上調用accept()
,同時將已連入的連接發送給一個I/O-workers. - I/O-worker(s) - 一個或多個線程從linsener中接收連接並將他們添加到epoll中。
- Data Processing worker(s) - 一個或多個線程從I/O-workers中寫入或讀取數據,並進行處理。
如你所見,epoll()
API相當簡單,但是卻很強大。線性擴展將允許你管理大量的平行連接,這相比與一個連接一個進程來比,應用了更少的進程。