基于多反应堆的高并发服务器【C/C++/Reactor】(中)在EventLoop中处理被激活的文件描述符的事件

文件描述符处理与回调函数

一、主要概念

  1. 反应堆模型:一种处理系统事件或网络事件的模型,当文件描述符被激活时,可以检测到
  2. 文件描述符:在操作系统中,用于标识打开的文件、套接字等的一种数据类型
  3. 处理激活的文件描述符的函数:当文件描述符被激活时,需要有一个函数来处理这些事件
  4. dispatch函数:用于分发或处理不同类型事件的函数
  5. channel结构体:存储与文件描述符相关的事件处理动作的结构体
  6. 回调函数 :在初始化channel 对象时指定的读回调和写回调,用于处理不同类型的事件
  7. select 函数:用于检测多个文件描述符的状态,看是否有数据可读可写
  8. fd_set 集合:用于存储文件描述符的集合,通过宏函数FD_ISSET判断文件描述符是否被触发

二、处理流程

  1. 当反应堆模型启动,检测到被激活的文件描述符
  2. 调用dispatch 函数,得到文件描述符fd
  3. 根据fdchannelMap 中取出对应的channel ,判断是读事件还是写事件
  4. 调用对应的回调函数,处理事件
  5. select 函数中,通过fd_set 集合判断是否有数据可读可写 ,调用eventActivate函数处理事件
  6. epoll 函数中,通过epoll_wait 进行检测,遍历返回的events 数组,调用eventActivate函数处理事件
  7. poll 函数中,通过poll 进行检测,遍历返回的事件列表, 调用eventActivate函数处理事件

三、注意事项

  • 在调用回调函数时,需要传入注册时指定的参数
  • 在使用回调函数 时,需要注意处理函数的参数和返回值

四、概括

  • 本文主要介绍了在EventLoop中处理被激活的文件描述符的事件和回调机制
  • 反应堆模型启动 时,可以检测到被激活的文件描述符 ,并使用dispatch 函数获取文件描述符 。(EventLoop初始化和启动)
  • 根据文件描述符ChannelMap 中取出对应的channel ,判断是读事件还是写事件 ,并调用相应的回调函数处理

核心观点:

  1. 反应堆模型启动后,可以检测到被激活的文件描述符
  2. 使用dispatch 函数获取文件描述符,并根据文件描述符从ChannelMap 中取出对应的channel
  3. 根据channel判断是读事件还是写事件,并调用相应的回调函数处理
  4. select 函数中,通过fd_set 集合判断是否有数据可读可写 ,调用eventActivate函数处理事件
  5. epoll 函数中,通过epoll_wait 进行检测,遍历返回的events 数组,调用eventActivate函数处理事件
  6. poll 函数中,通过poll 进行检测,遍历返回的事件列表, 调用eventActivate函数处理事件

>>回顾 ChannelMap 模块的实现 Channel 模块的实现

  • Channel.h
cpp 复制代码
// 定义函数指针
typedef int(*handleFunc)(void* arg);
 
// 定义文件描述符的读写事件
enum FDEvent {
    TimeOut = 0x01;
    ReadEvent = 0x02;
    WriteEvent = 0x04;
};
 
struct Channel {
    // 文件描述符
    int fd;
    // 事件
    int events;
    // 回调函数
    handleFunc readCallback;// 读回调
    handleFunc writeCallback;// 写回调
    // 回调函数的参数
    void* arg;
};

>>在EventLoop 中处理被激活的文件描述符的事件

  • EpollLoop.h
cpp 复制代码
// 处理被激活的文件描述符fd
int eventActivate(struct EventLoop* evLoop,int fd,int event);
  • EpollLoop.c
cpp 复制代码
// 处理被激活的文件描述符fd
int eventActivate(struct EventLoop* evLoop,int fd,int event) {
    if(fd < 0 || evLoop == NULL) {+
        return -1;
    }
    // 取出channel
    struct Channel* channel = evLoop->channelMap->list[fd];
    assert(channel->fd == fd);
    if(event & ReadEvent && channel->readCallback) {
        channel->readCallback(channel->arg);
    }
    if(event & WriteEvent && channel->writeCallback) {
        channel->writeCallback(channel->arg);
    }
    return 0;
}

>>回顾Dispatcher 模块,Dispatcher模块的实现思路和定义 ,补充代码

  • SelectDispatcher.c
cpp 复制代码
static int selectDispatch(struct EventLoop* evLoop,int timeout) {
    struct SelectData* data = (struct SelectData*)evLoop->dispatcherData;
    struct timeval val;
    val.tv_sec = timeout;
    val.tv_usec = 0;
    fd_set rdtmp = data->readSet;
    fd_set wrtmp = data->writeSet;
    int count = select(Max,&rdtmp,&wrtmp,NULL,&val);
    if(count == -1) {
        perror("select");
        exit(0);
    }
    for(int i=0;i<Max;++i) { 
        if(FD_ISSET(i,&rdtmp)) {
            // 已续写...
            eventActivate(evLoop,i,ReadEvent);
        }
        if(FD_ISSET(i,&wrtmp)) {
            // 已续写...
            eventActivate(evLoop,i,WriteEvent);
        }
    }
    return 0;
}
  • PollDispatcher.c
cpp 复制代码
static int pollDispatch(struct EventLoop* evLoop,int timeout) {
    struct PollData* data = (struct PollData*)evLoop->dispatcherData;
    int count = poll(data->fds,data->maxfd + 1,timeout * 1000);
    if(count == -1) {
        perror("poll");
        exit(0);
    }
    for(int i=0;i<=data->maxfd;++i) {
        if(data->fds[i].fd == -1) {
            continue;
        }
        if(data->fds[i].revents & POLLIN) {
            // 已续写...
            eventActivate(evLoop,data->fds[i].fd,ReadEvent);
        }
        if(data->fds[i].revents & POLLOUT) {
            // 已续写...
            eventActivate(evLoop,data->fds[i].fd,WriteEvent);
        }
    }
    return 0;
}
  • EpollDispatcher.c
cpp 复制代码
static int epollDispatch(struct EventLoop* evLoop,int timeout) {
    struct EpollData* data = (struct EpollData*)evLoop->dispatcherData;
    int count = epoll_wait(data->epfd,data->events,Max,timeout * 1000);
    for(int i=0;i<count;++i) {
        int events = data->events[i].events;
        int fd = data->events[i].data.fd;
        if(events & EPOLLERR || events & EPOLLHUP) {
            // 对方断开了连接,删除 fd
            // epollRemove(&evLoop->channels[fd],evLoop);
            continue;
        }
        if(events & EPOLLIN) {
            // 已续写...
            eventActivate(evLoop,fd,ReadEvent);
        }
        if(events & EPOLLOUT) {
            // 已续写...
            eventActivate(evLoop,fd,WriteEvent);
        }
    }
    return 0;
}

五、回顾select 函数、poll 函数、epoll_wait 函数(CodeGeex生成)

  • select函数

Linux 中的 select 函数主要用于等待多个文件描述符(fd)就绪,从而可以一次性处理多个 文件描述符。select 函数会检查指定的文件描述符是否就绪 ,如果其中任何一个是就绪的,则 select 函数返回。select函数的原型为:

cpp 复制代码
#include <sys/select.h>
int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,const struct timeval *timeout);

其中,nfds 表示要检查的文件描述符数量,readfds、writefds 和 exceptfds 分别表示要检查的读、写和异常文件描述符集合,timeout表示等待的时间限制。

例如,下面代码等待两个文件描述符**(fd)** 就绪,最多等待 5秒钟:

cpp 复制代码
int fd1 = open("file1", O_RDONLY);
int fd2 = open("file2", O_RDONLY);

fd_set readfds;
FD_ZERO(&readfds);
FD_SET(fd1, &readfds);
FD_SET(fd2, &readfds);

struct timeval timeout;
timeout.tv_sec = 5;
timeout.tv_usec = 0;

int ret = select(2, &readfds, NULL, NULL, &timeout);

if (ret > 0) {
   if (FD_ISSET(fd1, &readfds)) {
       // fd1 is ready to read
   }
   if (FD_ISSET(fd2, &readfds)) {
       // fd2 is ready to read
   }
}
  • poll函数

Linux 中的 poll 函数与 select 函数类似,用于等待多个文件描述符就绪,但是 poll 函数返回的是一个事件列表, 而不是像 select 函数那样返回就绪的文件描述符数量poll函数的原型为:

cpp 复制代码
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

其中,fds 是一个 pollfd 结构数组,每个结构中包含一个文件描述符和一个事件掩码,timeout表示等待的时间限制。

例如,下面代码使用 poll 函数等待两个文件描述符(FD)就绪,最多等待 5 秒钟:

cpp 复制代码
int fd1 = open("file1", O_RDONLY);
int fd2 = open("file2", O_RDONLY);

struct pollfd fds[2];
fds[0].fd = fd1;
fds[0].events = POLLIN;
fds[1].fd = fd2;
fds[1].events = POLLIN;

int timeout = 5000; // 5 秒

int ret = poll(fds, 2, timeout);

if (ret > 0) {
  if (fds[0].revents & POLLIN) {
      // fd1 is ready to read
  }
  if (fds[1].revents & POLLIN) {
      // fd2 is ready to read
  }
}

如果 poll 函数返回 0,则表示超时,如果返回值大于 0,则表示有文件描述符就绪。与 select 函数不同的是,poll函数返回的是一个事件列表,而不是具体的文件描述符。

  • epoll函数

Linux 中的 epoll 函数是 Linux 2.6.8 引入的一个新的 I/O 模型,它提供了高效的 I/O 事件通知机制,能够处理大量文件描述符,并且具有低延迟和高吞吐量的优点。

epoll函数的原型为:

cpp 复制代码
#include <sys/epoll.h>
int epoll_create(int size);
int epoll_ctl(int epoll_fd, int op, int fd, int event);
int epoll_wait(int epoll_fd, struct epoll_event *events, int maxevents, int timeout);

其中,epoll_create 函数用于创建一个 epoll 实例,size 参数指定要创建的 epoll 实例可以容纳的最大文件描述符数量。epoll_ctl 函数用于控制 epoll 实例,op 参数指定要进行的操作(例如,添加修改删除 文件描述符),fd 参数指定要操作的文件描述符,event 参数指定要设置的事件掩码。epoll_wait 函数用于等待 epoll 实例中的文件描述符就绪,events 参数指向一个 epoll_event 结构数组,用于存储就绪的文件描述符和事件信息,maxevents 参数指定最多等待的文件描述符数量,timeout参数指定等待的时间限制。

例如,下面代码使用 epoll 函数等待两个文件描述符**(fd)**就绪,最多等待 5 秒钟:

cpp 复制代码
int fd1 = open("file1", O_RDONLY);
int fd2 = open("file2", O_RDONLY);

int epoll_fd = epoll_create(10);

struct epoll_event event;
event.events = EPOLLIN;
event.data.fd = fd1;

int ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd1, &event);

event.data.fd = fd2;
ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd2, &event);

struct epoll_event events[2];
int timeout = 5000; // 5 秒

int num = epoll_wait(epoll_fd, events, 2, timeout);

if (num > 0) {
 if (events[0].data.fd == fd1 && events[0].events & EPOLLIN) {
     // fd1 is ready to read
 }
 if (events[1].data.fd == fd2 && events[1].events & EPOLLIN) {
     // fd2 is ready to read
 }
}

如果 epoll 函数返回 0,则表示超时,如果返回值大于 0,则表示有文件描述符就绪。与 selectpoll 函数不同的是,epoll 函数能够处理大量的文件描述符 ,并且具有低延迟和高吞吐量的优点

相关推荐
huisheng_qaq1 个月前
【netty系列-08】深入Netty组件底层原理和基本实现
java·netty·context·eventloop·channelhandler·netty原理及实现
呵呵哒( ̄▽ ̄)"9 个月前
基于多反应堆的高并发服务器【C/C++/Reactor】(中)HttpRequest模块 解析http请求协议
基于多反应堆的·高并发服务器·c/c++/reactor
_snowstorm_9 个月前
Linux学习之网络编程3(高并发服务器)
linux·服务器·学习·高并发服务器·多进程高并发服务器·多线程高并发服务器
呵呵哒( ̄▽ ̄)"9 个月前
基于多反应堆的高并发服务器【C/C++/Reactor】(中)完整代码
高并发服务器·c/c++/reactor·多反应堆
呵呵哒( ̄▽ ̄)"9 个月前
基于多反应堆的高并发服务器【C/C++/Reactor】(中)HttpResponse的定义和初始化 以及组织 HttpResponse 响应消息
基于多反应堆的·高并发服务器·c/c++/reactor
呵呵哒( ̄▽ ̄)"9 个月前
基于多反应堆的高并发服务器【C/C++/Reactor】(中)HttpRequest 提取请求行、解析请求行和优化 以及解析请求头并存储
请求行·请求头·基于多反应堆的·高并发服务器·c/c++/reactor
呵呵哒( ̄▽ ̄)"9 个月前
基于多反应堆的高并发服务器【C/C++/Reactor】(中)创建一个TcpConnection实例 以及 接收客户端数据
基于多反应堆的·高并发服务器·c/c++/reactor
呵呵哒( ̄▽ ̄)"9 个月前
基于多反应堆的高并发服务器【C/C++/Reactor】(中)处理任务队列中的任务
删除·基于多反应堆的·高并发服务器·c/c++/reactor·处理任务队列中的任务·添加·修改
呵呵哒( ̄▽ ̄)"9 个月前
基于多反应堆的高并发服务器【C/C++/Reactor】(中)主线程给子线程添加任务以及如何处理该任务
基于多反应堆的·高并发服务器·c/c++/reactor
呵呵哒( ̄▽ ̄)"9 个月前
基于多反应堆的高并发服务器【C/C++/Reactor】(中)子线程 WorkerThread的实现 和 线程池ThreadPool的初始化
高并发服务器·c/c++/reactor·多反应堆·workerthread的实现