關于epoll的原理,以及和poll、select、IOCP之間的比較,網上的資料很多,這些都屬于I/O復用的實現方法,即可以同時監聽發生在多個I/O端口(socket套接字描述符或文件描述符)的事件,并將事件從內核通知到用戶區,實現對特定事件的響應處理,而epoll可認為是poll的改進版,在多個方面大幅度提高了性能(當然也是在監聽描述符多、活躍描述符少的條件下)。
epoll的主要特點有以下幾點:
- 1.支持一個進程打開最大數目的socket描述符,通常數目只受限于系統內存;
- 2.IO效率不隨FD數目的增加而下降,它只對“活躍”的socket進行操作;
- 3.使用內存映射加速內核與用戶空間的消息傳遞。
這里只是簡單介紹了epoll的幾個重要特征,總之,epoll的高性能使其在服務器網絡連接層開發中應用的很廣泛,包括很多開源的服務器框架底層也采用了epoll。下面我們主要來設計實現一個epoll操作封裝類。
首先說明一下,epoll主要三個操作函數:
- int epoll_create(int size);
創建一個epoll的句柄。自從linux2.6.8之后,size參數是被忽略的。需要注意的是,當創建好epoll句柄后,它就是會占用一個fd值,在linux下如果查看/proc/進程id/fd/,是能夠看到這個fd的,所以在使用完epoll后,必須調用close()關閉,否則可能導致fd被耗盡。
- int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll的事件注冊函數,它不同于select()是在監聽事件時告訴內核要監聽什么類型的事件,而是在這里先注冊要監聽的事件類型。
第一個參數是epoll_create()的返回值。
第二個參數表示動作,用三個宏來表示:
EPOLL_CTL_ADD:注冊新的fd到epfd中;
EPOLL_CTL_MOD:修改已經注冊的fd的監聽事件;
EPOLL_CTL_DEL:從epfd中刪除一個fd;
第三個參數是需要監聽的fd。
第四個參數是告訴內核需要監聽什么事件,struct epoll_event結構如下:
typedef union epoll_data
{
pointer ptr;
int fd;
uint u32;
uint64 u64;
} epoll_data_t;
struct epoll_event
{
uint events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
events可以是以下幾個宏的集合:
- EPOLLIN :表示對應的文件描述符可以讀(包括對端SOCKET正常關閉);
- EPOLLOUT:表示對應的文件描述符可以寫;
- EPOLLPRI:表示對應的文件描述符有緊急的數據可讀(這里應該表示有帶外數據到來);
- EPOLLERR:表示對應的文件描述符發生錯誤;
- EPOLLHUP:表示對應的文件描述符被掛斷;
- EPOLLET:將EPOLL設為邊緣觸發(Edge Triggered)模式,這是相對于水平觸發(Level Triggered)來說的。
- EPOLLONESHOT:只監聽一次事件,當監聽完這次事件之后,如果還需要繼續監聽這個socket的話,需要再次把這個socket加入到EPOLL隊列里。
epoll_data_t是一個聯合結構,64位大小,可以存fd。這里具體實現中我們存一個CEpollObject對象的指針,以確保epoll_wait從網絡中接收到的消息確實是我們通過一個CEpollObject對象監聽到的。
- 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表示已超時。
Epoll封裝類實現
設計思想:通過一個模板類實現向Epoll注冊、修改和刪除事件等操作,需要使用epoll類都必須走這個模塊類,類似一種委托的功能回調模板類實例化對象的epoll監聽事件響應處理操作,主要實現類:CEpollObjectInf、CEpoll和CEpollObject模板類。
CEpollObjectInf類的實現
主要功能:表達epoll_data_t的存儲內容,以及提供對epoll_wait監聽到的事件提供響應處理接口
實現代碼:
#include < sys/socket.h >
#include < netinet/in.h >
#include < arpa/inet.h >
#include < sys/epoll.h >
#define INVAILD_SOKET (~0)
class CEpoll;
class CEpollObjectInf
{
friend class CEpoll;
protected:
//這兩個變量為CEpoll的WaitAndEvent中做對象合法檢驗,
//如果是64位系統,則不要SOCKET變量
CEpoll *m_pstEpoll;
SOCKET m_iSocket;
public:
CEpollObjectInf()
:m_pstEpoll(NULL),
m_iSocket(INVAILD_SOKET )
{}
virtual CEpollObjectInf(){}
protected:
virtual void OnEpollEvent(int iEvent) = 0;
}
CEpoll類的實現
主要功能:封裝epoll的各項操作
代碼實現:
#define UINT64_MAKE(high, low) ((uint64)(((unsigned int)((low) & 0xFFFFFFFF)) | ((uint64)((unsigned int)((high) & 0xFFFFFFFF))) < < 32))
#define UINT64_LOW(i) ((unsigned int)((uint64)(i) & 0xFFFFFFFF))
#define UINT64_HIGH(i) ((unsigned int)((uint64)(i) > > 32))
class CEpoll
{
public:
CEpoll()
:m_kdpfd(0),
m_size(0),
m_iWaitSize(0),
m_astEvents(0)
{}
virtual ~CEpoll()
{
Exit();
}
public:
//初始化
int Init(int iEpollSize, int iWaitSize)
{
m_size = iEpollSize;
m_iWaitSize = iWaitSize;
m_astEvents = new epoll_event[m_iWaitSize];
if(!m_astEvents)
return -1;
m_kdpfd = epoll_create(m_size);
if(m_kdpfd < 0)
return -2;
return 0;
}
/**
*等待時間發生或超時
*iTimeout 等待的超時時限單位毫秒
*return < 0 表示出錯 =0表示超過時間 >0 表示收到并處理的事件個數
**/
int Wait(int iTimeOut)
{
return epoll_wait(m_kdpfd,m_astEvents,m_iWaitSize,iTimeOut);
}
/**
*等待事件發生或超時,并調用方法
*iTimeOut 等待超時時限,單位毫秒
*return < 0 表示出錯 =0 表示沒有 >0 表示收到并處理的事件個數
*/
int WaitAndEvent(int iTimeOut)
{
int iEventCount = Wait(iTimeOut);
if(iEventCount < 0)
{
return iEventCount;
}
else if(iEventCount == 0) // 超時
{
return 0;
}
//一次最多處理1000個事件
for(int i = 0;i < iEventCount && i < 1000; ++i)
{
//在64位系統下uData只能存放一個指針
uint64 uData = GetData(i);
#ifdef BIT64
CEpollObjectInf *pstObjectPos = (CEpollObjectInf *)uData;
#else
CEpollObjectInf *pstObjectPos = (CEpollObjectInf *)(UINT64_LOW(uData));
#endif
uint uiEvent = GetEvent(i); // event
//判斷對象是否合法
if(pstObjectPos == NULL || pstObjectPos- >m_pstEpoll != this)
{
//不處理本次事件,繼續處理下一個事件
continue;
}
pstObjectPos- >OnEpollEvent(uiEvent);
}
return iEventCount;
}
uint64 GetData(int i) const
{
ASSERT(i < m_iWaitSize)
return m_astEvents[i].data.u64;
}
uint GetEvent(int i) const
{
ASSERT(i < m_iWaitSize)
return m_astEvents[i].events;
}
static bool IsInputEvent(int iEvent)
{
return (iEvent & EPOLLIN) != 0;
}
static bool IsOutputEvent(int iEvent)
{
return (iEvent & EPOLLOUT) != 0;
}
static bool IsCloseEvent(int iEvent)
{
return (iEvent & (EPOLLHUP|EPOLLERR)) != 0;
}
int Add(SOCKET s, uint64 data, uint event)
{
m_stEvent.events = event|EPOLLERR|EPOLLHUP;
m_stEvent.data.u64 = data;
int iRet = epoll_ctl(m_kdpfd,EPOLL_CTL_ADD,s,&m_stEvent);
return iRet;
}
int Del(SOCKET s, uint64 data = 0, uint event = EPOLLIN)
{
m_stEvent.events = 0;
m_stEvent.data.u64 = data;
int iRet = epoll_ctl(m_kdpfd,EPOLL_CTL_DEL,s,&m_stEvent);
return iRet;
}
int Mod(SOCKET s, uint64 data, uint event)
{
m_stEvent.events = event|EPOLLERR|EPOLLHUP;
m_stEvent.data.u64 = data;
int iRet = epoll_ctl(m_kdpfd,EPOLL_CTL_MOD,s,&m_stEvent);
return iRet;
}
protected:
void Exit()
{
if(m_astEvents)
{
delete []m_astEvents;
m_astEvents = 0;
}
if(m_kdpfd > 0)
{
close(m_kdpfd);
mkdpfd = 0;
}
}
protected:
int m_kdpfd;
int m_size;
int m_iWaitSize;
struct epoll_event *m_astEvents;
struct epoll_event m_stEvent;
};
CEpollObject模版類的實現
主要功能:托管CEpoll類的具體操作,注冊事件到Epoll,必須實例化CEpollObject模版類,并覆蓋實現具體的事件處理函數,以回調不同對象對事件的處理函數。
代碼實現:
template< typename Owner >
class CEpollObject: public CEpollObjectInf
{
friend class CEpoll;
public:
typedef void (Owner::*PF_EPOLL_EVENT)(CEpollObject *pstObject,Socket iSocket, int iEvnet);
protected:
Owner *m_pstOwner;
PF_EPOLL_EVENT m_pfEvent;
unsigned int m_iRegEvent;
public:
CEpollObject()
:m_pstOwner(NULL),
m_pfEvent(NULL),
m_iRegEvent(0)
{}
virtual ~CEpollObject() {Unregister();}
/**
*注冊到Epoll中
**/
int Register(Owner &stOwner, PF_EPOLL_EVENT pfEvent,CEpoll &stEpoll,SOCKET iSocket, unsigned int iRegEvent)
{
ASSERT(iSocket != INVALID_SOCKET && iRegEvent > 0 && pfEvent != NULL);
int iRet = Unregister();
if(iRet)
return iRet;
m_pstOwner = &stOwner;
m_pstEpoll = &stEpoll;
m_pfEvent = pfEvent;
m_iRegEvent = iRegEvent;
m_iSocket = iSocket;
uint64 uData = CreateData(m_iSocket);
iRet = m_pstEpoll- >Add(m_iSocket,uData,m_iRegEvent);
return iRet;
}
/**
*更改關注的事件
**/
int ModRegEvent(int iRegEvent)
{
m_iRegEvent = iRegEvent;
if(m_pstEpoll)
{
uint64 uData = CreateData(m_iSocket);
return m_pstEpoll- >Mod(m_iSocket,uData,m_iRegEvent);
}
return 0;
}
protected:
virtual void OnEpollEvent(int iEvent)
{
ASSERT(m_pstOwner != NULL && m_pfEvent != NULL);
(m_pstOwner- >*m_pfEvent)(this,m_iSocket,iEvent);
}
int Unregister()
{
int iRet = 0;
if(m_pstEpoll)
{
iRet = m_pstEpoll- >Del(m_iSocket);
m_pstEpoll = NULL;
}
m_pstOwner = NULL;
m_pfEvent = NULL;
m_iRegEvent = 0;
m_iSocket = INVALID_SOCKET;
return iRet;
}
uint64 CreateData(SOCKET iSocket)
{
#ifdef BIT64
return (uint64)this;
#else
return UINT64_MAKE(iSocket, (unsigned int)this);
#endif
}
};
-
封裝
+關注
關注
126文章
7784瀏覽量
142724 -
服務器
+關注
關注
12文章
9021瀏覽量
85184 -
端口
+關注
關注
4文章
955瀏覽量
32014 -
epoll
+關注
關注
0文章
28瀏覽量
2947
發布評論請先 登錄
相關推薦
評論