epoll关于ET、LT模式和socket非阻塞模式的几个总结

epoll的ET和LT模式

LT水平触发是默认的模式,只要缓冲区有消息就会触发,如果这次事件没有被处理,那么下一次调用epoll_wait的时候,事件仍然会被触发;ET边沿触发只会在第一次有消息的时候触发,之后再次调用epoll_wait的时候,事件不会被再次触发了。Linux的epoll默认是LT模式。

下面这一段来自于:https://blog.csdn.net/liuxuejiang158blog/article/details/12290725
二者的差异在于level-trigger模式下只要某个socket处于readable/writable状态,无论什么时候进行epoll_wait都会返回该socket;而edge-trigger模式下只有某个socket从unreadable变为readable或从unwritable变为writable时,epoll_wait才会返回该socket。在epoll的ET模式下,正确的读写方式为:读:只要可读,就一直读,直到返回0,或者 errno = EAGAIN;写:只要可写,就一直写,直到数据发送完,或者 errno = EAGAIN

也就是说,ET模式是在不可读写变化到可读写的时候,才会触发。

编程时的应用

如果使用默认的LT处理模式,则意味着socket只要可读或者可写,那么就会一直有事件发生,这样可以防止事件被忽略,但是会造成额外的开销;如果使用ET模式,则要求程序员获取事件后,要一直处理socket,因为socket再次可读或者可写的时候,不会再有事件发生了。

ET或者LT模式更多地是和阻塞与非阻塞模式进行配合。可以这总结:如果使用了ET模式,那么必须使用while循环处理socket上所有的数据或者消息,否则可能会存在有数据永远不会被读取的情况。

ET模式的优势在于开销小,不用过度处理消息,但是要求程序员必须一次性处理完一个可读写事件,否则下次不会激发。只有状态再次变化的时候才行。

补充一点:accept函数不论什么时候,都要设置为非阻塞模式,参考这篇博客,给出理由:TCP连接被客户端夭折,即在服务器调用accept之前,客户端主动发送RST终止连接,导致刚刚建立的连接从就绪队列中移出,如果套接口被设置成阻塞模式,服务器就会一直阻塞在accept调用上,直到其他某个客户建立一个新的连接为止。但是在此期间,服务器单纯地阻塞在accept调用上,就绪队列中的其他描述符都得不到处理。 解决办法是把监听套接口设置为非阻塞,当客户在服务器调用accept之前中止某个连接时,accept调用可以立即返回-1,这时源自Berkeley的实现会在内核中处理该事件,并不会将该事件通知给epool,而其他实现把errno设置为ECONNABORTED或者EPROTO错误,我们应该忽略这两个错误.

下面给出一个一般的例子:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define MAX_EVENTS 200
#define MAX_BUFFER_SIZE 1000

int setnonblocking(int fd) {
    int old_option = fcntl(fd, F_GETFL);
    int new_option = old_option | O_NONBLOCK;
    fcntl(fd, F_SETFL, new_option);
    return old_option;
}


int main() {
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in addr;
    bzero(&addr, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(8001);
    addr.sin_addr.s_addr = htonl(INADDR_ANY);

    int ret = bind(listenfd, (struct sockaddr*)&addr, sizeof(addr));
    if (ret < 0) {
        perror("bind() error\n");
        close(listenfd);
        return 1;
    }

    ret = listen(listenfd, 32);
    if (ret < 0) {
        perror("listen() error\n");
        close(listenfd);
        return 1;
    }
    
    setnonblocking(listenfd);  // 设置为非阻塞模式

    int epollfd = epoll_create1(0);
    if (epollfd < 0) {
        perror("epoll_create1() error\n");
        close(listenfd);
        return 1;
    }

    epoll_event events[MAX_EVENTS];
    
    epoll_event ev;
    bzero(&ev, sizeof(ev));
    ev.data.fd = listenfd;
    ev.events = EPOLLIN | EPOLLET | EPOLLRDHUP;    // 监听到来的连接、ET模式、用户主动断开连接

    ret = epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &ev);
    if (ret < 0) {
        perror("epoll_ctl() error\n");
        close(listenfd);
        close(epollfd);
        return 1;
    }

    while (1) {
        int nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);
        if (nfds < 0) {
            perror("epoll_wait error\n");
            close(listenfd);
            close(epollfd);
            return 1;
        }
        int connfd = -1;
        for (int  i = 0; i < nfds; ++i) {
            if (events[i].data.fd == listenfd) {
                struct sockaddr_in client_address;
                socklen_t client_addrlen = sizeof(client_address);
                // 这里使用while循环处理accept,否则如果并发连接,ET模式的accept无法激发后续的连接事件
                while ((connfd = accept(listenfd, (struct sockaddr*)& client_address,
                                        &client_addrlen)) > 0) {
                    // 在这里注册新的连接
                    ev.data.fd = connfd;
                    ev.events &= 0;
                    ev.events |= EPOLLIN | EPOLLET;
                    epoll_ctl(epollfd, EPOLL_CTL_DEL, connfd, &ev);
                    // 下面添加一些新的操作
                } 
            } else if (events[i].events == EPOLLRDHUP) {  // 断开连接应该在EPOLLIN之前,以免不确定事件发生
                // 有用户断开连接,取消注册
                ev.data.fd = connfd;
                ev.events &= 0;
                ev.events |= EPOLLIN | EPOLLET;
                epoll_ctl(epollfd, EPOLL_CTL_DEL, events[i].data.fd, &ev);
                // 添加其它自定义操作
            } else if (events[i].events == EPOLLIN) {  // 有用户添加数据
                char buf[MAX_BUFFER_SIZE];
                memset(buf, 0, MAX_BUFFER_SIZE);
                // ET模式必须是一次性循环接受完毕,MSG_DONTWAIT参数可以更改,在这里是非阻塞模式
                while((ret = recv(events[i].data.fd, buf, MAX_BUFFER_SIZE, MSG_DONTWAIT)) > 0) {
                    // 这里是循环处理数据
                }
                if (ret < 0) {
                    perror("recv() error\n");
                    // 添加错误处理代码
                }
            } else {
                perror("unknown message\n");
                break;
            }
        }
    }
    return 0;
}

你可能感兴趣的:(Unix/Linux)