muduo_net库源码分析(三)

本文详细介绍了Linux中的多种定时器函数,着重讨论了timerfd_create在多线程网络编程中的优势,如避免信号处理的复杂性,融入select/poll事件框架,以及在Reactor模式中的作用。通过一个muduo_net库的测试程序展示了如何使用timerfd_create创建和管理定时器,并与EPollPoller结合处理超时事件。
摘要由CSDN通过智能技术生成

定时器函数选择

1.定时函数,用于让程序等待一段时间或安排计划任务:
sleep
alarm
usleep
nanosleep
clock_nanosleep
getitimer / setitimer
timer_create / timer_settime / timer_gettime / timer_delete
timerfd_create / timerfd_gettime / timerfd_settime
2.timerfd_* 入选的原因:
(1)sleep / alarm / usleep在实现时有可能用了信号SIGALRM,在多线程程序中处理信号是个相当麻烦的事情,应当尽量避免
(2)nanosleep 和 clock_nanosleep是线程安全的,但是在非阻塞网络编程中,绝对不能用让线程挂起的方式来等待一段时间,程序会失去响应。正确的做法是注册一个时间回调函数。
(3)getitimer 和 timer_create也是用信号来 deliver 超时,在多线程程序中也会有麻烦。
(4)timer_create可以指定信号的接收方是进程还是线程,算是一个进步,不过在信号处理函数(signal handler)能做的事情实在很受限。
(5)timerfd_create把时间变成了一个文件描述符,该“文件”在定时器超时的那一刻变得可读(相当于触发了可读事件),这样就能很方便地融入到 select/poll 框架中,用统一的方式来处理 IO 事件和超时事件,这也正是 Reactor 模式的长处。
3.函数

#include <sys/ timerfd.h >
int timerfd_create ( int clockid , int flags);
int timerfd_settime ( int fd , int flags, const struct itimerspec * new_value , struct itimerspec * old_value );
int timerfd_gettime ( int fd , struct itimerspec * curr_value )

测试(muduo_net库源码分析(二)的测试程序)

#include <muduo/net/Channel.h>
#include <muduo/net/EventLoop.h>

#include <boost/bind.hpp>

#include <stdio.h>
#include <sys/timerfd.h>

using namespace muduo;
using namespace muduo::net;

EventLoop* g_loop;
int timerfd;

void timeout(Timestamp receiveTime)
{
	printf("Timeout!\n");
	uint64_t howmany;
	::read(timerfd, &howmany, sizeof howmany);//一定要读完,因为是LT触发,否则会一直处于触发的状态
	//g_loop->quit();//结束EventLoop::loop中的while循环
	//注意:EventLoop::quit可以跨线程调用!
}

int main(void)
{
	EventLoop loop;//创建一个reactor
	g_loop = &loop;

	timerfd = ::timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC);
	Channel channel(&loop, timerfd);//一个Channel对象只属于一个EventLoop对象,一个EventLoop对象拥有多个Channel对象
	channel.setReadCallback(boost::bind(timeout, _1));//设置可读事件到来时触发的回调函数
	channel.enableReading();//关注它的可读事件(这里指定时器的超时事件)

	struct itimerspec howlong;
	bzero(&howlong, sizeof howlong);
	howlong.it_value.tv_sec = 1;
	::timerfd_settime(timerfd, 0, &howlong, NULL);

	loop.loop();

	::close(timerfd);
}

在这里插入图片描述
打开下面的代码后:

g_loop->quit();//结束EventLoop::loop中的while循环

在这里插入图片描述

EPollPoller详解(个人的深入理解)

EPollPoller.cc中有一个匿名命名空间,表示事件(Channel对象)的状态:
在这里插入图片描述
这里补充一个重点,那就是Channel类中的数据成员index_。
对于PollPoller类,index_表示 Channel* 在::poll的事件数组中的下标;对于EPollPoller类,index_表示事件的状态(即KNew、KAdded、KDeleted)。

为什么会这样呢,其实很好理解,对于::poll函数,std::vector<struct pollfd>pollfds_;
::poll函数的第一个参数是struct pollfd数组的首地址,是传入传出参数,::poll要监控的所有事件就是通过pollfds_设置后再传入函数中。::poll返回pollfds_后,还要对其进行遍历,找出其中触发的numEvents个事件。
在这里插入图片描述
而对于::epoll_wait函数,事件数组为std::vector<struct epoll_event>events_;
::epoll_wait函数的第二个参数是struct epoll_event数组的首地址,是传出参数,::epoll_wait要监控的所有事件不是通过events_设置的,与events_无关,而是通过::epoll_ctl对epoll的红黑树进行设置。::epoll_wait返回events_后,events_中的前numEvents个事件全部是触发的事件(所以这也就是为什么在EPollPoller类的构造函数中要给events_预先分配容量的原因),不需要像::poll一样进行寻找。
在这里插入图片描述

EPollPoller源码

EPollPoller.h

#ifndef MUDUO_NET_POLLER_EPOLLPOLLER_H
#define MUDUO_NET_POLLER_EPOLLPOLLER_H

#include <muduo/net/Poller.h>

#include <map>
#include <vector>

struct epoll_event;

namespace muduo
{
namespace net
{

///
/// IO Multiplexing with epoll(4).
///
class EPollPoller : public Poller
{
 public:
  EPollPoller(EventLoop* loop);
  virtual ~EPollPoller();

  virtual Timestamp poll(int timeoutMs, ChannelList* activeChannels);
  virtual void updateChannel(Channel* channel);
  virtual void removeChannel(Channel* channel);

 private:
  static const int kInitEventListSize = 16;

  void fillActiveChannels(int numEvents,
                          ChannelList* activeChannels) const;
  void update(int operation, Channel* channel);

  typedef std::vector<struct epoll_event> EventList;
  typedef std::map<int, Channel*> ChannelMap;

  int epollfd_;//epoll_create创建的句柄
  EventList events_;//::epoll_wait函数的第二个参数是struct epoll_event数组的首地址,第二个参数是传出参数!
  ChannelMap channels_;
};

}
}
#endif  // MUDUO_NET_POLLER_EPOLLPOLLER_H

EPollPoller.cc

#include <muduo/net/poller/EPollPoller.h>

#include <muduo/base/Logging.h>
#include <muduo/net/Channel.h>

#include <boost/static_assert.hpp>

#include <assert.h>
#include <errno.h>
#include <poll.h>
#include <sys/epoll.h>

using namespace muduo;
using namespace muduo::net;

// On Linux, the constants of poll(2) and epoll(4)
// are expected to be the same.
BOOST_STATIC_ASSERT(EPOLLIN == POLLIN);
BOOST_STATIC_ASSERT(EPOLLPRI == POLLPRI);
BOOST_STATIC_ASSERT(EPOLLOUT == POLLOUT);
BOOST_STATIC_ASSERT(EPOLLRDHUP == POLLRDHUP);
BOOST_STATIC_ASSERT(EPOLLERR == POLLERR);
BOOST_STATIC_ASSERT(EPOLLHUP == POLLHUP);

namespace
{
const int kNew = -1;//当前事件是新事件,既不在红黑树上,也不在map中
const int kAdded = 1;//当前事件在红黑树上,也在map中
const int kDeleted = 2;//当前事件不在红黑树上,但是当前事件仍在map中
}

EPollPoller::EPollPoller(EventLoop* loop)
  : Poller(loop),
    epollfd_(::epoll_create1(EPOLL_CLOEXEC)), //创建一个epoll句柄
    events_(kInitEventListSize)//创建一个vector,预先分配容量大小为16
    //EPollPoller构造函数中对vector预先分配容量的原因如下:
    //::epoll_wait的第二个参数是传出参数,::epoll_wait要监控的事件不是通过该参数设置的,而是通过::epoll_ctl
    //::poll的第一个参数是传入传出参数,要监控的事件通过该参数设置
{
  if (epollfd_ < 0)
  {
    LOG_SYSFATAL << "EPollPoller::EPollPoller";
  }
}

EPollPoller::~EPollPoller()
{
  ::close(epollfd_);
}

Timestamp EPollPoller::poll(int timeoutMs, ChannelList* activeChannels)
{
  int numEvents = ::epoll_wait(epollfd_,
                               &*events_.begin(),
                               static_cast<int>(events_.size()),
                               timeoutMs);

  //int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)
  /*
  struct epoll_event {
	uint32_t events; //Epoll events
	epoll_data_t data; // User data variable
  };
  typedef union epoll_data {
	  void *ptr;
	  int fd;
	  uint32_t u32;
	  uint64_t u64;
  } epoll_data_t;
  */



  Timestamp now(Timestamp::now());
  if (numEvents > 0)
  {
    LOG_TRACE << numEvents << " events happended";
    fillActiveChannels(numEvents, activeChannels);//将::epoll_wait返回的numEvents个事件放入活动通道中
    if (implicit_cast<size_t>(numEvents) == events_.size())
    {
      events_.resize(events_.size()*2);//vector扩容
    }
  }
  else if (numEvents == 0)
  {
    LOG_TRACE << " nothing happended";
  }
  else
  {
    LOG_SYSERR << "EPollPoller::poll()";
  }
  return now;
}

void EPollPoller::fillActiveChannels(int numEvents,
                                     ChannelList* activeChannels) const
{
  assert(implicit_cast<size_t>(numEvents) <= events_.size());
  for (int i = 0; i < numEvents; ++i)
  {
    Channel* channel = static_cast<Channel*>(events_[i].data.ptr);
#ifndef NDEBUG
    int fd = channel->fd();
    ChannelMap::const_iterator it = channels_.find(fd);
    assert(it != channels_.end());
    assert(it->second == channel);
#endif
    channel->set_revents(events_[i].events);
    activeChannels->push_back(channel);
  }
}

void EPollPoller::updateChannel(Channel* channel)
{
  Poller::assertInLoopThread();
  LOG_TRACE << "fd = " << channel->fd() << " events = " << channel->events();
  const int index = channel->index();
  if (index == kNew || index == kDeleted)// index为-1或2说明要把当前事件挂到红黑树上去
  {
    // a new one, add with EPOLL_CTL_ADD
    int fd = channel->fd();
    if (index == kNew)
    {
      assert(channels_.find(fd) == channels_.end());
      channels_[fd] = channel;//将新组成的key-value加入channels_,即加到map中//std::map<int, Channel*>channels_;
    }
    else // index == kDeleted
    {
      assert(channels_.find(fd) != channels_.end());
      assert(channels_[fd] == channel);
    }
    channel->set_index(kAdded);//修改当前通道状态为已添加
    update(EPOLL_CTL_ADD, channel);//当前事件挂到epoll的红黑树上去
  }
  else
  {
    //说明需要进行更新或移除(这里的移除只是将事件从红黑树上移除,并没有从map中移除)
    // update existing one with EPOLL_CTL_MOD/DEL
    int fd = channel->fd();
    (void)fd;
    assert(channels_.find(fd) != channels_.end());
    assert(channels_[fd] == channel);
    assert(index == kAdded);
    if (channel->isNoneEvent())//移除(这里的移除只是将事件从红黑树上移除,并没有从map中移除)
    {
      update(EPOLL_CTL_DEL, channel);
      channel->set_index(kDeleted);
    }
    else//更新
    {
      update(EPOLL_CTL_MOD, channel);
    }
  }
}

void EPollPoller::removeChannel(Channel* channel)//将事件移除(这里的移除是既将事件从红黑树上移除,也将事件从map中移除) 
{
  Poller::assertInLoopThread();
  int fd = channel->fd();
  LOG_TRACE << "fd = " << fd;
  assert(channels_.find(fd) != channels_.end());
  assert(channels_[fd] == channel);
  assert(channel->isNoneEvent());
  int index = channel->index();
  assert(index == kAdded || index == kDeleted);
  size_t n = channels_.erase(fd);
  (void)n;
  assert(n == 1);

  if (index == kAdded)//如果事件还在红黑树上,那么除了刚才将事件从map中移除外,还要将事件从红黑树上移除
  {
    update(EPOLL_CTL_DEL, channel);
  }
  channel->set_index(kNew);
}



//完成对epoll的红黑树的操作
//3种operation:
//EPOLL_CTL_ADD (注册新的fd到epfd)
//EPOLL_CTL_MOD (修改已经注册的fd的监听事件)
//EPOLL_CTL_DEL (从epfd删除一个fd)
void EPollPoller::update(int operation, Channel* channel)
{
  //挂到红黑树的每一个结点的类型都是struct epoll_event类型
  struct epoll_event event;
  bzero(&event, sizeof event);
  event.events = channel->events();
  event.data.ptr = channel;//重点
  int fd = channel->fd();
  if (::epoll_ctl(epollfd_, operation, fd, &event) < 0)
  {
    if (operation == EPOLL_CTL_DEL)
    {
      LOG_SYSERR << "epoll_ctl op=" << operation << " fd=" << fd;
    }
    else
    {
      LOG_SYSFATAL << "epoll_ctl op=" << operation << " fd=" << fd;
    }
  }
}


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值