前面的基础库已经足够我们目前使用,开始小试网络库吧!
1. EventLoop
创建EventLoop 的线程将是IO 线程,而One Loop Per Thread 认为每个线程只有一个EventLoop,不可跨线程调用(跨线程调用没有意义,若出现证明设计出错);
assertInLoopThread() 实现了此设计的检错;
void assertInLoopThread() {
if (!isInLoopThread()) {
abortNotInLoopThread();
}
}
EventLoop 持有:
class EventLoop {
// ...
typedef std::vector<Channel *> ChannelList;
private:
std::unique_ptr<Poller> _poller;
ChannelList _activeChannels;
// ...
}
开启事件循环监听:
while (!_quit) {
_activeChannels.clear();
_poller->poll(kPollTimeMs, &_activeChannels);
for (auto iter : _activeChannels) {
iter->handleEvent();
}
}
首先清空上轮活跃通道的记录,poll 轮询事件发生,并将通道存储于_activeChannels,再依次进行调用;
2. Poller
Poller 有指向拥有该Poller 的EventLoop指针,从而保证当前线程为IO 线程;
Poller 管理fd 表和相应的通道信息;
class Poller {
// ...
private:
// ...
EventLoop *_ownerLoop;
PollFdList _pollfds;
ChannelMap _channels;
}
紧接上Poller::poll 的详细介绍:
Timestamp Poller::poll(int timeoutMs, ChannelList *activeChannels) {
int numEvents = ::poll(_pollfds.data(), _pollfds.size(), timeoutMs);
Timestamp now(Timestamp::now());
if (numEvents > 0) {
LOG_TRACE << numEvents << " events happened";
fillActiveChannels(numEvents, activeChannels);
} else if (numEvents == 0) {
LOG_TRACE << " nothing happended";
} else {
LOG_SYSERR << "Poller::poll()";
}
return now;
}
poll 查看_pollfds 中有反应的文件描述符,numEvents 接受其数量,并通过fillActiveChannels 添加到活跃通道中:
void Poller::fillActiveChannels(int numEvents,
ChannelList *activeChannels) const {
for (auto pfd = _pollfds.begin(); pfd != _pollfds.end() && numEvents > 0;
pfd++) {
if (pfd->revents > 0) {
--numEvents;
auto ch = _channels.find(pfd->fd);
assert(ch != _channels.end());
auto channel = ch->second;
assert(channel->fd() == pfd->fd);
channel->set_revents(pfd->revents);
activeChannels->push_back(channel);
}
}
}
pfd->revents 包含此文件符的含义,POLLIN POLLOUT POLLPRI … 若为真证明是poll 中反应文件描述符;
接着在_channels (map<fd, channel>)找到相应Channel(Channel 是提前设置的,在loop 前),并设置revents,将此通道放入活跃通道中等待回调;
3. Channel
开启事件分发要:
- 生成EventLoop
- 创建文件描述符
- 设置通道(一个通道对应一个fd,并有对应的回调函数和事件信息,可以通过控制通道实现对fd 的管理,如禁止读写等)
- 开启Loop
设置通道包括安置回调函数和开启读,前者就是记录回调函数,后者复杂;
enableReadiing 会使当前Channel 管理的fd 改变Poll 属性,从而使得在handleEvent(Loop 中会以iter->handleEvent() 调用)以不同方式进行;而enableReading 后,作为Loop 的主体,EventLoop 自然持有Channel信息(_activeChannel),但是轮询的Poller 没有(只有fd <-> Channel 对应表),所以要从Channel 跳转到Loop 再更新Poller 中的数据,分两种情况:
- 没有此描述符
if (channel->index() < 0) {
assert(_channels.find(channel->fd()) == _channels.end());
struct pollfd pfd;
pfd.fd = channel->fd();
pfd.events = static_cast<short>(channel->events());
pfd.revents = 0;
_pollfds.push_back(pfd);
int idx = static_cast<int>(_pollfds.size()) - 1;
channel->set_index(idx);
_channels[pfd.fd] = channel;
}
- 有此描述符
else {
assert(_channels.find(channel->fd()) != _channels.end());
assert(_channels[channel->fd()] == channel);
int idx = channel->index();
assert(idx >= 0 && idx < static_cast<int>(_pollfds.size()));
struct pollfd &pfd = _pollfds[idx];
assert(pfd.fd == channel->fd() || pfd.fd == -1);
pfd.events = static_cast<short>(channel->events());
pfd.revents = 0;
if (channel->isNoneEvent()) {
pfd.fd = -1;
}
}
第二种情况若当前通道被设置为黑名单,则忽视:后面将pfd.fd = -1 改为求相反数再减一(暂且留坑);
4. Other
关于前向声明:两个头文件相互引用时,在头文件剔除包含另一个头文件,并使用前向声明,然后在源文件包含另一个头文件;
Muduo Log 中getenv 可以获取当前日志的系统变量来确定Log 等级,可以在export MUDUO_LOG_TRACE=1 中实现类似效果;