Muduo源码剖析

1、总体流程

1. acceptor 进行listen阶段后, 往channel中注册可读事件。
2. acceptor可读处理中生成TcpConnection指针,通过EventloopThreadPool
轮询出其中一个线程的eventloop, 并将此TcpConnection的可读、可写等事件注册到自己Channel(eventLoop)中。
3. 每个EventLoop监听到具体channel的事件后,分析是哪种事件并在当前线程执行绑定的TcpConnection的具体事件处理函数。
4.Channel是创建与TcpConnection和acceptor 中,但是在Eventloop中的poller保存了一份。不用管什么时候保存的。因为是在channel具体添加事件或者更新事件的时候发现不在容器中,顺便添加进去的。

5.于一些阻塞型或者耗时型的任务,例如 MySQL 操作等。这些显然是不能放在 IO 线程(即 EventLoop 所在的线程)中运行的,因为会严重影响 EventLoop 的正常运行。这些需要用户自己启用worker线程。muduo中有ThreadPool可以使用。

2、IO线程池的实现

        类似于业务线程池ThreadPool, 这里只列出不同的部分,参考:worker线程剖析

成员变量

  EventLoop* baseLoop_;
  string name_; 
  bool started_;
  int numThreads_;
  int next_;  //在getNextLoop中使用,为新的socket连接分配EventLoop
  std::vector<std::unique_ptr<EventLoopThread>> threads_;
  std::vector<EventLoop*> loops_;

主要区别在:1.  IO线程池没有任务队列, 主要是创建IO线程,并在IO线程中创建线程私有的EventLoop, 并保存在IO线程池的eventloop容器中,供acceptor分配socket连接使用。

IO线程池运行

void EventLoopThreadPool::start(const ThreadInitCallback& cb)
{
  assert(!started_);
  baseLoop_->assertInLoopThread();

  started_ = true;

  for (int i = 0; i < numThreads_; ++i)
  {
    char buf[name_.size() + 32];
    snprintf(buf, sizeof buf, "%s%d", name_.c_str(), i);
    EventLoopThread* t = new EventLoopThread(cb, buf);  //创建IO线程
    threads_.push_back(std::unique_ptr<EventLoopThread>(t)); //保存IO线程
    loops_.push_back(t->startLoop());   //保存IO线程私有的Eventloop到线程池容器中。供acceptor分配socket连接使用
  }
  if (numThreads_ == 0 && cb)
  {
    cb(baseLoop_);
  }
}

提供Eventloop:以轮询的方式供accepor分配tcpconnection给不同的线程eventloop。


EventLoop* EventLoopThreadPool::getNextLoop()
{
  baseLoop_->assertInLoopThread();
  assert(started_);
  EventLoop* loop = baseLoop_;  //默认是主loop,loops不为空时loop是线程池中的loop。

  if (!loops_.empty())
  {
    // round-robin
    loop = loops_[next_];
    ++next_;
    if (implicit_cast<size_t>(next_) >= loops_.size())   //这里implicit_cast是muduo自己封装的转换
    {
      next_ = 0;
    }
  }
  return loop; 
}

3. Acceptor实现,在主反应堆中实现

建立一个简单TCP服务需要四步骤:

  • 步骤 1. socket() // 调用 socket 函数建立监听 socket
  • 步骤 2. bind() // 绑定地址和端口
  • 步骤 3. listen() // 开始监听端口
  • 步骤 4. accept() // 返回新建立连接的 fd

 构造Acceptro时,完成了步骤1和2.

Acceptor::Acceptor(EventLoop* loop, const InetAddress& listenAddr, bool reuseport)
  : loop_(loop),
    acceptSocket_(sockets::createNonblockingOrDie(listenAddr.family())),  //创建socket并设置为非阻塞状态
    acceptChannel_(loop, acceptSocket_.fd()),
    listening_(false),
    idleFd_(::open("/dev/null", O_RDONLY | O_CLOEXEC))
{
  assert(idleFd_ >= 0);
  acceptSocket_.setReuseAddr(true);
  acceptSocket_.setReusePort(reuseport);  
  acceptSocket_.bindAddress(listenAddr); //绑定地址
  acceptChannel_.setReadCallback(
      std::bind(&Acceptor::handleRead, this));
}

TcpServer start的时候acceptor完成了步骤3 listening, 并将listen socket 可读事件注册到Eventloop中。


void Acceptor::listen()
{
  loop_->assertInLoopThread();
  listening_ = true;
  acceptSocket_.listen();         //开启监听
  acceptChannel_.enableReading(); //往事件循环注册可读事件
}

步骤四,在eventloop进行事件循环时,通过channel返回给acceptor可读事件已发生。此时进行accept.

//监听socket有事件发生
void Acceptor::handleRead()
{
  loop_->assertInLoopThread();
  InetAddress peerAddr;
  //FIXME loop until no more
  int connfd = acceptSocket_.accept(&peerAddr);
  if (connfd >= 0)
  {
    // string hostport = peerAddr.toIpPort();
    // LOG_TRACE << "Accepts of " << hostport;
    if (newConnectionCallback_)
    {
      newConnectionCallback_(connfd, peerAddr); //生成TCPConnection,并注册连接、断开、可读、写完成事件.
    }
    else
    {
      sockets::close(connfd);
    }
  }
  else
  {
    LOG_SYSERR << "in Acceptor::handleRead";
    // Read the section named "The special problem of
    // accept()ing when you can't" in libev's doc.
    // By Marc Lehmann, author of libev.
    if (errno == EMFILE)
    {
      ::close(idleFd_);
      idleFd_ = ::accept(acceptSocket_.fd(), NULL, NULL);
      ::close(idleFd_);
      idleFd_ = ::open("/dev/null", O_RDONLY | O_CLOEXEC);
    }
  }
}

最后,newConnectionCallback_是TCpServer注册的函数。所以执行此函数就是在TCPServer中连接IO线程池和acceptor这两者进行socket 连接的分配。

void TcpServer::newConnection(int sockfd, const InetAddress& peerAddr)
{
  loop_->assertInLoopThread();
  EventLoop* ioLoop = threadPool_->getNextLoop(); //为新连接分配IO线程
  char buf[64];
  snprintf(buf, sizeof buf, "-%s#%d", ipPort_.c_str(), nextConnId_);
  ++nextConnId_;
  string connName = name_ + buf;

  LOG_INFO << "TcpServer::newConnection [" << name_
           << "] - new connection [" << connName
           << "] from " << peerAddr.toIpPort();
  InetAddress localAddr(sockets::getLocalAddr(sockfd));
  // FIXME poll with zero timeout to double confirm the new connection
  // FIXME use make_shared if necessary
  TcpConnectionPtr conn(new TcpConnection(ioLoop,
                                          connName,
                                          sockfd,
                                          localAddr,
                                          peerAddr));
  connections_[connName] = conn;
  conn->setConnectionCallback(connectionCallback_);
  conn->setMessageCallback(messageCallback_);
  conn->setWriteCompleteCallback(writeCompleteCallback_);
  conn->setCloseCallback(
      std::bind(&TcpServer::removeConnection, this, _1)); // FIXME: unsafe
  /*
      连接建立后,放到属于此tcpconnect的eventloop中执行TcpConnection::connectEstablished函数。
      connectEstablished函数中,监听此tcp连接的可读事件。并通知用户的onConnection
  */
  
  ioLoop->runInLoop(std::bind(&TcpConnection::connectEstablished, conn));  

}

未完待续。。。。。。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

YanWenCheng_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值