EventLoop的简化封装
(有在上篇文章中出现过但是连贯一下就还是搬过来…,看完类图关系就可以分析完善的代码了
可以直接跳转到正文类图关系
.h文件👇
namespace muduo
{
namespace net
{
///
/// Reactor, at most one per thread. EventLoop就是Recator模式的封装,每个线程最多一个
///
/// This is an interface class, so don't expose too much details.
class EventLoop : noncopyable
{
public:
typedef std::function<void()> Functor;
EventLoop();
~EventLoop(); // force out-line dtor, for std::unique_ptr members.
///
/// Loops forever.
///
/// Must be called in the same thread as creation of the object.
///
void loop();//事件循环函数,该函数不能跨线程调用
void assertInLoopThread()
{
if (!isInLoopThread())
{
abortNotInLoopThread();
}
}
bool isInLoopThread() const {
return threadId_ == CurrentThread::tid(); }
private:
void abortNotInLoopThread();
static EventLoop* getEventLoopOfCurrentThread();
bool looping_; /* atomic */ //是否处于循环的状态
const pid_t threadId_;//记录当前对象属于哪一个线程,当前对象所属线程id
};
}
}
#endif //MUDUO_NET_EVENTLOOP_H
.cc文件👇
每一个线程最多有一个EventLoop对象,.cc
文件第一行代码就定义一个线程局部存储的当前线程EventLoop对象指针:__thread EventLoop* t_loopInThisThread = 0;
,再提一下,因为有__thread
这个关键字就表示每一个线程都有这样的一个变量,否则的话这个变量是共享的
创建对象成功的话,这个指针t_loopInThisThread
就指向当前对象,当然这个对象得是第一次创建,因为对象最多只能有一个。
报错都用日志的记录方式
构造函数
初始化looping为false,表示当前还没有处于循环的状态,并把当前线程的真实id初始化给ThreadId_
EventLoop::EventLoop():looping(false),thraedId_(CurrentThread::tid())
{
LOG_TRACE << "EventLoop create "<<this<<"in thread "<<threadId_;//日志的记录
if (t_loopInThisThread)//检查当前线程是否已经创建了其他EventLoop对象,如果已经创建了则LOG_FATAL终止程序
{
LOG_FATAL << "Another EventLoop " << t_loopInThisThread
<< " exists in this thread " << threadId_;
}
else
{
t_loopInThisThread = this;//既然创建了一个对象,那么当前的对象指针就指向this
}
}
**析构函数:**指向对象的指针置空
EventLoop::~EventLoop()
{
LOG_DEBUG << "EventLoop " << this << " of thread " << threadId_
<< " destructs in thread " << CurrentThread::tid();
t_loopInThisThread = NULL;
}
loop函数:事件循环
事件循环,该函数不能跨线程调用,只能在创建该对象的线程中调用
void EventLoop::loop()
{
assert(!looping_);//断言还没有开始循环
assertInLoopThread();//断言当前处于创建该对象的线程中,函数的实现👇
looping_ = true;//开始要事件循环了,先将looping_置为true
LOG_TRACE << "EventLoop " << this << " start looping";//打印事件
::poll(NULL, 0, 5*1000);//NULL表示当前没有关注事件,关注事件个数为0,等待5秒,仅仅而已,走个场
LOG_TRACE << "EventLoop " << this << " stop looping";//loop完也打印一下
looping_ = false;//并置回false
}
assertInLoopThread()
判断是都处于创建该对象的线程中
void assertInLoopThread()
{
if (!isInLoopThread())//只需要将当前线程的id与threadId比较是否相等👇
{
abortNotInLoopThread();//阻止程序👇
}
}
bool isInLoopThread() const {
return threadId_ == CurrentThread::tid(); }
void EventLoop::abortNotInLoopThread()
{
LOG_FATAL << "EventLoop::abortNotInLoopThread - EventLoop " << this
<< " was created in threadId_ = " << threadId_
<< ", current thread id = " << CurrentThread::tid();
}//这就是日志的用法啦,开心
muduo类图关系
muduo网络库相关类图的关系其实就是muduo库Reactor模式的实现:
Reactor模式简介: Reactor是一种事件驱动机制。它和普通函数调用的不同之处在于:应用程序不是主动调用某个API完成处理,恰恰相反,Reactor逆置了事件处理流程,应用程序需要提供相应的接口并注册到Reactor上,如果相应的事件发生,Reactor将主动调用应用程序注册的接口,这些接口又称为“回调函数”
类图如下
muduo中Reactor的关键结构包括:EventLoop
、Poller
和Channel
如类图所示,EventLoop类和Poller类属于组合的关系,EventLoop类和Channel类属于聚合的关系, 这两种关系都是关联关系的特例
聚合关系体现的是整体与部分的关系,整体与部分是可分离的,具有各自的生命周期,EventLoop类不负责Channel类的生命周期,由图可知,Channel的生命周期是由TcpConnection类,Acceptor类,Connector类控制(空心菱形)
组合关系比聚合关系强,也称为强聚合,虽然也是整体和部分的关系,但是这个整体和部分是不能分割的,整体的生命周期结束意味着部分的生命周期也结束了(实心菱形)
另外:类图中白色部分类EventLoop
、TcpConnection
、TcpServer
、TcpClient
都是对外公开的,是外部类;其他灰色部分表示内部类,对外是不可见的
先详细讲讲这些类是干嘛的
EventLoop
类是对事件循环的抽象
Poller
类是对IO复用的抽象,是muduo库中唯一用面向对象实现的,Poller
是一个抽象类,继承有PollPoller
、EPollPoller
两个子类,但这三个都是内部类,虽然用面向对象的方法实现了,但还是不用虚函数来暴露接口
Channel
类是对IO事件的注册与相应的封装,有一个Update函数负责IO事件的可读可写事件,handleEvent事件负责对锁发生的IO事件进行处理
Acceptor
类是被动连接的抽象,关注的是套接字的可读事件,Connector
类是对主动连接的抽象,但不管是主动还是被动,连接建立起来了就会有一个已连接套接字,那么对已连接套接字的抽象就是 TcpConnection
类;TcpServer
包含一个Acceptor
, TcpClient
包含一个Connector
一个EventLoop包含多个Channel, 即它可以捕捉多个通道的可读可写事件;
Channel不拥有文件描述符,即Channel对象被销毁的时候不关闭文件描述符,不会调用close()
去关闭文件描述符;
Channel跟文件描述符FileDescripto是关联关系,一个Channel一个fd,
一个EventLoop多个fd,监听多个fd的相关事件;
fd是被套接字Socket拥有的,即fd生存期由套接字控制,当套接字对象被销毁的时候文件描述符就会被套接字类的析构函数调用close()
销毁;
另外⭐:从类及其函数我们发现:
当我们调用Channel中的Update来注册或者更新IO的读和写事件的时候,会调用到EventLoop的updateChannel函数,从而又调用到Poller的updateChannel函数,这相当于就是将Channel注册到Poller当中(上篇文章的muduo的TCP网络本质
Acceptor接收到客户端请求,调用TcpServer回调函数
TcpServer回调函数中创建TcpConnection对象,代表着一个Tcp连接
TcpConnection构造函数中创建Channel对象,保存客户端套接字fd和关心的事件(可读)
Channel 注册自己到所属事件驱动循环(EventLoop)中的Poller上
Poller开始监听,当发现Channel被激活后将其添加到EventLoop的激活队列中
EventLoop在poll返回后处理激活队列中的Channel,调用其处理函数
Channel在处理函数中根据被激活的原因调用不同的回调函数(可读/可写
用时序图来根据事件的循环流程来理解它们的实现
EventLoop::loop()调用Poller::poll()获得当前活动事件的Channel列表,再遍历该列表,执行每个Channel的Channel::handleEvent()完成相应就绪事件回调。
代码分析 (根据类图与时序图
在简化代码上增加类图上的点:先前向声明两个类,class Channel
、class Poller
;增加数据成员:首先多了一个Poller类对象scoped_ptr<Poller> poller_
、Timestamp pollReturnTime_
调用poller函数时返回的时间戳、activeChannels_
poller函数返回的活动通道、currentActiveChannel_
当前正在处理的活动通道、对应looping_
是否处于循环的状态有quit_
用于标识是否退出、eventHandling
当前是否处于事件处理的状态
还多了两个成员函数updateChannel()
、removeChannel()
EventLoop.h
namespace muduo
{
namespace net
{
class Channel;
class Poller;
///
/// Reactor, at most one per thread. EventLoop就是Recator模式的封装,每个线程最多一个
///
/// This is an interface class, so don't expose too much details.
class EventLoop : noncopyable
{
public:
typedef std::function<void()> Functor;
EventLoop();
~EventLoop(); // force out-line dtor, for std::unique_ptr members.
///
/// Loops forever.
///
/// Must be called in the same thread as creation of the object.
///
void loop();//事件循环函数,该函数不能跨线程调用
void quit();
///
/// Time when poll returns, usually means data arrival
///
Timestamp pollReturnTime()