epoll:EPOLLLT和EPOLLET的区别

概念:

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 10
 
static 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


--------------------- 
作者:带鱼兄 
来源:CSDN 
原文:https://blog.csdn.net/daiyudong2020/article/details/50439029 
版权声明:本文为博主原创文章,转载请附上博文链接!

猜你喜欢

转载自blog.csdn.net/u010325193/article/details/86413438