• epoll的lt和et模式的实验


    针对epoll api的两种触发模式,lt和et,仿照一些例子写了代码进行实验。

    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <sys/epoll.h>
    #include <pthread.h>
    
    #include <assert.h>
    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    
    #include <errno.h>
    #include <string.h>
    #include <fcntl.h>
    
    #define MAX_EVENT_NUMBER 1024
    #define BUFFER_SIZE 10
    
    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;
    }
    
    void addfd(int epollfd, int fd, bool enable_et) {
            epoll_event event;
            event.data.fd = fd;
            event.events = EPOLLIN;
            if (enable_et) {
                    event.events |= EPOLLET;
            }
            epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);
            setnonblocking(fd);
    }
    
    void lt(epoll_event *events, int number, int epollfd, int listenfd) {
            char buf[BUFFER_SIZE];
            for (int i=0; i<number; i++) {
                    int sockfd = events[i].data.fd;
                    if (sockfd == listenfd) {
                            sockaddr_in client_address;
                            socklen_t client_addrlen = sizeof(client_address);
                            int connfd = accept(listenfd, (sockaddr*)&client_address,
                                                    &client_addrlen);
                            addfd(epollfd, connfd, false);
                    }
                    else if (events[i].events & EPOLLIN) {
                            printf("lt event trigger once
    ");
                            memset(buf, '', BUFFER_SIZE);
                            int ret = recv(sockfd, buf, BUFFER_SIZE-1, 0);
                            if (ret <= 0) {
                                    close(sockfd);
                                    continue;
                            }
                            printf("get %d bytes of content: %s
    ", ret, buf);
                    }
                    else {
                            printf("something else happened
    ");
                    }
            }
    }
    
    void et(epoll_event *events, int number, int epollfd, int listenfd) {
            char buf[BUFFER_SIZE];
            for (int i=0; i<number; i++) {
                    int sockfd = events[i].data.fd;
                    if (sockfd == listenfd) {
                            sockaddr_in client_address;
                            socklen_t client_addrlen = sizeof(client_address);
                            int connfd = accept(listenfd, (sockaddr*)&client_address,
                                                    &client_addrlen);
                            addfd(epollfd, connfd, true);
                    }
                    else if(events[i].events & EPOLLIN) {
                            // Need to read complete
                            printf("et event trigger once
    ");
                            while (true) {
                                    memset(buf, '', BUFFER_SIZE);
                                    int ret = recv(sockfd, buf, BUFFER_SIZE-1, 0);
                                    if (ret < 0) {
                                            // Below shows complete
                                            if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) {
                                                    printf("read later
    ");
                                                    break;
                                            }
                                            printf("some error happens
    ");
                                            close(sockfd);
                                            break;
                                    }
                                    else if (ret == 0) {
                                            close(sockfd);
                                            break;
                                    }
                                    else {
                                            printf("get %d bytes of content: %s
    ", ret, buf);
                                    }
                            }
                    }
                    else {
                            printf("something else happened
    ");
                    }
            }
    }
    
    int main(int argc, char *argv[]) {
            if (argc <= 1) {
                    printf("usage: %s port_number ip_address
    ", basename(argv[0]));
                    return 1;
            }
    
            int port = atoi(argv[1]);
            int ret = 0;
            sockaddr_in address;
            bzero(&address, sizeof(address));
            address.sin_family = AF_INET;
            if (argc >= 3) {
                    const char *ip =argv[2];
                    inet_pton(AF_INET, ip, &address.sin_addr);
            }
            else {
                    address.sin_addr.s_addr = INADDR_ANY;
            }
            address.sin_port = htons(port);
    
            int listenfd = socket(PF_INET, SOCK_STREAM, 0);
            assert(listenfd >= 0);
    
            ret = bind(listenfd, (sockaddr*)&address, sizeof(address));
            assert(ret != - 1);
    
            ret = listen(listenfd, 5);
            assert(ret != -1);
    
            epoll_event events[MAX_EVENT_NUMBER];
            int epollfd = epoll_create(5);
            assert(epollfd != -1);
            addfd(epollfd, listenfd, true);
    
            while(true) {
                    ret = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1);
                    if (ret < 0) {
                            printf("epoll failure
    ");
                            break;
                    }
                    lt(events, ret, epollfd, listenfd); // lt
                    //et(events, ret, epollfd, listenfd); // et
            }
    
            close(listenfd);
            return 0;
    
    }

    Makefile文件:

    epoll_test : epoll_test.cpp
            g++ -o epoll_test epoll_test.cpp -lpthread

    以上程序有个问题,就是在端口被占用时候,因为bind失败,会assert失败然后core dump. 在重复测试时候,可以换个端口。

    首先,注释掉et,使用lt:

                    lt(events, ret, epollfd, listenfd); // lt
                    //et(events, ret, epollfd, listenfd); // et

    运行 ./epoll_test 12888 并在另一个窗口用telnet输入超过10个(BUFFERSIZE)字符:

    $telnet 127.0.0.1 12888
    Trying 127.0.0.1...
    Connected to 127.0.0.1.
    Escape character is '^]'.
    12345678901234567890123456
    1234567890123456789012345678

    在服务器端得到:

    $./epoll_test 12888
    lt event trigger once
    get 9 bytes of content: 123456789
    lt event trigger once
    get 9 bytes of content: 012345678
    lt event trigger once
    get 9 bytes of content: 90123456
    lt event trigger once
    get 1 bytes of content: 
    
    lt event trigger once
    get 9 bytes of content: 123456789
    lt event trigger once
    get 9 bytes of content: 012345678
    lt event trigger once
    get 9 bytes of content: 901234567
    lt event trigger once
    get 3 bytes of content: 8

    可以看出因为buffersize的限制,服务器端进行了多次读取,event也触发了多次。

    换成et模式:

                    //lt(events, ret, epollfd, listenfd); // lt
                    et(events, ret, epollfd, listenfd); // et

    运行服务器后,telnet客户端输入:

    $telnet 127.0.0.1 12889
    Trying 127.0.0.1...
    Connected to 127.0.0.1.
    Escape character is '^]'.
    12345678901234567890123456
    1234567890123456789012345678

    服务器显示:

    $./epoll_test 12889
    et event trigger once
    get 9 bytes of content: 123456789
    get 9 bytes of content: 012345678
    get 9 bytes of content: 90123456
    get 1 bytes of content: 
    
    read later
    et event trigger once
    get 9 bytes of content: 123456789
    get 9 bytes of content: 012345678
    get 9 bytes of content: 901234567
    get 3 bytes of content: 8
    
    read later

    可以看出,每次客户端的字符串,只触发了一次。

    其实,上面的例子还不够严谨,因为服务器一次已经把字符都读完了。那么如果没读完,会继续出发吗。

    如下修改服务器代码:

                            //修改
                            //while (true) {
                            if (true) {
                                    memset(buf, '', BUFFER_SIZE);
                                    int ret = recv(sockfd, buf, BUFFER_SIZE-1, 0);
                                    if (ret < 0) {
                                            // Below shows complete
                                            if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) {
                                                    printf("read later
    ");
                                                    break;
                                            }
                                            printf("some error happens
    ");
                                            close(sockfd);
                                            //修改
                                            //break;
                                    }
                                    else if (ret == 0) {
                                            close(sockfd);
                                            //修改
                                            //break;
                                    }
                                    else {
                                            printf("get %d bytes of content: %s
    ", ret, buf);
                                    }
                            }        

    运行服务器之后,telnet输入长字符串:

    $telnet 127.0.0.1 12888
    Trying 127.0.0.1...
    Connected to 127.0.0.1.
    Escape character is '^]'.
    12345678901234567890123456

    服务器端只显示了BUFFERSIZE长度的一行,没有读入的数据也没有进行event触发:

    $./epoll_test 12888
    et event trigger once
    get 9 bytes of content: 123456789

    如果客户端,再输入一行:

    $telnet 127.0.0.1 12888
    Trying 127.0.0.1...
    Connected to 127.0.0.1.
    Escape character is '^]'.
    12345678901234567890123456
    1234567890123456789012345678

    服务器端也仅仅把之前没读入的上一次客户端发来的数据中,再读入BUFFERSIZE长度:

    $./epoll_test 12888
    et event trigger once
    get 9 bytes of content: 123456789
    et event trigger once
    get 9 bytes of content: 012345678

    另外,对上面的服务器端程序,增加了et模式下对recv函数的返回ret=0的打印:

                                    else if (ret == 0) {
                                            printf("get 0 data
    ");
                                            close(sockfd);
                                            break;
                                    }

    发现在et模式下,没有走到ret==0的分支:

    $telnet 127.0.0.1 12889
    Trying 127.0.0.1...
    Connected to 127.0.0.1.
    Escape character is '^]'.
    12345678901234567890123456

    服务器端:

    $./epoll_test 12889
    et event trigger once
    get 9 bytes of content: 123456789
    get 9 bytes of content: 012345678
    get 9 bytes of content: 90123456
    get 1 bytes of content: 
    
    read later

    走的是如下的判断结束的分支:

                                            if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) {
                                                    printf("read later
    ");
                                                    break;
                                            }

    以上。:)

  • 相关阅读:
    建立一个能持续处理的C/S网络程序
    建立一个可以不停地接收客户端新的连接,但不能处理复杂的业务的C/S网络程序
    归并排序
    堆排序
    计数排序
    不要在PHP7中踩这些坑
    关于生活学习
    topthink/think-swoole 扩展包的使用 之 WebSocket
    PHP 三元运算符
    topthink/think-swoole 扩展包的使用 之 Task
  • 原文地址:https://www.cnblogs.com/charlesblc/p/5521086.html
Copyright © 2020-2023  润新知