epoll用法

以下內容整理自網絡。

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);
        }
    }
}




發佈了20 篇原創文章 · 獲贊 1 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章