muduo库发送数据简述

TCP连接发送数据和接收数据的处理方式略有不同,并没有让IO复用函数全权负责,这样做减少了程序从poll函数返回的次数,提高了服务器的效率。

具体做法是,当消息产生可以像某个socket写入的时候,我们可以调用runInLoop,让IO线程在执行doPendlingFunctor的时候替我们做发送的操作。具体来看TcpConnection模块的代码。

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

但是和事件的接受一样,发送不可能是一帆风顺的,不可能每次发送都保证可以把所有信息都发出去,也正因为如此我们用到了发送缓冲,具体可见https://blog.csdn.net/qq_33113661/article/details/88533686

muduo的做法是,如果当前发送缓冲里没有数据,我们便直接把数据写入socket,如果数据全部被写入socekt,那我们这次发送操作便圆满结束,不需要动用poll去监听socket上的可写时间。相对的,如果这次只写入了一部分数据,muduo并不会去尝试重复向socket重复写入,因为这样做几乎一定会得到一个EAGAIN,而剩下的这部分数据,我们把它存入socket的输出缓冲,并且向poll注册该socket的可写事件,等下次从poll返回的时候再去调用相应的handlerWrite去处理这些数据。而如果打一开始socket的发送缓冲中就有数据没有发送出去,那我们就直接把这次的数据append到后边,等着handlerWrite去帮我们处理。

void TcpConnection::sendInLoop(const void* data, size_t len)
{
  loop_->assertInLoopThread();
  ssize_t nwrote = 0;
  size_t remaining = len;
  bool faultError = false;
  if (state_ == kDisconnected)
  {
    LOG_WARN << "disconnected, give up writing";
    return;
  }
  // if no thing in output queue, try writing directly
  if (!channel_->isWriting() && outputBuffer_.readableBytes() == 0)
  {
    nwrote = sockets::write(channel_->fd(), data, len);
    if (nwrote >= 0)
    {
      remaining = len - nwrote;
      if (remaining == 0 && writeCompleteCallback_)
      {
        loop_->queueInLoop(std::bind(writeCompleteCallback_, shared_from_this()));
      }
    }
    else // nwrote < 0
    {
      nwrote = 0;
      if (errno != EWOULDBLOCK)
      {
        LOG_SYSERR << "TcpConnection::sendInLoop";
        if (errno == EPIPE || errno == ECONNRESET) // FIXME: any others?
        {
          faultError = true;
        }
      }
    }
  }

  assert(remaining <= len);
  if (!faultError && remaining > 0)
  {
    size_t oldLen = outputBuffer_.readableBytes();
    if (oldLen + remaining >= highWaterMark_
        && oldLen < highWaterMark_
        && highWaterMarkCallback_)
    {
      loop_->queueInLoop(std::bind(highWaterMarkCallback_, shared_from_this(), oldLen + remaining));
    }
    outputBuffer_.append(static_cast<const char*>(data)+nwrote, remaining);
    if (!channel_->isWriting())
    {
      channel_->enableWriting();
    }
  }
}

上述代码中可以看到有关于writeCompleteCallback和highWaterMarkCallback两个回调函数的使用,这里来介绍一下这两个函数。

现在考虑一个代理服务器有C和S两个链接,S向C发送数据,经由代理服务器转发,现在S的数据发送的很快,但是C的接受速率却较慢,如果本代理服务器不加以限制,那S到来的数据迟早会撑爆这C连接的发送缓冲区,解决的办法就是当C的发送缓冲中堆积数据达到了某个标志的时候,调用highWaterMarkCallback去让S的连接停止接受数据,等到C发送缓冲的数据被发送完了,调用writeCompleteCallback再开始接受S连接的数据。这样就确保了数据不会丢失,缓冲不会被撑爆。

知道了这两个回调的作用,我们来看她们的应用方法。这两个函数都是使用queueInLoop向EventLoop的pendingfunction队列中添加相应的函数对象,当然,这两个添加的操作也肯定是在EventLoop执行doPendingFunctors的时候执行的,这里曾有一个困扰我许久的问题——为什么要用queueInLoop而不是用runInLoop呢?如果我们要做的只是打开或者关闭某个连接的读写,那用runInloop可能不会出现什么问题,但是请你设想一下,假如有人偏偏要在writeCompleteCallback中向所在的socket写入1byte的信息呢?结合runInLoop和queueInLoop以及eventLoop的loop函数还有上边的send和sendInloop想一想。

答案是会stackoverflow,函数会递归调用把栈撑爆。因为要知道,run和queue的区别在于,在IO线程中调用run这个函数会立刻执行,而调用queue的话,会等下次从poll返回之后执行doPend的时候才执行,发送的过程是要调用send的,send又调用了sendInLoop,而当你在sendinLoop中把这1byte发送出去之后,我们正好又触发了writeComplete,循环开始,爆栈了。可以说有点隐晦了,以上也是我在给陈硕先生发了邮件询问,得到指点之后才得到的答案,在这里再一次感谢陈硕先生。

最后来看看handlerWrite,这是帮我们处理发送缓冲中积攒的数据的函数,是planB,因为为了保证服务器效率和数据有序的发送,我们不得不向poll注册这个soceket的写时间,poll返回后由管理连接的channel调用handlerWrite。

handlerWrite要做的事情就是再次尝试往socket里写入缓冲区的数据,如果能写完就取消监听写事件,否则就继续监听。

这里还有另一种情况,当我们打算关闭一个连接,但是他的应用层buffer里还有数据没有发送出去的时候,我们要实现先发送buffer的数据再关闭连接的功能,这里就涉及到了这一点。我们调用关闭连接的函数后,连接的状态会被设置为kDisconnecting,当handleWrite把buffer里的数据都写完了,而且连接还处于这个状态的时候,我们调用shutdownInloop,继续执行剩下的关闭操作。

void TcpConnection::handleWrite()
{
  loop_->assertInLoopThread();
  if (channel_->isWriting())
  {
    ssize_t n = sockets::write(channel_->fd(),
                               outputBuffer_.peek(),
                               outputBuffer_.readableBytes());
    if (n > 0)
    {
      outputBuffer_.retrieve(n);
      if (outputBuffer_.readableBytes() == 0)
      {
        channel_->disableWriting();
        if (writeCompleteCallback_)
        {
          loop_->queueInLoop(std::bind(writeCompleteCallback_, shared_from_this()));
        }
        if (state_ == kDisconnecting)
        {
          shutdownInLoop();
        }
      }
    }
    else
    {
      LOG_SYSERR << "TcpConnection::handleWrite";
    }
  }
  else
  {
    LOG_TRACE << "Connection fd = " << channel_->fd()
              << " is down, no more writing";
  }
}

猜你喜欢

转载自blog.csdn.net/qq_33113661/article/details/88568032
今日推荐