muduo源码剖析-TcpServer连接关闭

Server关闭连接的情况有两种,一种是被动关闭,即对方先关闭连接,本地read(2)返回零,触发关闭逻辑。另一种是主动断开连接,即调用TcpConnection::forceClose()函数。

1 被动关闭

时序图如下
在这里插入图片描述
对方关闭时,read(2)会收到0字节,因此触发关闭逻辑。首先loop事件循环中channel触发handleEvent,然后调用readCallback_(),readCallback_在TcpConnection的构造函数中已绑定在TTcpConnection::handleRead上,如下:


void Channel::handleEvent(Timestamp receiveTime)
{
  std::shared_ptr<void> guard;
  if (tied_)
  {
    guard = tie_.lock();
    if (guard)
    {
      handleEventWithGuard(receiveTime);
    }
  }
  else
  {
    handleEventWithGuard(receiveTime);
  }
}

void Channel::handleEventWithGuard(Timestamp receiveTime)
{
  eventHandling_ = true;
  LOG_TRACE << reventsToString();
  if ((revents_ & POLLHUP) && !(revents_ & POLLIN))
  {
    if (logHup_)
    {
      LOG_WARN << "fd = " << fd_ << " Channel::handle_event() POLLHUP";
    }
    if (closeCallback_) closeCallback_();
  }

  if (revents_ & POLLNVAL)
  {
    LOG_WARN << "fd = " << fd_ << " Channel::handle_event() POLLNVAL";
  }

  if (revents_ & (POLLERR | POLLNVAL))
  {
    if (errorCallback_) errorCallback_();
  }
  if (revents_ & (POLLIN | POLLPRI | POLLRDHUP))
  {
    if (readCallback_) readCallback_(receiveTime);
  }
  if (revents_ & POLLOUT)
  {
    if (writeCallback_) writeCallback_();
  }
  eventHandling_ = false;
}
//TcpConnection的构造函数
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)
{
    //通道可读事件到来的时候,回调TcpConnection::handleRead,_1是事件发生的时间
  channel_->setReadCallback(
      std::bind(&TcpConnection::handleRead, this, _1));
  //通道可写事件到来的时候,回调cpConnection::handleWrite
  channel_->setWriteCallback(
      std::bind(&TcpConnection::handleWrite, this));
  //连接关闭,回调TcpConnection::handleClose
  channel_->setCloseCallback(
      std::bind(&TcpConnection::handleClose, this));
  //发生错误,回调TcpConnection::handleError
  channel_->setErrorCallback(
      std::bind(&TcpConnection::handleError, this));
  LOG_DEBUG << "TcpConnection::ctor[" <<  name_ << "] at " << this
            << " fd=" << sockfd;
  socket_->setKeepAlive(true);
}
void TcpConnection::handleRead(Timestamp receiveTime)
{
  loop_->assertInLoopThread();
  int savedErrno = 0;
  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();
  }
}

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(); //取消对channel的关注,并更新poller
  TcpConnectionPtr guardThis(shared_from_this());
  connectionCallback_(guardThis);
  // must be the last line
  closeCallback_(guardThis); //调用TcpServer::removeConnection()
}

由于读取到了0字节,因此inputBuffer_.readFd函数返回0,回调TcpConnection::handleClose,TcpConnection::handleClose()会取消channel的关注,并且调用调用connectionCallback_和closeCallback_,closeCallback_这个回调绑定到TcpServer::removeConnection().这个绑定发生在TcpConnection对象建立的时候,如下:

void TcpServer::newConnection(int sockfd, const InetAddress& peerAddr)
{
  loop_->assertInLoopThread();
  EventLoop* ioLoop = threadPool_->getNextLoop();
  char buf[64];
  snprintf(buf, sizeof buf, "-%s#%d", ipPort_.c_str(), nextConnId_);
  ++nextConnId_;
  string connName = name_ + buf;

  LOG_INFO << "TcpServer::newConnection [" << name_
           << "] - new connection [" << connName
           << "] from " << peerAddr.toIpPort();
  InetAddress localAddr(sockets::getLocalAddr(sockfd));
  // FIXME poll with zero timeout to double confirm the new connection
  // FIXME use make_shared if necessary
  TcpConnectionPtr conn(new TcpConnection(ioLoop,
                                          connName,
                                          sockfd,//客户端socket文件描述符
                                          localAddr,
                                          peerAddr));
  connections_[connName] = conn;
  conn->setConnectionCallback(connectionCallback_);
  conn->setMessageCallback(messageCallback_);
  conn->setWriteCompleteCallback(writeCompleteCallback_);
  //设置CloseCallback
  conn->setCloseCallback(
      std::bind(&TcpServer::removeConnection, this, _1)); // FIXME: unsafe
  ioLoop->runInLoop(std::bind(&TcpConnection::connectEstablished, conn));
}

在TcpServer::removeConnection()会把conn从ConnectionMap(TcpServer的数据成员,记录连接列表,即TcpConnection(share_ptr形式)和其名称)中移除,并将TcpConnection::connectionDestroyed()函数加入pendingFunctors_,在事件循环时调用TcpConnection::connectionDestroyed()。

void TcpServer::removeConnection(const TcpConnectionPtr& conn)
{
  // FIXME: unsafe
  loop_->runInLoop(std::bind(&TcpServer::removeConnectionInLoop, this, conn));
}

void TcpServer::removeConnectionInLoop(const TcpConnectionPtr& conn)
{
  loop_->assertInLoopThread();
  LOG_INFO << "TcpServer::removeConnectionInLoop [" << name_
           << "] - connection " << conn->name();
  size_t n = connections_.erase(conn->name());//返回key为conn->name()的value的个数
  (void)n;
  assert(n == 1);
  EventLoop* ioLoop = conn->getLoop();
  ioLoop->queueInLoop(
      std::bind(&TcpConnection::connectDestroyed, conn));
}
void TcpConnection::connectDestroyed()
{
  loop_->assertInLoopThread();
  //在TCphandleClose中已经设置了state_和使channel取消关注事件,因此下面if语句正常情况条件为假
  if (state_ == kConnected)
  {
    setState(kDisconnected);
    channel_->disableAll();

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

在TcpConnection::connectDestroyed()中,会移除channel,下面附上channel的移除逻辑

void Channel::remove()
{
  assert(isNoneEvent()); //移除前需要使channel取消关注事件
  addedToLoop_ = false;
  loop_->removeChannel(this);
}
void EventLoop::removeChannel(Channel* channel)
{
  assert(channel->ownerLoop() == this);
  assertInLoopThread();
  if (eventHandling_)
  {
    assert(currentActiveChannel_ == channel ||
        std::find(activeChannels_.begin(), activeChannels_.end(), channel) == activeChannels_.end());
  }
  poller_->removeChannel(channel);
}
void EPollPoller::removeChannel(Channel* channel)
{
  Poller::assertInLoopThread();
  int fd = channel->fd();
  LOG_TRACE << "fd = " << fd;
  assert(channels_.find(fd) != channels_.end());
  assert(channels_[fd] == channel);
  assert(channel->isNoneEvent());
  int index = channel->index();
  assert(index == kAdded || index == kDeleted);
  size_t n = channels_.erase(fd);
  (void)n;
  assert(n == 1);

  if (index == kAdded)
  {
    update(EPOLL_CTL_DEL, channel);
  }
  channel->set_index(kNew);
}

2 主动关闭

(1)调用TcpConnection::forceClose()函数。

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();
  }

(2)主动断开连接之半关闭
用户可以在相关回调函数(messageCallback_,writeCompleteCallback_,connectionCallback_)中调用TcpConnecion::shutdown函数来实现半关闭。函数如下:

void TcpConnection::shutdown()
{
  // FIXME: use compare and swap
  if (state_ == kConnected)
  {
    setState(kDisconnecting);
    // FIXME: shared_from_this()?
    loop_->runInLoop(std::bind(&TcpConnection::shutdownInLoop, this));
  }
}

void TcpConnection::shutdownInLoop()
{
  loop_->assertInLoopThread();
  if (!channel_->isWriting()) //当channel不关注可写事件时才能关闭连接
  {
    // we are not writing
    socket_->shutdownWrite(); //关闭写端
  }
}

shutdown为半关闭状态,即关闭写端,但仍然能接收到对端发送来的数据。shutdown()函数中将连接的状态设置为了state_ = kdisConnecting,然后将shutdownInLoop加入事件循环中去处理。
观察shutdownInLoop()我们会发现存在一些情况导致该函数无法执行socket_->shutdownWrite()。

  1. 当outputBuffer_中的数据没有发送完成时,此时仍关注着写事件,因此导致channel_->isWriting()条件为真,无法执行socket_->shutdownWrite()。
    别急,由于此时仍关注着POLLOUT事件,因此在事件循环中会触发handleWrite来发送数据。我们不妨先看看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. 在handleWrite函数中我们会发现,当缓冲区中有数据时,会将数据发送完毕,当数据发送完毕时,由于在上面的shutdown函数中我们已经设置连接状态为kdisConnecting,因此在handleWrite函数中会调用shutdownInLoop()来确保完成半关闭。
  2. 此外,思考一下完成半关闭后如何最后完成关闭呢?半关闭时我们会发送TCP FIN分节,对方会收到0字节,然后对方通常会关闭连接。这样我们会读到0字节,触发handleRead函数,读到0字节时触发handleClose()函数完成连接的关闭。
void TcpConnection::handleRead(Timestamp receiveTime)
{
  loop_->assertInLoopThread();
  int savedErrno = 0;
  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();
  }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值