包含頭文件:html
#include <sys/epoll.h>編程
epoll的接口很是簡單,一共就三個函數:
1. int epoll_create(int size);
建立一個epoll的句柄,size用來告訴內核這個監聽的數目一共有多大。數組
2.int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);併發
epoll的事件註冊函數,它不一樣與select()是在監聽事件時告訴內核要監聽什麼類型的事件,而是在這裏先註冊要監聽的事件類型.異步
第一個參數是epoll_create()的返回值,socket
第二個參數表示動做,用三個宏來表示:
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 */
};oop
events能夠是如下幾個宏的集合;
EPOLLIN :表示對應的文件描述符能夠讀(包括對端SOCKET正常關閉);
EPOLLOUT:表示對應的文件描述符能夠寫;
EPOLLPRI:表示對應的文件描述符有緊急的數據可讀(這裏應該表示有帶外數據到來);
EPOLLERR:表示對應的文件描述符發生錯誤;
EPOLLHUP:表示對應的文件描述符被掛斷;
EPOLLET: 將EPOLL設爲邊緣觸發(Edge Triggered)模式,這是相對於水平觸發(Level Triggered)來講的。
EPOLLONESHOT:只監聽一次事件,當監聽完此次事件以後,若是還須要繼續監聽這個socket的話,須要再次把這個socket加入到EPOLL隊列裏ui
3. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
等待事件的產生,相似於select()調用。
第一個參數:epfd,epoll_create返回的句柄
第二個參數:events用來從內核獲得事件的集合
第三個參數:maxevents告以內核這個events有多大,這個 maxevents的值不能大於建立epoll_create()時的size,
第四個參數timeout是超時時間(毫秒,0會當即返回,-1將不肯定,也有說法說是永久阻塞)。
該函數返回須要處理的事件數目,如返回0表示已超時。
4.close(epollfd)
退出時記得釋放建立的epoll句柄;
五、關於ET、LT兩種工做模式:
能夠得出這樣的結論:
ET模式僅當狀態發生變化的時候纔得到通知,這裏所謂的狀態的變化並不包括緩衝區中還有未處理的數據,也就是說,若是要採用ET模式,須要一直read/write直到出錯爲止,不少人反映爲何採用ET模式只接收了一部分數據就再也得不到通知了,大多由於這樣;
而LT模式是隻要有數據沒有處理就會一直通知下去的.
兩者的差別在於:
level-trigger(LT)模式下只要某個socket處於readable/writable狀態,不管何時進行epoll_wait都會返回該socket;
edge-trigger(ET)模式下只有某個socket從unreadable變爲readable或從unwritable變爲writable時,epoll_wait纔會返回該socket。
因此,在epoll的ET模式下,正確的讀寫方式爲:
讀:只要可讀,就一直讀,直到返回0,或者 errno = EAGAIN
寫:只要可寫,就一直寫,直到數據發送完,或者 errno = EAGAIN
PS:
epoll工做在ET模式的時候,必須使用非阻塞套接口
//設置socket鏈接爲非阻塞模式 void setnonblocking(int sockfd) { int opts; // 獲得文件狀態標誌 opts = fcntl(sockfd, F_GETFL, 0); if(opts < 0) { perror("fcntl(F_GETFL)\n"); exit(1); }
// 設置文件狀態標誌 opts = (opts | O_NONBLOCK); if(fcntl(sockfd, F_SETFL, opts) < 0) { perror("fcntl(F_SETFL)\n"); exit(1); } }
6.
參考網址:
epoll詳解
http://blog.chinaunix.net/uid-24517549-id-4051156.html
高併發的epoll+線程池,epoll在線程池內
http://blog.chinaunix.net/uid-311680-id-2439723.html
基於epoll實現socket編程完整實例
http://blog.chinaunix.net/uid-20771605-id-4596400.html
epoll使用詳解(精髓)
http://blog.csdn.net/ljx0305/article/details/4065058
1)
#define ERR_EXIT(m) do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
//聲明epoll_event結構體的變量,event用於註冊事件,數組用於回傳要處理的事件
struct epoll_event event;
struct epoll_event events[20];
// 生成用於處理accept的epoll專用的文件描述符
int epfd = epoll_create(256);
// 註冊監聽的事件
memset(&event,0,sizeof(event));
event.data.fd = listenFd;
event.events = EPOLLIN | EPOLLET | EPOLLOUT;
// 註冊epoll事件
rlt = epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &event);
if(rlt == -1)
{
ERR_EXIT("註冊事件失敗!\n");
}
struct timeval outtime;
memset(&outtime, 0x00, sizeof(struct timeval));
const int MAX_EVENTS = 1024; // 最大事件數 void *epoll_loop(void* para) { int nfds; // 臨時變量,存放返回值 int epfd; // 監聽用的epoll句柄,epoll_create返回值 //struct epoll_event events[MAX_EVENTS]; // 監聽事件數組 //struct epoll_event event; // struct sockaddr_in client_addr; int i; int connfd; // for(;;) { // 等待epoll事件的發生 nfds = epoll_wait(epfd,events,MAX_EVENTS,-1); // -1: timeout //printf("nfds = %d\n", nfds); // 處理所發生的全部事件 if(nfds > 0) { for(i=0; i<nfds; ++i) { if(events[i].data.fd == listenfd) // 若是新監測到一個SOCKET用戶鏈接到了綁定的SOCKET端口,創建新的鏈接。 { //while(1) //{ // socklen_t cliaddrlen; connfd = accept(listenfd,(struct sockaddr *)&client_addr, &cliaddrlen); if(connfd > 0) { //cout << "AcceptThread, accept:" << connfd << ",errno:" << errno << ",connect:" << inet_ntoa(cliaddr.sin_addr) << ":" << ntohs(cliaddr.sin_port) << endl; event.data.fd=connfd; event.events = EPOLLIN | EPOLLET; // ET模式 epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&event); // //註冊ev.將新的fd添加到epoll的監聽隊列中 //fd_Setnonblocking(event.data.fd); //event.events=EPOLLIN|EPOLLET; //epoll_ctl(epfd,EPOLL_CTL_ADD,event.data.fd,&event); } else { //cout << "AcceptThread, accept:" << connfd << ",errno:" << errno << endl; if (errno == EAGAIN) // 沒有鏈接須要接收了 { break; } else if(errno == EINTR) // 可能被中斷信號打斷,,通過驗證對非阻塞socket並未收到此錯誤,應該能夠省掉該步判斷 { ; } else // 其它狀況能夠認爲該描述字出現錯誤,應該關閉後從新監聽 { //... } } //} } else if(events[i].events & EPOLLIN ) // 接收到數據,讀socket { n = read(sockfd, line, MAXLINE)) < 0; // 讀 ev.data.ptr = md; //md爲自定義類型,添加數據 ev.events=EPOLLOUT|EPOLLET; epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);//修改標識符,等待下一個循環時發送數據,異步處理的精髓 // 或者以下處理: char recvBuf[1024] = {0}; int ret = 999; int rs = 1; while(rs) { ret = recv(events[i].data.fd, recvBuf, 1024, 0);// 接受客戶端消息 if(ret < 0) { // 因爲是非阻塞的模式,因此當errno爲EAGAIN時,表示當前緩衝區已無數據可讀,在這裏就看成是該次事件已處理過。 if(errno == EAGAIN) { printf("EAGAIN\n"); break; } else { printf("recv error!\n"); epoll_ctl(epfd, EPOLL_CTL_DEL, events[i].data.fd, &event); // 其中 struct epoll_event event; // memset(&event,0,sizeof(event)); // event.data.fd=listenfd; // event.events=EPOLLIN|EPOLLET; close(events[i].data.fd); break; } } else if(ret == 0) { // 這裏表示對端的socket已正常關閉. rs = 0; } if(ret == sizeof(recvBuf)) { rs = 1; // 須要再次讀取 } else { rs = 0; } } } else if(events[i].events & EPOLLOUT) // 有數據待發送,寫socket {
struct myepoll_data* md = (myepoll_data*)events[i].data.ptr; //取數據
sockfd = md->fd;
send( sockfd, md->ptr, strlen((char*)md->ptr), 0 ); //發送數據
event.data.fd=sockfd;
event.events=EPOLLIN|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev); //修改標識符,等待下一個循環時接收數據
//sprintf(buf,"HTTP/1.0 200 OK\r\nContent-type: text/plain\r\n\r\n%s","Hello world!\n"); send(events[i].data.fd,buf,strlen(buf),0); close(events[i].data.fd); } else { close(events[i].data.fd); } //else if (events[i].events & EPOLLERR || events[i].events & EPOLLHUP) // 有異常發生 //{ // //此時說明該描述字已經出錯了,須要從新建立和監聽 // //close(listenfd); // // epoll_ctl(epfd, EPOLL_CTL_DEL, listenfd, &event); // // 建立監聽socket // //listenfd = socket(AF_INET, SOCK_STREAM, 0); // //... //} //epoll_ctl(epfd, EPOLL_CTL_DEL, events[i].data.fd, &event); //close(events[i].data.fd); } } } }