在上篇文章(处理任务队列中的任务)中我们讲解了处理任务队列中的任务的具体流程,eventLoopProcessTask函数的作用:
- 处理队列中的任务,需要遍历链表并根据type进行对应处理,也就是处理dispatcher中的任务。
// 处理任务队列中的任务
int eventLoopProcessTask(struct EventLoop* evLoop) {
...
while (head!=NULL) {
struct Channel* channel = head->channel;
if(head->type == ADD) {
// 添加
eventLoopAdd(evLoop,channel);
}
else if(head->type == DELETE) {
// 删除
eventLoopRemove(evLoop,channel);
}
else if(head->type == MODIFY) {
// 修改
eventLoopModify(evLoop,channel);
}
...
}
...
return 0;
}
- 处理dispatcher中的任务 -- 添加 删除 修改
// 处理dispatcher中的任务
int eventLoopAdd(struct EventLoop* evLoop,struct Channel* channel);
int eventLoopRemove(struct EventLoop* evLoop,struct Channel* channel);
int eventLoopModify(struct EventLoop* evLoop,struct Channel* channel);
一、添加fd到Dispatcher的文件描述符检测集合中
把文件描述符fd和channel的对应关系存储到channelMap。这么做是为了什么?
- 在dispatcher里边,还有dispatch函数指针,也就是dispatcher->dispatch(evLoop,timeout)。这个是一个检测函数,通过调用dipatch函数,就可以得到激活的文件描述符,得到了激活的文件描述符之后,需要通过这个文件描述符找到它所对应的channel
channelMap->list[fd] = channel;
把文件描述符添加到dispatcher对应的文件描述符检测集合中
- 首先从evLoop里边把dispatcher这个实例给取出来:evLoop->dispatcher,在dispatcher里边有一系列的函数指针,其中有一个叫做add。这个add就是把文件描述符添加到dispatcher对应的文件描述符检测集合中,函数指针add,指向的底层函数可能是不一样的,这个取决于我们选择的dispatcher模型,它有可能是poll,有可能是epoll,也有可能是select。选择的IO模型不一样,add这个函数指针指向的函数的处理动作也就不一样
evLoop->dispatcher->add(channel,evLoop);
(1)eventLoopAdd
// 将任务队列中的任务添加到Dispatcher的文件描述符检测集合中
int eventLoopAdd(struct EventLoop* evLoop,struct Channel* channel) {
int fd = channel->fd;// 取出文件描述符fd
struct ChannelMap* channelMap = evLoop->channelMap;// channelMap存储着channel和fd之间的对应关系
// 需要判断channelMap里边是否有fd 和 channel对应的键值对(其中,文件描述符fd对应的就是数组的下标)
if(fd >= channelMap->size) {
// 没有足够的空间存储键值对 fd->channel ==> 扩容
if(!makeMapRoom(channelMap,fd,sizeof(struct Channel*))) {
return -1;
}
}
// 找到fd对应的数组元素位置,并存储
if(channelMap->list[fd] == NULL) {
channelMap->list[fd] = channel;
evLoop->dispatcher->add(channel,evLoop);
}
return 0;
}
1. 主题: 文件描述符与Channel在Dispatcher中的交互
2. 重要信息:
- Channel结构体:封装了文件描述符,并在其中存储了所需的数据
- 添加文件描述符到Dispatcher:当有新文件描述符需要添加时,它会被放入Dispatcher的检测集合中
- 文件描述符激活与处理:如果某个文件描述符在Dispatcher的检测集合中被激活,可以通过调用dispatch函数获取这个文件描述符,并进一步找到对应的Channel
- 回调函数与事件处理:一旦找到对应的Channel,可以根据触发的事件调用在Channel结构体中注册的回调函数
- ChannelMap结构体:用于存储文件描述符与Channel之间的对应关系。它是一个数组,下标(即数组索引)对应文件描述符的值
- 扩容ChannelMap:如果当前ChannelMap的容量不足,可以通过调用 makeMapRoom 函数进行扩容
3. 总结:详细介绍了如何通过Channel结构体和Dispatcher处理文件描述符,以及如何通过回调函数处理不同的事件。同时,还提到了如何使用和扩容ChannelMap结构体来存储文件描述符与Channel之间的对应关系
4. 思考与理解: 深入理解文件描述符、Channel和Dispatcher之间的关系及其工作原理。 在实际编程中,考虑如何有效地使用和扩展ChannelMap结构体,以适应更多的文件描述符和事件处理需求。 考虑如何在代码中实现更清晰的事件处理逻辑,以提高代码的可读性和可维护性
总结:主要描述了如何通过文件描述符在channel中添加、激活和触发事件。其中,channelMap结构体用于存储文件描述符和channel的对应关系,而EventLoop结构体则用于存储channel和文件描述符的对应关系
核心观点 :
- 文件描述符与channel的对应关系存储在channelMap中,通过数组索引对应文件描述符的值
- 如果channelMap容量不足,需要调用函数进行扩容
二、从Dispatcher的文件描述符检测集合中删除fd
把任务队列里面的节点从dispatcher的检测集合中删除,调用dispatcher里边的remove函数
- 如果我们要删除的这个文件描述符并不在channelMap中存储着,说明我们要操作的这个文件描述符并不在dispatcher的检核集合中。因为它在检测集合里边,在添加的时候就会把文件描述符fd和channel的映射关系也存储到channelMap里边去了。故只要它在检测集合里边,它肯定就在channelMap里边。如果它不在channelMap里边,那么它就肯定不在检测集合里边。如果它不在检测集合里边,就无需做任何事情,直接返回-1
-
如果文件描述符fd在检测集合里,就从中把它删除
int eventLoopRemove(struct EventLoop* evLoop,struct Channel* channel) {
int fd = channel->fd;
struct ChannelMap* channelMap = evLoop->channelMap;
if(fd >= channelMap->size) {
return -1;
}
int ret = evLoop->dispatcher->remove(channel,evLoop);
return ret;
}
三、修改Dispatcher的检测集合里边文件描述符事件的函数
当我们需要修改检测集合中的某个文件描述符事件时,首先需要判断该文件描述符是否在channelMap
中。如果文件描述符存在,我们可以根据它获取一个非零的地址,这个地址实际上是channel
实例的地址
- 如果获取的地址为空,说明传入的文件描述符对应的
channel
有问题; - 如果不为空,我们可以通过
evLoop
实例调用其函数指针modify
int eventLoopModify(struct EventLoop* evLoop,struct Channel* channel) {
int fd = channel->fd;
struct ChannelMap* channelMap = evLoop->channelMap;
if(fd >= channelMap->size || channelMap->list[fd] == NULL) {
return -1;
}
int ret = evLoop->dispatcher->modify(channel,evLoop);
return ret;
}
四、释放channel
// 释放channel
int destroyChannel(struct EventLoop* evLoop,struct Channel* channel);
当从检测集合中删除文件描述符后,对应的channel
实例就没有用了,因此需要释放它。在释放channel
之前,我们需要关闭文件描述符,因为不再需要检测它的事件。此外,由于channel
本身是一个指向堆内存的指针,我们需要释放这块堆内存。
// 释放channel
int destroyChannel(struct EventLoop* evLoop,struct Channel* channel) {
// 删除 channel 和 fd 的对应关系
evLoop->channelMap->list[channel->fd] = NULL;
// 关闭 fd
close(channel->fd);
// 释放 channel 内存
free(channel);
return 0;
}