epoll监听文件_epoll使用详解
epoll介紹
epoll的行為與poll(2)相似,監(jiān)視多個有IO事件的文件描述符。epoll除了提供select/poll那種IO事件的水平觸發(fā)(Level Triggered)外,還提供了邊緣觸發(fā)(Edge Triggered),這就使得用戶空間程序有可能緩存IO狀態(tài),減少epoll_wait/epoll_pwait的調(diào)用,提高應(yīng)用程序效率。
epoll_create(2) 創(chuàng)建一個新的epoll實例,并返回一個引用該實例的文件描述符
epoll_ctl(2) 創(chuàng)建epoll實例后,注冊對感興趣的文件描述符。當(dāng)前注冊在epoll實例上的文件描述符集被稱為epoll集合。
epoll_wait(2) 等待I/O事件,如果當(dāng)前沒有事件可用,則阻塞調(diào)用線程。
水平觸發(fā)和邊沿觸發(fā)
epoll事件分布接口既可以表現(xiàn)為邊緣觸發(fā)(ET),也可以表現(xiàn)為水平觸發(fā)(LT)。這兩種機(jī)制的區(qū)別
可以這樣描述。假設(shè)有這種情況發(fā)生:
表示管道(rfd)的讀側(cè)的文件描述符在epoll實例上注冊。
管道寫入器在管道的寫入端寫入2 kB的數(shù)據(jù)。
調(diào)用epoll_wait(2)將返回rfd作為就緒文件描述符。
管道讀取器從rfd讀取1kb的數(shù)據(jù)。
epoll_wait(2)調(diào)用完成。
如果使用邊緣觸發(fā)標(biāo)志將rfd文件描述符注冊到epoll接口,那么第五步的epoll_wait(2)的調(diào)用可能會掛起,盡管文件輸入緩沖區(qū)仍然有1kb數(shù)據(jù)可讀;同時,遠(yuǎn)程對等端可能正在期望基于它已發(fā)送的數(shù)據(jù)的應(yīng)答。這樣做的原因是,只有在被監(jiān)視文件描述符上發(fā)生更改時,邊緣觸發(fā)模式才交付事件。因此,在步驟5中,調(diào)用者可能會以等待那些仍在輸入緩沖區(qū)中的數(shù)據(jù)的狀態(tài)下結(jié)束。
在上面的例子中,將生成rfd上的一個事件,因為在2中完成了寫入,而在3中使用了該事件。由于在4中完成的讀操作不會消耗整個緩沖區(qū)數(shù)據(jù),所以在步驟5中完成的對epoll_wait(2)的調(diào)用可能會無限期阻塞。
使用EPOLLET標(biāo)志的應(yīng)用程序應(yīng)該使用非阻塞文件描述符,以避免在處理多個文件描述符時出現(xiàn)有阻塞的讀寫?zhàn)囸I任務(wù)。建議使用epoll作為邊沿觸發(fā)(EPOLLET)接口的方式如下:
i、 具有非阻塞文件描述符
ii、只有在read(2)或write(2)返回EAGAIN后才等待事件。
相反,當(dāng)EPOLLET作為水平觸發(fā)接口使用時(默認(rèn)情況下,沒有指定EPOLLET), epoll只是一個更快的poll(2),并且可以在使用后者的任何地方使用,因為它具有相同的語義。
Epoll的優(yōu)點:
1、支持一個進(jìn)程打開大數(shù)目的socket描述符(FD)
select能打開的文件描述符有一定的限制,FD_SETSIZE設(shè)置,默認(rèn)值是2048,有兩種解決方法,1、修改它的值,然后重新編譯內(nèi)核。2、使用多進(jìn)程加入要并發(fā)20w個客戶,那么就要開100進(jìn)程;epoll則沒有這個限制,它所支持的FD上限是最大可以打開文件的數(shù)目,這個數(shù)字一般遠(yuǎn)大于2048,舉個例子,在1GB內(nèi)存的機(jī)器上大約是2萬左右,具體數(shù)目可以cat /proc/sys/fs/file-max察看,一般來說這個數(shù)目和系統(tǒng)內(nèi)存關(guān)系很大。
2、IO效率不隨FD數(shù)目增加而線性下降
select/poll采用輪詢的方式掃描文件描述符,文件描述符數(shù)量越多,性能越差;內(nèi)核 / 用戶空間內(nèi)存拷貝問題,select/poll需要復(fù)制大量的句柄數(shù)據(jù)結(jié)構(gòu),產(chǎn)生巨大的開銷;select/poll返回的是含有整個句柄的數(shù)組,應(yīng)用程序需要遍歷整個數(shù)組才能發(fā)現(xiàn)哪些句柄發(fā)生了事件,導(dǎo)致效率呈現(xiàn)線性下降。但是epoll不存在這個問題,它只會對"活躍"的socket進(jìn)行操作---這是因為在內(nèi)核實現(xiàn)中epoll是根據(jù)每個fd上面的callback函數(shù)實現(xiàn)的。
3、支持邊緣觸發(fā)模式
select/poll的觸發(fā)方式是水平觸發(fā),應(yīng)用程序如果沒有完成對一個已經(jīng)就緒的文件描述符進(jìn)行IO操作,那么之后每次select/poll調(diào)用還是會將這些文件描述符通知進(jìn)程。
4、使用mmap加速內(nèi)核與用戶空間的消息傳遞。
select/poll和epoll都需要內(nèi)核把FD消息通知給用戶空間,如何避免不必要的內(nèi)存拷貝很重要,在這點上,select/poll需要復(fù)制整個FD數(shù)組,產(chǎn)生巨大的開銷;而epoll是通過內(nèi)核于用戶空間mmap同一塊內(nèi)存實現(xiàn)的。
epoll的系統(tǒng)調(diào)用
epoll_create
int epoll_create(int size);
int epoll_create1(int flags);
創(chuàng)建一個epoll的句柄。自從linux2.6.8之后,size參數(shù)是被忽略的,更推薦使用epoll_crete1(0)來替代,flags可以設(shè)置EPOLL_CLOEXEC標(biāo)志
epoll_ctl
#include
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
該系統(tǒng)調(diào)用對文件描述符epfd引用的epoll(7)實例執(zhí)行控制操作。它請求對目標(biāo)文件描述符fd執(zhí)行操作op。
epfd : epoll_create創(chuàng)建的文件描述符.
op :參數(shù)的有效參數(shù)為:
EPOLL_CTL_ADD
在文件描述符epfd引用的epoll實例上注冊目標(biāo)文件描述符fd。
EPOLL_CTL_MOD
修改已注冊描述符fd關(guān)聯(lián)的事件。
EPOLL_CTL_DEL
從epfd引用的epoll實例中刪除(取消注冊)目標(biāo)文件描述符fd。該事件將被忽略,并且可以是NULL
fd :待監(jiān)聽的fd
epoll_event : 描述鏈接到文件描述符fd的對象,它的定義如下
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event {
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
events成員是由以下可用事件類型的零個或多個組合在一起組成的位掩碼:
EPOLLIN :關(guān)聯(lián)的文件描述符可以讀(包括對端SOCKET正常關(guān)閉);
EPOLLOUT:關(guān)聯(lián)的文件描述符可以寫;
EPOLLPRI:關(guān)聯(lián)的文件描述符有緊急的數(shù)據(jù)可讀(這里應(yīng)該表示有帶外數(shù)據(jù)到來);
EPOLLERR:關(guān)聯(lián)的文件描述符發(fā)生錯誤;
EPOLLHUP:關(guān)聯(lián)的文件描述符被掛斷;
EPOLLRDHUP:流套接字對等關(guān)閉連接,或半關(guān)閉寫。(當(dāng)使用邊緣觸發(fā)監(jiān)視時,此標(biāo)記對于編寫簡單代碼檢測對等端是否關(guān)閉特別有用。2.6.17引入)
EPOLLET: 將EPOLL設(shè)為邊緣觸發(fā)(Edge Triggered)模式,這是相對于水平觸發(fā)(Level Triggered)來說的。
EPOLLONESHOT:只監(jiān)聽一次事件,當(dāng)監(jiān)聽完這次事件之后,如果還需要繼續(xù)監(jiān)聽這個fd的話,需要再次把這個fd加入到EPOLL隊列里
它們在內(nèi)核頭文件里的定義如下:
33
34 enum EPOLL_EVENTS
35 {
36 EPOLLIN = 0x001,
37 #define EPOLLIN EPOLLIN
38 EPOLLPRI = 0x002,
39 #define EPOLLPRI EPOLLPRI
40 EPOLLOUT = 0x004,
41 #define EPOLLOUT EPOLLOUT
42 EPOLLRDNORM = 0x040,
43 #define EPOLLRDNORM EPOLLRDNORM
44 EPOLLRDBAND = 0x080,
45 #define EPOLLRDBAND EPOLLRDBAND
46 EPOLLWRNORM = 0x100,
47 #define EPOLLWRNORM EPOLLWRNORM
48 EPOLLWRBAND = 0x200,
49 #define EPOLLWRBAND EPOLLWRBAND
50 EPOLLMSG = 0x400,
51 #define EPOLLMSG EPOLLMSG
52 EPOLLERR = 0x008,
53 #define EPOLLERR EPOLLERR
54 EPOLLHUP = 0x010,
55 #define EPOLLHUP EPOLLHUP
56 EPOLLRDHUP = 0x2000,
57 #define EPOLLRDHUP EPOLLRDHUP
58 EPOLLEXCLUSIVE = 1u << 28,
59 #define EPOLLEXCLUSIVE EPOLLEXCLUSIVE
60 EPOLLWAKEUP = 1u << 29,
61 #define EPOLLWAKEUP EPOLLWAKEUP
62 EPOLLONESHOT = 1u << 30,
63 #define EPOLLONESHOT EPOLLONESHOT
64 EPOLLET = 1u << 31
65 #define EPOLLET EPOLLET
66 };
67
68
69 /* Valid opcodes ( "op" parameter ) to issue to epoll_ctl(). */
70 #define EPOLL_CTL_ADD 1 /* Add a file descriptor to the interface. */
71 #define EPOLL_CTL_DEL 2 /* Remove a file descriptor from the interface. */
72 #define EPOLL_CTL_MOD 3 /* Change file descriptor epoll_event structure. */
epoll_wait
#include
int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);
int epoll_pwait(int epfd, struct epoll_event *events,
int maxevents, int timeout,
const sigset_t *sigmask);
等待在epoll監(jiān)控的事件中已經(jīng)發(fā)生的事件。
epfd : epoll_create() 的返回值.
events : 分配好的epoll_event結(jié)構(gòu)體數(shù)組,epoll將會把發(fā)生的事件賦值到events數(shù)組中(events不可以是空指針,內(nèi)核只負(fù)責(zé)把數(shù)據(jù)復(fù)制到這個events數(shù)組中,不會去幫助我們在用戶態(tài)中分配內(nèi)存)
maxevents : maxevents告知內(nèi)核這個events有多大,這個 maxevents的值大于0(否則Error :Invalid argument)
timeout : 超時時間(毫秒,0會立即返回,-1將不確定,也有說法說是永久阻塞)。如果函數(shù)調(diào)用成功,返回對應(yīng)I/O上已準(zhǔn)備好的文件描述符數(shù)目,如返回0表示已超時,它會阻塞直到
一個文件描述符有事件發(fā)生;
信號處理器中斷;
超時;
epoll示例程序
此程序簡單測試一下三個API,注冊標(biāo)準(zhǔn)輸出的描述符到epoll,監(jiān)視標(biāo)準(zhǔn)輸出的讀事件,觸發(fā)后回顯一遍,quit退出程序.
#include
#include
#include
#include
#include
#include
#include
#include
#include
typedef std::vector PollFdList;
int main(int argc ,char **argv)
{
int fd;
char buf[1024];
int i,res,real_read, maxfd;
if((fd=open("/dev/stdin",O_RDONLY|O_NONBLOCK)) < 0)
{
fprintf(stderr,"open data1 error:%s",strerror(errno));
return 1;
}
PollFdList m_pollfds;
int epfd = epoll_create1(EPOLL_CLOEXEC);
struct epoll_event ev;
ev.events = EPOLLIN | EPOLLPRI;
ev.data.fd = fd;
epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);
m_pollfds.resize(1024);
while(1)
{
int ret = epoll_wait(epfd, m_pollfds.data(), m_pollfds.size(), 5000);
if (ret < 0)
{
printf("ePoll error : %s\n",strerror(errno));
return 1;
}
if(ret == 0){
printf("ePoll timeout\n");
continue;
}
for (i = 0; i< 1; i++)
{
if (m_pollfds[i].events & EPOLLIN)
{
memset(buf, 0, 1024);
real_read = read(m_pollfds[i].data.fd, buf, 1024);
if (real_read < 0)
{
if (errno != EAGAIN)
{
printf("read eror : %s\n",strerror(errno));
continue;
}
}
else if (!real_read)
{
close(m_pollfds[i].data.fd);
m_pollfds[i].events = 0;
}
else
{
if (i == 0)
{
buf[real_read] = '\0';
printf("%s", buf);
if ((buf[0] == 'q') || (buf[0] == 'Q'))
{
printf("quit\n");
return 1;
}
}
else
{
buf[real_read] = '\0';
printf("%s", buf);
}
}
}
}
}
exit(0);
}
./test
hello
hello
hello epoll
hello epoll
ePoll timeout
quit
quit
quit
總結(jié)
以上是生活随笔為你收集整理的epoll监听文件_epoll使用详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: amd为什么还用针脚_为什么AMD不取消
- 下一篇: hdfs中8031是什么端口号_在宿主机