Design and implementation of muduo library learning 07-TcpConnection disconnect

Dongyang's study notes

Muduo has only one way to close the connection: passive closing (p. 191).

  • That is: the other party closes the connection first, the local read(2) returns 0, and the close logic is triggered.

If necessary, you can also add forceClose() member function to TcpConnection to actively close the connection. The implementation is very simple, just call handleClose().

The logic of function call is as follows:

  • Generally speaking, deleting data is more troublesome than creating a new one, and TCP connection is no exception. The process of closing the connection seems a bit "windy" because of the need for object lifecycle management (p. 274)
  • Where X means that TcpConnection will usually be destructed here
    Insert picture description here

1. Channel changes

The Channel class adds the CloseCallback event callback, and asserts 在事件处理期间本 Channel 不会析构that the error situation described in p. 274 will not occur.

  • POLLHUB: When the other end of the socket is closed or when the end of the file is read.
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;
}

Two, TcpConnection changes

The TcpConnection class also adds a CloseCallback event callback, but:

  • This callback is for TcpServer and TcpClient 通知他们移除所持有的 TcpConnectionPtr. It is not for ordinary users. Ordinary users continue to use 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()Will check read(2) 的返回值and call messageCallback_, handleClose(), and handleError() respectively according to the return value.
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()The main function is 调用 closeCallback_that this callback is bound toTcpConnection::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() did not take any further action , just output an error message in the log,这并不影响连接的正常关闭。

 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() is TcpConnection 析构前最后调用的一个成员函数:

  1. It informs the user that the connection has been disconnected. The &6 is the same as the &7 in handleClose, because in some cases it can be called directly without going through handleClose() connectDestoryed().
void TcpConnection::connectDestroyed()
{
     
     
 loop_->assertInLoopThread();
 assert(state_ == kConnected);
 setState(kDisconnected);
 channel_->disableAll();
 connectionCallback_(shared_from_this());

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

Three, TcpServer changes

TcpServer registers CloseCallback with TcpConnection to receive disconnected messages.

  • Connect to save std::map<std::string, TcpConnectionPtr> ConnectionMapthe
  • Register the corresponding callback function
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

Usually the lifetime of TcpServer is longer than the TcpConnection it establishes 因此不用担心 TcpServer 对象失效(so it is useless here shared_from_this). In muduo, the destructor of TcpServer closes the connection, so it is also safe.

  • TcpServer :: removeConnection () from the conn ConnectionMapremoved in. At this time, TcpConnection has been " 奄奄一息" .

Note that queueInLoop() must be used here, otherwise there will be problems with object lifetime management.

  • Use bind here to make the life of TcpConnection long to call 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));
}

Think and verify: If the user does not hold the TcpConnectionPtr, when exactly is the TcpConnection object destructed? ?

Four, EventLoop and Poller changes

The TcpConnection in this section is no longer just alive, so EventLoop is required to also provide the unregister function. EventLoop adds the removeChannel() member function, which will call Poller::removeChannel():

  • From EventLoop managed Poller managed std::vector<struct pollfd>to remove the
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();
 }
}

Note that deleting an element from the array pollfds_ is O(1) complexity. The way is to exchange the element to be deleted with the last element, and then pollfds_.pop_back(). This requires the code of Poller::updateChannel to be modified accordingly:

    // 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;
    }
  }
}

Guess you like

Origin blog.csdn.net/qq_22473333/article/details/113726109