主要参考了陈硕的书《Linux多线程服务端编程》,结合自己的理解摘了一些重要的的句子和函数、数据成员进行分析。
使用的代码自己用C++11抄了一个muduo库,简单版本,文章的代码删除了所有的Log和assert。
TCP网络编程最本质是的处理三个半事件
- 连接建立:服务器accept(被动)接受连接,客户端connect(主动)发起连接
- 连接断开:主动断开(close、shutdown),被动断开(read返回0)
- 消息到达:文件描述符可读
- 消息发送完毕:这算半个。对于低流量的服务,可不必关心这个事件;这里的发送完毕是指数据写入操作系统缓冲区,将由TCP协议栈负责数据的发送与重传,不代表对方已经接收到数据。
Reactor的关键结构
Reactor最核心的是事件分发机制,即将IO multiplexing拿到的IO事件分发给各个文件描述符(fd)的事件处理函数。
Reactor 模型 有四个组件
The reactor design pattern is an event handling pattern for handling service requests delivered concurrently to a service handler by one or more inputs. The service handler then demultiplexes the incoming requests and dispatches them synchronously to the associated request handlers.
反应器设计模式是一种事件处理模式,用于处理由一个或多个输入并发传递给服务处理程序的服务请求。 然后,服务处理程序对传入的请求进行多路分解,并将它们同步分派到相关联的请求处理程序。
重要组件:Event事件、Reactor反应堆、Demultiplex事件分发器、Eventhandler事件处理器
Channel —— one fd per Channel (Event & Eventhandler)
Channel的构造函数,绑定一个loop,一个fd
Channel::Channel(EventLoop *loop, int fd)
: loop_(loop), fd_(fd), events_(0), revents_(0), index_(-1), tied_(false)
{
}
- 每个Channel对象自始至终只属于一个EventLoop,因此每个Channel对象都只属于某一个IO线程。每个Channel对象自始至终**只负责一个文件描述符(fd)**的IO事件分发,但它并不拥有这个fd,
也不会在析构的时候关闭这个fd。- Channel class的数据成员。其中events_是它关心的IO事件,由用户设置;revents_是目前活动的事件,由EventLoop/Poller设置;这两个字段都是bit pattern,它们的名字来自poll(2)的struct pollfd。
Channel 理解为通道,封装了sockfd和其感兴趣的event,如EPOLLIN、EPOLLOUT事件
-
负责设置回调函数(Read、Write、Close、Error)
-
文件描述符的事件状态
- 设置状态的(enableReading \ disableReading \ enableWriting \ disableWriting \ disableAll)
- 返回当前的 isWriting 、isReading、isNone
Channel的重要数据成员
fd、events_、revents_、callback
EventLoop *loop_; // 事件循环
const int fd_; // fd, Poller监听的对象
int events_; // 注册fd感兴趣的事件
int revents_; // poller返回的具体发生的事件,执行的事件
int index_; // 自己在pollfds_数组中的下标,-1表示无所属
std::weak_ptr<void> tie_;
bool tied_;
// 因为channel通道里面能够获知fd最终发生的具体的事件revents,所以它负责调用具体事件的回调操作
ReadEventCallback readCallback_;
EventCallback writeCallback_;
EventCallback closeCallback_;
EventCallback errorCallback_;
与Channel的交互
EventLoop => Channel
-
根据poller通知的channel发生的具体事件, 由channel负责调用具体的回调操作
-
void Channel::handleEventWithGuard(Timestamp receiveTime)
-
// 根据poller通知的channel发生的具体事件, 由channel负责调用具体的回调操作 void Channel::handleEventWithGuard(Timestamp receiveTime) { if ((revents_ & EPOLLHUP) && !(revents_ & EPOLLIN)) 调用 closeCallback_ if (revents_ & EPOLLERR) ...... if (revents_ & (EPOLLIN | EPOLLPRI)) ...... if (revents_ & EPOLLOUT) ...... }
-
Channel => Poller
- Channel::update()会调用EventLoop::updateChannel(Channel *),后者会转而调用Poller::updateChannel(Channel *)更新channel。
Poller 和 EPollPoller - Demultiplex
-
Poller class是IO multiplexing的封装。在muduo中是个抽象基类,因为muduo同时支持poll(2)和epoll(4)两种IOmultiplexing机制。Poller是EventLoop的间接成员,只供其owner——EventLoop在IO线程调用,因此无须加锁。其生命期与EventLoop相等。
Poller并不拥有Channel,Channel在析构之前必须自己unregister(EventLoop::removeChannel()),避免空悬指针。 -
ChannelMap
Poller 的重要数据成员
protected:
// map的key: sockfd value: sockfd所属的channel通道类型,one fd per Channel
using ChannelMap = std::unordered_map<int, Channel*>;
ChannelMap channels_;
private:
EventLoop *ownerLoop_; // 定义Poller所属的事件循环EventLoop
// EventLoop可以通过该接口获取默认的IO复用的具体实现
static Poller* newDefaultPoller(EventLoop *loop);
EPollPoller
数据成员
/**
* 负责epoll的使用
* epoll_create
* epoll_ctl add/mod/del
* epoll_wait
*/
class EPollPoller : public Poller
{
public:
EPollPoller(EventLoop *loop);
~EPollPoller() override;
// 重写基类Poller的抽象方法
Timestamp poll(int timeoutMs, ChannelList *actibeChannels) override;
void updateChannel(Channel *channel) override;
void removeChannel(Channel *channel) override;
private:
static const int kInitEventListSize = 16;
// 填写活跃的连接
void fillActiveChannels(int numEvents, ChannelList *activeChannels) const;
// 更新channel通道
void update(int operation, Channel *channel);
using EventList = std::vector<epoll_event>;
int epollfd_;
EventList events_; // events_ 对应的 Channel,Channel的数组
};
EPollPoller的构造函数
EPollPoller::EPollPoller(EventLoop *loop)
: Poller(loop)
, epollfd_(::epoll_create1(EPOLL_CLOEXEC))
, events_(kInitEventListSize) // vector<epoll_event>的大小
{
if (epollfd_ < 0)
{
LOG_FATAL("epoll_create error:%d \n", errno);
}
}
- **fillActiveChannels()遍历pollfds_,找出有活动事件的fd,把它对应的Channel填入activeChannels。**这个函数的复杂度是O(N),其中N是pollfds_的长度,即文件描述符数目。为了提前结束循环,每找到一个活动fd就递减numEvents,这样当numEvents减为0时表示活动fd都找完了,不必做无用功。当前活动事件revents会保存在Channel中,供Channel::handleEvent()使用。
// 填写活跃的连接
void EPollPoller::fillActiveChannels(int numEvents, ChannelList *activeChannels) const
{
for (int i=0; i < numEvents; ++i)
{
Channel *channel = static_cast<Channel*>(events_[i].data.ptr);
channel->set_revents(events_[i].events);
activeChannels->push_back(channel); // EventLoop就拿到了它的poller给它返回的所有发生事件的channel列表了
}
}
-
update()的主要功能是负责维护和更新pollfds_数组。 —— epoll_ctl
红黑树(自己用的是hashmap)
添加新Channel的复杂度是O(logN),
更新已有的Channel的复杂度是O(1) (因为Channel记住了自己在pollfds_数组中的下标,因此可以快速定位)。
removeChannel()的复杂度也将会是O(logN)。
void EPollPoller::updateChannel(Channel *channel)
{
const int index = channel->index();
if (index == kNew || index == kDeleted) // 注册 EPOLL_CTL_ADD
else // channel已经在poller上注册过了
{
// 查看 channel 的event 为0删除 EPOLL_CTL_DEL
// 否则 更改 EPOLL上 fd对应的event EPOLL_CTL_MOD
}
}
// 更新channel通道 epoll_ctl add/mod/del
void EPollPoller::update(int operation, Channel *channel)
{
epoll_event event;
bzero(&event, sizeof event);
int fd = channel->fd();
event.events = channel->events();
event.data.fd = fd;
event.data.ptr = channel; // 放弃了 fd 成员使用channel,channel里存了fd
if (::epoll_ctl(epollfd_, operation, fd, &event) < 0) ...
}
EventLoop —— Reactor
one loop per thread顾名思义每个线程只能有一个EventLoop对象,因此EventLoop的构造函数会检查当前线程是否已经创建了其他EventLoop对象,遇到错误就终止程序(LOG_FATAL)。
-
EventLoop的构造函数会记住本对象所属的线程(threadId_)。创建了EventLoop对象的线程是IO线程,其主要功能是运行事件循环EventLoop:: loop()。EventLoop对象的生命期通常和其所属的线程一样长,它不必是heap对象。
-
EventLoop通过scoped_ptr来间接持有Poller,因此不必包含,只需前向声明Poller class。
-
一个 loop 只有一个 wakeup_fd ,EventLoop通过往 wakeup_fd写东西,唤醒相应的 loop
每一个 wakeup_fd 也封装成 Channel注册在 Poller上 ——std::unique_ptr<Channel> wakeupChannel_
EventLoop的构造函数
EventLoop::EventLoop()
: looping_(false) // 标识未开启loop循环
, quit_(false) // 标识未退出
, callingPendingFunctors_(false) //
, threadId_(CurrentThread::tid()) // 标识执行当前loop的线程id
, poller_(Poller::newDefaultPoller(this)) // 构造一个poller
, wakeupFd_(createEventfd()) // 通过往 wakeup_fd写东西,唤醒相应的 loop
, wakeupChannel_(new Channel(this, wakeupFd_)) // wakeup_fd注册进Channel
{
LOG_DEBUG("EventLoop created %p in thread %d \n", this, threadId_);
if (t_loopInThisThread) { /*报警,两个loop出现在一个thread*/ }
else
{
t_loopInThisThread = this;
}
// 设置wakeupfd的事件类型以及发生事件后的回调操作
wakeupChannel_->setReadCallback(std::bind(&EventLoop::handleRead, this));
// 每一个eventloop都将监听wakeupchannel的EPOLLIN读事件了
wakeupChannel_->enableReading();
}
EventLoop的重要数据成员
using ChannelList = std::vector<Channel*>;
std::atomic_bool looping_; // 原子操作,通过CAS实现的,是否执行looping_
std::atomic_bool quit_; // 标识退出loop循环
const pid_t threadId_; // 记录当前loop所在线程的id
Timestamp pollReturnTime_; // poller返回发生事件的channels的时间点
std::unique_ptr<Poller> poller_;
int wakeupFd_; // 主要作用,当mainLoop获取一个新用户的channel,通过轮询算法选择一个subloop,通过该成员唤醒subloop处理channel
std::unique_ptr<Channel> wakeupChannel_;
ChannelList activeChannels_;
std::atomic_bool callingPendingFunctors_; // 标识当前loop是否有需要执行的回调操作
std::vector<Functor> pendingFunctors_; // 存储loop需要执行的所有的回调操作
std::mutex mutex_; // 互斥锁,用来保护上面vector容器的线程安全操作
EventLoop::runInLoop(const Functor& cb) —— 区分回调函数的执行线程
EventLoop有一个非常有用的功能:在它的IO线程内执行某个用户任务回调,即EventLoop::runInLoop(const Functor& cb),其中Functor是boost::function<void()>。
- 如果用户在当前IO线程调用这个函数,回调会同步进行;
- 如果用户在其他线程调用runInLoop(),cb会被加入队列,IO线程会被唤醒来调用这个Functor;
loop函数 -> Reactor组件调用 时序图
时序图
最终是由Channel 负责回调函数的执行
loop
void EventLoop::loop()
{
looping_ = true; // 正在looping
quit_ = false; // 未退出
while(!quit_)
{
activeChannels_.clear();
// 监听两类fd 一种是client的fd,一种wakeupfd
pollReturnTime_ = poller_->poll(kPollTimeMs, &activeChannels_); // 调用poll()
for (Channel *channel : activeChannels_)
{
channel->handleEvent(pollReturnTime_); // channel处理相应的事件
}
// 执行当前EventLoop事件循环需要处理的回调操作
doPendingFunctors();
}
looping_ = false;
}
pool
Timestamp EPollPoller::poll(int timeoutMs, ChannelList *activeChannels)
{
int numEvents = ::epoll_wait(epollfd_, &*events_.begin(), static_cast<int>(events_.size()), timeoutMs);
int saveErrno = errno;
Timestamp now(Timestamp::now());
if (numEvents > 0)
{
fillActiveChannels(numEvents, activeChannels); // 返回所有活跃的channel
if (numEvents == events_.size()) {}//扩容
}
else if (numEvents == 0) {} // log_bug
else
{
if (saveErrno != EINTR) // EINTR(Interrupted system call) 可忽略
{
errno = saveErrno;
LOG_ERROR("EPollPoller::poll() err!");
}
}
return now;
}
handleEvent
// fd得到poller通知以后,处理事件,负责执行具体的回调
void Channel::handleEvent(Timestamp receiveTime)
{
if (tied_)
{
std::shared_ptr<void> guard = tie_.lock();
if (guard)
{
handleEventWithGuard(receiveTime);
}
}
else
{
handleEventWithGuard(receiveTime);
}
}