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
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 checkread(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
析构前最后调用的一个成员函数
:
- 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> ConnectionMap
the - 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 hereshared_from_this
). In muduo, the destructor of TcpServer closes the connection, so it is also safe.
- TcpServer :: removeConnection () from the conn
ConnectionMap
removed 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 thevoid 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;
}
}
}