epoll函式——ET模式與LT模式的區別
阿新 • • 發佈:2018-12-17
LT模式(普通模式):也叫水平觸發。描述符上有資料就緒,如果使用者沒有處理完,可以反覆提醒,當下一輪I/O函式執行時會繼續提醒使用者該描述符上有資料,直到使用者將資料讀完為止。
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <assert.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/epoll.h> #define MAXFD 10 void epoll_add(int epfd,int fd)//往核心事件表中新增檔案描述符 { struct epoll_event ev; ev.events = EPOLLIN;//註冊讀事件,設定關注 ev.data.fd = fd; if(epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&ev )== -1) //EPOLL_CTL_ADD為操作方法新增 { perror("epoll_ctl add error"); } } void epoll_del(int epfd,int fd)//從核心事件表中移除檔案描述符 { if(epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL) == -1 ) //EPOLL_CTL_ADD為操作方法移除 { perror("epoll_ctl del error"); } } int main() { int sockfd = socket(AF_INET,SOCK_STREAM,0); assert(sockfd != -1); struct sockaddr_in saddr,caddr; memset(&saddr,0,sizeof(saddr)); saddr.sin_family = AF_INET; saddr.sin_port = htons(6000); saddr.sin_addr.s_addr = inet_addr("127.0.0.1"); int res = bind(sockfd,(struct sockaddr *)&saddr,sizeof(saddr)); assert(res != -1); listen(sockfd,5); assert(sockfd != -1); int epfd = epoll_create(MAXFD); //系統呼叫,在核心空間中建立核心事件表,實際為一種紅黑樹的資料結構 if(epfd == -1) { perror("epoll_create failed"); } epoll_add(epfd,sockfd);//把事件和檔案描述符新增到紅黑樹中的結點中去 struct epoll_event events[MAXFD]; //因為epoll_wait()會把就緒的檔案描述符存在一個數組中,所以定義這個陣列 while(1) { printf("epoll wait\n"); int n = epoll_wait(epfd,events,MAXFD,5000); //檢查就緒並且將其新增到events這個陣列中 if(n == -1) //失敗 { perror("epoll_wait error"); continue; } else if(n == 0)//超時 { printf("time out\n"); continue; } //有n個數據元素就緒 else { int i = 0; for(;i < n;i++) { int fd = events[i].data.fd; if(events[i].events & POLLIN) //因為有多種事件,因此要檢查是哪種型別的時間,在此關注檢查讀事件 { if(fd == sockfd) //監聽套接字 { int len = sizeof(caddr); int c = accept(sockfd,&caddr,&len); if(c <= 0) { continue; } printf("accept c=%d\n",c); epoll_add(epfd,c); //新的連線產生,新增到核心事件表中 } else { char buff[128] = {0}; int num = recv(fd,buff,1,0) //每次讀一個字元,看LT模式下的執行規則 if( num <= 0) //對方關閉 { //先epoll_del()移除再close()關閉,因為epoll_del()中epoll_ctl()這 //個系統呼叫要用到fd,所以不能先關閉,如果先關閉的話,就找不到了,在執 //行時會提示為無效描述符close()不會使值發生變化,但會使得值無效 epoll_del(epfd,fd); close(fd); printf("one client close\n"); continue; } printf("recv(%d):%s\n",fd,buff); send(fd,"OK",2,0); } } } } } }
執行結果:
ET模式(高效模式):也叫邊沿觸發。描述符上有資料就緒,如果使用者把資料沒有處理或沒處理完,只提醒一次,下一輪I/O函式執行時不會提醒,除非有新資料到達。
ET模式下,一次將資料讀完的思路:
1、設定描述符為非阻塞
2、迴圈讀取
# include<stdio.h> # include<sys/epoll.h> # include<sys/socket.h> # include<assert.h> # include<arpa/inet.h> # include<string.h> # include<unistd.h> # include<sys/socket.h> # include<stdlib.h> # include<errno.h> # include<fcntl.h> #define MAXFD 10 void setnonblock(int fd)//對檔案描述符設定非阻塞模式,防止迴圈式把自己阻塞住 { int oldfl = fcntl(fd, F_GETFL);//獲取原來的屬性資訊 int newfl = oldfl | O_NONBLOCK;//加非阻塞屬性 if(fcntl(fd, F_SETFL, newfl) == -1) { perror("fcntl error\n"); } } void epoll_add(int epfd, int fd) { struct epoll_event ev; ev.events = EPOLLIN|EPOLLET; ev.data.fd = fd; if(epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev) == -1) { perror("epoll ctl error"); } setnonblock(fd);//在此呼叫設定非阻塞函式 } void epoll_del(int epfd, int fd) { if(epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL) == -1) { perror("epoll ctl del error\n"); } } int main() { int sockfd = socket(AF_INET, SOCK_STREAM, 0); assert(sockfd != -1); struct sockaddr_in saddr, caddr; memset(&saddr, 0, sizeof(saddr)); saddr.sin_family = AF_INET; saddr.sin_port = htons(6000); saddr.sin_addr.s_addr = inet_addr("127.0.0.1"); int res = bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)); assert(res != -1); listen(sockfd, 5); int epfd = epoll_create(MAXFD); epoll_add(epfd, sockfd); struct epoll_event events[MAXFD]; while(1) { printf("epoll_wait\n"); int n = epoll_wait(epfd, events, MAXFD, 5000); if(n == -1) { printf("epoll_wait error\n"); continue; } else if(n == 0) { printf("time out\n"); continue; } else { int i = 0; for(; i < n; i++) { int fd = events[i].data.fd; if(events[i].events & EPOLLIN) { if(fd == sockfd) { int len = sizeof(caddr); int c = accept(sockfd, (struct sockaddr*)&caddr, &len); if( c < 0) { continue; } printf("accept c = %d\n", c); epoll_add(epfd, c); } else { while(1) { char buff[128] = {0}; int num = recv(fd, buff, 1, 0); if(num == -1)//一波資料讀完了 { //出錯原因:由於沒資料出錯,EAGAIN 和 EWOULDBLOCK表示在非 //阻塞模式下,資料未準備好 if(errno == EAGAIN || errno == EWOULDBLOCK) { send(fd, "ok", 2, 0); } break; } else if(num == 0)//對方關閉 { epoll_del(epfd, fd); close(fd); printf("one client over\n"); break; } printf("recv %d = %s\n",fd, buff); } } } } } } }