原文地址:http://www.cnblogs.com/haippy/archive/2012/01/09/2317269.html
------------------------
epoll簡介
epoll 是Linux核心中的一種可擴充IO事件處理機制,最早在 Linux 2.5.44核心中引入,可被用於代替POSIX select 和 poll 系統調用,並且在具有大量應用程式請求時能夠獲得較好的效能( 此時被監視的檔案描述符數目非常大,與舊的 select 和 poll 系統調用完成操作所需 O(n) 不同, epoll能在O(1)時間內完成操作,epool用了特殊的結構體epoll_data,裡麵包含了IO請求的詳細資料,當IO活躍時,根據epool_data裡存的資訊,在O(1)的時間內就可以定位的IO事件,不必掃描整個fd集合,所以效能相當高),epoll 與 FreeBSD的kqueue類似,都向使用者空間提供了自己的檔案描述符來進行操作。
int epoll_create(int size);
建立一個epoll的控制代碼,size用來告訴核心需要監聽的數目一共有多大。當建立好epoll控制代碼後,它就是會佔用一個fd值,在linux下如果查看/proc/進程id/fd/,是能夠看到這個fd的,所以在使用完epoll後,必須調用close() 關閉,否則可能導致fd被耗盡。
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll的事件註冊函數,第一個參數是 epoll_create() 的傳回值,第二個參數表示動作,使用如下三個宏來表示:
EPOLL_CTL_ADD //註冊新的fd到epfd中;EPOLL_CTL_MOD //修改已經註冊的fd的監聽事件;EPOLL_CTL_DEL //從epfd中刪除一個fd;
第三個參數是需要監聽的fd,第四個參數是告訴核心需要監聽什麼事,struct epoll_event 結構如下:
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 可以是以下幾個宏的集合:
EPOLLIN //表示對應的檔案描述符可以讀(包括對端SOCKET正常關閉);EPOLLOUT //表示對應的檔案描述符可以寫;EPOLLPRI //表示對應的檔案描述符有緊急的資料可讀(這裡應該表示有帶外資料到來);EPOLLERR //表示對應的檔案描述符發生錯誤;EPOLLHUP //表示對應的檔案描述符被掛斷;EPOLLET //將EPOLL設為邊緣觸發(Edge Triggered)模式,這是相對於水平觸發(Level Triggered)來說的。EPOLLONESHOT//只監聽一次事件,當監聽完這次事件之後,如果還需要繼續監聽這個socket的話,需要再次把這個socket加入到EPOLL隊列裡。
當對方關閉串連(FIN), EPOLLERR,都可以認為是一種EPOLLIN事件,在read的時候分別有0,-1兩個傳回值。
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
參數events用來從核心得到事件的集合,maxevents 告之核心這個events有多大,這個 maxevents 的值不能大於建立 epoll_create() 時的size,參數 timeout 是逾時時間(毫秒,0會立即返回,-1將不確定,也有說法說是永久阻塞)。該函數返回需要處理的事件數目,如返回0表示已逾時。
EPOLL事件有兩種模型 Level Triggered (LT) 和 Edge Triggered (ET):
LT(level triggered,水平觸發模式)是預設的工作方式,並且同時支援 block 和 non-block socket。在這種做法中,核心告訴你一個檔案描述符是否就緒了,然後你可以對這個就緒的fd進行IO操作。如果你不作任何操作,核心還是會繼續通知你的,所以,這種模式編程出錯誤可能性要小一點。
ET(edge-triggered,邊緣觸發模式)是高速工作方式,只支援no-block socket。在這種模式下,當描述符從未就緒變為就緒時,核心通過epoll告訴你。然後它會假設你知道檔案描述符已經就緒,並且不會再為那個檔案描述符發送更多的就緒通知,等到下次有新的資料進來的時候才會再次出發就緒事件。 epoll 例子
我們將實現一個簡單的TCP 伺服器,該迷你伺服器將會在標準輸出上列印處用戶端發送的資料,首先我們建立並綁定一個 TCP 通訊端:
static intcreate_and_bind (char *port){ struct addrinfo hints; struct addrinfo *result, *rp; int s, sfd; memset (&hints, 0, sizeof (struct addrinfo)); hints.ai_family = AF_UNSPEC; /* Return IPv4 and IPv6 choices */ hints.ai_socktype = SOCK_STREAM; /* We want a TCP socket */ hints.ai_flags = AI_PASSIVE; /* All interfaces */ s = getaddrinfo (NULL, port, &hints, &result); if (s != 0) { fprintf (stderr, "getaddrinfo: %s\n", gai_strerror (s)); return -1; } for (rp = result; rp != NULL; rp = rp->ai_next) { sfd = socket (rp->ai_family, rp->ai_socktype, rp->ai_protocol); if (sfd == -1) continue; s = bind (sfd, rp->ai_addr, rp->ai_addrlen); if (s == 0) { /* We managed to bind successfully! */ break; } close (sfd); } if (rp == NULL) { fprintf (stderr, "Could not bind\n"); return -1; } freeaddrinfo (result); return sfd;}
create_and_bind() 包含了如何建立 IPv4 和 IPv6 通訊端的代碼塊,它接受一字串作為連接埠參數,並在 result 中返回一個 addrinfo 結構,
struct addrinfo{ int ai_flags; int ai_family; int ai_socktype; int ai_protocol; size_t ai_addrlen; struct sockaddr *ai_addr; char *ai_canonname; struct addrinfo *ai_next;};
如果函數成功則返回通訊端,如果失敗,則返回 -1,
下面,我們將一個通訊端設定為非阻塞形式,函數如下:
static intmake_socket_non_blocking (int sfd){ int flags, s; flags = fcntl (sfd, F_GETFL, 0); if (flags == -1) { perror ("fcntl"); return -1; } flags |= O_NONBLOCK; s = fcntl (sfd, F_SETFL, flags); if (s == -1) { perror ("fcntl"); return -1; } return 0;}
接下來,便是主函數代碼,主要用於事件迴圈:
#define MAXEVENTS 64intmain (int argc, char *argv[]){ int sfd, s; int efd; struct epoll_event event; struct epoll_event *events; if (argc != 2) { fprintf (stderr, "Usage: %s [port]\n", argv[0]); exit (EXIT_FAILURE); } sfd = create_and_bind (argv[1]); if (sfd == -1) abort (); s = make_socket_non_blocking (sfd); if (s == -1) abort (); s = listen (sfd, SOMAXCONN); if (s == -1) { perror ("listen"); abort (); } efd = epoll_create1 (0); if (efd == -1) { perror ("epoll_create"); abort (); } event.data.fd = sfd; event.events = EPOLLIN | EPOLLET; s = epoll_ctl (efd, EPOLL_CTL_ADD, sfd, &