生活随笔
收集整理的這篇文章主要介紹了
Linux网络编程——I/O复用函数之epoll
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
https://blog.csdn.net/lianghe_work/article/details/46544567
一、epoll概述
epoll 是在 2.6 內核中提出的,是之前的 select() 和 poll() 的增強版本。相對于 select() 和 poll() 來說,epoll 更加靈活,沒有描述符限制。epoll 使用一個文件描述符管理多個描述符,將用戶關系的文件描述符的事件存放到內核的一個事件表中,這樣在用戶空間和內核空間的 copy 只需一次。
二、epoll操作過程需要的四個接口函數
四接口函數分別是:
#include <sys/epoll.h>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: 表示動作,用三個宏來表示:
EPOLL_CTL_ADD:注冊新的 fd 到 epfd 中;
EPOLL_CTL_MOD:修改已經注冊的fd的監聽事件;
EPOLL_CTL_DEL:從 epfd 中刪除一個 fd;
fd: 需要監聽的文件描述符
event: 告訴內核要監聽什么事件,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_data_t data; }; events?可以是以下幾個宏的集合:
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同時收發數據
#include <string.h>#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <sys/select.h>#include <sys/time.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <sys/epoll.h> 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); if( -1 == epfd ){ perror ("epoll_create"); return -1; } event.data.fd = 0; event.events = EPOLLIN; ret = epoll_ctl(epfd, EPOLL_CTL_ADD, 0, &event); if(-1 == ret){ perror("epoll_ctl"); return -1; } event.data.fd = udpfd; event.events = EPOLLIN; ret = epoll_ctl(epfd, EPOLL_CTL_ADD, udpfd, &event); if(-1 == ret){ perror("epoll_ctl"); return -1; } while(1){ 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);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 )){ 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;} 運行結果:
源碼下載:
總結
以上是生活随笔為你收集整理的Linux网络编程——I/O复用函数之epoll的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。