epoll:EPOLLLT和EPOLLET的区别

来源:互联网 发布:医保 车祸 知乎 编辑:程序博客网 时间:2024/05/17 00:50

概念:

Level-triggered :水平触发,缺省模式

edge-triggered :边缘触发


比如redis用LT模式,nginx用ET模式


通知模式:

LT模式时,事件就绪时,假设对事件没做处理,内核会反复通知事件就绪

ET模式时,事件就绪时,假设对事件没做处理,内核不会反复通知事件就绪


事件通知的细节:

1.调用epoll_ctl,ADD或者MOD事件EPOLLIN

LT:如果此时缓存区没有可读数据,则epoll_wait不会返回EPOLLIN,如果此时缓冲区有可读数据,则epoll_wait会持续返回EPOLLIN

ET:如果此时缓存区没有可读数据,则epoll_wait不会返回EPOLLIN,如果此时缓冲区有可读数据,则epoll_wait会返回一次EPOLLIN

2.调用epoll_ctl,ADD或者MOD事件EPOLLOUT

LT:如果不调用epoll_ctl将EPOLLOUT修改为EPOLLIN,则epoll_wait会持续返回EPOLLOUT(前提条件是写缓冲区未满)

ET:epoll_wait只会返回一次EPOLLOUT



针对TCP的测试详请,都是non-blocking:

1.listenfd设置为LT

    struct epoll_event ev, events[MAX_EVENTS];    int epollfd = epoll_create(10);    if (-1 == epollfd) {        perror("epoll_create fail");        exit(EXIT_FAILURE);    }    ev.events = EPOLLIN; // LT     ev.data.fd = listenfd;    if (-1 == epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &ev)) {        perror("epoll_ctl: listenfd fail");        exit(EXIT_FAILURE);    }    for (;;) {        int nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);

表现:当3次握手完成,如果不进行accept操作,那么内核会反复通知


2.listenfd设置为ET

    struct epoll_event ev, events[MAX_EVENTS];    int epollfd = epoll_create(10);    if (-1 == epollfd) {        perror("epoll_create fail");        exit(EXIT_FAILURE);    }    ev.events = EPOLLIN | EPOLLET; // ET     ev.data.fd = listenfd;    if (-1 == epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &ev)) {        perror("epoll_ctl: listenfd fail");        exit(EXIT_FAILURE);    }    for (;;) {        int nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);

表现:当3次握手完成,如果不进行accept操作,那么内核只会通知一次


3.connectfd设置为LT

            if (events[n].data.fd == listenfd) {                int connfd = accept(listenfd, NULL, NULL);                if (-1 == connfd) {                    perror("accept fail");                    continue;                }                setnonblocking(connfd);                struct epoll_event ev;                ev.events = EPOLLIN;                ev.data.fd = connfd;                if (-1 == epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &ev)) {                    perror("epoll_ctl: sock");                    continue;                }

表现:

1.客户端发送了24个字节

2.服务器一次只读取8个字节

3.内核会连续通知,epoll_wait会持续返回,直到缓冲区的数据被读取完毕或者说socket不在处于readable/writable状态


4.connectfd设置为ET

if (events[n].data.fd == listenfd) {                int connfd = accept(listenfd, NULL, NULL);                if (-1 == connfd) {                    perror("accept fail");                    continue;                }                setnonblocking(connfd);                struct epoll_event ev;                ev.events = EPOLLIN | EPOLLET;                ev.data.fd = connfd;                if (-1 == epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &ev)) {                    perror("epoll_ctl: sock");                    continue;                }

表现:

1.客户端发送了24个字节

2.服务器一次只读取8个字节

3.内核只会通知一次,剩下的数据会留在缓冲区中


在ET模式下何时会再次通知可读写事件,也是epoll_wait有返回?

只有socket从unreadable/unwritable变为readable/writable状态:

4.当客户端再次发送数据时,则内核会继续通知可读事件

5.当服务器在每次读取完数据,显式的调用epoll_ctl来注册可读事件,如果缓冲区有可读数据则内核会继续通知可读事件


下面提供简单的测试源码,需要微调才能测试完以上几种情况:

------------------------------------------------------------------------------------------------------------------------------------------------

客户端代码:client.cpp

#include <stdio.h>  #include <sys/types.h>  #include <sys/socket.h>  #include <netinet/in.h>  #include <arpa/inet.h>  #include <stdlib.h>  #include <unistd.h>  #include <sys/epoll.h>  #include <fcntl.h>#include <errno.h>#include <string.h>  #define MAX_EVENTS 10  int main()  {      // socket    struct sockaddr_in servaddr;      short port = 9527;      int sockfd = socket(AF_INET, SOCK_STREAM, 0);      servaddr.sin_family = AF_INET;      servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");      servaddr.sin_port = htons(port);    if (connect(sockfd, (sockaddr *) &servaddr, sizeof(sockaddr_in)) < 0) {        perror("connect fail");        exit(EXIT_FAILURE);    }    const char* buf = "daiyudong";    for (;;) {        int len = (int)write(sockfd, buf, strlen(buf));        if (len > 0) {            printf("write len=%d\n", len);        }        sleep(1);    }}  


服务器代码:server.cpp

#include <stdio.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <stdlib.h>#include <unistd.h>#include <sys/epoll.h>#include <fcntl.h>#include <errno.h>#define MAX_EVENTS 10static void setnonblocking(int fd) {    int flag = fcntl(fd, F_GETFL, 0);    if (flag < 0) {        perror("fcntl F_GETFL:");        return;    }    if (fcntl(fd, F_SETFL, flag | O_NONBLOCK) < 0) {        perror("fcntl F_SETFL:");    }}static int epoll_add(int efd, int sock) {    struct epoll_event ev;    ev.events = EPOLLIN;    ev.data.fd = sock;    if (-1 == epoll_ctl(efd, EPOLL_CTL_ADD, sock, &ev)) {        perror("epoll_ctl: sock");        return 1;    }    return 0;}static void epoll_write(int efd, int sock, bool enable) {    struct epoll_event ev;    ev.events = EPOLLIN | (enable ? EPOLLOUT : 0);    ev.data.fd = sock;    epoll_ctl(efd, EPOLL_CTL_MOD, sock, &ev);}static void epoll_del(int efd, int sock) {    epoll_ctl(efd, EPOLL_CTL_DEL, sock , NULL);}int main(){    // socket    int listenfd;    struct sockaddr_in servaddr;    short port = 9527;      servaddr.sin_family = AF_INET;    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);    servaddr.sin_port = htons(port);    listenfd = socket(AF_INET, SOCK_STREAM, 0);    setnonblocking(listenfd);    int res = bind(listenfd, (sockaddr *)&servaddr, sizeof(sockaddr_in));    if (0 == res)        printf("server bind success, 0.0.0.0:%d\n", port);    else {        perror("bind fail");        exit(EXIT_FAILURE);    }    res = listen(listenfd, 100);    if (0 == res)        printf("server listen success\n");    else {        perror("listen fail");        exit(EXIT_FAILURE);    }    // epoll    struct epoll_event ev, events[MAX_EVENTS];    int epollfd = epoll_create(10);    if (-1 == epollfd) {        perror("epoll_create fail");        exit(EXIT_FAILURE);    }    ev.events = EPOLLIN; // LT     ev.data.fd = listenfd;    if (-1 == epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &ev)) {        perror("epoll_ctl: listenfd fail");        exit(EXIT_FAILURE);    }    for (;;) {        int nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);        if (-1 == nfds) {            perror("epoll_wait fail");            exit(EXIT_FAILURE);        }        for (int n = 0; n < nfds; ++n) {            if (events[n].data.fd == listenfd) {                int connfd = accept(listenfd, NULL, NULL);                if (-1 == connfd) {                    perror("accept fail");                    continue;                }                setnonblocking(connfd);                epoll_add(epollfd, connfd);                printf("connfd:%d\n", connfd);            }            else if (events[n].events & EPOLLIN) {                char buf[2] = {0};                int fd = events[n].data.fd;                size_t count = 1;                int len = (int)read(fd, buf, count);                if (len > 0) {                    printf("read len=%d buf=%s\n", len, buf);                    //epoll_write(epollfd, fd, false);                }                else if (len < 0) {                    switch(errno) {                    case EINTR:                    case EAGAIN:                        printf("try again\n");                        //epoll_write(epollfd, fd, false);                        break;                    default:                        epoll_del(epollfd, fd);                        close(fd);                        printf("game over\n");                    }                }                else if (len == 0) {                    epoll_del(epollfd, fd);                    close(fd);                    printf("game over\n");                }            }            else {                // pass            }        }    }}



小结:

针对listenfd,默认使用LT模式,ET模式并无实际意义,也无收益

针对connectfd,使用ET模式则需要注意到与LT模式的读写逻辑不同,比如读取数据,则需要在一个事件内读取完毕


参考man 7 epoll

http://linux.die.net/man/7/epoll




1 0
原创粉丝点击