muduoライブラリ学習の設計と実装07-TcpConnection切断

東陽の研究ノート

Muduoには、接続を閉じる唯一の方法があります。パッシブクローズ(p.191)です。

  • つまり、相手が最初に接続を閉じ、ローカルのread(2)が0を返し、閉じるロジックがトリガーされます。

必要に応じて、forceClose()メンバー関数をTcpConnectionに追加して、接続をアクティブに閉じることもできます。実装は非常に簡単で、handleClose()を呼び出すだけです。

関数呼び出しのロジックは次のとおりです。

  • 一般的に、データの削除は新しいデータの作成よりも面倒であり、TCP接続も例外ではありません。オブジェクトのライフサイクル管理(p。274)が必要なため、接続を閉じるプロセスは少し「風が強い」ようです。
  • ここで、Xは、TcpConnectionが通常ここで破棄されることを意味します
    ここに画像の説明を挿入します

1.チャンネルの変更

Channelクラスは、CloseCallbackイベントコールバックを追加し、p。274で在事件处理期间本 Channel 不会析构説明されているエラー状況が発生しないことを表明します。

  • POLLHUB:ソケットのもう一方の端が閉じているとき、またはファイルの端が読み取られたとき。
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;
}

2、TcpConnectionの変更

TcpConnectionクラスは、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()を経由せずに直接呼び出すことができる場合があるため、handleCloseの&7と同じconnectDestoryed()です。
void TcpConnection::connectDestroyed()
{
     
     
 loop_->assertInLoopThread();
 assert(state_ == kConnected);
 setState(kDisconnected);
 channel_->disableAll();
 connectionCallback_(shared_from_this());

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

3、TcpServerの変更

TcpServerはCloseCallbackをTcpConnectionに登録して、切断されたメッセージを受信します。

  • 接続して保存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 ::でConnectionMap削除された接続からremoveConnection()現時点では、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オブジェクトはいつ破棄されますか?

4、EventLoopとPollerの変更

このセクションのTcpConnectionはもはや有効ではないため、登録解除機能も提供するためにEventLoopが必要です。EventLoopは、Poller :: removeChannel()を呼び出すremoveChannel()メンバー関数を追加します。

  • EventLoopから管理されたポーラーはなんとか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)の複雑さであることに注意してください。方法は、削除する要素を最後の要素と交換してから、polfds_.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