東陽の研究ノート
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
析构前最后调用的一个成员函数
です:
- 接続が切断されたことをユーザーに通知します。&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;
}
}
}