TCP网络编程本质
TCP网络编程最本质的是处理三个半事件
- 连接建立:服务器accept(被动)接收连接,客户端connect(主动)发起连接
- 连接断开:主动断开(close、shutdown),被动断开(read返回0)
- 消息到达:文件描述符可读
- 消息发送完毕:这算半个。对于低流量的服务,可不必关心这个事件;这里的发送完毕是指数据写入操作系统缓冲区,将由TCP协议栈负责数据的发送与重传,不代表对方已经接收到数据
1、EventLoop之one loop per thread
one loop per thread意味着每个线程只能有一个EventLoop对象,用变量
__thread EventLoop* t_loopInThisThread = 0;
表示,在创建EventLoop对象时将t_loopInThisThread赋值,以后再创建时就可以检查这个变量,如果已经赋值就说明当前线程已经创建过EventLoop对象了。线程调用静态函数EventLoop::getEventLoopOfCurrentThread就可以获得当前线程的EventLoop对象的指针了。
isInLoopThread():判断当前线程是否是创建EventLoop对象的线程
bool isInLoopThread() const
{
return threadId_ == CurrentThread::tid();
}
构造函数:
- 初始化wakeupChannel_,它与queueInLoop()中的wakeup()有关,后面再详细介绍
- 初始化poller_
- 初始化timerQueue_
EventLoop::EventLoop()
: looping_(false), //没调用loop()函数
quit_(false),
eventHandling_(false),
callingPendingFunctors_(false),
iteration_(0),
threadId_(CurrentThread::tid()), //当前线程的真实ID
poller_(Poller::newDefaultPoller(this)), //创建默认的Poller,即epoll
timerQueue_(new TimerQueue(this)), //时间队列
wakeupFd_(createEventfd()), //创建用于[等待/通知]的Eventfd
wakeupChannel_(new Channel(this, wakeupFd_)), //用Eventfd创建通道
currentActiveChannel_(NULL)
{
LOG_DEBUG << "EventLoop created " << this << " in thread " << threadId_;
//如果当前线程已经创建了EventLoop对象,终止(LOG_FATAL)
if (t_loopInThisThread)
{
LOG_FATAL << "Another EventLoop " << t_loopInThisThread
<< " exists in this thread " << threadId_;
}
else
{
t_loopInThisThread = this;
}
//给wakeupChannel_设置回调函数handleRead
wakeupChannel_->setReadCallback(
std::bind(&EventLoop::handleRead, this));
//激活读事件
wakeupChannel_->enableReading();
}
2、EventLoop之事件循环loop()
与libevent的event_base类似,EventLoop也是一个事件循环,调用loop()函数执行事件循环,其中:loop()函数本质上是一个while循环,在while循环中调用了epoll_wait/poll监听注册在EventLoop上的Channel。
下面是loop()函数的时序图:
1:调用了EventLoop::loop()
2:内部调用了Poller::poll()
3与4:表示poll()函数返回后将触发的Channel填充到activeChannels_中
5:遍历activeChannels_中的每个激活的Channel,执行该Channel的回调函数
6:doPendingFunctors(),即处理pending队列中的回调函数
7:继续下一次Poller::poll()
loop()代码解读:
- loop()中调用poll(),当程序没有发生事件时,[阻塞]在此处
- pool()函数返回后,activeChannels_被赋值为被激活的通道
其中,poll()函数返回有两种情况:
① 注册的read/write事件被激活
② wakeupChannel_被wakeup() - poll()函数返回后,会执行两种操作
① 执行activeChannels_中激活的事件回调函数
② 调用doPendingFunctors():执行queueInLoop放入[等待队列]中的回调函数
//事件循环,该函数不能跨线程调用,只能在创建该对象的线程中调用
void EventLoop::loop()
{
assert(!looping_);
assertInLoopThread(); //断言当前处于创建该对象的线程中
looping_ = true;
quit_ = false; // FIXME: what if someone calls quit() before loop() ?
LOG_TRACE << "EventLoop " << this << " start looping";
while (!quit_)
{
//清除活动通道
activeChannels_.clear();
//调用poll(),当程序没有发生事件时,[阻塞]在此处
//pool()函数返回后,activeChannels_被赋值为被激活的通道
//其中,poll()函数返回有两种情况:
// 1.注册的read/write事件被激活
// 2.wakeupChannel_被wakeup()
//poll()函数返回后,会执行两种操作
// 1.执行activeChannels_中激活的事件回调函数
// 2.调用doPendingFunctors():执行queueInLoop放入[等待队列]中的回调函数
pollReturnTime_ = poller_->poll(kPollTimeMs, &activeChannels_);
++iteration_;
if (Logger::logLevel() <= Logger::TRACE)
{
printActiveChannels();
}
// TODO sort channel by priority
eventHandling_ = true;
//遍历每一个活动通道,并处理事件handleEvent
for (Channel* channel : activeChannels_)
{
currentActiveChannel_ = channel;
currentActiveChannel_->handleEvent(pollReturnTime_);
}
currentActiveChannel_ = NULL;
eventHandling_ = false;
//执行queueInLoop放入[等待队列]中的回调函数
doPendingFunctors();
}
LOG_TRACE << "EventLoop " << this << " stop looping";
looping_ = false;
}
3、与Channel相关的操作
对Channel的添加和删除操作,就是向EventLoop中添加/删除了对Channel事件的监听。
void updateChannel(Channel* channel); //在poller中添加或者更新通道
void removeChannel(Channel* channel); //从poller中删除通道
bool hasChannel(Channel* channel); //判断channel是否在poller中
4、与[定时器]相关的操作
// 在某个时刻
TimerId runAt(Timestamp time, TimerCallback cb);
// 在一段时间,执行定时器回调函数cb
TimerId runAfter(double delay, TimerCallback cb);
// 每隔一段时间,执行定时器回调函数cb
TimerId runEvery(double interval, TimerCallback cb);
//取消定时器
void cancel(TimerId timerId);
5、runInLoop与queueInLoop
以上都是讲述了在创建EventLoop对象的线程下的操作,而runInLoop与queueInLoop引出了其他的线程。因此,要从这两个函数是否在IO线程
中被调用,来分析它们的执行情况。
void EventLoop::runInLoop(Functor cb)
{
//如果在[创建EventLoop对象的线程中]调用runInLoop,回调会同步进行;
if (isInLoopThread())
{
cb();
}
else //如果在[`非`创建EventLoop对象的线程中]调用runInLoop
{
//cb会被加入队列,唤醒[创建EventLoop对象的线程]来调用这个Functor
queueInLoop(std::move(cb));
}
}
void EventLoop::queueInLoop(Functor cb)
{
{
MutexLockGuard lock(mutex_);
//将cb放入pendingFunctors_队列
pendingFunctors_.push_back(std::move(cb));
}
//调用queueInLoop的线程不是[创建EventLoop对象的线程] || 正在调用pending functors
if (!isInLoopThread() || callingPendingFunctors_)
{
wakeup(); //使得IO线程执行pendingFunctors_队列中的回调函数
}
}
(1) 分析runInLoop
的执行情况
调用runInLoop()函数的执行情况根据是否在[创建EventLoop对象的线程中]调用runInLoop()分为两种:
case 1: 在
[创建EventLoop对象的线程中]调用runInLoop(cb)
⇒ 同步地执行回调函数cb()
case2:不在
[创建EventLoop对象的线程中]调用runInLoop(cb)
⇒ 转去调用queueInLoop()函数将cb放入pendingFunctors_队列
⇒ 执行wakeup()函数,导致activeChannels_可读事件被触发,进一步使loop()中的poll()函数返回,最后程序执行到doPendingFunctors()函数,即调用了pendingFunctors_队列中的回调函数
无论是不是当前IO线程调用了runInLoop(CallBack__)函数,CallBack__都会被回调。
① 如果是当前IO线程,则直接同步回调;
② 如果不是当前IO线程,则先将CallBack__添加到它的IO线程中的pendFunctors队列中,再唤醒它的IO线程去逐个执行pendFunctors队列中的函数
(2) 分析queueInLoop
的执行情况
调用queueInLoop()函数的执行情况根据是否在[创建EventLoop对象的线程中]调用queueInLoop()也分为两种:
case 1:在
[创建EventLoop对象的线程中]调用queueInLoop(cb)
(1) 先将cb放入pendingFunctors_队列
(2) 执行下面代码,根据callingPendingFunctors_是否为true,判断是否调用wakeup
if (callingPendingFunctors_)
{
wakeup();
}
case 2:不在
[创建EventLoop对象的线程中]调用queueInLoop(cb)
先将cb放入pendingFunctors_队列 ==> 执行wakeup()