關(guān)于epoll的原理,以及和poll、select、IOCP之間的比較,網(wǎng)上的資料很多,這些都屬于I/O復(fù)用的實(shí)現(xiàn)方法,即可以同時(shí)監(jiān)聽發(fā)生在多個(gè)I/O端口(socket套接字描述符或文件描述符)的事件,并將事件從內(nèi)核通知到用戶區(qū),實(shí)現(xiàn)對特定事件的響應(yīng)處理,而epoll可認(rèn)為是poll的改進(jìn)版,在多個(gè)方面大幅度提高了性能(當(dāng)然也是在監(jiān)聽描述符多、活躍描述符少的條件下)。
epoll的主要特點(diǎn)有以下幾點(diǎn):
- 1.支持一個(gè)進(jìn)程打開最大數(shù)目的socket描述符,通常數(shù)目只受限于系統(tǒng)內(nèi)存;
- 2.IO效率不隨FD數(shù)目的增加而下降,它只對“活躍”的socket進(jìn)行操作;
- 3.使用內(nèi)存映射加速內(nèi)核與用戶空間的消息傳遞。
這里只是簡單介紹了epoll的幾個(gè)重要特征,總之,epoll的高性能使其在服務(wù)器網(wǎng)絡(luò)連接層開發(fā)中應(yīng)用的很廣泛,包括很多開源的服務(wù)器框架底層也采用了epoll。下面我們主要來設(shè)計(jì)實(shí)現(xiàn)一個(gè)epoll操作封裝類。
首先說明一下,epoll主要三個(gè)操作函數(shù):
- int epoll_create(int size);
創(chuàng)建一個(gè)epoll的句柄。自從linux2.6.8之后,size參數(shù)是被忽略的。需要注意的是,當(dāng)創(chuàng)建好epoll句柄后,它就是會占用一個(gè)fd值,在linux下如果查看/proc/進(jìn)程id/fd/,是能夠看到這個(gè)fd的,所以在使用完epoll后,必須調(diào)用close()關(guān)閉,否則可能導(dǎo)致fd被耗盡。
- int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll的事件注冊函數(shù),它不同于select()是在監(jiān)聽事件時(shí)告訴內(nèi)核要監(jiān)聽什么類型的事件,而是在這里先注冊要監(jiān)聽的事件類型。
第一個(gè)參數(shù)是epoll_create()的返回值。
第二個(gè)參數(shù)表示動(dòng)作,用三個(gè)宏來表示:
EPOLL_CTL_ADD:注冊新的fd到epfd中;
EPOLL_CTL_MOD:修改已經(jīng)注冊的fd的監(jiān)聽事件;
EPOLL_CTL_DEL:從epfd中刪除一個(gè)fd;
第三個(gè)參數(shù)是需要監(jiān)聽的fd。
第四個(gè)參數(shù)是告訴內(nèi)核需要監(jiān)聽什么事件,struct epoll_event結(jié)構(gòu)如下:
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可以是以下幾個(gè)宏的集合:
- EPOLLIN :表示對應(yīng)的文件描述符可以讀(包括對端SOCKET正常關(guān)閉);
- EPOLLOUT:表示對應(yīng)的文件描述符可以寫;
- EPOLLPRI:表示對應(yīng)的文件描述符有緊急的數(shù)據(jù)可讀(這里應(yīng)該表示有帶外數(shù)據(jù)到來);
- EPOLLERR:表示對應(yīng)的文件描述符發(fā)生錯(cuò)誤;
- EPOLLHUP:表示對應(yīng)的文件描述符被掛斷;
- EPOLLET:將EPOLL設(shè)為邊緣觸發(fā)(Edge Triggered)模式,這是相對于水平觸發(fā)(Level Triggered)來說的。
- EPOLLONESHOT:只監(jiān)聽一次事件,當(dāng)監(jiān)聽完這次事件之后,如果還需要繼續(xù)監(jiān)聽這個(gè)socket的話,需要再次把這個(gè)socket加入到EPOLL隊(duì)列里。
epoll_data_t是一個(gè)聯(lián)合結(jié)構(gòu),64位大小,可以存fd。這里具體實(shí)現(xiàn)中我們存一個(gè)CEpollObject對象的指針,以確保epoll_wait從網(wǎng)絡(luò)中接收到的消息確實(shí)是我們通過一個(gè)CEpollObject對象監(jiān)聽到的。
- int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
收集在epoll監(jiān)控的事件中已經(jīng)發(fā)送的事件。參數(shù)events是分配好的epoll_event結(jié)構(gòu)體數(shù)組,epoll將會把發(fā)生的事件賦值到events數(shù)組中(events不可以是空指針,內(nèi)核只負(fù)責(zé)把數(shù)據(jù)復(fù)制到這個(gè)events數(shù)組中,不會去幫助我們在用戶態(tài)中分配內(nèi)存)。maxevents告之內(nèi)核這個(gè)events有多大,這個(gè) maxevents的值不能大于創(chuàng)建epoll_create()時(shí)的size,參數(shù)timeout是超時(shí)時(shí)間(毫秒,0會立即返回,-1將不確定,也有說法說是永久阻塞)。如果函數(shù)調(diào)用成功,返回對應(yīng)I/O上已準(zhǔn)備好的文件描述符數(shù)目,如返回0表示已超時(shí)。
Epoll封裝類實(shí)現(xiàn)
設(shè)計(jì)思想:通過一個(gè)模板類實(shí)現(xiàn)向Epoll注冊、修改和刪除事件等操作,需要使用epoll類都必須走這個(gè)模塊類,類似一種委托的功能回調(diào)模板類實(shí)例化對象的epoll監(jiān)聽事件響應(yīng)處理操作,主要實(shí)現(xiàn)類:CEpollObjectInf、CEpoll和CEpollObject模板類。
CEpollObjectInf類的實(shí)現(xiàn)
主要功能:表達(dá)epoll_data_t的存儲內(nèi)容,以及提供對epoll_wait監(jiān)聽到的事件提供響應(yīng)處理接口
實(shí)現(xiàn)代碼:
#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:
//這兩個(gè)變量為CEpoll的WaitAndEvent中做對象合法檢驗(yàn),
//如果是64位系統(tǒng),則不要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類的實(shí)現(xiàn)
主要功能:封裝epoll的各項(xiàng)操作
代碼實(shí)現(xiàn):
#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;
}
/**
*等待時(shí)間發(fā)生或超時(shí)
*iTimeout 等待的超時(shí)時(shí)限單位毫秒
*return < 0 表示出錯(cuò) =0表示超過時(shí)間 >0 表示收到并處理的事件個(gè)數(shù)
**/
int Wait(int iTimeOut)
{
return epoll_wait(m_kdpfd,m_astEvents,m_iWaitSize,iTimeOut);
}
/**
*等待事件發(fā)生或超時(shí),并調(diào)用方法
*iTimeOut 等待超時(shí)時(shí)限,單位毫秒
*return < 0 表示出錯(cuò) =0 表示沒有 >0 表示收到并處理的事件個(gè)數(shù)
*/
int WaitAndEvent(int iTimeOut)
{
int iEventCount = Wait(iTimeOut);
if(iEventCount < 0)
{
return iEventCount;
}
else if(iEventCount == 0) // 超時(shí)
{
return 0;
}
//一次最多處理1000個(gè)事件
for(int i = 0;i < iEventCount && i < 1000; ++i)
{
//在64位系統(tǒng)下uData只能存放一個(gè)指針
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)
{
//不處理本次事件,繼續(xù)處理下一個(gè)事件
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模版類的實(shí)現(xiàn)
主要功能:托管CEpoll類的具體操作,注冊事件到Epoll,必須實(shí)例化CEpollObject模版類,并覆蓋實(shí)現(xiàn)具體的事件處理函數(shù),以回調(diào)不同對象對事件的處理函數(shù)。
代碼實(shí)現(xiàn):
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;
}
/**
*更改關(guān)注的事件
**/
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
}
};
-
封裝
+關(guān)注
關(guān)注
128文章
8685瀏覽量
145514 -
服務(wù)器
+關(guān)注
關(guān)注
13文章
9795瀏覽量
88002 -
端口
+關(guān)注
關(guān)注
4文章
1046瀏覽量
32954 -
epoll
+關(guān)注
關(guān)注
0文章
28瀏覽量
3164
發(fā)布評論請先 登錄
epoll的使用
我讀過的最好的epoll講解
epoll使用方法與poll的區(qū)別
epoll_wait的事件返回的fd為錯(cuò)誤是怎么回事?
揭示EPOLL一些原理性的東西
【米爾王牌產(chǎn)品MYD-Y6ULX-V2開發(fā)板試用體驗(yàn)】socket通信和epoll
關(guān)于Epoll,你應(yīng)該知道的那些細(xì)節(jié)
Linux中epoll IO多路復(fù)用機(jī)制

深度剖析Linux的epoll機(jī)制
一文詳解epoll的實(shí)現(xiàn)原理
用epoll來實(shí)現(xiàn)多路復(fù)用

epoll 的實(shí)現(xiàn)原理

epoll的基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)

epoll源碼分析

評論