epoll及實例(二)

1、epoll工作原理

       epoll通常使用epoll_ctreate()、epoll_ctl()和epoll_wait()這三個函數來處理epoll文件系統中的句柄資源。epoll無序遍歷所有被偵聽的句柄,只要遍歷被喚醒而加入read隊列的句柄即可,從而達到處理大量併發任務的能力。

(1)epoll_create()

 int epoll_create(int size);

       調用epoll_create()時會建立一個epoll對象/句柄(epoll文件系統會爲此對象分配資源)。

       當某一進程調用epoll_create方法時,Linux內核會創建一個eventpoll結構體,這個結構體中有兩個成員與epoll的使用方式密切相關,如下所示:

struct eventpoll {
  ...
  /*紅黑樹的根節點,這棵樹中存儲着所有添加到epoll中的事件,
  也就是這個epoll監控的事件*/
  struct rb_root rbr;
  /*雙向鏈表rdllist保存着將要通過epoll_wait返回給用戶的、滿足條件的事件*/
  struct list_head rdllist;
  ...
};

       我們在調用epoll_create時,內核除了幫我們在epoll文件系統裏建了個file結點,在內核cache裏建了個紅黑樹用於存儲以後epoll_ctl傳來的socket外,還會再建立一個rdllist雙向鏈表,用於存儲準備就緒的事件,當epoll_wait調用時,僅僅觀察這個rdllist雙向鏈表裏有沒有數據即可。有數據就返回,沒有數據就sleep,等到timeout時間到後即使鏈表沒數據也返回。所以,epoll_wait非常高效。

       所有添加到epoll中的事件都會與設備(如網卡)驅動程序建立回調關係,也就是說相應事件的發生時會調用這裏的回調方法。這個回調方法在內核中叫做ep_poll_callback,它會把這樣的事件放到上面的rdllist雙向鏈表中。

  在epoll中對於每一個事件都會建立一個epitem結構體,如下所示:

struct epitem {
  ...
  //紅黑樹節點
  struct rb_node rbn;
  //雙向鏈表節點
  struct list_head rdllink;
  //事件句柄等信息
  struct epoll_filefd ffd;
  //指向其所屬的eventepoll對象
  struct eventpoll *ep;
  //期待的事件類型
  struct epoll_event event;
  ...
}; // 這裏包含每一個事件對應着的信息。

       當調用epoll_wait檢查是否有發生事件的連接時,只是檢查eventpoll對象中的rdllist雙向鏈表是否有epitem元素而已,如果rdllist鏈表不爲空,則這裏的事件複製到用戶態內存(使用共享內存提高效率)中,同時將事件數量返回給用戶。因此epoll_waitx效率非常高。epoll_ctl在向epoll對象中添加、修改、刪除事件時,從rbr紅黑樹中查找事件也非常快,也就是說epoll是非常高效的,它可以輕易地處理百萬級別的併發連接。

(2)epoll_ctl()

      

int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event);

       調用epoll_ctl()用來控制epoll文件系統對應的句柄的事件。

(3)epoll_wait()

int epoll_wait(int epfd, struct epoll_event* events,int maxevents, int timeout);

       調用epoll_wait()用來等待I/O事件的發生。

注:以上三個函數具體參數見我前面一篇博客:

https://blog.csdn.net/King_weng/article/details/100699331

(4)小結

  • 執行epoll_create()時,創建了紅黑樹和就緒鏈表;
  • 執行epoll_ctl()時,如果增加socket句柄,則檢查在紅黑樹中是否存在,存在立即返回,不存在則添加到樹幹上,然後向內核註冊回調函數,用於當中斷事件來臨時向準備就緒鏈表中插入數據;
  • 執行epoll_wait()時立刻返回準備就緒鏈表裏的數據即可。

 

2、epoll的兩種觸發模式

       epoll有EPOLLLT和EPOLLET兩種觸發模式,LT(水平觸發)是默認的模式,ET(邊緣觸發)是“高速”模式。

(1)LT(水平觸發)

       LT(水平觸發)模式下,只要這個文件描述符還有數據可讀,每次 epoll_wait都會返回它的事件,提醒用戶程序去操作。

(2)ET(邊緣觸發)

       ET(邊緣觸發)模式下,在它檢測到有 I/O 事件時,通過 epoll_wait 調用會得到有事件通知的文件描述符,對於每一個被通知的文件描述符,如可讀,則必須將該文件描述符一直讀到空,讓 errno 返回 EAGAIN 爲止,否則下次的 epoll_wait 不會返回餘下的數據,會丟掉事件。如果ET模式不是非阻塞的,那這個一直讀或一直寫勢必會在最後一次阻塞。

【epoll爲什麼要有EPOLLET觸發模式?】:

  如果採用EPOLLLT模式的話,系統中一旦有大量你不需要讀寫的就緒文件描述符,它們每次調用epoll_wait都會返回,這樣會大大降低處理程序檢索自己關心的就緒文件描述符的效率.。而採用EPOLLET這種邊緣觸發模式的話,當被監控的文件描述符上有可讀寫事件發生時,epoll_wait()會通知處理程序去讀寫。如果這次沒有把數據全部讀寫完(如讀寫緩衝區太小),那麼下次調用epoll_wait()時,它不會通知你,也就是它只會通知你一次,直到該文件描述符上出現第二次可讀寫事件纔會通知你!!!這種模式比水平觸發效率高,系統不會充斥大量你不關心的就緒文件描述符。

 

3、epoll反應堆模型

(1)epoll模型原來的流程

epoll_create(); // 創建監聽紅黑樹
epoll_ctl(); 	// 向樹上添加監聽fd
epoll_wait(); 	// 監聽
有監聽fd事件發送--->返回監聽滿足數組--->判斷返回數組元素--->
lfd滿足accept--->返回cfd---->read()讀數據--->write()給客戶端迴應。

(2)epoll反應堆模型的流程

epoll_create(); // 創建監聽紅黑樹
epoll_ctl(); 	// 向樹上添加監聽fd
epoll_wait(); 	// 監聽
有客戶端連接上來--->lfd調用acceptconn()--->將cfd掛載到紅黑樹上監聽其讀事件--->
epoll_wait()返回cfd--->cfd回調recvdata()--->將cfd摘下來監聽寫事件--->
epoll_wait()返回cfd--->cfd回調senddata()--->將cfd摘下來監聽讀事件--->...--->

 

4、實例

(1)服務端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <unistd.h>

#define IPADDRESS "127.0.0.1"
#define PORT 8787
#define MAXSIZE 1024
#define LISTENQ 5
#define FDSIZE 1000
#define EPOLLEVENTS 100

/*****函數聲明*****/

// 創建套接字並進行綁定
static int socket_bind(const char* ip, int port);
// IO多路複用epoll
static void do_epoll(int listenfd);
// 事件處理函數
static void handle_events(int epolld, struct epoll_event *event, int num, int listenfd, char *buf);
// 處理接收到的連接
static void handle_accept(int epollfd, int listenfd);
// 讀處理
static void do_read(int epollfd, int fd, char *buf);
// 寫處理
static void do_write(int epollfd, int fd, char *buf);
// 添加事件
static void add_event(int epollfd, int fd, int state);
// 修改事件
static void modify_event(int epollfd, int fd, int state);
// 刪除事件
static void delete_event(int epollfd, int fd, int state);

int main(int argc, char *argv[])
{
	int	listenfd = socket_bind(IPADDRESS, PORT);
	// 監聽
	listen(listenfd, LISTENQ);
	do_epoll(listenfd);

	return 0;
}


/*****函數定義*****/

// 創建套接字並進行綁定
static int socket_bind(const char* ip, int port)
{
	int listenfd;
	struct sockaddr_in servaddr;	// 網絡通信地址結構體
	// 創建socket
	listenfd = socket(AF_INET, SOCK_STREAM, 0);
	if (-1 == listenfd){
		perror("socket error:");
		exit(1);
	}

	bzero(&servaddr, sizeof(servaddr));	// 將servaddr初始化爲0
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(port);
	inet_pton(AF_INET, ip, &servaddr.sin_addr);
	// 地址綁定
	int ret = bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
	if (-1 == ret) {
		perror("bind error");
		exit(1);
	}

	return listenfd;
}

// IO多路複用epoll
static void do_epoll(int listenfd)
{
	// 用於註冊感興趣的事件和回傳發生待處理的事件
	struct epoll_event events[EPOLLEVENTS];
	char buf[MAXSIZE];
	memset(buf, 0, MAXSIZE);
	// 創建描述符
	int epollfd = epoll_create(FDSIZE);
	// 添加監聽描述符事件
	add_event(epollfd, listenfd, EPOLLIN);
	while (true) {
		// 獲取已準備好的描述符事件
		// 接收客戶端發送來的IO事件
		int ret = epoll_wait(epollfd, events, EPOLLEVENTS, -1);
		handle_events(epollfd, events, ret,listenfd, buf);
	}
	close(epollfd);
}

// 事件處理函數
static void handle_events(int epolld, struct epoll_event *events, int num, int listenfd, char *buf)
{
	for (int i = 0; i < num; i++) {
		int fd = events[i].data.fd;
		// 根據描述符的類型和事件類型進行處理
		if ((fd == listenfd) && (events[i].events & EPOLLIN)) {
			handle_accept(epolld, listenfd);
		} 
		else if (events[i].events & EPOLLIN){
			do_read(epolld, fd, buf);
		}
		else if (events[i].events & EPOLLOUT) {
			do_write(epolld, fd, buf);
		}
	}
	
}

// 處理接收到的連接
static void handle_accept(int epollfd, int listenfd)
{
	struct  sockaddr_in cliaddr;
	socklen_t cliaddrlen;
	int clifd = accept(listenfd, (struct sockaddr*)&cliaddr, &cliaddrlen);
	if (-1 == clifd) {
		perror("accept error");
	} 
	else {
		printf("accept a new client: %s:%d\n", inet_ntoa(cliaddr.sin_addr), cliaddr.sin_port);
		// 添加客戶描述符和事件
		add_event(epollfd, clifd, EPOLLIN);
	}
}

// 讀處理
static void do_read(int epollfd, int fd, char *buf)
{
	int iRead = read(fd, buf, MAXSIZE);
	if (-1 == iRead) {
		perror("read error:");
		close(fd);
		delete_event(epollfd, fd, EPOLLIN);
	} 
	else if(0 == iRead) {
		fprintf(stderr, "client close.\n");
		close(fd);
		delete_event(epollfd, fd, EPOLLIN);
	}
	else {
		printf("read message is : %s", buf);
		//修改描述符對應的事件,由讀改爲寫
		modify_event(epollfd, fd, EPOLLIN);
	}
}

// 寫處理
static void do_write(int epollfd, int fd, char *buf)
{
	int iWrite = write(fd, buf, strlen(buf));
	if (-1 == iWrite) {
		perror("write error:");
		close(fd);
		delete_event(epollfd, fd, EPOLLOUT);
	} 
	else {
		// 修改描述符對應事件,由寫改成讀
		modify_event(epollfd, fd, EPOLLIN);	
	}
	memset(buf, 0, MAXSIZE);
}

// 添加事件
static void add_event(int epollfd, int fd, int state)
{
	struct epoll_event ev;
	ev.events = state;
	ev.data.fd = fd;
	epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev);		// 添加一個新的epoll事件
}

// 修改事件
static void modify_event(int epollfd, int fd, int state)
{
	struct epoll_event ev;
	ev.events = state;
	ev.data.fd = fd;
	epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, &ev);
}

// 刪除事件
static void delete_event(int epollfd, int fd, int state)
{
	struct epoll_event ev;
	ev.events = state;
	ev.data.fd = fd;
	epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, &ev);
}

(2)客戶端

#include <netinet/in.h>
#include <sys/socket.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>
#include <arpa/inet.h>

#define MAXSIZE     1024
#define IPADDRESS   "127.0.0.1"
#define SERV_PORT   8787
#define FDSIZE        1024
#define EPOLLEVENTS 20

/*****函數聲明*****/

// 連接處理
static void handle_connection(int sockfd);
// 事件處理函數
static void handle_events(int epolld, struct epoll_event *event, int num, int sockfd, char *buf);
// 讀處理
static void do_read(int epollfd, int fd, int sockfd, char *buf);
// 寫處理
static void do_write(int epollfd, int fd, int sockfd, char *buf);
// 添加事件
static void add_event(int epollfd, int fd, int state);
// 修改事件
static void modify_event(int epollfd, int fd, int state);
// 刪除事件
static void delete_event(int epollfd, int fd, int state);

int main(int argc, char *argv[])
{
	struct sockaddr_in servaddr;
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
	bzero(&servaddr, sizeof(sockaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(SERV_PORT);
	inet_pton(AF_INET, IPADDRESS, &servaddr.sin_addr);
	connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
	// 連接處理
	handle_connection(sockfd);
	close(sockfd);

	return 0;
}

// 連接
static void handle_connection(int sockfd)
{
	struct epoll_event events[EPOLLEVENTS];
	char buf[MAXSIZE];
	int epollfd = epoll_create(FDSIZE);
	add_event(epollfd, STDIN_FILENO, EPOLLIN);
	while (true) {
		int ret = epoll_wait(epollfd, events, EPOLLEVENTS, -1);
		handle_events(epollfd, events, ret, sockfd, buf);
	}
	close(epollfd);
}

// 事件處理函數
static void handle_events(int epolld, struct epoll_event *events, int num, int sockfd, char *buf)
{
	for (int i = 0; i < num; i++) {
		int fd = events[i].data.fd;
		if (events[i].events & EPOLLIN) {
			do_read(epolld, fd, sockfd, buf);
		}
		else if (events[i].events & EPOLLOUT) {
			do_write(epolld, fd, sockfd, buf);
		}
		else {
			printf("continue\n");
		}
	}
}

// 讀處理
static void do_read(int epollfd, int fd, int sockfd, char *buf)
{
	int iRead = read(fd, buf, MAXSIZE);
	if (-1 == iRead) {
		perror("read error:");
		close(fd);
	} 
	else if( 0 == iRead) {
		fprintf(stderr, "server close.\n");
		close(fd);
	}
	else {
		if (STDIN_FILENO == fd) {
			add_event(epollfd, sockfd, EPOLLOUT);
		}
		else {
			add_event(epollfd, STDOUT_FILENO, EPOLLOUT);
		}
	}
}


// 寫處理
static void do_write(int epollfd, int fd, int sockfd, char *buf)
{
	int iWrite = write(fd, buf, strlen(buf));
	if (-1 == iWrite) {
		perror("write error:");
		close(fd);
	} 
	else {
		if (STDOUT_FILENO == fd) {
			delete_event(epollfd, fd, EPOLLOUT);
		} 
		else {
			modify_event(epollfd, fd, EPOLLIN);
		}
	}
	memset(buf, 0, MAXSIZE);
}

// 添加事件
static void add_event(int epollfd, int fd, int state)
{
	struct epoll_event ev;
	ev.events = state;
	ev.data.fd = fd;
	epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev);
}

// 修改事件
static void modify_event(int epollfd, int fd, int state)
{
	struct epoll_event ev;
	ev.events = state;
	ev.data.fd = fd;
	epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, &ev);
}

// 刪除事件
static void delete_event(int epollfd, int fd, int state)
{
	struct epoll_event ev;
	ev.events = state;
	ev.data.fd = fd;
	epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, &ev);
}

輸出:

參考:

https://blog.csdn.net/qq_36359022/article/details/81355897

https://www.cnblogs.com/Anker/archive/2013/08/17/3263780.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章