epoll的简单使用

736 阅读3分钟
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <iostream>


//捕获Ctrl+C
volatile sig_atomic_t flag = 0;

static void my_handler(int sig) { // can be called asynchronously
    flag = 1; // set flag
}

ssize_t count_flag = 0;


//基于epoll实现的http服务器
void HandleEvents(int epoll_fd, epoll_event* events, int max_event, int listen_socket) {
    if (events == nullptr || max_event <= 0) {
        return;
    }

    for (int i = 0; i < max_event; ++i) {
        char buf[1024 * 10] = {0};

        //(a)说明server_socket已经就绪
        if (events[i].data.fd == listen_socket && (events[i].events & EPOLLIN)) {
            sockaddr_in peer;
            socklen_t peer_len = sizeof(peer);
            int acc_sock = accept(listen_socket, (sockaddr*) &peer, &peer_len);
            if (acc_sock < 0) {
                std::cout << "Error, accept failed" << std::endl;
                continue;
            } else {
                //将已经建立连接的文件描述符加入到epfd中,并且设置为关心读事件
                //让epoll_wait()监听该文件的读事件就绪
                epoll_event event;
                event.data.fd = acc_sock;
                event.events = EPOLLIN;

                int ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, acc_sock, &event);
                std::cout << "INFO, EPOLL_CTL_ADD, EPOLLIN client socket ->" << acc_sock << std::endl;
                if (ret < 0) {
                    std::cout << "Error, epoll ctl failed" << std::endl;
                    continue;
                }
            }
            continue;
        }

        //(b.1)先关心读事件
        if (events[i].events & EPOLLIN) {
            //这里只读取一次,但是这样其实是不安全的,因为一次并不能保证把缓冲区所有是数据都读走
            //有可能造成粘包问题
            ssize_t read_size = read(events[i].data.fd, buf, sizeof(buf) - 1);

            if (events[i].data.fd == -1) {
                std::cout << "Error, data.fd==-1" << std::endl;
            }

            std::cout << "read size->" << read_size << std::endl;
            if (read_size < 0) {
                std::cout << "Error, read failed" << std::endl;
                close(events[i].data.fd);
                epoll_ctl(epoll_fd, EPOLL_CTL_DEL, events[i].data.fd, NULL);
                std::cout << "INFO, EPOLL_CTL_DEL, client socket ->" << events[i].data.fd << std::endl;
                continue;
            }
            if (read_size == 0) {
                std::cout << "Error, client disconnect" << std::endl;
                close(events[i].data.fd);
                epoll_ctl(epoll_fd, EPOLL_CTL_DEL, events[i].data.fd, NULL);
                std::cout << "INFO, EPOLL_CTL_DEL, client socket ->" << events[i].data.fd << std::endl;
                continue;
            }
            buf[read_size] = '\0';
            std::cout << "Read msg->" << buf << std::endl;
            //将已经读完的文件描述符加入到epfd中,并且设置为关心写事件
            //让epoll_wait()监听该文件的写事件就绪
            epoll_event event;
            event.data.fd = events[i].data.fd;
            event.events = EPOLLIN | EPOLLOUT;
            int ret = epoll_ctl(epoll_fd, EPOLL_CTL_MOD, events[i].data.fd, &event);
            std::cout << "INFO, EPOLL_CTL_ADD, EPOLLOUT client socket ->" << events[i].data.fd << std::endl;
            std::cout << "epoll ctl ret->" << ret << std::endl;
        }

        //(b.2)再关心写事件
        if (events[i].events & EPOLLOUT) {
            std::cout << "Write to " << events[i].data.fd << std::endl;
            ///这里对应所有的响应都恢复一个html页面
            const char* recv = "HTTP/1.1 200 OK\r\n\r\n<html><h1>hello world</h1><html>";
            write(events[i].data.fd, recv, strlen(recv));
            //短连接,一次响应之后将连接断开
            close(events[i].data.fd);
            epoll_ctl(epoll_fd, EPOLL_CTL_DEL, events[i].data.fd, NULL);
            std::cout << "INFO, EPOLL_CTL_DEL, client socket ->" << events[i].data.fd << std::endl;
        }
    }
}

//启动服务器
int ServerStart(const char* ip, const short port) {
    int server_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (server_socket < 0) {
        std::cout << "Error, server_socket<0" << std::endl;
        return -1;
    }

    sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr(ip);
    addr.sin_port = htons(port);

    int bind_result = bind(server_socket, (sockaddr*) &addr, sizeof(addr));
    if (bind_result < 0) {
        std::cout << "Error, bind_result<0" << std::endl;
        return -1;
    }

    int listen_ret = listen(server_socket, 5);
    if (listen_ret < 0) {
        std::cout << "Error, listen_result<0" << std::endl;
        return -1;
    }
    return server_socket;
}

//main()函数
int main(int argc, char* argv[]) {
    if (argc != 3) {
        std::cout << "Usage :./socket_base [ip] [port]" << std::endl;
        return -1;
    }

    int server_socket = ServerStart(argv[1], atoi(argv[2]));
    if (server_socket < 0) {
        std::cout << "Error, fail to start server" << std::endl;
        return -1;
    }
    std::cout << "Start server successfully" << std::endl;


    int epoll_fd = epoll_create(256);
    if (epoll_fd < 0) {
        std::cout << "Error, fail to create epoll" << std::endl;
        return -1;
    }

    signal(SIGINT, my_handler);

    epoll_event event;
    //将用户要监听的文件描述符赋值
    event.data.fd = server_socket;
    //为该文件描述符只注册读事件
    event.events = EPOLLIN;
    int ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_socket, &event);

    std::cout << "INFO, EPOLL_CTL_ADD, EPOLLIN server socket ->" << server_socket << std::endl;

    if (ret < 0) {
        std::cout << "Error, epoll ctl failed" << std::endl;
        return -1;
    }
    std::cout << "INFO, epoll ctl success" << std::endl;

    while (true) {
        //调用epoll_wait()开始监听
        epoll_event events[128];
        memset(events, 0, sizeof(events) / sizeof(epoll_event));
        int size = epoll_wait(epoll_fd, events, sizeof(events) / sizeof(events[0]), -1);//ms
        std::cout << "----While true, epoll wait size->" << size << std::endl;

        switch (size) {
            case -1:
                std::cout << "Error, epoll waiting pool size<0\n";
                break;
            case 0:
                std::cout << "Warning, epoll waiting pool size==0\n";
                break;
            default:
                //返回值大于0 的说明一定有事件就绪了
                HandleEvents(epoll_fd, events, size, server_socket);
                break;
        }

        if (flag) {
            std::cout << "Catch Ctrl+C, exit" << std::endl;
            flag = 0;
            break;
        }

    }
    close(server_socket);
    close(epoll_fd);
    return 0;
}