epoll的lt和et模式的實驗

針對epoll api的兩種觸發模式,lt和et,仿照一些例子寫了代碼進行實驗。api

#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\n");
                        memset(buf, '\0', BUFFER_SIZE);
                        int ret = recv(sockfd, buf, BUFFER_SIZE-1, 0);
                        if (ret <= 0) {
                                close(sockfd);
                                continue;
                        }
                        printf("get %d bytes of content: %s\n", ret, buf);
                }
                else {
                        printf("something else happened\n");
                }
        }
}

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\n");
                        while (true) {
                                memset(buf, '\0', 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\n");
                                                break;
                                        }
                                        printf("some error happens\n");
                                        close(sockfd);
                                        break;
                                }
                                else if (ret == 0) {
                                        close(sockfd);
                                        break;
                                }
                                else {
                                        printf("get %d bytes of content: %s\n", ret, buf);
                                }
                        }
                }
                else {
                        printf("something else happened\n");
                }
        }
}

int main(int argc, char *argv[]) {
        if (argc <= 1) {
                printf("usage: %s port_number ip_address\n", 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\n");
                        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. 在重複測試時候,能夠換個端口。app

 

首先,註釋掉et,使用lt:socket

                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也觸發了屢次。spa

 

換成et模式:code

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

運行服務器後,telnet客戶端輸入:blog

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

服務器顯示:ip

$./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, '\0', 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\n");
                                                break;
                                        }
                                        printf("some error happens\n");
                                        close(sockfd);
                                        //修改
                                        //break;
                                }
                                else if (ret == 0) {
                                        close(sockfd);
                                        //修改
                                        //break;
                                }
                                else {
                                        printf("get %d bytes of content: %s\n", 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\n");
                                        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\n");
                                                break;
                                        }

以上。:)

相關文章
相關標籤/搜索