epoll ET(邊緣觸發) LT(水平觸發)


EPOLL事件有兩種模型:
Edge Triggered (ET) 邊緣觸發只有數據到來,才觸發,不管緩存區中是否還有數據。
Level Triggered (LT) 水平觸發只要有數據都會觸發。

 

首先介紹一下LT工作模式:

LT(level triggered)是缺省的工作方式,並且同時支持block和no-block socket.在這種做法中,內核告訴你一個文件描述符是否就緒了,然后你可以對這個就緒的fd進行IO操作。如果你不作任何操作,內核還是會繼續通知你的,所以,這種模式編程出錯誤可能性要小一點。傳統的select/poll都是這種模型的代表.

優點:當進行socket通信的時候,保證了數據的完整輸出,進行IO操作的時候,如果還有數據,就會一直的通知你。

缺點:由於只要還有數據,內核就會不停的從內核空間轉到用戶空間,所有占用了大量內核資源,試想一下當有大量數據到來的時候,每次讀取一個字節,這樣就會不停的進行切換。內核資源的浪費嚴重。效率來講也是很低的。

ET:

ET(edge-triggered)是高速工作方式,只支持no-block socket。在這種模式下,當描述符從未就緒變為就緒時,內核通過epoll告訴你。然后它會假設你知道文件描述符已經就緒,並且不會再為那個文件描述符發送更多的就緒通知。請注意,如果一直不對這個fd作IO操作(從而導致它再次變成未就緒),內核不會發送更多的通知(only once).

優點:每次內核只會通知一次,大大減少了內核資源的浪費,提高效率。

缺點:不能保證數據的完整。不能及時的取出所有的數據。

應用場景: 處理大數據。使用non-block模式的socket。

例子:

《client》

#include <stdio.h>

#include <string.h>

#include <unistd.h>

#include <stdlib.h>

#include <fcntl.h>

#define MAXLINE  10

#define SERV_PROT 8001

int main(void)

{

  struct sockaddr_in seraddr, cliaddr;

  char buf[MAXLINE];

  int connfd, i;

  char ch = 'a';

  connfd = socket(AF_INET,SOCK_STREAM,0);

  bzero(&cliaddr,sizeof(cliaddr));

  cliaddr.sin_family = AF_INET;

  cliaddr.sin_port = htons(SERV_PROT);

  cliaddr.sin_addr.s_addr = htonl(INADDR_ANY);

  connect(connfd, (struct sockaddr *)&cliaddr, sizeof(cliaddr));

  while(1){

    for(i=0;i < MAXLINE;i++){

      buf[i] = ch;

    }

    buf[i-1] = '\n';

    ch++;

    for(;i<MAXLINE;i++)

      buf[i] = ch;

    ch++;

    write(connfd, buf, sizeof(buf));

    sleep(5);

  }

  close(connfd);

  return 0;

}

 

以上代碼由於瀏覽器不支持粘貼,所以全部手打,如有錯誤,稍微調試即可。由於代碼簡單就不解釋代碼的意思了。。。。

《server》

#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <unistd.h>
#define MAXLINE 10
#define SERV_PORT 8000
int main(void)
{
  struct sockaddr_in servaddr, cliaddr;
  socklen_t cliaddr_len;
  int listenfd, connfd;
  char buf[MAXLINE];
  char str[INET_ADDRSTRLEN];
  int i, efd;
  listenfd = socket(AF_INET, SOCK_STREAM, 0);

  bzero(&servaddr, sizeof(servaddr));
  servaddr.sin_family = AF_INET;
  servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
  servaddr.sin_port = htons(SERV_PORT);
  bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
  listen(listenfd, 20);
  struct epoll_event event;
  struct epoll_event resevent[10];
  int res, len;
  efd = epoll_create(10);
/* ET 邊沿觸發 ,默認是水平觸發 */
  event.events = EPOLLIN | EPOLLET;
/* event.events = EPOLLIN; */
  printf("Accepting connections ...\n");
  cliaddr_len = sizeof(cliaddr);
  connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
  printf("received from %s at PORT %d\n",
  inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
  ntohs(cliaddr.sin_port));
  event.data.fd = connfd;
  epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &event);
  while (1) {
    res = epoll_wait(efd, resevent, 10, -1);
    printf("res %d\n", res);
    if (resevent[0].data.fd == connfd) {
    len = read(connfd, buf, MAXLINE/2);
  write(STDOUT_FILENO, buf, len);
  }
}
  return 0;
}

此時服務器端如果使用lt模式:啟動server,啟動client,運行結果:

res 1

aaaa

res 1

bbbb

過5s ........

如果使用et模式 運行結果:

res 1

aaaa

過5s

res 1

bbbb

。。。。。。

所有當我們使用ET模式的時候,需要使用非阻塞的文件描述符。即對connfd使用fcntl改為非阻塞

即:

#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <fcntl.h>
#define MAXLINE 10
#define SERV_PORT 8000
int main(void)
{
  struct sockaddr_in servaddr, cliaddr;
  socklen_t cliaddr_len;
  int listenfd, connfd;
  char buf[MAXLINE];  
  char str[INET_ADDRSTRLEN];  
  int i, efd, flag;
  listenfd = socket(AF_INET, SOCK_STREAM, 0);

  bzero(&servaddr, sizeof(servaddr));
  servaddr.sin_family = AF_INET;
  servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
  servaddr.sin_port = htons(SERV_PORT);
  bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
  listen(listenfd, 20);
  struct epoll_event event;
  struct epoll_event resevent[10];
  int res, len;
  efd = epoll_create(10);
/* ET 邊沿觸發 ,默認是水平觸發 */
  event.events = EPOLLIN | EPOLLET;
/* event.events = EPOLLIN; */
  printf("Accepting connections ...\n");
  cliaddr_len = sizeof(cliaddr);
  connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);  
printf("received from %s at PORT %d\n",
  inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
  ntohs(cliaddr.sin_port));
  flag = fcntl(connfd, F_GETFL);
  flag |= O_NONBLOCK;
  fcntl(connfd, F_SETFL, flag);
  event.data.fd = connfd;
  epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &event);
  while (1) {
    printf("epoll_wait begin\n");
    res = epoll_wait(efd, resevent, 10, -1);
    printf("epoll_wait end res %d\n", res);
    if (resevent[0].data.fd == connfd) {
    while ((len = read(connfd, buf, MAXLINE/2)) >0 )
      write(STDOUT_FILENO, buf, len);
    }
  }
  return 0;
}

添加了以上代碼;運行結果為:

epoll_wait begin

epoll_wait end res 1

aaaa

bbbb

 

epoll_wait begin

過5秒

epoll_wait end res 1

cccc

dddd

 

由此可以看出已經恢復了正常 。一次性全部讀出了全部的數據,當沒有數據的時候,read返回-1,結束循環繼續監聽。。。。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM