> 文章列表 > epoll进阶

epoll进阶

epoll进阶

        epoll除了提供select/poll那种IO事件的电平触发(Level Triggered)外,还提供了边沿触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率。

【epoll为什么要有EPOLLET触发模式?】:

        如果采用EPOLLLT模式的话,系统中一旦有大量你不需要读写的就绪文件描述符,它们每次调用epoll_wait都会返回,这样会大大降低处理程序检索自己关心的就绪文件描述符的效率.。而采用EPOLLET这种边缘触发模式的话,当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据全部读写完(如读写缓冲区太小),那么下次调用epoll_wait()时,它不会通知你,也就是它只会通知你一次,直到该文件描述符上出现第二次可读写事件才会通知你!!!这种模式比水平触发效率高,系统不会充斥大量你不关心的就绪文件描述符。 

 事件模型

EPOLL事件有两种模型:

        Edge Triggered (ET) 边缘触发只有数据到来才触发,不管缓存区中是否还有数据。

        Level Triggered (LT) 水平触发只要有数据都会触发。

LT是默认的模式,ET是“高速”模式

        LT(水平触发)模式下,只要这个文件描述符还有数据可读,每次 epoll_wait都会返回它的事件,提醒用户程序去操作;

        ET(边缘触发)模式下,在它检测到有 I/O 事件时,通过 epoll_wait 调用会得到有事件通知的文件描述符,对于每一个被通知的文件描述符,如可读,则必须将该文件描述符一直读到空,让 errno 返回 EAGAIN 为止,否则下次的 epoll_wait 不会返回余下的数据,会丢掉事件。如果ET模式不是非阻塞的,那这个一直读或一直写势必会在最后一次阻塞

案例一:基于管道epoll ET/LT 触发模式 

#include <stdio.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <errno.h>
#include <unistd.h>#define MAXLINE 10int main(int argc, char *argv[])
{int efd, i;int pfd[2];pid_t pid;char buf[MAXLINE], ch = 'a';pipe(pfd);pid = fork();if (pid == 0) {             //子 写close(pfd[0]);while (1) {//aaaa\\nfor (i = 0; i < MAXLINE/2; i++)buf[i] = ch;buf[i-1] = '\\n';ch++;//bbbb\\nfor (; i < MAXLINE; i++)buf[i] = ch;buf[i-1] = '\\n';ch++;//aaaa\\nbbbb\\nwrite(pfd[1], buf, sizeof(buf));sleep(5);}close(pfd[1]);} else if (pid > 0) {       //父 读struct epoll_event event;struct epoll_event resevent[10];        //epoll_wait就绪返回eventint res, len;close(pfd[1]);efd = epoll_create(10);event.events = EPOLLIN | EPOLLET;     // ET 边沿触发// event.events = EPOLLIN;                 // LT 水平触发 (默认)event.data.fd = pfd[0];epoll_ctl(efd, EPOLL_CTL_ADD, pfd[0], &event);while (1) {res = epoll_wait(efd, resevent, 10, -1);printf("res %d\\n", res);if (resevent[0].data.fd == pfd[0]) {len = read(pfd[0], buf, MAXLINE/2);write(STDOUT_FILENO, buf, len);}}close(pfd[0]);close(efd);} else {perror("fork");exit(-1);}return 0;
}

案例二:基于网络C/S模型的epoll ET触发模式(过渡案例,一般不这么写

如果ET模式不是非阻塞的,那这个一直读或一直写势必会在最后一次阻塞。

ET模式 只支持 非阻塞模式

/* block_epoll_server.c */
#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 9000int main(void)
{struct sockaddr_in servaddr, cliaddr;socklen_t cliaddr_len;int listenfd, connfd;char buf[MAXLINE];char str[INET_ADDRSTRLEN];int 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);event.events = EPOLLIN | EPOLLET;     /* ET 边沿触发, 默认 LT 水平触发 */printf("Accepting connections ...\\n");cliaddr_len = sizeof(cliaddr);connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);//自己建立连接,不用监听listenfd了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) {//如果ET模式不是非阻塞的,那这个一直读或一直写势必会在最后一次阻塞(阻塞在epoll_wait)。res = epoll_wait(efd, resevent, 10, -1); //只监听cfdprintf("res %d\\n", res);if (resevent[0].data.fd == connfd) {//如果ET模式不是非阻塞的,那这个一直读或一直写势必会在最后一次阻塞。(阻塞在epoll_wait)len = read(connfd, buf, MAXLINE/2);         //readn(500)   write(STDOUT_FILENO, buf, len);}}return 0;
}

案例三:基于网络C/S非阻塞模型的epoll ET触发模式(重)

 ET模式 只支持 非阻塞模式

/* noblock_epoll_server.c */
#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 8000int main(void)
{struct sockaddr_in servaddr, cliaddr;socklen_t cliaddr_len;int listenfd, connfd;char buf[MAXLINE];char str[INET_ADDRSTRLEN];int 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 res_event[10];int res, len;efd = epoll_create(10);event.events = EPOLLIN | EPOLLET;     /* ET 边沿触发,默认是水平触发 *///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);        /* 修改connfd为非阻塞读 */ //是指所读套接字变为非阻塞flag |= O_NONBLOCK;fcntl(connfd, F_SETFL, flag);event.data.fd = connfd;epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &event);    //将connfd加入监听红黑树 //是指所读套接字变为非阻塞while (1) {printf("epoll_wait begin\\n");res = epoll_wait(efd, res_event, 10, -1);        //最多10个, 阻塞监听printf("epoll_wait end res %d\\n", res);if (res_event[0].data.fd == connfd) {while ((len = read(connfd, buf, MAXLINE/2)) >0 )    //非阻塞读, 轮询write(STDOUT_FILENO, buf, len);}}return 0;
}
/* client.c */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>#define MAXLINE 10
#define SERV_PORT 9000int main(int argc, char *argv[])
{struct sockaddr_in servaddr;char buf[MAXLINE];int sockfd, i;char ch = 'a';sockfd = socket(AF_INET, SOCK_STREAM, 0);bzero(&servaddr, sizeof(servaddr));servaddr.sin_family = AF_INET;inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);servaddr.sin_port = htons(SERV_PORT);connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));while (1) {//aaaa\\nfor (i = 0; i < MAXLINE/2; i++)buf[i] = ch;buf[i-1] = '\\n';ch++;//bbbb\\nfor (; i < MAXLINE; i++)buf[i] = ch;buf[i-1] = '\\n';ch++;//aaaa\\nbbbb\\nwrite(sockfd, buf, sizeof(buf));sleep(5);}close(sockfd);return 0;
}