徹底學會使用epoll(三)——ET的讀操作例項分析

gettogetto發表於2017-03-26

首先看程式一,這個程式想要實現的功能是當使用者從控制檯有任何輸入操作時,輸出hello world

程式一

  

點選(此處)摺疊或開啟

  1. #include <unistd.h>
  2. #include <iostream>
  3. #include <sys/epoll.h>
  4. using namespace std;
  5. int main(void)
  6. {
  7.     int epfd,nfds;
  8.     struct epoll_event ev,events[5];//ev用於註冊事件,陣列用於返回要處理的事件
  9.     epfd=epoll_create(1);//只需要監聽一個描述符——標準輸入
  10.     ev.data.fd=STDIN_FILENO;
  11.     ev.events=EPOLLIN|EPOLLET;//監聽讀狀態同時設定ET模式
  12.     epoll_ctl(epfd,EPOLL_CTL_ADD,STDIN_FILENO,&ev);//註冊epoll事件
  13.     for(;;)
  14.    {
  15.      nfds=epoll_wait(epfd,events,5,-1);
  16.      for(int i=0;i<nfds;i++)
  17.      {
  18.         if(events[i].data.fd==STDIN_FILENO)
  19.            cout<<"hello world!"<<endl;
  20.      }
  21.    }
  22. }

執行結果:

程式一中對標準輸入的監聽使用ET模式,結果實現了我們想要的功能。那麼實際原理是如何呢,我們將過程分析一下:

(1) 當使用者輸入一組字元,這組字元被送入buffer,字元停留在buffer中,又因為buffer由空變為不空,所以ET返回讀就緒,輸出hello world

(2) 之後程式再次執行epoll_wait,此時雖然buffer中有內容可讀,但是根據我們上節的分析,ET並不返回就緒,導致epoll_wait阻塞。(底層原因是ET下就緒fdepitem只被放入rdlist一次)。

(3) 使用者再次輸入一組字元,導致buffer中的內容增多,根據我們上節的分析這將導致fd狀態的改變,是對應的epitem再次加入rdlist,從而使epoll_wait返回讀就緒,再次輸出“hello world!”。

 我們在看看LT的情況如何,將程式以下修改:

    ev.events=EPOLLIN;//預設使用LT模式

執行結果:

結果正如我們所料,程式出現死迴圈,因為使用者輸入任意資料後,資料被送入buffer且沒有被讀出,所以LT模式下每次epoll_wait都認為buffer可讀返回讀就緒。導致每次都會輸出hello world。下面在看程式二。

程式二


點選(此處)摺疊或開啟

  1. #include <unistd.h>
  2. #include <iostream>
  3. #include <sys/epoll.h>
  4. using namespace std;
  5. int main(void)
  6. {
  7.     int epfd,nfds;
  8.     char buf[256];
  9.     struct epoll_event ev,events[5];//ev用於註冊事件,陣列用於返回要處理的事件
  10.     epfd=epoll_create(1);//只需要監聽一個描述符——標準輸入
  11.     ev.data.fd=STDIN_FILENO;
  12.     ev.events=EPOLLIN;//使用預設LT模式
  13.     epoll_ctl(epfd,EPOLL_CTL_ADD,STDIN_FILENO,&ev);//註冊epoll事件
  14.     for(;;)
  15.    {
  16.      nfds=epoll_wait(epfd,events,5,-1);
  17.      for(int i=0;i<nfds;i++)
  18.      {
  19.        if(events[i].data.fd==STDIN_FILENO)
  20.        {
  21.           read(STDIN_FILENO,buf,sizeof(buf));//將緩衝中的內容讀出
  22.           cout<<"hello world!"<<endl;
  23.        }
  24.     }
  25.   }
  26. }

執行結果:

程式二依然使用LT模式,但是每次epoll_wait返回讀就緒的時候我們都將buffer(緩衝)中的內容read出來,所以導致buffer再次清空,下次呼叫epoll_wait就會阻塞。所以能夠實現我們所想要的功能——當使用者從控制檯有任何輸入操作時,輸出hello world。我們再來看看程式三。

程式三


點選(此處)摺疊或開啟

  1. int main(void)
  2. {
  3.     int epfd,nfds;
  4.     struct epoll_event ev,events[5];//ev用於註冊事件,陣列用於返回要處理的事件
  5.     epfd=epoll_create(1);//只需要監聽一個描述符——標準輸入
  6.     ev.data.fd=STDIN_FILENO;
  7.     ev.events=EPOLLIN|EPOLLET;//使用預設LT模式
  8.     epoll_ctl(epfd,EPOLL_CTL_ADD,STDIN_FILENO,&ev);//註冊epoll事件
  9.     for(;;)
  10.    {
  11.      nfds=epoll_wait(epfd,events,5,-1);
  12.      for(int i=0;i<nfds;i++)
  13.      {
  14.        if(events[i].data.fd==STDIN_FILENO)
  15.         {
  16.           cout<<"hello world!"<<endl;
  17.           ev.data.fd=STDIN_FILENO;
  18.           ev.events=EPOLLIN|EPOLLET;//使用預設LT模式
  19.           epoll_ctl(epfd,EPOLL_CTL_MOD,STDIN_FILENO,&ev);//重新MOD事件(ADD無效)
  20.         }
  21.      }
  22.    }
  23. }

程式三依然使用ET,但是每次讀就緒後都主動的再次MOD IN事件,我們發現程式再次出現死迴圈,也就是每次返回讀就緒。這就驗證了上一節討論ET讀就緒的第三種情況。但是注意,如果我們將MOD改為ADD,將不會產生任何影響。別忘了每次ADD一個描述符都會在epitem組成的紅黑樹中新增一個項,我們之前已經ADD過一次,再次ADD將阻止新增,所以在次呼叫ADD IN事件不會有任何影響。

相關文章