文章目錄
- Linux多路復用
- 1. select
- 1.1 select的概念
- 1.2 select的函數使用
- 1.3 select的優缺點
- 2. poll
- 2.1 poll的概念
- 2.2 poll的函數使用
- 2.3 poll的優缺點
- 3. epoll
- 3.1 epoll的概念
- 3.2 epoll的函數使用
- 3.3 epoll的優點
- 3.4 epoll工作模式
Linux多路復用
??IO多路復用是一種操作系統的技術,用于在單個線程或進程中管理多個輸入輸出操作。它的主要目的是通過將多個IO操作合并到一個系統調用中來提高系統的性能和資源利用率,避免了傳統的多線程或多進程模型中因為阻塞IO而導致的資源浪費和低效率問題。
??在IO多路復用中,通常使用的系統調用有 select()、poll()、epoll() 等,它們允許程序等待多個文件描述符(sockets、文件句柄等)中的任何一個變為可讀或可寫,然后再進行實際的IO操作。這種模型相比于傳統的多線程或多進程模型,具有更高的并發處理能力和更低的系統開銷。
??
1. select
1.1 select的概念
??系統提供select函數來實現多路復用輸入/輸出模型。
??select系統調用是用來讓我們的程序監視多個文件描述符的狀態變化的;
??程序會停在select這里等待,直到被監視的文件描述符有一個或多個發生了狀態改變。
??
1.2 select的函數使用
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
函數參數:
??nfds:是需要監視的最大的文件描述符值+1。
??readfds:需要檢測的可讀文件描述符的集合。
??writefds:需要檢測的可寫文件描述符的集合。
??exceptfds:需要檢測的異常文件描述符的集合。
??timeout:為結構體timeval,用來設置select()的等待時間;
??當timeout等于NULL:則表示select()沒有timeout,select將一直被阻塞,直到某個文件描述符上發生了事件;
??當timeout為0:僅檢測描述符集合的狀態,然后立即返回,并不等待外部事件的發生。
??當timeout為特定的時間值:如果在指定的時間段里沒有事件發生,select將超時返回。
??其中的可讀,可寫,異常文件描述符的集合是一個fd_set類型,fd_set是系統提供的位圖類型,位圖的位置是否是1,表示是否關系該事件。
??例如:
????輸入時:假如我們要關心 0 1 2 3 文件描述符
????0000 0000->0000 1111 比特位的位置,表示文件描述符的編號
???????? 比特位的內容 0or1 表示是否需要內核關心
????輸出時:
????0000 0100->此時表示文件描述符的編號
???????? 比特位的內容 0or1哪些用戶關心的fd 上面的讀事件已經就緒了,這里表示2描述符就緒了
??
??系統提供了關于fd_set的接口,便于我們使用位圖:
void FD_CLR(int fd, fd_set *set); // 用來清除描述詞組set中相關fd 的位int FD_ISSET(int fd, fd_set *set); // 用來測試描述詞組set中相關fd 的位是否為真void FD_SET(int fd, fd_set *set); // 用來設置描述詞組set中相關fd的位void FD_ZERO(fd_set *set); // 用來清除描述詞組set的全部位
??
函數返回值:
??執行成功則返回文件描述詞狀態已改變的個數。
??如果返回0代表在描述詞狀態改變前已超過timeout時間,沒有返回。
??當有錯誤發生時則返回-1,錯誤原因存于errno,此時參數readfds,writefds, exceptfds和timeout的值變成不可預測。
??錯誤值可能為:
??EBADF 文件描述詞為無效的或該文件已關閉
??EINTR 此調用被信號所中斷
??EINVAL 參數n 為負值。
??ENOMEM 核心內存不足
??
select的執行過程:
??(1)執行fd_set set; FD_ZERO(&set);則set用位表示是0000,0000。
??(2)若fd=5,執行FD_SET(fd,&set);后set變為0001,0000(第5位置為1) 。
??(3)若再加入fd=2,fd=1,則set變為0001,0011 。
??(4)執行select(6,&set,0,0,0)阻塞等待,表示最大文件描述符+1是6,監控可讀事件,立即返回。
??(5)若fd=1,fd=2上都發生可讀事件,則select返回,此時set變為0000,0011。注意:沒有事件發生的fd=5被清空。
??
1.3 select的優缺點
select的特點:
??(1)可監控的文件描述符個數取決與sizeof(fd_set)的值。一般大小是1024,但是fd_set的大小可以調整。
??(2)將fd加入select監控集的同時,還要再使用一個數據結構array保存放到select監控集中的fd。
????1. 是用于再select 返回后,array作為源數據和fd_set進行FD_ISSET判斷。
????2. 是select返回后會把以前加入的但并無事件發生的fd清空,則每次開始select前都要重新從array取得fd逐一加入(FD_ZERO最先),掃描array的同時取得fd最大值maxfd,用于select的第一個參數。
??
select缺點
??(1)每次調用select, 都需要手動設置fd集合, 從接口使用角度來說也非常不便。
??(2)每次調用select,都需要把fd集合從用戶態拷貝到內核態,這個開銷在fd很多時會很大。
??(3)同時每次調用select都需要在內核遍歷傳遞進來的所有fd,這個開銷在fd很多時也很大。
??(4)select支持的文件描述符數量太小。
??
select使用代碼:
#pragma once#include <iostream>
#include <sys/select.h>
#include <sys/time.h>
#include "Socket.hpp"using namespace std;static const uint16_t defaultport = 888;
static const int fd_num_max = (sizeof(fd_set) * 8);
int defaultfd = -1;class SelectServer
{
public:SelectServer(uint16_t port = defaultport) : _port(port){for (int i = 0; i < fd_num_max; i++){fd_array[i] = defaultfd;// std::cout << "fd_array[" << i << "]" << " : " << fd_array[i] << std::endl;}}bool Init(){_listensock.Socket();_listensock.Bind(_port);_listensock.Listen();return true;}void Accepter(){// 我們的連接事件就緒了std::string clientip;uint16_t clientport = 0;int sock = _listensock.Accept(&clientip, &clientport); // 會不會阻塞在這里?不會if (sock < 0) return;lg(Info, "accept success, %s: %d, sock fd: %d", clientip.c_str(), clientport, sock);// sock -> fd_array[]int pos = 1;for (; pos < fd_num_max; pos++) // 第二個循環{if (fd_array[pos] != defaultfd)continue;elsebreak;}if (pos == fd_num_max){lg(Warning, "server is full, close %d now!", sock);close(sock);}else{fd_array[pos] = sock;PrintFd();// TODO}}void Recver(int fd, int pos){// demochar buffer[1024];ssize_t n = read(fd, buffer, sizeof(buffer) - 1); // bug?if (n > 0){buffer[n] = 0;cout << "get a messge: " << buffer << endl;}else if (n == 0){lg(Info, "client quit, me too, close fd is : %d", fd);close(fd);fd_array[pos] = defaultfd; // 這里本質是從select中移除}else{lg(Warning, "recv error: fd is : %d", fd);close(fd);fd_array[pos] = defaultfd; // 這里本質是從select中移除}}void Dispatcher(fd_set &rfds){for (int i = 0; i < fd_num_max; i++) // 這是第三個循環{int fd = fd_array[i];if (fd == defaultfd)continue;if (FD_ISSET(fd, &rfds)){if (fd == _listensock.Fd()){Accepter(); // 連接管理器}else // non listenfd{Recver(fd, i);}}}}void Start(){int listensock = _listensock.Fd();fd_array[0] = listensock;for (;;){fd_set rfds;FD_ZERO(&rfds);int maxfd = fd_array[0];for (int i = 0; i < fd_num_max; i++) // 第一次循環{if (fd_array[i] == defaultfd)continue;FD_SET(fd_array[i], &rfds);if (maxfd < fd_array[i]){maxfd = fd_array[i];lg(Info, "max fd update, max fd is: %d", maxfd);}}// accept?不能直接accept!檢測并獲取listensock上面的事件,新連接到來,等價于讀事件就緒// struct timeval timeout = {1, 0}; // 輸入輸出,可能要進行周期的重復設置struct timeval timeout = {0, 0}; // 輸入輸出,可能要進行周期的重復設置// 如果事件就緒,上層不處理,select會一直通知你!// select告訴你就緒了,接下來的一次讀取,我們讀取fd的時候,不會被阻塞// rfds: 輸入輸出型參數。 1111 1111 -> 0000 0000int n = select(maxfd + 1, &rfds, nullptr, nullptr, /*&timeout*/ nullptr);switch (n){case 0:cout << "time out, timeout: " << timeout.tv_sec << "." << timeout.tv_usec << endl;break;case -1:cerr << "select error" << endl;break;default:// 有事件就緒了,TODOcout << "get a new link!!!!!" << endl;Dispatcher(rfds); // 就緒的事件和fd你怎么知道只有一個呢???break;}}}void PrintFd(){cout << "online fd list: ";for (int i = 0; i < fd_num_max; i++){if (fd_array[i] == defaultfd)continue;cout << fd_array[i] << " ";}cout << endl;}~SelectServer(){_listensock.Close();}private:Sock _listensock;uint16_t _port;int fd_array[fd_num_max]; // 數組, 用戶維護的!// int wfd_array[fd_num_max];
};
??
2. poll
2.1 poll的概念
??poll和select實現原理基本類似,
??poll只為了解決select的兩個硬傷:
??1.等待的fd是有上限的,(底層類似鏈表儲存實現,而不是位圖)
??2.每次要對關心的fd進行事件重置,(pollfd結構包含了要監視的event和發生的event,使用前后不用初始化fd_set)
??
2.2 poll的函數使用
int poll(struct pollfd *fds, nfds_t nfds, int timeout);// pollfd結構
struct pollfd {int fd; /* file descriptor */short events; /* requested events */short revents; /* returned events */
};
函數參數解釋:
??fds:是一個poll函數監聽的結構列表. 每一個元素中, 包含了三部分內容: 文件描述符, 監聽的事件集合, 返回的事件集合。
??nfds:表示fds數組的長度。
??timeout:表示poll函數的超時時間, 單位是毫秒(ms)。
返回結果:
??返回值小于0, 表示出錯。
??返回值等于0, 表示poll函數等待超時。
??返回值大于0, 表示poll由于監聽的文件描述符就緒而返回。
??
2.3 poll的優缺點
poll的優點
??(1)pollfd結構包含了要監視的event和發生的event,不再使用select“參數-值”傳遞的方式. 接口使用比 select更方便。
??(2)poll并沒有最大數量限制 (但是數量過大后性能也是會下降)。
poll的缺點
??(1)和select函數一樣,poll返回后,需要輪詢pollfd來獲取就緒的描述符。
??(2)每次調用poll都需要把大量的pollfd結構從用戶態拷貝到內核中。
??(3)同時連接的大量客戶端在一時刻可能只有很少的處于就緒狀態, 因此隨著監視的描述符數量的增長, 其效率也會線性下降。
??
poll使用代碼:
#pragma once#include <iostream>
#include <poll.h>
#include <sys/time.h>
#include "../select/Socket.hpp"using namespace std;static const uint16_t defaultport = 8888;
static const int fd_num_max = 64;
int defaultfd = -1;
int non_event = 0;class PollServer
{
public:PollServer(uint16_t port = defaultport) : _port(port){for (int i = 0; i < fd_num_max; i++){_event_fds[i].fd = defaultfd;_event_fds[i].events = non_event;_event_fds[i].revents = non_event;// std::cout << "fd_array[" << i << "]" << " : " << fd_array[i] << std::endl;}}bool Init(){_listensock.Socket();_listensock.Bind(_port);_listensock.Listen();return true;}void Accepter(){// 我們的連接事件就緒了std::string clientip;uint16_t clientport = 0;int sock = _listensock.Accept(&clientip, &clientport); // 會不會阻塞在這里?不會if (sock < 0) return;lg(Info, "accept success, %s: %d, sock fd: %d", clientip.c_str(), clientport, sock);// sock -> fd_array[]int pos = 1;for (; pos < fd_num_max; pos++) // 第二個循環{if (_event_fds[pos].fd != defaultfd)continue;elsebreak;}if (pos == fd_num_max){lg(Warning, "server is full, close %d now!", sock);close(sock);// 擴容}else{// fd_array[pos] = sock;_event_fds[pos].fd = sock;_event_fds[pos].events = POLLIN;_event_fds[pos].revents = non_event;PrintFd();// TODO}}void Recver(int fd, int pos){// demochar buffer[1024];ssize_t n = read(fd, buffer, sizeof(buffer) - 1); // bug?if (n > 0){buffer[n] = 0;cout << "get a messge: " << buffer << endl;}else if (n == 0){lg(Info, "client quit, me too, close fd is : %d", fd);close(fd);_event_fds[pos].fd = defaultfd; // 這里本質是從select中移除}else{lg(Warning, "recv error: fd is : %d", fd);close(fd);_event_fds[pos].fd = defaultfd; // 這里本質是從select中移除}}void Dispatcher(){for (int i = 0; i < fd_num_max; i++) // 這是第三個循環{int fd = _event_fds[i].fd;if (fd == defaultfd)continue;if (_event_fds[i].revents & POLLIN){if (fd == _listensock.Fd()){Accepter(); // 連接管理器}else // non listenfd{Recver(fd, i);}}}}void Start(){_event_fds[0].fd = _listensock.Fd();_event_fds[0].events = POLLIN;int timeout = 3000; // 3sfor (;;){int n = poll(_event_fds, fd_num_max, timeout);switch (n){case 0:cout << "time out... " << endl;break;case -1:cerr << "poll error" << endl;break;default:// 有事件就緒了,TODOcout << "get a new link!!!!!" << endl;Dispatcher(); // 就緒的事件和fd你怎么知道只有一個呢???break;}}}void PrintFd(){cout << "online fd list: ";for (int i = 0; i < fd_num_max; i++){if (_event_fds[i].fd == defaultfd)continue;cout << _event_fds[i].fd << " ";}cout << endl;}~PollServer(){_listensock.Close();}private:Sock _listensock;uint16_t _port;struct pollfd _event_fds[fd_num_max]; // 數組, 用戶維護的!// struct pollfd *_event_fds;// int fd_array[fd_num_max];// int wfd_array[fd_num_max];
};
??
3. epoll
3.1 epoll的概念
??epoll: 是為處理大批量句柄而作了改進的poll(真的是大改進)。
??epoll是IO多路復用技術,在實現上維護了一個用于返回觸發事件的Socket的鏈表和一個記錄監聽事件的紅黑樹,epoll的高效體現在:
??(1)對監聽事件的修改是 log N(紅黑樹)。
??(2)用戶程序無需遍歷所有的Socket(發生事件的Socket被放到鏈表中直接返回)。
??(3)內核無需遍歷所有的套接字,內核使用回調函數在事件發生時直接轉到對應的處理函數。
??
3.2 epoll的函數使用
??epoll 有3個相關的系統調用:
epoll_create
int epoll_create(int size);
??創建一個epoll的句柄,自從linux2.6.8之后,size參數是被忽略的,用完之后, 必須調用close()關閉。
??
epoll_ctl
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll的事件注冊函數:
??它不同于select()是在監聽事件時告訴內核要監聽什么類型的事件, 而是在這里先注冊要監聽的事件類型。
??第一個參數是epoll_create()的返回值(epoll的句柄)。
??第二個參數表示動作,用三個宏來表示。
??第三個參數是需要監聽的fd。
??第四個參數是告訴內核需要監聽什么事。
第二個參數的取值:
??EPOLL_CTL_ADD :注冊新的fd到epfd中。
??EPOLL_CTL_MOD :修改已經注冊的fd的監聽事件。
??EPOLL_CTL_DEL :從epfd中刪除一個fd。
struct epoll_event結構如下:
??
events可以是以下幾個宏的集合:
??EPOLLIN : 表示對應的文件描述符可以讀 (包括對端SOCKET正常關閉)。
??EPOLLOUT : 表示對應的文件描述符可以寫。
??EPOLLPRI : 表示對應的文件描述符有緊急的數據可讀 (這里應該表示有帶外數據到來)。
??EPOLLERR : 表示對應的文件描述符發生錯誤。
??EPOLLHUP : 表示對應的文件描述符被掛斷。
??EPOLLET : 將EPOLL設為邊緣觸發(Edge Triggered)模式, 這是相對于水平觸發(Level Triggered)來說的。
??EPOLLONESHOT:只監聽一次事件, 當監聽完這次事件之后, 如果還需要繼續監聽這個socket的話, 需要再次把這個socket加入到EPOLL隊列里。
??
epoll_wait
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
收集在epoll監控的事件中已經發送的事件:
??參數events是分配好的epoll_event結構體數組。
??epoll將會把發生的事件賦值到events數組中 (events不可以是空指針,內核只負責把數據復制到這個events數組中,不會去幫助我們在用戶態中分配內存)。
??maxevents告之內核這個events有多大,這個 maxevents的值不能大于創建epoll_create()時的size。
??參數timeout是超時時間 (毫秒,0會立即返回,-1是永久阻塞)。
??如果函數調用成功,返回對應I/O上已準備好的文件描述符數目,如返回0表示已超時, 返回小于0表示函數失敗。
??
epoll原理:
??(1)當某一進程調用epoll_create方法時,Linux內核會創建一個eventpoll結構體,這個結構體中有兩個成員與epoll的使用方式密切相關。
??(2)每一個epoll對象都有一個獨立的eventpoll結構體,用于存放通過epoll_ctl方法向epoll對象中添加進來的事件。
??(3)這些事件都會掛載在紅黑樹中,如此,重復添加的事件就可以通過紅黑樹而高效的識別出來(紅黑樹的插入時間效率是lgn,其中n為樹的高度)。
??(4)而所有添加到epoll中的事件都會與設備(網卡)驅動程序建立回調關系,也就是說,當響應的事件發生時會調用這個回調方法。
??(5)這個回調方法在內核中叫ep_poll_callback,它會將發生的事件添加到rdlist雙鏈表中。
??(6)在epoll中,對于每一個事件,都會建立一個epitem結構體。
??(7)當調用epoll_wait檢查是否有事件發生時,只需要檢查eventpoll對象中的rdlist雙鏈表中是否有epitem元素即可。
??(8)如果rdlist不為空,則把發生的事件復制到用戶態,同時將事件數量返回給用戶. 這個操作的時間復雜度是O(1)。
??
struct eventpoll{ .... /*紅黑樹的根節點,這顆樹中存儲著所有添加到epoll中的需要監控的事件*/ struct rb_root rbr; /*雙鏈表中則存放著將要通過epoll_wait返回給用戶的滿足條件的事件*/ struct list_head rdlist; ....
};
struct epitem{ struct rb_node rbn;//紅黑樹節點 struct list_head rdllink;//雙向鏈表節點 struct epoll_filefd ffd; //事件句柄信息 struct eventpoll *ep; //指向其所屬的eventpoll對象 struct epoll_event event; //期待發生的事件類型
}
總結一下, epoll的使用過程簡單看就三步:
??(1)調用epoll_create創建一個epoll句柄。
??(2)調用epoll_ctl, 將要監控的文件描述符進行注冊。
??(3)調用epoll_wait, 等待文件描述符就緒。
??
3.3 epoll的優點
??(1)接口使用方便: 雖然拆分成了三個函數,但是反而使用起來更方便高效,不需要每次循環都設置關注的文件描述符,也做到了輸入輸出參數分離開。
??(2)數據拷貝輕量: 只在合適的時候調用 EPOLL_CTL_ADD 將文件描述符結構拷貝到內核中,這個操作并不頻繁(而select/poll都是每次循環都要進行拷貝)。
??(3)事件回調機制: 避免使用遍歷,而是使用回調函數的方式,將就緒的文件描述符結構加入到就緒隊列中,epoll_wait 返回直接訪問就緒隊列就知道哪些文件描述符就緒,這個操作時間復雜度O(1),即使文件描述符數目很多,效率也不會受到影響。
??(4)沒有數量限制: 文件描述符數目無上限。
??
3.4 epoll工作模式
??epoll默認:LT模式,事件到來但是上層不處理,高電平,一直有效。
??? ? ? ? ET模式,數據或者連接,從無到有,從有到多,變化的時候才通知我們一次。
??ET的通知效率更高:倒逼程序員,每次通知都必須把本輪的數據取走 -> 循環讀取,讀取錯誤 -> fd默認是阻塞的 -> ET,所有的fd必須是非阻塞的。
??ET的IO效率也更高 -> tcp會向對方通告一個更大的窗口,從而概率上讓對方一次給我發生更多數據,如果LT每次也可以就緒,那效率差不多。
??本質就是向就緒隊列,添加一次或者是多次就緒節點。
??
Epoller.hpp Epoller對epoll進行封裝
#pragma once#include "nocopy.hpp"
#include <sys/epoll.h>
#include "Log.hpp"
#include <cstring>
#include <cerrno>//封裝我們的epoll,epoll公有繼承于我們的nocopy類,不能被拷貝
class Epoller: public nocopy
{static const int size=128;public:Epoller(){_epfd=epoll_create(size);if(_epfd<0){lg(Error,"epoll_create error: %s",strerror(errno));}else{lg(Info,"epoll_create success: %d",_epfd);}}//進行epoll事件等待//返回的是就緒事件的數量int EpollerWait(struct epoll_event revents[], int num){int n=epoll_wait(_epfd,revents,num,-1/*_timeout*/);return n;}//我們所要更新的時間操作和套接字監控的事件int EpollerUpdate(int oper, int sock, uint32_t event){int n=0;if(oper==EPOLL_CTL_DEL) //刪除操作{n=epoll_ctl(_epfd,oper,sock,nullptr);if(n!=0){lg(Error,"epoll_ctl delete error!");}}else //新增和修改{struct epoll_event ev;ev.events=event;ev.data.fd=sock; //傳入sock,方便我們知道是哪一個fd就緒//完成了我們對于哪一個文件和那一個文件的描述符進行事件關心//接下來進行注冊n=epoll_ctl(_epfd,oper,sock,&ev);if(n!=0){lg(Error,"epoll_ctl error!");}}return n;}~Epoller(){if(_epfd>0){close(_epfd);}}private:int _epfd;int _timeout{3000};
};
??
EpollServer.hpp Epoll服務器
#pragma once#include <iostream>
#include <memory>
#include <sys/epoll.h>
#include "Socket.hpp"
#include "Epoller.hpp"
#include "Log.hpp"
#include "nocopy.hpp"uint32_t EVENT_IN = (EPOLLIN); //表示更新讀事件
uint32_t EVENT_OUT = (EPOLLOUT); //表示更新寫事件class EpollServer : public nocopy
{static const int num = 64;public:EpollServer(uint16_t port): _port(port),_listsocket_ptr(new Sock()),_epoll_ptr(new Epoller()){}void Init(){_listsocket_ptr->Socket();_listsocket_ptr->Bind(_port);_listsocket_ptr->Listen();lg(Info,"create listen socket success: %d\n",_listsocket_ptr->Fd());}void Accepter(){//獲取了一個連接 std::string clientip;uint16_t clientport;int sock=_listsocket_ptr->Accept(&clientip,&clientport);if(sock>0){//我們不能直接讀取數據//ssize_t n=read(sock,...);_epoll_ptr->EpollerUpdate(EPOLL_CTL_ADD,sock,EVENT_IN);lg(Info,"get a new link, client info @ %s:%d",clientip.c_str(),clientport);}}void Recver(int fd){// demochar buffer[1024];ssize_t n = read(fd, buffer, sizeof(buffer) - 1); // bug?if (n > 0){buffer[n] = 0;std::cout << "get a messge: " << buffer << std::endl;// wrirtestd::string echo_str = "server echo $ ";echo_str += buffer;write(fd, echo_str.c_str(), echo_str.size());}else if (n == 0){lg(Info, "client quit, me too, close fd is : %d", fd);//細節3_epoll_ptr->EpollerUpdate(EPOLL_CTL_DEL, fd, 0);close(fd);}else{lg(Warning, "recv error: fd is : %d", fd);_epoll_ptr->EpollerUpdate(EPOLL_CTL_DEL, fd, 0);close(fd);}}void Dispatcher(struct epoll_event revs[], int num){//遍歷獲取文件描述符中已經就緒的事件for(int i=0;i<num;i++){uint32_t events=revs[i].events;int fd=revs[i].data.fd;if(events & EVENT_IN) //判斷事件類型,這是讀事件就緒{if(fd==_listsocket_ptr->Fd()){//獲取了一個連接 Accepter();}else {//其他fd上面的普通讀取事件就緒Recver(fd);}}else if(events & EVENT_OUT) //寫事件就緒{}else {}}}//開始我們的epoll事件監聽void Start(){//將listensock添加到epoll中 -> listensock和他關心的事件,添加到內核epoll模型的rb_tree_epoll_ptr->EpollerUpdate(EPOLL_CTL_ADD,_listsocket_ptr->Fd(),EVENT_IN);//我們將我們的監聽套接字listsocket給epoll進行讀事件管理,接下來由紅黑樹自動關心我們的事件struct epoll_event revs[num];for(;;){int n=_epoll_ptr->EpollerWait(revs,num);if(n>0){//有事件就緒lg(Debug,"event happend, fd is %d",revs[0].data.fd);//處理就緒事件Dispatcher(revs,n);}else if(n==0){lg(Info,"time out...");}else{lg(Error,"epoll wait error");}}}~EpollServer(){_listsocket_ptr->Close();}private:std::shared_ptr<Sock> _listsocket_ptr;std::shared_ptr<Epoller> _epoll_ptr;uint16_t _port;
};