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