epoll

本文详细介绍了epoll的工作原理,包括epoll_create、epoll_ctl及epoll_wait等关键函数的使用方法,并通过示例代码展示了如何利用epoll进行多路I/O转接,最后讨论了epoll的优点和局限。

1 epoll 相关函数

epoll_create 函数

#include <sys/epoll.h>

int epoll_create(int size)    
/*
 * function: 创建一个epoll句柄(创建一颗红黑树)
 *
 * function arguments:
 *  argv1: 监听数目(创建的红黑树的监听节点数量) 【仅供内核参考】
 *
 * ruturn value:
 *  success: 非负文件描述符(指向新创建的红黑树的根节点);
 *  faild: -1,设置相应的errno
*/

epoll_ctl 函数

#include <sys/epoll.h>

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
/*
 * function: 控制某个epoll监控的文件描述符上的事件:注册、修改、删除。(操作监听红黑树)
 *
 * function arguments:
 *  argv1: epoll_creat的句柄epfd(epoll_creat函数返回值)
 *
 *  argv2: 表示动作,用3个宏来表示:
 *         EPOLL_CTL_ADD (注册新的fd到epfd),
 *         EPOLL_CTL_MOD (修改已经注册的fd的监听事件),
 *         EPOLL_CTL_DEL (从epfd删除一个fd);
 *
 *  argv3: 待监听的fd(listenfd/connfd)
 *
 *  argv4: 传入参数,告诉内核需要监听的事件【本质是一个struct epoo_event结构体(包括evetns和data)】
 *          events: 
 *                  EPOLLIN :    表示对应的文件描述符可以读(包括对端SOCKET正常关闭)
 *                  EPOLLOUT:    表示对应的文件描述符可以写
 *                  EPOLLPRI:    表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来)
 *                  EPOLLERR:    表示对应的文件描述符发生错误
 *                  EPOLLHUP:    表示对应的文件描述符被挂断;
 *                  EPOLLET:     将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)而言的
 *                  EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
 *          data:【共用体/联合体】
 *                  fd----对应监听事件的fd(argv3)
 *                  *ptr--可以将一个结构体的地址传入(此结构体中包括了int fd 和 一个函数指针)
 * 					u32---基本不用
 * 					u64---基本不用
 *
 *          struct epoll_event {
 *              uint32_t     events;      // Epoll events 
 *              epoll_data_t data;        // User data variable 
 *          };
 *
 *          typedef union epoll_data {
 *              void        *ptr;
 *              int          fd;
 *              uint32_t     u32;
 *              uint64_t     u64;
 *          } epoll_data_t;
 *
 *
 *
 * ruturn value:
 *  success: 0
 *  faild: -1,设置相应的errno
 */

epoll_wait 函数

#include <sys/epoll.h>

int epoll_wait(int epfd, struct epoll_event *events,
        int maxevents, int timeout);
/*
 * function: 等待所监控文件描述符上有事件的产生,类似于select()调用
 *
 * function arguments:
 *  argv1: epoll_creat的句柄epfd(epoll_creat函数返回值)
 *  argv2: 传出参数,用来存内核得到事件的集合,可以简单看作数组。
 *  argv3: 告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size.【如果struct epoll_event events[1024]; 填1024】
 *  argv4: 超时时间
 *         -1: 阻塞
 *         0:  立即返回,非阻塞
 *         >0: 指定毫秒
 *
 * ruturn value:
 *  >0: 成功返回有多少个文件描述符就绪.(满足监听事件的总个数,可以用在循环上限)
 *  0:  超时时间到,并且没有fd满足监听时间
 *  -1: 失败,并设置errno
*/


2 epoll实现多路IO转接(代码)

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <errno.h>
#include <sys/epoll.h>

#define OPEN_MAX 1024

int main()
{
    int listenfd, connfd;   // 监听套接字、连接套接字
    listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == listenfd)
    {
        perror("socket error");
        exit(1);
    }

    // 端口复用
    int opt = 1;
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    struct sockaddr_in saddr, caddr;

    memset(&saddr, 0, sizeof(saddr));
    saddr.sin_family = AF_INET;
    saddr.sin_port   = htons(8000);
    saddr.sin_addr.s_addr = inet_addr("192.168.71.132");
    int ret = bind(listenfd, (struct sockaddr*)&saddr, sizeof(saddr));
    if (-1 == ret)
    {
        perror("bind error");
        close(listenfd);
        exit(1);
    }

    ret = listen(listenfd, 5);
    if (-1 == ret)
    {
        perror("listen error");
        exit(1);
    }

    struct epoll_event tep{};               // tep:  epoll_ctl参数
    struct epoll_event ep[OPEN_MAX]{};      // ep[]: epoll_wait参数


    int epfd = epoll_create(OPEN_MAX);      // 创建epoll模型, epfd指向红黑线根节点
    if (-1 == epfd)
    {
        perror("epoll_create error");
        exit(1);
    }

    tep.events = EPOLLIN;
    tep.data.fd = listenfd; // 指定listenfd的监听事件为"读"

    ret = epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &tep);   // 将listenfd以及对应的结构体设置到数上,epfd可找到该树
    if (-1 == ret)
    {
        perror("epoll_ctl error");
        exit(1);
    }

    int nReady = 0;
    while (1)
    {
        // epoll为server阻塞监听事件, ep为struct epoll_event类型数组, OPEN_MAX为数组容量, -1代表永久阻塞
        nReady = epoll_wait(epfd, ep, OPEN_MAX, -1);
        if (-1 == nReady)
        {
            perror("epoll_wait error");
            exit(1);
        }
        /*
        else if (0 == nReady)  // 阻塞等待,不会出现这种情况
        {}    
        */

        for (int ii = 0; ii < nReady; ++ii)
        {
            if (!(ep[ii].events & EPOLLIN))         // 如果不是"读"事件, 循环继续
                continue;

            if (ep[ii].data.fd == listenfd)     // 判断满足条件的fd是不是lisenfd
            {
                socklen_t cAddrLen = sizeof(caddr);
                connfd = accept(listenfd, (struct sockaddr*)&caddr, &cAddrLen);     // 接受连接
                if (-1 == connfd)
                {
                    perror("accept eorror");
                    exit(1);
                }

                printf("建立连接成功:%s:%d\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));

                tep.events = EPOLLIN;
                tep.data.fd = connfd;

                ret = epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &tep);     // 加入红黑树
                if (-1 == ret)
                {
                    perror("epoll_ctl error");
                    exit(1);
                }
            }
            else    // 不是listenfd
            {
                int sockfd = ep[ii].data.fd;

                char buf[1024] = {0};

                int n = read(sockfd, buf, sizeof(buf));
                if (-1 == n) // 出错
                {
                    perror("read error");
                    epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, NULL);
                    close(sockfd);
                    exit(1);
                }
                else if (0 == n)    // 客户端断开连接
                {
                    printf("断开连接\n");
                    ret = epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, NULL);     // 将该文件描述符从红黑树删除
                    if (-1 == ret)
                    {
                        perror("epoll_ctl error");
                        exit(1);
                    }
                    close(sockfd);
                }
                else
                {
                    printf("recv:%s\n", buf);
                }
            }
        }
    }

    close(listenfd);

    return 0;
}

3 epoll 优缺点

  • 优点

    • 高效;可以突破1024文件描述符的限制;
  • 缺点:

    • 不能跨平台。(linux)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值