【Muduo源码分析】 解读muduo如何发送数据 TcpConnection Buffer

本文详细分析了Muduo网络库中TcpConnection的发送数据流程,包括使用水平触发的epoll,send函数的工作机制,以及如何在socket可写时通过handleWrite()继续发送数据。Muduo通过writeCompleteCallback_和HighWaterMarkCallback_解决数据发送过快的问题,确保网络程序的稳定运行。

本文旨在讲述muduo网络库数据的发送流程。

首先明确一点,Muduo中的epoll使用了水平触发方式,而不是边缘触发。

TcpConnection首先需要增加应用层的缓冲区

Buffer outputBuffer_; 

我们看到在TcpConnection类内,提供了两个数据发送函数,分别是TcpConnection::send,TcpConnection::sendInLoop。前者也是调用后者,保证数据的发送在IO线程内的EventLoop执行。(本博客系列都是基于Muduo的示例教程代码分析,个人认为这样更能理解整个网络库的整体流程)

void TcpConnection::send(const std::string& message)
{
  if (state_ == kConnected) {
    if (loop_->isInLoopThread()) {
      sendInLoop(message);
    } else {
      loop_->runInLoop(
          boost::bind(&TcpConnection::sendInLoop, this, message));
    }
  }
}

void TcpConnection::sendInLoop(const std::string& message)
{
  loop_->assertInLoopThread();
  ssize_t nwrote = 0;
  // if no thing in output queue, try writing directly
  //如果channel没有在写,并且buffer没有数据
  if (!channel_->isWriting() && outputBuffer_.readableBytes() == 0) {
    //直接先尽力写
    nwrote = ::write(channel_->fd(), message.data(), message.size());
    if (nwrote >= 0) {
      //如果写的长度小于消息长度
      if (implicit_cast<size_t>(nwrote) < message.size()) {
        LOG_TRACE << "I am going to write more data";
      } else if (writeCompleteCallback_) {
       //如果写完了,回调writeCompleteCallback_
        loop_->queueInLoop(
            boost::bind(writeCompleteCallback_, shared_from_this()));
      }
    } else {
      nwrote = 0;
      if (errno != EWOULDBLOCK) {
        LOG_SYSERR << "TcpConnection::sendInLoop";
      }
    }
  }
   
  assert(nwrote >= 0);
  //如果消息没有写完,那么将消息添加到Buffer中
  if (implicit_cast<size_t>(nwrote) < message.size()) {
    outputBuffer_.append(message.data()+nwrote, message.size()-nwrote);
    if (!channel_->isWriting()) {
      channel_->enableWriting();
    }
  }
}

我们可以看到sendInLoop()会先尝试直接发送数据,如果一次发送完毕启用WriteCallback;如果只发送了部分数据,则把剩余的数据放入outputBuffer_,并开始关注writable事件,以后在handlerWrite()中发送剩余的数据。

channel::enableWriting的代码如下,通知poller关心本channel的可读事件

void enableWriting() { events_ |= kWriteEvent; update(); }

上面我们只是写了一部分的数据,剩下来的数据什么时候写呢?当socket变得可写时,Channel会调用TcpConnection::handleWrite(),这里我们继续发送outputBuffer_中的数据。 这里socket可写,主要是指发送缓冲区可写字节数,低于低水位标志。

我们来看看TcpConnection::handleWrite(),当可读时,就将Buffer中数据写入,如果写完了,需要关闭对套接字读事件的关心(因为是水平触发,如果不关闭,将一直触发)。然后执行writeCompleteCallback_。在TcpConnection::sendInLoop中,当数据一次发送完成后,也调用了writeCompleteCallback_。这个就是用户的写完数据的回调函数。

void TcpConnection::handleWrite()
{
  loop_->assertInLoopThread();
  //如果是可写状态
  if (channel_->isWriting()) {
    //将Buffer中数据写入
    ssize_t n = ::write(channel_->fd(),
                        outputBuffer_.peek(),
                        outputBuffer_.readableBytes());
   //如果写成功
    if (n > 0) {
      outputBuffer_.retrieve(n);
      if (outputBuffer_.readableBytes() == 0) {
        channel_->disableWriting();
        if (writeCompleteCallback_) {
          loop_->queueInLoop(
              boost::bind(writeCompleteCallback_, shared_from_this()));
        }
        if (state_ == kDisconnecting) {
          shutdownInLoop();
        }
      } else {   //如果一次没有写完,下次还需要继续写
        LOG_TRACE << "I am going to write more data";
      }
    } else {
      LOG_SYSERR << "TcpConnection::handleWrite";
    }
  } else {
    LOG_TRACE << "Connection is down, no more writing";
  }
}

网络程序中,数据的发送可能过快,在本地堆积,所以Muduo为了解决数据发送的困难,提供了两个回调函数,分别是writeCompleteCallback_和HighWaterMarkCallback_,可以称为低水位回调和高水位回调。如果当前缓冲区中数据大小超出上限值,那么调用HighWaterMarkCallback_。高水位回调将停止从用户接收数据。WriteCompleteCallback函数为发送缓冲区为空时调用,在这个函数重启开启接收数据。

最后总结发送流程

1、调用TcpConnection::send

2、调用TcpConnection::sendInLoop

      1、如果目前可以发送(channel没有在写,并且缓冲区没有数据),先尝试发送

       2、如果没有完全发送,将数据添加到buffer中,添加对channel对应的socket写事件关心,等待socket可写,然后调用          handlewrite

       3、如果发送完全,调用用户回调函数

3、如果没有发送完全,并且socket可读,调用HandleWrite。继续发送,如果发送完全,就取消对socket可写事件关系。如果没有发送完全,等待下一次的发送。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值