muduo库笔记1(TcpConnection是如何收发数据的)

1.发送数据

  • 首先, 消息的发送动作(write)一定是在IO线程中进行的。为什么这样做呢?
    这是为了保证应用层消息发送的顺序性。
    当非IO线程想使用套接字发送消息时,会把消息传送给IO线程(sendInLoop)。

  • 为什么不会在epoll循环中一直保持注册的写事件?
    muduo的epoll使用的是LT触发模式。在LT模式EPOLLOUT只要在写缓冲区有空间下,就会触发,因此会造成busy loop。

  • muduo的发送数据(TcpConnection::send)是
    (1)先尝试直接发送数据。当然为了保证顺序性,应当在应用层缓冲outputBuffer_中没有数据的情况下,才能直接write。
    (2)如果一次write没有发送完全,也就是内核写缓冲区可能塞满,会注册EPOLLOUT等待缓冲区可写后, 通过回调(TcpConnection::handleWrite)执行写。
    每次写只调用一次write,原因是如果第一次write没发完,那么第二次几乎肯定会EAGAIN。
    (3)当outputBuffer_没数据后,会在epoll中停止观察可写事件,避免触发EPOLLOUT引起busy loop。

  • 由于muduo只接受被动关闭(保证收发数据完整性)。当我们想在服务端写关闭的时候,需要先把outputBuffer_都发送完,才可调用shutdown。因此,当kDisconnecting状态(写关闭)时,需要在TcpConnection中,主动调用shutdownInLoop来完成TCP协议栈的shutdown write。

  • muduo在写事件中,不处理错误而是放在读事件处理。一旦错误,read会读到0字节,继而关闭连接。

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(data)+nwrote, remaining);
    if (!channel_->isWriting())
    {
      channel_->enableWriting();
    }
  }
}

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";
      // if (state_ == kDisconnecting)
      // {
      //   shutdownInLoop();
      // }
    }
  }
  else
  {
    LOG_TRACE << "Connection fd = " << channel_->fd()
              << " is down, no more writing";
  }
}

2.接收数据

  • 接收数据没有特殊的逻辑处理。对于read返回0或者error会关闭这个套接字。
  • readFd只调用一次read,没有反复read直至EAGAIN,这样做书中解释是为了兼顾多个连接的公平性,降低延迟,不会因为某个连接数据过大而使得该IO线程其他Fd得不到处理。
【总结自】 Linux多线程服务端编程:使用muduo C++网络库(陈硕)

文中有些是自己的理解,如有错误请留言。

你可能感兴趣的:(muduo库笔记1(TcpConnection是如何收发数据的))