muduo源码剖析-接受数据和发送数据

1 接受数据

客户端与服务器建立连接,就有了一个TcpConnection对象,该TcpConnection对象包含客户端套接字文件描述符,对应的channel,以及所属的EventLoop对象。上述三个TcpConnection对象非常重要的数据成员,他们标识了这个TcpConnection对象的身份。TcpConnection对象还拥有inputBuffer_和outputBuffer_两个重要数据成员,用于接受数据和发送数据。
当事件循环收到可读事件的到来时,会调用fd对应的channel的读事件的回调函数ReadCallback_,这个回调函数在TcpConnection对象建立时便绑定在TcpConnection::handleRead函数上(见TcpConnection类的构造函数),因此可读事件触发TcpConnection::handleRead函数,如下:

void TcpConnection::handleRead(Timestamp receiveTime)
{
  loop_->assertInLoopThread();
  int savedErrno = 0;
  //将读到的数据放入inputBuffer_中
  ssize_t n = inputBuffer_.readFd(channel_->fd(), &savedErrno); //接受数据
  if (n > 0) //读取n字节数据
  {
    messageCallback_(shared_from_this(), &inputBuffer_, receiveTime);
  }
  else if (n == 0) //对端关闭连接
  {
    handleClose();
  }
  else
  {
    errno = savedErrno;
    LOG_SYSERR << "TcpConnection::handleRead";
    handleError();
  }
}

ssize_t Buffer::readFd(int fd, int* savedErrno)
{
  // saved an ioctl()/FIONREAD call to tell how much to read
  char extrabuf[65536]; //64k
  struct iovec vec[2];
  const size_t writable = writableBytes();
  vec[0].iov_base = begin()+writerIndex_;
  vec[0].iov_len = writable;
  vec[1].iov_base = extrabuf;
  vec[1].iov_len = sizeof extrabuf;
  // when there is enough space in this buffer, don't read into extrabuf.
  // when extrabuf is used, we read 128k-1 bytes at most.
  const int iovcnt = (writable < sizeof extrabuf) ? 2 : 1;
  const ssize_t n = sockets::readv(fd, vec, iovcnt);
  if (n < 0)
  {
    *savedErrno = errno;
  }
  else if (implicit_cast<size_t>(n) <= writable) //还有剩余空间
  {
    writerIndex_ += n;
  }
  else //buffer中readable空间不够
  {
    writerIndex_ = buffer_.size();
    append(extrabuf, n - writable);
  }
  // if (n == writable + sizeof extrabuf)
  // {
  //   goto line_30;
  // }
  return n;
}

通过客户端fd读入数据,并将数据放入输入缓冲inputBuffer_中,当读取的字节n>0时,说明成功读取到了数据,因此调用用户回调函数来处理inputBuffer_中的数据。

messageCallback_(shared_from_this(), &inputBuffer_, receiveTime);

2 发送数据

发送数据是通过TcpConnection::send()函数和TcpConnection::handleWrite()函数进行。send函数如下:

void TcpConnection::send(const void* data, int len)
{
  send(StringPiece(static_cast<const char*>(data), len));
}

void TcpConnection::send(const StringPiece& message)
{
  if (state_ == kConnected)
  {
    if (loop_->isInLoopThread())
    {
      sendInLoop(message);
    }
    else
    {
      void (TcpConnection::*fp)(const StringPiece& message) = &TcpConnection::sendInLoop;
      loop_->runInLoop(
          std::bind(fp,
                    this,     // FIXME
                    message.as_string()));
                    //std::forward<string>(message)));
    }
  }
}

// FIXME efficiency!!!
void TcpConnection::send(Buffer* buf)
{
  if (state_ == kConnected)
  {
    if (loop_->isInLoopThread())
    {
      sendInLoop(buf->peek(), buf->readableBytes());
      buf->retrieveAll();
    }
    else
    {
      void (TcpConnection::*fp)(const StringPiece& message) = &TcpConnection::sendInLoop;
      loop_->runInLoop(
          std::bind(fp,
                    this,     // FIXME
                    buf->retrieveAllAsString()));
                    //std::forward<string>(message)));
    }
  }
}

void TcpConnection::sendInLoop(const StringPiece& message)
{
  sendInLoop(message.data(), message.size());
}

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
  //通道没有关注可写事件,并且发送缓冲区没有数据,直接write
  if (!channel_->isWriting() && outputBuffer_.readableBytes() == 0)
  {
    nwrote = sockets::write(channel_->fd(), data, len);
    if (nwrote >= 0)
    {
      remaining = len - nwrote;
      if (remaining == 0 && writeCompleteCallback_)
      {
          //写完了,回调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);
  //没有错误,并且还有未写完的数据,(说明内核缓冲区已满要将未写完的数据添加到output buffer中)
  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(); //关注POLLOUT事件
    }
  }
}

读上述代码可知,数据的发送都是通过sendInLoop函数在I/O线程中进行。发送数据的逻辑是:

  1. 当channel_没有关注POLLOUT事件并且输出缓冲区没有要发送的数据时开始尝试一次性发送完毕。
  2. 当一次没有发送完毕还剩余数据时,就将剩余数据加入Tcpconnection的输出缓冲区outputBuffer_中,并且注册POLLOUT(这里注意因为muduo采用水平触发LT模式,因此只有在缓冲区有数据要发送时才注册POLLOUT事件,发送完毕应该立即取消注册,不然会一直触发可写事件)
  3. 由于注册了PLOLLOUT事件,且采用LT触发,因此事件循环时Tcpconnection的channel会触发写事件,回调WriteCallback_,由于在Tcpconnection对象建立时channel的WriteCallback_就被绑定在TcpConnection::handleWrite上(见Tcpconnection类的构造函数),因此调用TcpConnection::handleWrite函数,如下:
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(); //停止关注POLLOUT事件,以免出现busy loop
        if (writeCompleteCallback_) //回调writeCompleteCallback_
        {
          loop_->queueInLoop(std::bind(writeCompleteCallback_, shared_from_this()));
        }
        if (state_ == kDisconnecting) //发送缓冲区已经清空,并且连接状态为kDisconnecting,要关闭连接
        {
          shutdownInLoop(); //关闭连接
        }
      }
    }
    else
    {
      LOG_SYSERR << "TcpConnection::handleWrite";
      // if (state_ == kDisconnecting)
      // {
      //   shutdownInLoop();
      // }
    }
  }
  else
  {
    LOG_TRACE << "Connection fd = " << channel_->fd()
              << " is down, no more writing";
  }
}
  1. TcpConnection::handleWrite函数会把输入缓冲区中的数据全部发送给对方,当发送完毕时,立即停止关注POLLOUT事件,以免出现busy loop,如果设置了用户回调函数writeCompleteCallback_就将回调函数loop的 pendingFunctors_中,在事件循环中调用writeCompleteCallback_函数。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值