epoll反应堆

简介

        epoll反应堆实际上就是将文件描述符、监听的事件、回调函数用结构体封装在一起,发生事件时触发对应的回调函数即可。下面举一个简易版的例子。

        在epoll_ctl()函数中,我们需要操作一个struct epoll_event *ev对象,结构体如下:

struct epoll_event
{
    uint32_t events; // epoll事件
    epoll_data_t data;
};
typedef union epoll_data
{
    void *ptr;
    int fd;
    uint32_t u32;
    uint64_t u64;
}epoll_data_t;

        epoll反应堆的核心是使用data中的ptr成员,而非fd成员,因为ptr是一个void类型指针,我们可以定义一个结构体来保存所有的信息,包括回调函数地址,当对应事件发生,就可以自动触发这个回调函数即可,在这里,结构体可定义为如下形式:

// 事件驱动结构体
// 主要是前三个成员
typedef struct xx_event {
    int fd; // 文件描述符
    int events; // 需要监听的事件
    void (*call_back)(int fd, int events, void* arg); // 回调函数
    void* arg;
    char buf[1024];
    int buflen;
    int epfd;
}xevent;

下面给出完整的简易版epoll反应堆代码:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<sys/epoll.h>
#include"wrap.h"
#include<unistd.h>
#define _BUF_LEN_ 1024
#define _EVENT_SIZE_ 1024

// 全局epoll树的根
int gepfd = 0;

// 事件驱动结构体
typedef struct xx_event {
    int fd;
    int events;
    void (*call_back)(int fd, int events, void* arg);
    void* arg;
    char buf[1024];
    int buflen;
    int epfd;
}xevent;

xevent myevents[_EVENT_SIZE_ + 1];

void readData(int fd, int events, void* arg);

// 添加事件
void eventadd(int fd, int events, void (*call_back)(int, int, void*), void* arg, xevent* ev)
{
    ev->fd = fd;
    ev->events = events;
    ev->call_back = call_back;

    struct epoll_event epv;
    epv.events = events;
    epv.data.ptr = ev; // 核心
    epoll_ctl(gepfd, EPOLL_CTL_ADD, fd, &epv); // 上树
}

// 修改事件
void eventset(int fd, int events, void (*call_back)(int, int, void*), void* arg, xevent* ev)
{
    ev->fd = fd;
    ev->events = events;
    // ev->arg = arg;
    ev->call_back = call_back;

    struct epoll_event epv;
    epv.events = events;
    epv.data.ptr = ev;
    epoll_ctl(gepfd, EPOLL_CTL_MOD, fd, &epv); // 修改
}

// 删除事件
void eventdel(xevent* ev, int fd, int events)
{
    printf("begin call %s\n", __FUNCTION__);

    ev->fd = 0;
    ev->events = 0;
    ev->call_back = NULL;
    memset(ev->buf, 0x00, sizeof(ev->buf));
    ev->buflen = 0;

    struct epoll_event epv;
    epv.data.ptr = NULL;
    epv.events = events;
    epoll_ctl(gepfd, EPOLL_CTL_DEL, fd, &epv); // 下树
}

// 写数据
void senddata(int fd, int events, void* arg)
{
    printf("begin call %s \n", __FUNCTION__);

    xevent* ev = arg;
    Write(fd, ev->buf, ev->buflen);
    eventset(fd, EPOLLIN, readData, arg, ev);
}
// 读数据
void readData(int fd, int events, void* arg)
{
    printf("begin call %s\n", __FUNCTION__);
    xevent* ev = arg;

    ev->buflen = Read(fd, ev->buf, sizeof(ev->buf));
    if (ev->buflen > 0) // 读到数据
    {
        // void eventset(int fd, int events, void (*call_back)(int, int, void*), void *arg, xevent *ev);
        eventset(fd, EPOLLOUT, senddata, arg, ev); // 修改成写事件
    }
    else if (ev->buflen == 0) // 对方关闭连接
    {
        close(fd);
        eventdel(ev, fd, EPOLLIN);
    }
}

// 新连接处理
void initAccept(int fd, int events, void* arg)
{
    printf("begin call %s, gepfd = %d\n", __FUNCTION__, gepfd);

    struct sockaddr_in addr;
    socklen_t len = sizeof(addr);
    int cfd = Accept(fd, (struct sockaddr*)&addr, &len);

    //查找myevents数组中可用的位置
    int i;
    for (i = 0; i < _EVENT_SIZE_; i++)
    {
        if (myevents[i].fd == 0)
        {
            break;
        }
    }

    // 设置读事件
    eventadd(cfd, EPOLLIN, readData, &myevents[i], &myevents[i]);
}
int main()
{
    // 创建socket
    int lfd = Socket(AF_INET, SOCK_STREAM, 0);

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

    // 绑定
    struct sockaddr_in servaddr;
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(8888);
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    Bind(lfd, (struct sockaddr*)&servaddr, sizeof(servaddr));

    // 监听
    Listen(lfd, 128);

    // 创建epoll树根节点
    gepfd = epoll_create(1024);
    printf("gepfd = %d\n", gepfd);

    struct epoll_event events[1024];

    // 添加最初始事件,将监听套接字上树
    eventadd(lfd, EPOLLIN, initAccept, &myevents[_EVENT_SIZE_], &myevents[_EVENT_SIZE_]);
    while (1)
    {
        int nready = epoll_wait(gepfd, events, 1024, -1);
        if (nready < 0) // 调用epoll_wait失败
        {
            perror("epoll_wait error");
        }
        else if (nready > 0)
        {
            int i = 0;
            for (i = 0; i < nready; i++)
            {
                xevent* xe = events[i].data.ptr; // 取ptr指向结构体地址
                printf("fd = %d\n", xe->fd);
                if (xe->events & events[i].events)
                    xe->call_back(xe->fd, xe->events, xe); // 调用事件对应的回调
            }
        }
    }
    return 0;
}

上述代码的缺点:

        回调函数运行时,如果此时来了新的客户端请求连接,会导致不能及时处理请求,这在高并发的环境中可能会导致有很大的延迟。所以可以使用多线程的方式,具体是连接任务和客户端任务分别用不同的线程来处理,可以使用线程池的方式来解决,在下一篇文章中写具体如何实现。

  • 8
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值