https://blog.csdn.net/lianghe_work/article/details/46544567
一、epoll概述
epoll 是在 2.6 內核中提出的,是之前的 select() 和 poll() 的增強版本。相對于 select() 和 poll() 來說,epoll 更加靈活,沒有描述符限制。epoll 使用一個文件描述符管理多個描述符,將用戶關系的文件描述符的事件存放到內核的一個事件表中,這樣在用戶空間和內核空間的 copy 只需一次。
二、epoll操作過程需要的四個接口函數
四接口函數分別是:
int epoll_create(int size); int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); int close(int epfd);int epoll_create(int size);
功能:
該函數生成一個epoll專用的文件描述符(其余的接口函數一般都用使用這個專用的文件描述符)
參數:
size: 用來告訴內核這個監聽的數目一共有多大,參數 size 并不是限制了 epoll 所能監聽的描述符最大個數,只是對內核初始分配內部數據結構的一個建議。自從 linux 2.6.8 之后,size 參數是被忽略的,也就是說可以填只有大于 0 的任意值。需要注意的是,當創建好 epoll 句柄后,它就是會占用一個 fd 值,在 linux 下如果查看 /proc/ 進程 id/fd/,是能夠看到這個 fd 的,所以在使用完 epoll 后,必須調用 close() 關閉,否則可能導致 fd 被耗盡
返回值:
成功:epoll 專用的文件描述符
失敗:-1
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
功能:
epoll 的事件注冊函數,它不同于 select() 是在監聽事件時告訴內核要監聽什么類型的事件,而是在這里先注冊要監聽的事件類型
參數:
epfd: epoll 專用的文件描述符,epoll_create()的返回值
op: 表示動作,用三個宏來表示:
fd: 需要監聽的文件描述符EPOLL_CTL_ADD:注冊新的 fd 到 epfd 中;
EPOLL_CTL_MOD:修改已經注冊的fd的監聽事件;
EPOLL_CTL_DEL:從 epfd 中刪除一個 fd;event: 告訴內核要監聽什么事件,struct epoll_event?結構如下:
events?可以是以下幾個宏的集合:
// 保存觸發事件的某個文件描述符相關的數據(與具體使用方式有關) 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 */ };
EPOLLIN?:表示對應的文件描述符可以讀(包括對端 SOCKET 正常關閉);
EPOLLOUT:表示對應的文件描述符可以寫;
EPOLLPRI:表示對應的文件描述符有緊急的數據可讀(這里應該表示有帶外數據到來);
EPOLLERR:表示對應的文件描述符發生錯誤;
EPOLLHUP:表示對應的文件描述符被掛斷;
EPOLLET?:將 EPOLL 設為邊緣觸發(Edge Triggered)模式,這是相對于水平觸發(Level Triggered)來說的。
EPOLLONESHOT:只監聽一次事件,當監聽完這次事件之后,如果還需要繼續監聽這個 socket 的話,需要再次把這個 socket 加入到 EPOLL 隊列里
返回值:
成功:0
失敗:-1
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
功能:
等待事件的產生,收集在 epoll 監控的事件中已經發送的事件,類似于 select() 調用。
參數:
epfd: epoll 專用的文件描述符,epoll_create()的返回值
events: 分配好的 epoll_event 結構體數組,epoll 將會把發生的事件賦值到events 數組中(events 不可以是空指針,內核只負責把數據復制到這個 events 數組中,不會去幫助我們在用戶態中分配內存)。
maxevents: maxevents 告之內核這個 events 有多大 。
timeout: 超時時間,單位為毫秒,為 -1 時,函數為阻塞
返回值:
成功:返回需要處理的事件數目,如返回 0 表示已超時。
失敗:-1
epoll 對文件描述符的操作有兩種模式:LT(level trigger)和 ET(edge trigger)。LT 模式是默認模式,LT 模式與 ET 模式的區別如下:
LT 模式:當 epoll_wait 檢測到描述符事件發生并將此事件通知應用程序,應用程序可以不立即處理該事件。下次調用 epoll_wait 時,會再次響應應用程序并通知此事件。
ET 模式:當 epoll_wait 檢測到描述符事件發生并將此事件通知應用程序,應用程序必須立即處理該事件。如果不處理,下次調用 epoll_wait 時,不會再次響應應用程序并通知此事件。
ET 模式在很大程度上減少了 epoll 事件被重復觸發的次數,因此效率要比 LT 模式高。epoll 工作在 ET 模式的時候,必須使用非阻塞套接口,以避免由于一個文件句柄的阻塞讀/阻塞寫操作把處理多個文件描述符的任務餓死
int close(int epfd);
在用完之后,記得用close()來關閉這個創建出來的epoll句柄
三、epoll示例:
接下來我們epoll實現udp同時收發數據
int main(int argc,char *argv[]) { int udpfd = 0; int ret = 0; struct sockaddr_in saddr; struct sockaddr_in caddr; bzero(&saddr,sizeof(saddr)); saddr.sin_family = AF_INET; saddr.sin_port = htons(8000); saddr.sin_addr.s_addr = htonl(INADDR_ANY); bzero(&caddr,sizeof(caddr)); caddr.sin_family = AF_INET; caddr.sin_port = htons(8000); //創建套接字 if( (udpfd = socket(AF_INET,SOCK_DGRAM, 0)) < 0) { perror("socket error"); exit(-1); } //套接字端口綁字 if(bind(udpfd, (struct sockaddr*)&saddr, sizeof(saddr)) != 0) { perror("bind error"); close(udpfd); exit(-1); } printf("input: \"sayto 192.168.220.X\" to sendmsg to somebody\033[32m\n"); struct epoll_event event; // 告訴內核要監聽什么事件 struct epoll_event wait_event; int epfd = epoll_create(10); // 創建一個 epoll 的句柄,參數要大于 0, 沒有太大意義 if( -1 == epfd ){ perror ("epoll_create"); return -1; } event.data.fd = 0; // 標準輸入 event.events = EPOLLIN; // 表示對應的文件描述符可以讀 // 事件注冊函數,將標準輸入描述符 0 加入監聽事件 ret = epoll_ctl(epfd, EPOLL_CTL_ADD, 0, &event); if(-1 == ret){ perror("epoll_ctl"); return -1; } event.data.fd = udpfd; // 有名管道 event.events = EPOLLIN; // 表示對應的文件描述符可以讀 // 事件注冊函數,將有udp描述符udpfd 加入監聽事件 ret = epoll_ctl(epfd, EPOLL_CTL_ADD, udpfd, &event); if(-1 == ret){ perror("epoll_ctl"); return -1; } while(1) { // 監視并等待多個文件(標準輸入,udp套接字)描述符的屬性變化(是否可讀) // 沒有屬性變化,這個函數會阻塞,直到有變化才往下執行,這里沒有設置超時 ret = epoll_wait(epfd, &wait_event, 2, -1); write(1,"UdpQQ:",6); if(ret == -1){ // 出錯 close(epfd); perror("epoll"); } else if(ret > 0){ // 準備就緒的文件描述符 char buf[100] = {0}; if( ( 0 == wait_event.data.fd ) && ( EPOLLIN == wait_event.events & EPOLLIN ) ){ // 標準輸入 fgets(buf, sizeof(buf), stdin); buf[strlen(buf) - 1] = '\0'; if(strncmp(buf, "sayto", 5) == 0) { char ipbuf[16] = ""; inet_pton(AF_INET, buf+6, &caddr.sin_addr);//給addr套接字地址再賦值. printf("\rsay to %s\n",inet_ntop(AF_INET,&caddr.sin_addr,ipbuf,sizeof(ipbuf))); continue; } else if(strcmp(buf, "exit")==0) { close(udpfd); exit(0); } sendto(udpfd, buf, strlen(buf),0,(struct sockaddr*)&caddr, sizeof(caddr)); } else if( ( udpfd == wait_event.data.fd ) && ( EPOLLIN == wait_event.events & EPOLLIN )){ //udp套接字 struct sockaddr_in addr; char ipbuf[INET_ADDRSTRLEN] = ""; socklen_t addrlen = sizeof(addr); bzero(&addr,sizeof(addr)); recvfrom(udpfd, buf, 100, 0, (struct sockaddr*)&addr, &addrlen); printf("\r\033[31m[%s]:\033[32m%s\n",inet_ntop(AF_INET,&addr.sin_addr,ipbuf,sizeof(ipbuf)),buf); } } else if(0 == ret){ // 超時 printf("time out\n"); } } return 0; }運行結果:
源碼下載: