徹底學會使用epoll(三)——ET的讀操作例項分析
首先看程式一,這個程式想要實現的功能是當用戶從控制檯有任何輸入操作時,輸出”hello world!”。
l 程式一
點選(此處)摺疊或開啟
-
#include <unistd.h>
-
#include <iostream>
-
#include <sys/epoll.h>
-
using namespace std;
-
int main(void)
-
{
-
int epfd,nfds;
-
struct epoll_event ev,events[5];//ev用於註冊事件,陣列用於返回要處理的事件
-
epfd=epoll_create(1);
-
ev.data.fd=STDIN_FILENO;
-
ev.events=EPOLLIN|EPOLLET;//監聽讀狀態同時設定ET模式
-
epoll_ctl(epfd,EPOLL_CTL_ADD,STDIN_FILENO,&ev);//註冊epoll事件
-
for(;;)
-
{
-
nfds=epoll_wait(epfd,events,5,-1);
-
for(int i=0;i<nfds;i++)
-
{
-
if(events[i].data.fd==STDIN_FILENO)
-
cout<
-
}
-
}
- }
執行結果:
程式一中對標準輸入的監聽使用ET模式,結果實現了我們想要的功能。那麼實際原理是如何呢,我們將過程分析一下:
(1) 當用戶輸入一組字元,這組字元被送入buffer,字元停留在buffer中,又因為buffer由空變為不空,所以ET返回讀就緒,輸出”hello world!”。
(2) 之後程式再次執行epoll_wait,此時雖然buffer中有內容可讀,但是根據我們上節的分析,ET並不返回就緒,導致epoll_wait阻塞。(底層原因是ET下就緒fd的epitem只被放入rdlist
(3) 使用者再次輸入一組字元,導致buffer中的內容增多,根據我們上節的分析這將導致fd狀態的改變,是對應的epitem再次加入rdlist,從而使epoll_wait返回讀就緒,再次輸出“hello world!”。
我們在看看LT的情況如何,將程式一以下修改:
ev.events=EPOLLIN;//預設使用LT模式
執行結果:
結果正如我們所料,程式出現死迴圈,因為使用者輸入任意資料後,資料被送入buffer且沒有被讀出,所以LT模式下每次epoll_wait都認為buffer可讀返回讀就緒。導致每次都會輸出”hello world!”。下面在看程式二。
l 程式二
點選(此處)摺疊或開啟
-
#include <unistd.h>
-
#include <iostream>
-
#include <sys/epoll.h>
-
using namespace std;
-
int main(void)
-
{
-
int epfd,nfds;
-
char buf[256];
-
struct epoll_event ev,events[5];//ev用於註冊事件,陣列用於返回要處理的事件
-
epfd=epoll_create(1);//只需要監聽一個描述符——標準輸入
-
ev.data.fd=STDIN_FILENO;
-
ev.events=EPOLLIN;//使用預設LT模式
-
epoll_ctl(epfd,EPOLL_CTL_ADD,STDIN_FILENO,&ev);//註冊epoll事件
-
for(;;)
-
{
-
nfds=epoll_wait(epfd,events,5,-1);
-
for(int i=0;i<nfds;i++)
-
{
-
if(events[i].data.fd==STDIN_FILENO)
-
{
-
read(STDIN_FILENO,buf,sizeof(buf));//將緩衝中的內容讀出
-
cout<<"hello world!"<<endl;
-
}
-
}
-
}
- }
執行結果:
程式二依然使用LT模式,但是每次epoll_wait返回讀就緒的時候我們都將buffer(緩衝)中的內容read出來,所以導致buffer再次清空,下次呼叫epoll_wait就會阻塞。所以能夠實現我們所想要的功能——當用戶從控制檯有任何輸入操作時,輸出”hello world!”。我們再來看看程式三。
l 程式三
點選(此處)摺疊或開啟
-
int main(void)
-
{
-
int epfd,nfds;
-
struct epoll_event ev,events[5];//ev用於註冊事件,陣列用於返回要處理的事件
-
epfd=epoll_create(1);//只需要監聽一個描述符——標準輸入
-
ev.data.fd=STDIN_FILENO;
-
ev.events=EPOLLIN|EPOLLET;//使用預設LT模式
-
epoll_ctl(epfd,EPOLL_CTL_ADD,STDIN_FILENO,&ev);//註冊epoll事件
-
for(;;)
-
{
-
nfds=epoll_wait(epfd,events,5,-1);
-
for(int i=0;i<nfds;i++)
-
{
-
if(events[i].data.fd==STDIN_FILENO)
-
{
-
cout<<"hello world!"<<endl;
-
ev.data.fd=STDIN_FILENO;
-
ev.events=EPOLLIN|EPOLLET;//使用預設LT模式
-
epoll_ctl(epfd,EPOLL_CTL_MOD,STDIN_FILENO,&ev);//重新MOD事件(ADD無效)
-
}
-
}
-
}
- }
程式三依然使用ET,但是每次讀就緒後都主動的再次MOD IN事件,我們發現程式再次出現死迴圈,也就是每次返回讀就緒。這就驗證了上一節討論ET讀就緒的第三種情況。但是注意,如果我們將MOD改為ADD,將不會產生任何影響。別忘了每次ADD一個描述符都會在epitem組成的紅黑樹中新增一個項,我們之前已經ADD過一次,再次ADD將阻止新增,所以在次呼叫ADD IN事件不會有任何影響。