以下內容整理自網絡。
1、epoll的優點:
(1)支持一個進程打開大數量的socket描述符(FD),這個數目和系統的內存有關,可以查看/proc/sys/fs/file-max來查看;
(2)epoll只對活躍的socket進行操作,而不必輪詢所有的描述符,因此IO效率不會隨着FD的增加而線型下降;
(3)epoll使用mmap使內核和用戶空間共享同一塊內存,可以加快內核與用戶空間之間的消息傳遞;
2、epoll的工作模式:
(1)LT:水平觸發模式爲缺省的工作方式,同時支持block和no-block socket。這種模式下內核會通知你一個文件描述符是否就緒,如果不進行任何操作,那麼內核還是會繼續通知你。
(2)ET:邊緣觸發模式是高速工作方式,只支持no-block socket。在該模式下,如果某個文件描述符就緒,內核只會通知你一次,直到某個操作導致該文件描述符不再是就緒狀態。
3、相關接口:
(1)int creat_epoll(int maxfds)
創建一個epoll句柄,參數maxfds告訴內核該epoll支持的最大句柄數。該函數會返回一個句柄,之後的所有操作都是通過該句柄來完成的。在用完之後要調用close()來關閉這個epoll句柄,避免fd被耗盡。
(2)int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
epfd:epoll的句柄,creat_epoll()的返回值;
op:表示動作,用三個宏表示:EPOLL_CTL_ADD(註冊新的fd到epfd),EPOLL_CTL_MOD(修改已經註冊的fd的監聽事件),EPOLL_CTL_DEL(從epfd刪除一個fd);
fd:需要監聽的fd;
event:告訴內核要監聽的事件,其結構如下:
struct epoll_event
{
__uint32_t events; /* Epoll events*/
epoll_data_t data; /* User data variable*/
}
typedef union epoll_data
{
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t;
其中epol_event中的event可以用以下幾個宏的集合:
EPOLLIN:表示對應的文件描述符可讀(包括對端socket正常關閉)
EPOLLOUT:表示對應的文件描述符可以寫
EPOLLPRI:表示對應的文件描述符有緊急的數據可讀(這裏應該表示有帶外數據到來)
EPOLLERR:表示對應的文件描述符發生錯誤
EPOLLHUP:表示對應的文件描述符被掛斷;
EPOLLET: 將EPOLL設爲邊緣觸發(Edge Triggered)模式,這是相對於水平觸發(Level Triggered)來說的
EPOLLONESHOT:只監聽一次事件,當監聽完這次事件之後,如果還需要繼續監聽這個socket的話,需要再次把這個socket加入到EPOLL隊列裏
(3)int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout)
該函數返回要處理的讀寫事件的數目,返回0表示超時。
epfd:epoll句柄;
event:從內核得到的事件集合,存儲所有的讀寫事件;
maxevents:告知內核這個events有多大,當前需要監聽的所有socket句柄數,它可能大於創建epoll句柄時的最大值maxfds;
timeout:超時時間,毫秒級,0表示馬上返回,-1表示阻塞,正整數表示等待時間。
4、實現步驟
(1)用epoll_creat()創建文件描述符,設置最大連接數;
(2)創建與epoll相關的接收線程,應用程序可以創建多個接收線程來處理epoll上的讀通知事件,線程的數量依賴於程序的具體需要;
(3)創建一個非阻塞模式的描述符來監聽socket,調用listen()偵聽有無新連接,使用epoll_ctl註冊事件;
(4)調用epoll_wait()啓動網絡監聽循環,等待epoll事件發生;
(5)如果epoll事件表明有新連接,則調用accept()接受連接,設置該新連接爲非阻塞,設置事件,註冊事件;
如果epoll事件表明有可讀數據,則將該描述符加入可讀隊列,接收線程讀入數據並處理;
如果epoll事件表明有數據可寫,則進行寫操作。
5、例子
#include <iostream>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <pthread.h>
#include <errno.h>
#define MAXLINE 10
#define OPEN_MAX 100
#define LISTENQ 20
#define SERV_PORT 5555
#define INFTIM 1000
//線程池任務隊列結構體
struct task
{
int fd; //需要讀寫的文件描述符
struct task *next; //下一個任務
};
//用於讀寫兩個的兩個方面傳遞參數
struct user_data
{
int fd;
unsigned int n_size;
char line[MAXLINE];
};
//線程的任務函數
void * readtask(void *args);
void * writetask(void *args);
//聲明epoll_event結構體的變量,ev用於註冊事件,數組用於回傳要處理的事件
struct epoll_event ev, events[20];
int epfd;
pthread_mutex_t mutex;
pthread_cond_t cond1;
struct task *readhead = NULL, *readtail = NULL, *writehead = NULL;
void setnonblocking(int sock)
{
int opts;
opts = fcntl(sock, F_GETFL);//fcntl函數改變已打開文件的性質
if (opts < 0)
{
perror("fcntl(sock,GETFL)");
exit(1);
}
opts = opts | O_NONBLOCK;
if (fcntl(sock, F_SETFL, opts) < 0)
{
perror("fcntl(sock,SETFL,opts)");
exit(1);
}
}
int main()
{
int i, maxi, listenfd, connfd, sockfd, nfds;
pthread_t tid1, tid2;
struct task *new_task = NULL;
struct user_data *rdata = NULL;
socklen_t clilen;
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&cond1, NULL);
//初始化用於讀線程池的線程
pthread_create(&tid1, NULL, readtask, NULL);
pthread_create(&tid2, NULL, readtask, NULL);
//生成用於處理accept的epoll專用的文件描述符
epfd = epoll_create(256);
struct sockaddr_in clientaddr;
struct sockaddr_in serveraddr;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
//把socket設置爲非阻塞方式
setnonblocking(listenfd);
//設置與要處理的事件相關的文件描述符
ev.data.fd = listenfd;
//設置要處理的事件類型
ev.events = EPOLLIN | EPOLLET;
//註冊epoll事件
epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev);
bzero(&serveraddr, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
char *local_addr = "200.200.200.222";
inet_aton(local_addr, &(serveraddr.sin_addr));//htons(SERV_PORT);
serveraddr.sin_port = htons(SERV_PORT);
bind(listenfd, (sockaddr *) &serveraddr, sizeof(serveraddr));
listen(listenfd, LISTENQ);
maxi = 0;
for (;;)
{
//等待epoll事件的發生
nfds = epoll_wait(epfd, events, 20, 500);
//處理所發生的所有事件
for (i = 0; i < nfds; ++i)
{
if (events[i].data.fd == listenfd)
{
connfd = accept(listenfd, (sockaddr *) &clientaddr, &clilen);
if (connfd < 0)
{
perror("connfd<0");
exit(1);
}
setnonblocking(connfd);
char *str = inet_ntoa(clientaddr.sin_addr);
std::cout << "connec_ from >>" << str << std::endl;
//設置用於讀操作的文件描述符
ev.data.fd = connfd;
//設置用於注測的讀操作事件
ev.events = EPOLLIN | EPOLLET;
//註冊ev
epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev);
}
else if (events[i].events & EPOLLIN)
{
printf("reading!\n");
if ((sockfd = events[i].data.fd) < 0) continue;
new_task = new task();
new_task->fd = sockfd;
new_task->next = NULL;
//添加新的讀任務
pthread_mutex_lock(&mutex);
if (readhead == NULL)
{
readhead = new_task;
readtail = new_task;
} else
{
readtail->next = new_task;
readtail = new_task;
}
//喚醒所有等待cond1條件的線程
pthread_cond_broadcast(&cond1);
pthread_mutex_unlock(&mutex);
}
else if(events[i].events & EPOLLOUT)
{
rdata = (struct user_data *) events[i].data.ptr;
sockfd = rdata->fd;
write(sockfd, rdata->line, rdata->n_size);
delete rdata;
//設置用於讀操作的文件描述符
ev.data.fd = sockfd;
//設置用於注測的讀操作事件
ev.events = EPOLLIN | EPOLLET;
//修改sockfd上要處理的事件爲EPOLIN
epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev);
}
}
}
}
void * readtask(void *args)
{
int fd = -1;
unsigned int n;
//用於把讀出來的數據傳遞出去
struct user_data *data = NULL;
while (1)
{
pthread_mutex_lock(&mutex);
//等待到任務隊列不爲空
while (readhead == NULL)
pthread_cond_wait(&cond1, &mutex);
fd = readhead->fd;
//從任務隊列取出一個讀任務
struct task *tmp = readhead;
readhead = readhead->next;
delete tmp;
pthread_mutex_unlock(&mutex);
data = new user_data();
data->fd = fd;
if ((n = read(fd, data->line, MAXLINE)) < 0)
{
if (errno == ECONNRESET)
{
close(fd);
}
else
{
std::cout << "readline error" << std::endl;
}
if (data != NULL)
{
delete data;
}
}
else if (n == 0)
{
close(fd);
printf("Client close connect!\n");
if (data != NULL) delete data;
}
else
{
data->n_size = n;
//設置需要傳遞出去的數據
ev.data.ptr = data;
//設置用於注測的寫操作事件
ev.events = EPOLLOUT | EPOLLET;
//修改sockfd上要處理的事件爲EPOLLOUT
epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev);
}
}
}