muduo库学习之设计与实现07——TcpConnection 断开连接

东阳的学习笔记

muduo 只有一种关闭连接的方式:被动关闭(p. 191)。

  • 即:对方先关闭连接,本地 read(2) 返回0,触发关闭逻辑。

如果有必要也可给 TcpConnection 新增 forceClose() 成员函数,用于主动关闭连接,实现很简单,调用 handleClose() 即可。

函数调用的逻辑如下图:

  • 一般来说数据的删除比新建要麻烦,TCP 连接也不例外。关闭连接的流程看上去有点 “绕”,原因是对象生命期管理的需要(p. 274)
  • 其中 X 表示 TcpConnection 通常会在此析构
    在这里插入图片描述

一、Channel 的改动

Channel class 新增了 CloseCallback 事件回调,并且断言在事件处理期间本 Channel 不会析构,即不会发生 p. 274 讲的出错情况。

  • POLLHUB:当 socket 的另一端关闭时或读到文件结尾时。
Channel::~Channel()
{
     
     
 // 断言在事件处理期间本channel对象不会析构
 assert(!eventHandling_);
}

void Channel::handleEvent()
{
     
     
+eventHandling_ = true;
 if (revents_ & POLLNVAL) {
     
     
   LOG_WARN << "Channel::handle_event() POLLNVAL";
 }

+if ((revents_ & POLLHUP) && !(revents_ & POLLIN)) {
     
         // 添加了 close 事件
+   LOG_WARN << "Channel::handle_event() POLLHUP";
+   if (closeCallback_) closeCallback_();
+ }
 if (revents_ & (POLLERR | POLLNVAL)) {
     
     
   if (errorCallback_) errorCallback_();
 }
 if (revents_ & (POLLIN | POLLPRI | POLLRDHUP)) {
     
     
   if (readCallback_) readCallback_();
 }
 if (revents_ & POLLOUT) {
     
     
   if (writeCallback_) writeCallback_();
 }
+eventHandling_ = false;
}

二、TcpConnection 的改动

TcpConnection class 也新增了 CloseCallback 事件回调,但:

  • 这个回调是给 TcpServer 和 TcpClient 用的,用于通知他们移除所持有的 TcpConnectionPtr,这不是给普通用户用的,普通用户继续使用 ConnectionCallback。
+   void setCloseCallback(const CloseCallback& cb)
+   {
     
      closeCallback_ = cb; }
  
    // called when TcpServer accepts a new connection
    void connectEstablished();   // should be called only once
+   // called when TcpServer has removed me from its map
+   void connectDestroyed();  // should be called only once
  
   private:
!   enum StateE {
     
      kConnecting, kConnected, kDisconnected, };
  
    void setState(StateE s) {
     
      state_ = s; }
    void handleRead();
+   void handleWrite();
+   void handleClose();
+   void handleError();

2.1 TcpConnection::handleRead()

  • TcpConnection::handleRead() 会检查 read(2) 的返回值,根据返回值分别调用 messageCallback_、handleClose()、handleError()。
void TcpConnection::handleRead()
{
     
     
  char buf[65536];
  ssize_t n = ::read(channel_->fd(), buf, sizeof buf);
+  if (n > 0) {
     
     
    messageCallback_(shared_from_this(), buf, n);
+  } else if (n == 0) {
     
     
+    handleClose();
+  } else {
     
     
+    handleError();
+  }
}

2.2 TcpConnection::handleClose()

TcpConnection::handleClose() 的主要功能是调用 closeCallback_,这个回调绑定到TcpConnection::removeConnection()

void TcpConnection::handleClose()
{
     
     
 loop_->assertInLoopThread();
 LOG_TRACE << "TcpConnection::handleClose state = " << state_;
 assert(state_ == kConnected);
 // we don't close fd, leave it to dtor, so we can find leaks easily.
 channel_->disableAll();
 // must be the last line
 closeCallback_(shared_from_this());   // 这个回调绑定到`TcpConnection::removeConnection()`
}

2.3 TcpConnection::handleError()

TcpConnection::handleError() 并没有采取进一步的行动,只是在日志中输出错误消息,这并不影响连接的正常关闭。

 void TcpConnection::handleError()
{
     
     
 int err = sockets::getSocketError(channel_->fd());
 LOG_ERROR << "TcpConnection::handleError [" << name_
           << "] - SO_ERROR = " << err << " " << strerror_tl(err);
}

2.4 TcpConnection::connectDestoryed()

TcpConnection::connectDestoryed() 是 TcpConnection 析构前最后调用的一个成员函数

  1. 它通知用户连接已断开。其中的 &6 与 handleClose 中的 &7 重复,这是因为在某些情况下可以不经由 handleClose() ,而直接调用connectDestoryed()
void TcpConnection::connectDestroyed()
{
     
     
 loop_->assertInLoopThread();
 assert(state_ == kConnected);
 setState(kDisconnected);
 channel_->disableAll();
 connectionCallback_(shared_from_this());

 loop_->removeChannel(get_pointer(channel_));
}

三、TcpServer的改动

TcpServer 向 TcpConnection 注册 CloseCallback,用于接收连接断开的消息。

  • 将连接保存到std::map<std::string, TcpConnectionPtr> ConnectionMap
  • 注册相应的回调函数
void TcpServer::newConnection(int sockfd, const InetAddress& peerAddr)
{
    
    
  // 省略没有变化的代码
  TcpConnectionPtr conn(
      new TcpConnection(loop_, connName, sockfd, localAddr, peerAddr));
  connections_[connName] = conn;   // 将连接保存在Map中
  conn->setConnectionCallback(connectionCallback_);
  conn->setMessageCallback(messageCallback_);
  conn->setCloseCallback(
      boost::bind(&TcpServer::removeConnection, this, _1));
  conn->connectEstablished();
}

3.1 TcpServer::removeConnection

通常 TcpServer 的生命期长于它建立的 TcpConnection,因此不用担心 TcpServer 对象失效(所以这里没用 shared_from_this)。在 muduo 中,TcpServer 的析构函数会关闭连接,因此也是安全的。

  • TcpServer::removeConnection() 把 conn 从 ConnectionMap 中移除。这时 TcpConnection 已经 “奄奄一息

注意这里一定要用 queueInLoop() ,否则就会出现对象生命期管理的问题。

  • 这里使用 bind, 让 TcpConnection 的生命期长到调用 connectDestroyed
void TcpServer::removeConnection(const TcpConnectionPtr& conn)
{
     
     
 loop_->assertInLoopThread();
 LOG_INFO << "TcpServer::removeConnection [" << name_
          << "] - connection " << conn->name();
 size_t n = connections_.erase(conn->name());
 assert(n == 1); (void)n;
 loop_->queueInLoop(
     boost::bind(&TcpConnection::connectDestroyed, conn));
}

思考并验证:如果用户不持有 TcpConnectionPtr,那么 TcpConnection 对象究竟是在什么时候析构的??

四、EventLoop 和 Poller的改动

本节的 TcpConnection 不再是只生不灭,因此要求 EventLoop 也提供 unregister 功能。EventLoop 新增了 removeChannel() 成员函数,它会调用 Poller::removeChannel():

  • 从 EventLoop 所管理的 Poller 所管理的 std::vector<struct pollfd> 中移除
void Poller::removeChannel(Channel* channel)
{
     
     
 assertInLoopThread();
 LOG_TRACE << "fd = " << channel->fd();
 assert(channels_.find(channel->fd()) != channels_.end());
 assert(channels_[channel->fd()] == channel);
 assert(channel->isNoneEvent());
 int idx = channel->index();
 assert(0 <= idx && idx < static_cast<int>(pollfds_.size()));
 const struct pollfd& pfd = pollfds_[idx]; (void)pfd;
 assert(pfd.fd == -channel->fd()-1 && pfd.events == channel->events());
 size_t n = channels_.erase(channel->fd());
 assert(n == 1); (void)n;
 if (implicit_cast<size_t>(idx) == pollfds_.size()-1) {
     
     
   pollfds_.pop_back();
 } else {
     
     
   int channelAtEnd = pollfds_.back().fd;
   iter_swap(pollfds_.begin()+idx, pollfds_.end()-1);
   if (channelAtEnd < 0) {
     
     
     channelAtEnd = -channelAtEnd-1;
   }
   channels_[channelAtEnd]->set_index(idx);
   pollfds_.pop_back();
 }
}

注意其中从数组 pollfds_ 中删除元素是 O(1) 复杂度,办法是将待删除的元素与最后一个元素交换,再 pollfds_.pop_back()。这需要相应地修改 Poller::updateChannel 的代码:

    // update existing one
    assert(channels_.find(channel->fd()) != channels_.end());
    assert(channels_[channel->fd()] == channel);
    int idx = channel->index();
    assert(0 <= idx && idx < static_cast<int>(pollfds_.size()));
    struct pollfd& pfd = pollfds_[idx];
    assert(pfd.fd == channel->fd() || pfd.fd == -channel->fd()-1);
    pfd.events = static_cast<short>(channel->events());
    pfd.revents = 0;
    if (channel->isNoneEvent()) {
    
    
      // ignore this pollfd
      pfd.fd = -channel->fd()-1;
    }
  }
}

猜你喜欢

转载自blog.csdn.net/qq_22473333/article/details/113726109