【仿muduo库实现并发服务器】Channel模块
仿muduo库实现并发服务器
- 1.Channel模块
- 2.成员变量
- 3.构造函数
- 4.针对事件监控操作
- 4.1是否监控了读事件
- 4.2是否监控了写事件
- 4.3启动读事件的监控
- 4.4启动写事件的监控
- 4.5关闭读事件的监控
- 4.6关闭写事件的监控
- 4.7关闭所有事件监控
- 4.8移除事件监控
- 5.针对回调处理操作
- 5.1设置回调函数
- 5.2处理回调函数
- 6.针对描述符的操作
- 7.针对要监控的事件的操作
- 8.针对真正就绪事件的操作
- 9.完整代码
1.Channel模块
Channel模块主要是对一个描述符进行事件监控管理。
比如:管理这个描述符监控了什么事件,以及事件就绪后如何操作等。启动事件监控,关闭事件监控等。
设计出了一个channelchannel模块,这个channel模块,它就是专门对一个描述符进行监控事件管理
他管理这描述符当前,它监控了什么样的事件。我们接下来要进行监控事件的时候,只需要去判断,它
是否已经监控了某个事件没有的话,那我们再监控一下那么已经监控了。我们就不需要再去监控了。
以及管理触发事件后应该干什么
我们的channel模块,它只是我们的一个连接管理Connection里边的一个子模块。
他只是一个连接管理里边的一个事件管理模块,当事件触发之后,对连接要进行什么样的操作?
也就是说,这个Channel里边的事件处理都是由我们Connection这个连接模块,给它直接设置的。
意义:对于描述符的监控事件在用户态更容易维护,以及触发事件后的操作流程更加清晰。
2.成员变量
private:int _fd;//一个channel对应一个描述符EventLoop *_loop;//channnel启动事件监控需要调用loop中的poller来添加uint32_t _events; // 当前需要监控的事件uint32_t _revents; // 当前连接触发的事件using EventCallback = std::function<void()>;EventCallback _read_callback; // 可读事件被触发的回调函数EventCallback _write_callback; // 可写事件被触发的回调函数EventCallback _error_callback; // 错误事件被触发的回调函数EventCallback _close_callback; // 连接断开事件被触发的回调函数EventCallback _event_callback; // 任意事件被触发的回调函数
1.int _fd:一个channel绑定一个描述符,用来管理该描述符的监控事件。
2.uint32_t _events:表示当前channel要监控什么样的事件,读事件还是写事件,要进行事件管理,就需要有一个uint32t类型的成员保存当前需要监控的事件
3.uint32_t _revents:表示该描述符真正就绪的是什么事件。
4.EventLoop *loop:channel真正想要将事件添加监控需要调用loop中封装poller对应的函数。
4.callback:读/写/错误/任意事件对应的回调处理函数。
3.构造函数
一个channel对象需要绑定一个描述符和对应的loop。用来管理该描述符的事件监控。
所以需要从外界传入描述符和loop对象。
public:Channel(EventLoop *loop, int fd) : _fd(fd), _events(0), _revents(0), _loop(loop) {}
4.针对事件监控操作
针对channel监控什么事件以及是否需要进行事件监控。
4.1是否监控了读事件
判断是否监控了读事件,就判断_event是否是EPOLLIN。
// 当前是否监控了可读bool ReadAble() { return (_events & EPOLLIN); }
4.2是否监控了写事件
判断是否监控了写事件,就判断_event是否是EPOLLOUT。
// 当前是否监控了可写bool WriteAble() { return (_events & EPOLLOUT); }
4.3启动读事件的监控
启动该描述符的读事件监控,就两个步骤:
1.将要监控的event事件设置为读事件EPOLLIN。
2.启动监控。(本质就是在更新监控)
注意:第一步只是将要监控的事件设置为读事件,并没有真正的监控读事件,而要真正的监控读事件,就需要使用Poller将该channel对象添加进内核中。而poller封装在loop中,所以只需要调用loop中封装好的接口即可。
// 启动读事件监控void EnableRead(){_events |= EPOLLIN;Update();}
在Channel前面只是声明了Eventloop,但Channel不知道Eventloopr类里的成员函数,在类里无法调用Eventloop的对象函数,只能放在外面定义。
void Channel::Update()
{return _loop->UpdateEvent(this);
}
void Channel::Remove()
{return _loop->RemoveEvent(this);
}
poller模块// 直接对epoll_ctl进行操作void Update(Channel *channel, int op){int fd = channel->Fd();struct epoll_event ev;ev.data.fd = fd;ev.events = channel->Events();int ret = epoll_ctl(_epfd, op, fd, &ev);if (ret < 0){ERRLog("epoll_ctl failed");}}
// 对于一个描述符添加或修改事件监控void UpdateEvent(Channel *channel){// 首先判断该连接是否已经被设置监控了,如果没有则添加监控,如果有则进行修改if (HasChannel(channel) == false){_channel.insert(std::make_pair(channel->Fd(), channel));//_channel[channel->Fd()]=channel;Update(channel, EPOLL_CTL_ADD);}else{Update(channel, EPOLL_CTL_MOD);}}
UpdateEvent会根据channel对象是否在poller中,如果不在则说明是第一次,要添加进来add。
如果在则说明已经添加进来了,本质是要对该描述符的监控事件进行修改modify。
4.4启动写事件的监控
启动该描述符的写事件监控,就两个步骤:
1.将要监控的event事件设置为写事件EPOLLOUT。
2.启动监控。(本质就是在更新监控)
// 启动写事件监控void EnableWrite(){_events |= EPOLLOUT;Update();}
4.5关闭读事件的监控
关闭该描述符的读事件监控,就两个步骤:
1.将对应的读事件设置为非读事件。
2.启动监控。(本质就是在更新监控)
// 关闭读事件监控void DisableRead(){_events &= ~EPOLLIN;Update();}
4.6关闭写事件的监控
关闭该描述符的写事件监控,就两个步骤:
1.将对应的写事件设置为非写事件。
2.启动监控。(本质就是在更新监控)
4.7关闭所有事件监控
关闭所有的事件监控,就是什么事件都不监控,直接让_event=0即可,然后再更新监控。
void DisableAll(){_events = 0;Update();}
4.8移除事件监控
移除事件监控是指将该描述符从poller中移除,不再对其进行监控。
void Channel::Remove()
{return _loop->RemoveEvent(this);
}poller模块
// 对于一个描述符移除事件监控void RemoveEvent(Channel *channel){auto it = _channel.find(channel->Fd());if (it != _channel.end()){_channel.erase(it);}Update(channel, EPOLL_CTL_DEL);}
5.针对回调处理操作
首先是设置各种事件的对应的回调函数。
5.1设置回调函数
//设置读事件回调函数void SetReadCallback(const EventCallback &cb) { _read_callback = cb; }
//设置写事件回调函数void SetWriteCallback(const EventCallback &cb) { _write_callback = cb; }
//设置错误事件回调函数 void SetErrorCallback(const EventCallback &cb) { _error_callback = cb; }
//设置关闭事件回调函数void SetCloseCallback(const EventCallback &cb) { _close_callback = cb; }
//设置任意事件回调函数void SetEventCallback(const EventCallback &cb) { _event_callback = cb; }
5.2处理回调函数
然后,当有事件就绪了,就要执行对应的回调函数。
即读事件就绪了,就执行读回调。
写事件就绪了,就执行写回调…
这个函数是由外界loop来控制channel来执行的(loop会获取所有就绪的channel对象,然后所有对象都调用HandlerEvent,根据channel对象中真正就绪的事件revents来执行对应的回调函数),当有事件就绪了,就去执行对应的回调函数。而具体怎么执行是由Channel来决定的。
void HandlerEvent(){//当读事件就绪,就执行读回调。而在处理读回调之前,先执行一下任意事件回调。if ((_revents & EPOLLIN) || (_revents & EPOLLRDHUP) || (_revents & EPOLLPRI)){/*读回调如果设置, 就调用*/if (_read_callback)_read_callback();/*不管任何事件,都调用的回调函数*/if (_event_callback)_event_callback();}/*有可能会释放连接的操作事件,一次只处理一个*/if (_revents & EPOLLOUT){/*如果写事件就绪,并且设置了,就调用*/ if (_write_callback)_write_callback();/*不管任何事件,都调用的回调函数*/if (_event_callback)_event_callback(); }else if (_revents & EPOLLERR){if (_event_callback)_event_callback();if (_error_callback)_error_callback(); // 一旦出错,就会释放连接,因此要放到前边调用任意回调,不然任意回调访问连接就会报错。}else if (_revents & EPOLLHUP)//对端关闭连接,就会发送EPOLLHUP信息,就会回调HandeClose函数{if (_event_callback)_event_callback();if (_close_callback)_close_callback(); // 会释放连接,因此要放到前边调用任意回调,不然任意回调访问连接就会报错。}}
就是有可能让连接断开的,有可能会释放连接的操作事件一次就只处理一个这样的。
只有这样,这次处理了一旦把连接释放掉了,我也就解除监控了,解除监控就不会再次触发他的其他的事件了。
任意事件回调主要处理的是刷新连接的活跃度。
任意事件回调函数要放在读写事件的后面,你不能放到事件之前,因为你刷新完连接活跃度,有可能处理事件完毕之后(花费比较长时间),直接就已经非活跃了那过来之后,直接就给他释放掉了那这是一件非常恶心的事情。所以需要放在读写事件执行完之后。
6.针对描述符的操作
当poller要监控一连接时,是需要对应连接的描述符的,所有需要外界可能需要Channel对应的fd,所以将fd返回出去。
int Fd() { return _fd; }
7.针对要监控的事件的操作
当poller要监控一连接时,不仅需要该连接的描述符,还需要知道该描述符要监控什么事件events,所以外界也是需要知道channel要监控什么事件。
uint32_t Events() { return _events; }// 获取想要监控的事件
8.针对真正就绪事件的操作
当poller开始进行事件监控,一旦有事件就绪了,epoll_wait就会返回,所有就绪的对象信息就会存到struct epoll_event _evs[1024]数组中,而这个时候channel对象还不知道它真正就绪的是什么事件,所以就需要从struct epoll_event _evs[1024]中将该描述符就绪的事件设置到channel对象中,这样才能让channel对象能够调用HandlerEvent处理就绪事件。
所以channel对象的真正就绪事件是由外界poller设置进去的,所以需要一个设置接口。
void SetRevents(uint32_t events) { _revents = events; } // 设置实际就绪的事件
9.完整代码
class EventLoop;
class Poller;
class Channel
{
private:int _fd;EventLoop *_loop;uint32_t _events; // 当前需要监控的事件uint32_t _revents; // 当前连接触发的事件using EventCallback = std::function<void()>;EventCallback _read_callback; // 可读事件被触发的回调函数EventCallback _write_callback; // 可写事件被触发的回调函数EventCallback _error_callback; // 错误事件被触发的回调函数EventCallback _close_callback; // 连接断开事件被触发的回调函数EventCallback _event_callback; // 任意事件被触发的回调函数
public:Channel(EventLoop *loop, int fd) : _fd(fd), _events(0), _revents(0), _loop(loop) {}int Fd() { return _fd; }uint32_t Events() { return _events; } // 获取想要监控的事件void SetRevents(uint32_t events) { _revents = events; } // 设置实际就绪的事件void SetReadCallback(const EventCallback &cb) { _read_callback = cb; }void SetWriteCallback(const EventCallback &cb) { _write_callback = cb; }void SetErrorCallback(const EventCallback &cb) { _error_callback = cb; }void SetCloseCallback(const EventCallback &cb) { _close_callback = cb; }void SetEventCallback(const EventCallback &cb) { _event_callback = cb; }// 当前是否监控了可读bool ReadAble() { return (_events & EPOLLIN); }// 当前是否监控了可写bool WriteAble() { return (_events & EPOLLOUT); }// 启动读事件监控void EnableRead(){_events |= EPOLLIN;Update();}// 启动写事件监控void EnableWrite(){_events |= EPOLLOUT;Update();}// 关闭读事件监控void DisableRead(){_events &= ~EPOLLIN;Update();}// 关闭写事件监控void DisableWrite(){_events &= ~EPOLLOUT;Update();}// 关闭所有事件监控void DisableAll(){_events = 0;Update();}// 移除监控void Remove();void Update();// 事件处理,一旦连接触发了事件,就调用这个函数,自己触发了什么事件如何处理自己决定void HandlerEvent(){if ((_revents & EPOLLIN) || (_revents & EPOLLRDHUP) || (_revents & EPOLLPRI)){if (_event_callback)_event_callback();/*不管任何事件,都调用的回调函数*/// if (_event_callback)// _event_callback();if (_read_callback)_read_callback();}/*有可能会释放连接的操作事件,一次只处理一个*/if (_revents & EPOLLOUT){// if (_event_callback)// _event_callback();if (_write_callback)_write_callback();}else if (_revents & EPOLLERR){// if (_event_callback)// _event_callback();if (_error_callback)_error_callback(); // 一旦出错,就会释放连接,因此要放到前边调用任意回调}else if (_revents & EPOLLHUP)//对端关闭连接,就会发送EPOLLHUP信息,就会回调HandeClose函数{// if (_event_callback)// _event_callback();if (_close_callback)_close_callback();}}
};// 在Channel前面只是声明了Eventloop,但Channel不知道Eventloopr类里的成员函数,在类里无法调用Eventloop的对象函数,只能放在外面
void Channel::Update()
{return _loop->UpdateEvent(this);
}
void Channel::Remove()
{return _loop->RemoveEvent(this);
}