[C++] epoll server实例

时光毁灭记忆、已成空白 提交于 2020-02-25 19:56:43
// 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;
}

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!