muduo源码剖析 - TcpConnection

说明

1.  TcpConnection封装了连接socket和他的不同事件的回调,以及两个缓冲区。一个TcpConnection只能由一个EventLoop管理. 具体由哪个loop管理由TcpServer轮询IO线程池给分配一个。

2 . enable_shared_from_this,把当前对象转换成share指针,全局使用的都是它的智能指针。

3.   在连接到来,创建一个TcpConnection对象,立刻使用shared_ptr来管理,引用计数为,在Channel中维护一个weak_ptr(tie_),将这个shared_ptr对象赋值给tie_,引用计数仍为1.当连接关闭,在handleEvent,将tie_提升,得到一个shared_ptr对象
4.   为什么使用那个weak_ptr呢,因为调用顺序是EventLoop–>Channel–>TcpConnection–>TcpServer,在TcpServer中移除TcpConnection,但此时Channel的handleEvent()还正在调用,直接删除TcpConnection对象会产生core dump错误,所以要让TcpConnection对象生命周期要长于handleEvent()函数

5.  这里要解决高低水位的问题

      (1) 非阻塞网络编程中要注意发送数据的速度高于对方接受数据的速度,会造成数据在本地内存中的堆积,这带来设计及安全性方面的难度。Muduo对此解决办法是提供两个回调,有的网络库把它们称为“高水位回调”和“低水位回调”,Muduo使用HighWaterMarkCallback和WriteCompleCallback这 两个作为高水位回调函数和低水位回调函数.调整发送频率,关注WriteCompleCallback,所有的数据都发送完,WriteCompleCallback回调,然后继续发送.  

      (2)WriteCompleCallback,如果发送缓存区被清空,就调用它。TcpConnection有两处可能触发此回调。TcpConnection::sendInLoop()。TcpConnection::handleWrite()。

      (3)HighWaterMarkCallback,如果输出缓冲的长度超过用户指定大小,就会触发回调(只在上升沿触发一次)。在非阻塞的发送数据情况下, 假设Server发给Client数据流,为防止Server发过来的数据撑爆Client的输出缓冲区

6. TcpConnection关于写事件何时关注,何时取消问题? 

用户send发送数据,直接write系统调用即可。根据返回写了多少计算出剩余量。剩余量存入输出缓冲区并关注写事件。

在写事件发送后,调用写回调函数,继续write输出缓冲区里面的数据。知道输出缓冲区大小为0,取消关注写事件。

因此,写事件只在内部使用,用户看不到。

成员变量

重点关注:

1. loop :具体管理此tcpconnection的EventLoop,由IO线程池分配;

2. socket :客户端连接socket的封装

3. channel:与eventLoop进行事件交互。

4. 五个回调函数

5.highWaterMark_ : 高水位标记,通知hightWater回调使用。

6. inputBuffer和outputBuffer, 输入输出缓冲区。

7. context: 任意类型上下文,可做心跳时间戳使用。

8. state_: 连接状态,在tcp的各个环节切换。

  EventLoop* loop_;
  const string name_;  //连接名字
  StateE state_;  // FIXME: use atomic variable  //当前状态,值为枚举类型中的几个选项
  bool reading_;  //判断通道是否可读
  // we don't expose those classes to client.
  std::unique_ptr<Socket> socket_; //客户端的连接socket_
  std::unique_ptr<Channel> channel_;  //该连接所属的通道
  const InetAddress localAddr_;   //本地IP地址
  const InetAddress peerAddr_;    //连接IP地址

  ConnectionCallback connectionCallback_;
  MessageCallback messageCallback_;
  WriteCompleteCallback writeCompleteCallback_;
  HighWaterMarkCallback highWaterMarkCallback_;
  CloseCallback closeCallback_;

  size_t highWaterMark_;  //高水位标记
  Buffer inputBuffer_;   //应用层接受缓冲区
  Buffer outputBuffer_;  // FIXME: use list<Buffer> as output buffer.  //应用层发送缓冲区
  boost::any context_;   //任意类型,一个未知类型的上下文对象
  // FIXME: creationTime_, lastReceiveTime_
  //        bytesReceived_, bytesSent_

方法

1. TcpConnection:构造函数,初始化变量,给自己的Channel中事件注册回调。

TcpConnection::TcpConnection(EventLoop *loop,
                             const string &nameArg,
                             int sockfd,
                             const InetAddress &localAddr,
                             const InetAddress &peerAddr)
    : loop_(CHECK_NOTNULL(loop)),
      name_(nameArg),
      state_(kConnecting),
      reading_(true),
      socket_(new Socket(sockfd)),
      channel_(new Channel(loop, sockfd)),
      localAddr_(localAddr),
      peerAddr_(peerAddr),
      highWaterMark_(64 * 1024 * 1024)
{
  channel_->setReadCallback(
      std::bind(&TcpConnection::handleRead, this, _1));
  channel_->setWriteCallback(
      std::bind(&TcpConnection::handleWrite, this));
  channel_->setCloseCallback(
      std::bind(&TcpConnection::handleClose, this));
  channel_->setErrorCallback(
      std::bind(&TcpConnection::handleError, this));
  LOG_DEBUG << "TcpConnection::ctor[" << name_ << "] at " << this
            << " fd=" << sockfd;
  socket_->setKeepAlive(true);
}

2.handleRead:读回调,使用输入缓冲区进行读。读完调用TcpServer注册的函数,此处可以看出这里触发onMessage是消息到达而不绝对是一个完整数据包到达(分两个包到达,触发两次)

void TcpConnection::handleRead(Timestamp receiveTime)
{
  loop_->assertInLoopThread();
  int savedErrno = 0;
  ssize_t n = inputBuffer_.readFd(channel_->fd(), &savedErrno);
  if (n > 0)
  {
    messageCallback_(shared_from_this(), &inputBuffer_, receiveTime);
  }
  else if (n == 0)
  {
    handleClose();
  }
  else
  {
    errno = savedErrno;
    LOG_SYSERR << "TcpConnection::handleRead";
    handleError();
  }
}

3. handleWrite:写回调。先判断写事件是否关注,关注了说明此时输出缓冲区有数据未发送。发送数据即可,如果输出缓冲区发完了,取消关注写事件。

void TcpConnection::handleWrite()
{
  loop_->assertInLoopThread();
  if (channel_->isWriting())
  {
    //此函数说明Tcp缓冲区有空余, 将outputBuffer写入tcp缓冲区中, 若outputbuffer没有数据了,则停止监听写事件。
    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";
  }
}

4. handleClose():关闭连接的回调, 读事件返回值=0就代表对方关闭了连接(在读事件中调用)。

设置状态,取消所有关注事件,调用connectionCallback通知用户的断开连接消息(onConnectinn)。最后调用closeCallback,在TcpServer的TcpConnection容器删除此连接并调用此连接的connectDestroyed()删除channel。

//关闭连接的回调,设置状态,关闭通道,调用用户设置的回调
void TcpConnection::handleClose()
{
  loop_->assertInLoopThread();
  LOG_TRACE << "fd = " << channel_->fd() << " state = " << stateToString();
  assert(state_ == kConnected || state_ == kDisconnecting);
  // we don't close fd, leave it to dtor, so we can find leaks easily.
  setState(kDisconnected);
  channel_->disableAll();

  TcpConnectionPtr guardThis(shared_from_this());
  connectionCallback_(guardThis);
  // must be the last line
  closeCallback_(guardThis);
}
//销毁连接,设置状态,调用用户的回调函数,移除通道
void TcpConnection::connectDestroyed()
{
  loop_->assertInLoopThread();
  if (state_ == kConnected)
  {
    setState(kDisconnected);
    channel_->disableAll();

    connectionCallback_(shared_from_this());
  }
  channel_->remove();
}

上面 2. 3. 4 函数都是关注事件的被动处理函数。下面是TcpServer主动调用的函数。

5. connectEstablished:TcpServer从Acceptor接受到一个新连接调用。

  1. 连接建立时调用一次,设置状态state_,在channel_中使用tie()把当前对象赋值为一个weak_ptr,这样是为了让TcpConnection的生存周期比Channel长一点
  2. 设置通道监听,调用连接回调函数,这个是用户指定的回调函数
/*
1. 连接建立时调用一次,设置状态state_,在channel_中使用tie()把当前对象赋值为一个weak_ptr,这样是为了让TcpConnection的生存周期比Channel长一点
2. 设置通道监听,调用连接回调函数,这个是用户指定的回调函数
*/
void TcpConnection::connectEstablished()
{
  loop_->assertInLoopThread();
  assert(state_ == kConnecting);
  setState(kConnected);
  channel_->tie(shared_from_this()); //将此通道绑定到shared_ptr管理的所有者对象,防止在handleEvent中销毁所有者对象。
  channel_->enableReading();
  connectionCallback_(shared_from_this());
}

 6. send:最后调用的都是sendInLoop.

   1. 直接write数据,统计剩余未写入内核缓冲区的字节数

  2.  若有则放入输出缓冲区。若没有直接调用低水位回调,即写完成事件回调。

  3. 若此时是放入输出缓冲区了,需要判断和旧数据的总和是否超过了高水位。高出了调用高水位回调。

 4. 关注写事件开始

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
  /*
    1.outputBuffer_为空,直接向socket写数据。
    2.如果向 socket 写数据没有写完,则统计剩余的字节个数remaining,没有写完可能是因为此时 socket 的 TCP 缓冲区已满了。
    3.如果此时 OutputBuffer 中的旧数据的个数和未写完字节个数之和大于 highWaterMark,则将 highWaterMarkCallback 放入待执行队列中
    4.将对应 socket 的可写事件注册到 EventLoop 中。

    注意:直到发送消息的时候,muduo 才会把 socket 的可写事件注册到了 EventLoop 中。在此之前只注册了可读事件。
    连接 socket 的可写事件对应的 callback 是 TcpConnection::handleWrite()
    当某个 socket 的可写事件触发时,TcpConnection::handleWrite 会做两个工作:
        1 尽可能将数据从 OutputBuffer 中向 socket 中 write 数据
        2 如果 OutputBuffer 没有剩余的,则 将该 socket 的可写事件移除,并调用 writeCompleteCallback
      */
  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;
        }
      }
    }
  }

6. shutdown():关闭一个连接的写端,只有在缓冲区写完的状态下才关闭写端。

 forceClose:强制关闭一个连接,不管写没写完。实际调用的就是handleClose.

//把shutdownInLoop()加入到事件循环中
//设置状态,如果还有数据没写,shutdownInLoop()什么事都没做,以后服务端要根据状态再次调用shutdownInLoop()
void TcpConnection::shutdown()
{
  // FIXME: use compare and swap
  if (state_ == kConnected)
  {
    setState(kDisconnecting);
    // FIXME: shared_from_this()?
    loop_->runInLoop(std::bind(&TcpConnection::shutdownInLoop, this));
  }
}

/*
   1. 调用socket_->shutdownWrite();如果还有数据没发送完,就不能关闭.isWriting()是看是否有POLLOUT
   2. 应用程序想关闭连接,但有可能正处于发送数据的过程中,output buffer 中有数据还没发完,不能直接调用close();
*/
void TcpConnection::shutdownInLoop()
{
  loop_->assertInLoopThread();
  if (!channel_->isWriting())
  {
    // we are not writing
    socket_->shutdownWrite();
  }
}
//调用handleClose()
void TcpConnection::forceClose()
{
  // FIXME: use compare and swap
  if (state_ == kConnected || state_ == kDisconnecting)
  {
    setState(kDisconnecting);
    loop_->queueInLoop(std::bind(&TcpConnection::forceCloseInLoop, shared_from_this()));
  }
}

void TcpConnection::forceCloseWithDelay(double seconds)
{
  if (state_ == kConnected || state_ == kDisconnecting)
  {
    setState(kDisconnecting);
    loop_->runAfter(
        seconds,
        makeWeakCallback(shared_from_this(),
                         &TcpConnection::forceClose)); // not forceCloseInLoop to avoid race condition
  }
}

void TcpConnection::forceCloseInLoop()
{
  loop_->assertInLoopThread();
  if (state_ == kConnected || state_ == kDisconnecting)
  {
    // as if we received 0 byte in handleRead();
    handleClose();
  }
}

7. setTcpNoDelay:关闭tcp的nagle算法,防止多个消息再一个tcp报文段发送过来。

void TcpConnection::setTcpNoDelay(bool on)
{
  socket_->setTcpNoDelay(on);
}

8. context_的几个操作:Tcpconnection的上下文,可用心跳检测,将上次的时间保存到这里。也可以使用更复杂的结构解决更复杂的问题。 反正他是any类型的。

  void setContext(const boost::any& context)
  { context_ = context; }

  const boost::any& getContext() const
  { return context_; }

  boost::any* getMutableContext()
  { return &context_; }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

YanWenCheng_

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

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

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

打赏作者

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

抵扣说明:

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

余额充值