【linux网络编程】3、epoll的基本用法

94 阅读3分钟

「这是我参与2022首次更文挑战的第28天,活动详情查看:2022首次更文挑战」。

epoll 简介

linux下多路复用IO接口的select/poll的增强版本,可以显著提高程序在大量并发连接中只有少量活跃的情况下的系统cpu的利用率,因为它会复用文件描述符集合来传递结果而不用迫使开发者每次等待事件之前都必须重新准备要被监听的文件描述符集合,另一个原因就是,获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行。

函数原型

epoll_create:
        #include <sys/epoll.h>
        int epoll_create(int size);
            参数:
                size: 创建的红黑树的监听节点数量
            返回值:
                成功:指向新创建的红黑树的跟节点的fd
                失败:-1,设置errno

        int epoll_create1(int flags);
        
    epoll_ctl:(控制红黑树)

        int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
        参数:
            1、epfd: epoll_create 的返回值
            2、op:对该监听红黑树所做的操作(增删改)
                EPOLL_CTR_ADD: 添加fd到红黑树
                EPOLL_CTR_MOD: 修改fd在监听红黑树上的监听事件
                EPOLL_CTL_DEL: 将一个fd 从监听红黑树上取消监听(摘下)
            
            3、fd: 待监听的fd

            4、event: 本质 struct epoll_event 结构体 地址
                events:
                    EPOLLIN / EPOLLOUT / EPOLLERR

                data: 联合体
                    int fd; 对应监听事件的fd
                    void *ptr;
                    uint32_t u32;
                    uint64_t u64;
        返回值:
            成功:0
            失败:-1,设置errno


    epoll_wait: 阻塞监听
        int epoll_wait(int epfd, struct epoll_event *events,
                      int maxevents, int timeout);
        参数:
            1、epfd: epoll_create 的返回值
            2、events: 传出参数【数组】,满足监听条件的 哪些 fd结构体
            3、maxevents: 数组 元素的总个数。1024
                struct epoll_event events[1024]
            4、timeout:超时时间
                -1:阻塞
                0:不阻塞
                >0:超时时间(毫秒)
        返回值:
            >0: 满足监听的总个数,可以用作循环上限。
            =0: 没有fd满足监听的事件
            <0: 失败,设置errno
 

epoll 实现server端代码实例

server端 ,实现小写转大写


// server.c
#include <strings.h>
#include <sys/epoll.h>
#include <ctype.h>

#include "wrap.c"

#define MAXLINE 8192
#define SERV_PORT 8000
#define OPEN_MAX 1000
int main(int argc, char *argv[])
{
    int i, lfd, cfd, sockfd; // lfd: listen fd , cfd : connect fd , sockfd : 记录需要监听的文件描述符

    int n, num = 0;
    ssize_t nready, efd, res; // nready : 满足监听条件的文件描述符个数 , efd : epoll_create 创建的文件描述符 ,res : 记录epoll_ctl的结果
       
    char buf[MAXLINE], str[INET_ADDRSTRLEN];
    // 创建一个socket连接
    lfd = socket(AF_INET, SOCK_STREAM, 0);
    int opt = 1;
    // 端口复用
    setsockopt(lfd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));
    // 服务端地址结构与客户端地址结构
    struct sockaddr_in serv_addr, clit_addr;
    socklen_t clit_addr_len; // 客户端地址长度
    bzero(&serv_addr, sizeof(serv_addr)); // 将服务端地址内容清空
    serv_addr.sin_family = AF_INET; // 网络类型
    serv_addr.sin_port = htons(SERV_PORT); // 指定的端口号
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 设置服务端ip
    
    // 绑定 
    Bind(lfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
    Listen(lfd, 10);

    // 创建epoll 模型,efd 指向红黑树的根节点
    efd = epoll_create(OPEN_MAX);
    if (efd == -1)
    {
        perr_exit("epoll_create error");
    }

   
    struct epoll_event tep, ep[OPEN_MAX];//tep:用来设置单个fd属性 , ep:epoll_wait传出的满足监听事件的数组
    tep.events = EPOLLIN;
    tep.data.fd = lfd; // 指定 lfd 的监听事件为 读事件

    // 将 lfd 及对应的结构体设置到树上,efd可以找到该树
    res = epoll_ctl(efd, EPOLL_CTL_ADD, lfd, &tep);
    if (res == -1)
    {
        perr_exit("epoll_ctl errpr");
    }

    for (;;)
    {
        nready = epoll_wait(efd, ep, OPEN_MAX, -1);
        if (nready == -1)
        {
            perr_exit("epoll_wait error");
        }
        
        sockfd = ep[i].data.fd;
        if (sockfd == lfd)
        {
            clit_addr_len = sizeof(clit_addr);
            cfd = Accept(lfd, (struct sockaddr *)&clit_addr, &clit_addr_len);
            printf("Received from %s , port:%d\n",
                   inet_ntop(AF_INET, &clit_addr.sin_addr, str, sizeof(str)),
                   ntohs(clit_addr.sin_port));
            printf("cfd:%d -- client %d\n", cfd, ++num);
            tep.events = EPOLLIN;
            tep.data.fd = cfd;
            // 将cfd加入红黑树
            res = epoll_ctl(efd, EPOLL_CTL_ADD, cfd, &tep);
            if (res == -1)
            {
                perr_exit("epoll_ctl error");
            }
        }
        else
        {
            sockfd = ep[i].data.fd;
            n = Read(sockfd, buf, MAXLINE);

            if (n == 0)
            { // 说明客户端关闭了连接
                res = epoll_ctl(efd, EPOLL_CTL_DEL, sockfd, NULL);
                if (res == -1)
                {
                    perr_exit("epoll_ctr_del error");
                }
                Close(sockfd);
            }
            else if (n < 0) // 出错
            {
                perror("read n<0 error: ");
                res = epoll_ctl(efd, EPOLL_CTL_DEL, sockfd, NULL);
                if (res == -1)
                {
                    perr_exit("epoll_ctr_del error");
                }
                Close(sockfd);
            }
            else
            { // 读到数据
                for (i = 0; i < n; i++)
                {
                    // 小写转大写
                    buf[i] = toupper(buf[i]);
                }
                // 输出到屏幕
                Write(STDOUT_FILENO, buf, n);
                // 向客户端文件描述符写转换后的字符串
                Write(sockfd, buf, n);
            }
        }
    }
    Close(lfd);
    Close(efd);
    return 0;
}