Epoll编程笔记

本文详细介绍了Epoll作为解决IO问题的编程模型,包括其创建、设置、监听等关键步骤,并通过一个Epoll回显服务器的实例展示了Epoll在实际服务器编程中的应用。此外,还对比了Epoll与其他IO模型如阻塞、多线程和Select的优缺点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

个人作品,未经允许禁止转载!!!

解决IO问题的编程模型及特点

1.阻塞等待

缺点:一个fd等待时,其他fd无法被监听

2.多线程阻塞等待

缺点:一个fd占用一个线程,线程过多,效率很低

3.非阻塞、忙轮询

缺点:无效轮询占用大量CPU时间

4.Select

缺点:能够打开的fd数量受内核限制,如需修改,需要编译内核。效率较低,每次需要遍历fd数组,复杂度和最大fd数量相同

5.Epoll

优点:用红黑树管理fd,每次返回可以读写或者发生其他事件的fd,大部分情况下效率高于select。

缺点:Linux系统的API。当每个连接都非常活跃时,效率会低于select

Epoll API(Doc地址:epoll Document

1.头文件:

#include <sys/epoll.h>

2.创建Epoll

//size是Epoll最大监听的fd的数量,对应内部红黑树的最大节点数
//返回一个epoll的描述符 epoll fd
int epoll_create(int size);

3.设置Epoll

/*
epfd为epoll描述符
op为需要的操作,可以是EPOLL_CTL_ADD,EPOLL_CTL_MOD,EPOLL_CTL_DEL,对应添加,修改,删除
fd为需要监听的描述符,可以是文件描述符、Socket描述符等
*event定义为:
struct epoll_event {
 __uint32_t events; /* epoll 事件 */
 epoll_data_t data; /* 用户传递的数据 */
}
其中,events为监听的事件,可以是:
EPOLLIN 可读
EPOLLOUT 可写
EPOLLRDHUP socket另一端关闭连接
POLLPRI 预料之外的错误,很少用,参见https://man7.org/linux/man-pages/man2/poll.2.html
EPOLLERR 发生错误,默认开启,无法关闭,所以也没必要传入
EPOLLHUP 描述符被挂起
EPOLLET 边沿触发事件。默认情况下是水平触发
等

typedef union epoll_data {
    void *ptr;
    int fd;
    uint32_t u32;
    uint64_t u64;
} epoll_data_t;
data是一个结构体,可以是用户自行传入的任意数据的指针(这是实现Reactor模式的关键)
*/
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

4.监听Epoll事件

/*
其中
epfd是epoll描述符
epoll_event在上一步中有解释,这里传入一个数组,数组大小必须大于epoll_create中传入的最大大小
maxevents是最大监听数量,和epoll_create保持一致
timeout代表超时事件
-1为永久阻塞,直到至少有一个fd返回事件
0为立即返回
大于0时,为阻塞事件,超时返回
*/
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

5.Epoll编程常用步骤

#define MAX_FD 1024
//创建 epoll
int epfd = epoll_crete(MAX_FD);

//将监听的socket添加进 epoll 中
epoll_ctl(epfd, EPOLL_CTL_ADD, listen_fd, &event);

while (1) {
    //阻塞等待 epoll 中 的fd 触发
    int active_cnt = epoll_wait(epfd, events, 1000, -1);
    # 对返回的fd进行
    for (i = 0 ; i < active_cnt; i++) {
        //如果监听的fd触发,那么accept请求,并将fd加入epoll
        if (evnets[i].data.fd == listen_fd) {
        }
        //如果其他fd触发,则进行读写
        else if (events[i].events & EPOLLIN) {
        }
        else if (events[i].events & EPOLLOUT) {
        }
    }
}

Epoll回显服务器
服务器端

#include <stdio.h>
#include <netinet/in.h>
#include <sys/epoll.h>
#include <unistd.h>

#define SERVER_PORT 9998
#define MAX_PENDING_CONNECTION 1024
#define EPOLL_MAX_FD (1024*1024)
#define MAX_BUFFER_SIZE (1024*1024)

using namespace std;
bool running = true;
int main(int argc, char* argv[]){
    int32_t listen_fd = 0, epoll_fd = 0;
    sockaddr_in server_addr{};

    // 创建监听套接字
    listen_fd = socket(AF_INET, SOCK_STREAM, 0);
    // 绑定端口
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(SERVER_PORT);
    bind(listen_fd, (struct sockaddr*)&server_addr, sizeof(server_addr));
    // 监听
    listen(listen_fd, MAX_PENDING_CONNECTION);

    // 创建epoll
    epoll_fd = epoll_create(EPOLL_MAX_FD);
    if (epoll_fd < 0){
        perror("Epoll create failed");
        return -1;
    }
    struct epoll_event event;
    event.events = EPOLLIN;
    event.data.fd = listen_fd;
    // 将监听套接字加入epoll
    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &event) < 0){
        perror("Epoll CTL add listen fd");
        return -1;
    }
    struct epoll_event *events = new struct epoll_event[EPOLL_MAX_FD];
    int client_fd, active_fd;
    sockaddr_in client_addr{};
    char buffer[MAX_BUFFER_SIZE];
    while(running){
        // 等待epoll事件触发
        active_fd = epoll_wait(epoll_fd, events ,EPOLL_MAX_FD, -1);

        // 遍历epoll
        for (int i=0; i<active_fd; i++){
            // 如果是连接事件,则连接并将新的fd加入到epoll监听read事件
            if (events[i].data.fd == listen_fd){
                socklen_t clientlen = sizeof(client_addr);
                client_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &clientlen);
                if (client_fd < 0){
                    perror("Cannot accept");
                    continue;
                }
                event = {};
                event.events = EPOLLIN;
                event.data.fd = client_fd;
                epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &event);
            }
            // 如果是读事件,读取并且发回去
            else if (events[i].events & EPOLLIN){
                client_fd = events[i].data.fd;
                int n = read(client_fd, buffer, MAX_BUFFER_SIZE);
                if (n < 0){
                    perror("read");
                    continue;
                }
                else if (n == 0){
                    event = {};
                    event.events = EPOLLIN | EPOLLOUT;
                    event.data.fd = client_fd;
                    epoll_ctl(epoll_fd, EPOLL_CTL_DEL, client_fd, &event);
                    close(client_fd);
                }
                else{
                    buffer[n] = '\0';
                    printf("Get Data -> %s", buffer);
                    write(client_fd, buffer, n);
                }
            }
            // 套接字可写事件,没有监听,也不处理
            else if (events[i].events & EPOLLOUT){
                printf("Send Data -> Epoll out end! \n");
            }
        }
    }
    close(epoll_fd);
    close(listen_fd);
    return 0;
}

客户端

#include <iostream>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <cstring>
#include <unistd.h>

#define MAX_BUFFER_SIZE (1024*1024)
#define SERVER_PORT 9998
int main(int argc, char* argv[]) {
    int sockfd;
    char buffer[MAX_BUFFER_SIZE];

    struct sockaddr_in server_addr{};
    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
        perror("socket");
        return -1;
    }
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(SERVER_PORT);
    if (inet_pton(AF_INET, "0.0.0.0", &server_addr.sin_addr) <= 0){
        perror("inet_pton");
        return -1;
    }

    if (connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0){
        perror("connect");
        return -1;
    }

    int opts = 0;
    opts = fcntl(sockfd, F_GETFL);
    opts = opts | O_NONBLOCK;
    fcntl(sockfd, F_SETFL);

    char input[1024];

    while(fgets(input, 1024, stdin) != nullptr){
        printf("Sending -> %s", input);
        int n = send(sockfd, input, strlen(input), 0);
        if (n < 0){
            perror("send");
            continue;
        }
        usleep(500000);
        n = read(sockfd, buffer, MAX_BUFFER_SIZE);
        if (n <= 0){
            perror("read");
            return -1;
        }
        else{
            buffer[n] = '\0';
            printf("Recv <- %s", buffer);
            continue;
        }
    }
    close(sockfd);
    return 0;
}

运行服务器和客户端后,从终端向客户端输入即可看到现象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值