[C++] epoll server實例


// IO多路復用,事件驅動+非阻塞,實現一個線程完成對多個fd的監控和響應,提升CPU利用率
// epoll優點:
//      1.select需要每次調用select時拷貝fd,epoll_ctl拷貝一次,epoll_wait就不需要重復拷貝
//      2.不需要像select遍歷fd做檢查,就緒的會被加入就緒list,遍歷list完成處理
//      3.沒有最大連接限制,與最大文件數目相關:cat /proc/sys/fs/file-max,與內存相關
// epoll實現相關:
//      1.epoll_ctl,將fd的event使用RB tree保存,讀寫O(logN);
//      2.一旦有event,內核負責添加到rdlist鏈表
//      3.epoll_wait檢查鏈表看是否有事件,並進行處理

// Ref
//      https://www.cnblogs.com/lojunren/p/3856290.html
//      http://blog.chinaunix.net/uid-28541347-id-4273856.html


// Question:
//      是否需要每個event一個實例?

#include <cstdlib>              /* exit() */
#include <cstdio>               /* perror(): 打印信息+發生錯誤的原因,可用於定位。 */
#include <iostream>             /* cin cout */
#include <cstdint>              /* uint32 */
#include <cstring>              /* memset memcpy*/
#include <sys/types.h>          /* 為了滿足一些 BSD系統添加頭文件*/
#include <sys/socket.h>         /* socket(); listen(); baccept(); socklen_t */
#include <netinet/in.h>         /* struct sockaddr_in: 
                                保存socket信息; ntohl(), ntohs(), htonl() and htons()*/
#include <arpa/inet.h>          /* inet_ntoa */
#include <sys/epoll.h>          /* epoll_create(); epoll_ctlstruct epoll_event*/
#include <unistd.h>             /* read() write(), 不是C語言范疇,所以沒有cxxxx的實現 */

#include <cerrno>               /* errno */
                                // http://minirighi.sourceforge.net/html/errno_8h.html

typedef void (*eventHandleFunc)(void);
typedef struct tzEventHandler{
    eventHandleFunc event_handler_func;
    void *ptr;
    int fd;
}TzEventHandler;

void handlerImpl(void){
    std::cout << "handle an event." << std::endl;
}
void read_handlerImpl(void){
    std::cout << "handle an read event." << std::endl;
}
void send_handlerImpl(void){
    std::cout << "handle an send event." << std::endl;
}

void checEventType(uint32_t type){
    std::cout << "type check:" << std::endl;
    if(type & EPOLLIN) std::cout << "\tEPOLLIN" << std::endl;
    if(type & EPOLLOUT) std::cout << "\tEPOLLOUT" << std::endl;
    if(type & EPOLLRDHUP ) std::cout << "\tEPOLLRDHUP " << std::endl;
    if(type & EPOLLPRI) std::cout << "\tEPOLLPRI" << std::endl;
    if(type & EPOLLERR) std::cout << "\tEPOLLERR" << std::endl;
    if(type & EPOLLHUP) std::cout << "\tEPOLLHUP" << std::endl;
    if(type & EPOLLET) std::cout << "\tEPOLLET" << std::endl;
    if(type & EPOLLONESHOT ) std::cout << "\tEPOLLONESHOT " << std::endl;
    if(type & EPOLLWAKEUP ) std::cout << "\tEPOLLWAKEUP " << std::endl;
}
/**
\brief  錯誤處理函數
*/
void tzError(const char *msg)
{
    perror(msg);
    exit(1);    // 一般不同原因不同的exit code更為規范
}

int main(int argc, char *argv[]){
    // listen socket
    int listenfd = socket(AF_INET, SOCK_STREAM, 0); // 監聽端口非阻塞
    if(listenfd<0){
        tzError("listen socket()");
    }

    // bind
    struct sockaddr_in listen_addr = {0};
    listen_addr.sin_family = AF_INET;
    listen_addr.sin_port = htons(8081);
    listen_addr.sin_addr.s_addr = INADDR_ANY;
    int socket_opt_ret = bind(listenfd, (struct sockaddr*)&listen_addr, sizeof(listen_addr));
    if(socket_opt_ret<0){
        tzError("bind()");
    }
    
    // listen
#define MAX_LISTEN_TCP 10
    socket_opt_ret = listen(listenfd, MAX_LISTEN_TCP);
    if(socket_opt_ret<0){
        tzError("listen()");
    }

    // epoll
    int epoll_fd = epoll_create(5);
        // int epoll_create(int size);
        // http://man7.org/linux/man-pages/man2/epoll_create.2.html
        // size:    用於告訴kernel可能被添加的caller數量,內核估計需要開辟的空間
        //          2.6.8被忽略,kernel會自動開辟需要的空間。但必須大於0(兼容)。
        // return:  epoll相關句柄,一組連接的管理只需要一個
    if(epoll_fd<0){
        tzError("epoll_create()");
    }

    // 自定義處理對象的設置
    TzEventHandler listen_handler;
    listen_handler.fd = listenfd;   // event相關fd
    listen_handler.event_handler_func = handlerImpl;
    int cnt = 0;
    listen_handler.ptr = &cnt;

    // event設置
    struct epoll_event event = {0}; // Hint:對於結構體,{0}觸發聚合初始化,全置0
        // typedef union epoll_data {
        //     void    *ptr;
        //     int      fd;
        //     uint32_t u32;
        //     uint64_t u64;
        // } epoll_data_t;

        // struct epoll_event {
        //     uint32_t     events;    /* Epoll events */
        //     epoll_data_t data;      /* User data variable */
        // };

        // Epoll events:
        //      EPOLLIN     可以read時觸發
                // read event:
                //      socket of TCP,三次握手結束,可以accept時
                //      socket 接收緩沖數據>SO_RCVLOWAT,default 1,使用read處理
                //      socket peer關閉連接時,且read為0;如果非阻塞無數據,read返回-1並設置errno=EAGAIN
                //      socket 有未處理的錯誤,此時可以用getsockopt來讀取和清除該錯誤
        //      EPOLLOUT    可以write時觸發
                // write event:
                //      socket 發送緩沖數據>SO_SNDLOWAIT
                //      socket 非阻塞模式下,connect返回之后,發起連接成功或失敗
                //      socket上有未處理的錯誤,此時可以用getsockopt來讀取和清除該錯誤
        //      EPOLLET     邊緣觸發模式???
        //      EPOLLRDHUP  >2.6.17,如果對方close connection或write時對方退出時觸發。可用於邊緣模式的探測。
    event.events = EPOLLIN;
    event.data.ptr = (void*)&listen_handler;    // 指向處理事件的對象

    // event注冊
    epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listenfd, &event);
        // int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
        // http://man7.org/linux/man-pages/man2/epoll_ctl.2.html
        // epfd: epoll實例的fd
        // op:
        //      EPOLL_CTL_ADD   添加事件,將要監聽的fd注冊,並設置event中的事件進行監聽
        //      EPOLL_CTL_MOD   修改事件
        //      EPOLL_CTL_DEL   刪除事件,刪除對應的df,event參數不為NULL但是被忽略(after 2.6.9可以為NULL)
        // fd:
        //      要被監聽事件的fd,所以epoll數量只受文件系統限制
        // event:
        //      指定了監聽設置的結構體,包含要監測的事件類型,處理方法
        // 注意:每個fd只能add一次,改變監聽事件使用MOD;
        //      如果添加多次fd,視為無效,並errno返回EEXIST,epoll_ctl返回-1
    
    // event處理
#define BUFSIZE 100     // read接收緩存
#define MAXNFD  10      // 一次最多接受read的數量
    struct epoll_event recv_events[MAXNFD] = {0};   // 用於保存獲取的事件隊列,依次處理
    int n_ready_event=0;
    char buf[MAXNFD][BUFSIZE] = {0};

    // wait事件
#define EPOLL_TIMEOUT_MS   -1  // -1 nonblock
    while(true){
        n_ready_event = epoll_wait(epoll_fd, recv_events, MAXNFD, EPOLL_TIMEOUT_MS);
            // int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
            // http://man7.org/linux/man-pages/man2/epoll_wait.2.html
            // timeout:
            //      如果-1非阻塞,如果有事件未處理,且為設置邊緣觸發,則一直觸發事件。
        if(n_ready_event<0){
            tzError("epoll_wait()");
        }
        int iter = 0;
        for(iter=0; iter<n_ready_event; iter++){
            // 處理事件
            TzEventHandler *returned_event_handler = (TzEventHandler *)recv_events[iter].data.ptr;
            std::cout   << ">>>get event, handler fd:" << returned_event_handler->fd 
                        << ", cnt:" << *(int*)returned_event_handler->ptr << std::endl;
            (*(int*)returned_event_handler->ptr)++;
            checEventType(recv_events[iter].events);

            // 如果是可讀事件
            if(recv_events[iter].events & EPOLLIN){
                // 如果是像listen發出的監聽請求
                if(returned_event_handler->fd==listenfd){
                    // listener
                    struct sockaddr_in clientaddr = {0};
                    socklen_t client_sock_addr_len = sizeof(clientaddr);
                    int tcp_socketfd = accept(listenfd, (struct sockaddr*)&clientaddr, &client_sock_addr_len);
                    if (tcp_socketfd<0){
                        tzError("accept()");
                    }
                    else{
                        std::cout << "accept" << std::endl;
                        std::cout << "incoming:" << inet_ntoa(clientaddr.sin_addr) << std::endl;
                        // 為新的socket注冊事件
                        TzEventHandler socket_read_handler;
                        socket_read_handler.fd = tcp_socketfd;   // event相關fd
                        socket_read_handler.event_handler_func = read_handlerImpl;
                        int cnt = 0;
                        socket_read_handler.ptr = &cnt;

                        struct epoll_event new_event = {0};
                        new_event.events = EPOLLIN;
                        new_event.data.ptr = (void*)&socket_read_handler;    // 指向處理事件的對象
                        int epoll_ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, tcp_socketfd, &new_event);
                        if(epoll_ret==0) std::cout << "add event" << std::endl;
                    }
                }
                else{
                    // tcp peer msg
                    returned_event_handler->event_handler_func();
#define BUF_SIZE 100
                    char buf[BUF_SIZE];
                    memset(buf, 0, BUF_SIZE);
                    int ret = read(returned_event_handler->fd, buf, BUF_SIZE);
                        // 注意:如果read的長度小於到達的數據,會留下剩余的數據再次觸發IN evnent
                    if(ret == 0){
                        // connect closed
                        std::cout << "TCP fd:" << returned_event_handler->fd << " disconnect." << std::endl;
                        epoll_ctl(epoll_fd, EPOLL_CTL_DEL, returned_event_handler->fd, &recv_events[iter]);
                            // 刪除event,否則會不斷報事件,其他處理方法?
                        close(returned_event_handler->fd);
                    }else{
                        std::cout << "recv content:" << buf << std::endl;
                        // server在收到時才被動應答,所以此時才設置發送event
                        recv_events[iter].events = EPOLLOUT;   // 修改events類型,這樣設置在有發送時不觸發接收事件
                        returned_event_handler->event_handler_func = send_handlerImpl;
                        int ctl_ret = epoll_ctl(epoll_fd, EPOLL_CTL_MOD, returned_event_handler->fd, &recv_events[iter]);
                        if(ctl_ret<0){
                            // 使用error檢查錯誤原因
                            std::cout << "ctl_ret:" << ctl_ret << " errno:";
                            std::cout << errno << std::endl;
                        }
                    }
                }
            }
            // 如果是可寫事件
            else if(recv_events[iter].events & EPOLLOUT){   
                returned_event_handler->event_handler_func();
                int write_ret = write(returned_event_handler->fd, "get one msg", 11);

                recv_events[iter].events = EPOLLIN;   // 修改events類型,這樣設置在有接受時不觸發發送事件
                returned_event_handler->event_handler_func = read_handlerImpl;
                epoll_ctl(epoll_fd, EPOLL_CTL_MOD, returned_event_handler->fd, &recv_events[iter]);
            }else{
                std::cout << "unknown event" << std::endl;
            }
        }
    } // while true
    return 0;
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM