前言
本篇文章所讲的是本人的个人项目仿muduo库高并发服务器中的Channel模块实现部分。
Channel模块介绍:
-
功能:
Channel模块是对⼀个描述符需要进⾏的IO事件管理的模块,实现对描述符可读,可写,错误...事件的管理操作,以及
Poller模块对描述符进⾏IO事件监控就绪后,根据不同的事件,回调不同的处理函数功能。
-
意义:让描述符的监控事件在用户态更易维护,触发事件后的操作流程更清晰
-
功能设计:
- 对监控事件的管理:
- 描述符是否可读
- 描述符是否可写
- 对描述符监控可读
- 对描述符监控可写
- 解除可读事件监控
- 解除可写事件监控
- 解除所有事件监控
- 对监控事件触发后的处理:
- 设置对于不同事件的回调处理函数,明确触发某个事件后该如何处理。
- 对监控事件的管理:
线程安全:
Channel为什么要保证线程安全? 不保证线程安全有什么后果?
简单来说,Channel 必须保证线程安全,但其目标并非让自身成为一个能被任意线程随意调用的"线程安全类",而是通过严格的规则,确保所有针对它的操作都在其所属的 IO 线程中执行,从而避免并发问题。
不保证线程安全的后果:
- 数据竞争:多个线程同时修改
_events(监控事件)或_revents(就绪事件),导致状态错乱,进而引发Poller监控错误的事件或漏掉事件。 - 生命周期管理混乱:线程 A 正在处理 Channel 的事件回调,线程 B 却将 Channel 及其关联的连接对象销毁,导致线程 A 访问了已释放的内存,程序崩溃。
- 事件处理乱序:线程池中的工作线程处理完业务逻辑后,想通过
send()发送数据。如果直接在其他线程调用,可能破坏 TCP 连接的字节流顺序,或与 IO 线程的读写事件产生竞争。
注意:这个模块应结合EventLoop模块学习,Channel中的EventLoop指针主要是为了保证线程安全 ,这也是muduo库的精髓之一,它并非采用复杂的锁机制来保证线程安全,那样会严重损害性能,而是采用了一种更为精巧的架构设计。其精髓可以概括为:通过"线程绑定"和"任务派发"机制,将可能引发竞态条件的操作约束在特定的单一线程内执行。
其实就是每个EventLoop都绑定了一个线程,内部含有一个线程ID,在开始时,都会调用assertInLoopThread()来检查当前线程的ID是否与threadId_一致。如果不一致,程序会直接报错终止。这就强制保证了诸如channel->EnableRead()这样的调用,最终必须在Channel所属的IO线程中执行。
想进一步了解,可以看看我写的关于EventLoop模块的博客。
代码实现
c++
class EventLoop;
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 SetEvents(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 HandleEvent()
{
// 1. 优先处理错误事件:连接已致命,无需也无权再操作数据
if (_revents & EPOLLERR) {
if (_error_callback) _error_callback();
// 错误通常是致命的,处理后直接返回,不再触发其他回调
if (_event_callback) _event_callback(); // 错误事件也触发通用回调
return;
}
// 2. 处理挂起事件:连接已完全断开且缓冲区数据已经读完
if ((_revents & EPOLLHUP) && !(_revents & EPOLLIN)) {
if (_close_callback) _close_callback();
if (_event_callback) _event_callback(); // 关闭事件也触发通用回调
return;
}
// 3. 处理读事件(包括普通数据、对端半关闭后的剩余数据)
if (_revents & (EPOLLIN | EPOLLPRI | EPOLLRDHUP)) {
if (_read_callback) _read_callback();
}
// 4. 处理写事件
if (_revents & EPOLLOUT) {
if (_write_callback) _write_callback();
}
// 5. 最后处理通用事件:任何事件触发(只要执行到这里)都会调用
// 注意:由于错误和挂起事件已在上方返回,通用事件在那里被提前触发。
// 而对于读/写事件,它在这里被触发。
if (_event_callback) _event_callback();
}
};
void Channel::Remove()
{
_loop->RemoveEvent(this);
}
void Channel::Update()
{
_loop->UpdateEvent(this);
}