关闭时序¶

TcpServer¶
当客户端关闭连接时:
面对该连接的TcpConnection对象 Conn就会触发channel_ 的可读事件,进行调用handleEvent()函数处理。
void Channel::handleEvent(Timestamp receiveTime) {
std::shared_ptr<void> guard;
if (tied_) {
guard = tie_.lock();
if (guard) {
LOG_TRACE << "[6] usecount=" << guard.use_count();
handleEventWithGuard(receiveTime);
LOG_TRACE << "[12] usecount=" << guard.use_count();
}
}
else {
handleEventWithGuard(receiveTime);
}
}
整个客户端关闭,从这里开始,最后也会回到这里。handleEventWithGuard 函数中调用了readCallback_回调函数。在Conn的创建时,传入的回调函数是:TcpConnection::handleRead:
void TcpConnection::handleRead(Timestamp receiveTime) {
loop_->assertInLoopThread();
int savedErrno = 0;
ssize_t n = inputBuffer_.readFd(channel_->fd(), &savedErrno);
if (n > 0) {
messageCallback_(shared_from_this(), &inputBuffer_, receiveTime);
}
else if (n == 0) {
handleClose();
}
else {
errno = savedErrno;
LOG_SYSERR << "TcpConnection::handleRead";
handleError();
}
}
handleClose():
void TcpConnection::handleClose() {
loop_->assertInLoopThread();
LOG_TRACE << "fd = " << channel_->fd() << " state = " << stateToString();
assert(state_ == kConnected || state_ == kDisconnecting);
setState(kDisconnected);
channel_->disableAll();
TcpConnectionPtr guardThis(shared_from_this()); // 引用计数加 1
connectionCallback_(guardThis);
closeCallback_(guardThis); // 引用计数减少1
}
handleClose()函数中,
+ 先调用connectionCallback_(guardThis);
+ 再调用closeCallback_(guardThis);
这两个回调函数都是由TcpServer在创建TcpConnection对象时传入,不同的是connectionCallback_(guardThis); 这个函数是由外部传入的,即由TcpServer的使用者自定义后传入,而closeCallback_(guardThis);是由TcpServer内部函数:
void TcpServer::newConnection(int sockfd, const InetAddress& peerAddr) {
...
TcpConnectionPtr conn(new TcpConnection(ioLoop, connName, sockfd,
localAddr, peerAddr));
connections_[connName] = conn;
conn->setConnectionCallback(connectionCallback_);
conn->setMessageCallback(messageCallback_);
conn->setWriteCompleteCallback(writeCompleteCallback_);
conn->setCloseCallback(std::bind(&TcpServer::removeConnection, this, _1));
onn->connectEstablished();
...
}
closeCallback_(guardThis)时,即执行:
void TcpServer::removeConnection(const TcpConnectionPtr& conn) {
loop_->assertInLoopThread();
LOG_INFO << "TcpServer::removeConnectionInLoop [" << name_
<< "] - connection " << conn->name();
size_t n = connections_.erase(conn->name());
(void)n;
assert(n == 1);
loop_->queueInLoop(
boost::bind(&TcpConnection::connectDestroyed, conn));
}
TcpConnection::connectDestroyed(),是因为要消除连接,那么就需要注销所有的通道,等这个函数也执行完,就会回到handleEvent()。对应的连接就会关闭了。
conn 的生命周期¶
TcpConnection对象是由shared_ptr管理,因此当最后一个std::shared_ptr销毁时,那么对应的这个连接对象conn才会结束其生命周期。可以通过追踪shared_ptr的引用计数来追踪生命周期。
+ shared_ptr引用计数增加的方式
+ 通过构造函数初始的引用计数总是1
+ 通过赋值/复制操作,增加引用计数
class Foo {/** ... */};
void fun1(std::shared_ptr<Foo>) {/** ...*/ }
void fun2(std::shared_ptr<Foo>& ) {/** ...*/ }
int main() {
Foo* foo = new Foo;
std::shared_ptr<Foo> sp1(foo);
std::shared_ptr<Foo> sp2(foo);
std::shared_ptr<Foo> sp3 = sp1;
}
=完成,就和sp1共享引用,引用计数为2,sp2的引用计数为1。
同理,`fun1`会增加引用计数,而`fun2`不会增加。
conn的生命周期
由于conn是通过share_ptr管理,那么它的生命周期也可通过引用计数来追踪。- 连接建立:
- 建立
conn时,引用计数为1 connections_[connName] = conn;引用计数加1conn->connectEstablished();执行函数时,内部会使得引用计数变为3,执行完恢复2.
cpp shared_from_this() // 创建的是一个临时std::shared_ptr对象TcpServer::newConnection执行完毕,创建的局部变量conn会被销毁,只是剩下一个connections_中的指向conn的shared_ptr对象。
因此,连接建立完成,引用计数为1。
- 建立
- 连接关闭
要想关闭这个连接,那么其引用计数最后必须是0,那么怎么变成0的?Channel::handleEvent中,guard = tie_.lock();使得引用计数增加为2.TcpConnectionPtr guardThis(shared_from_this());创建的guardThis使得引用计数增加为3,TcpServer::removeConnection(const TcpConnectionPtr& conn)运行结束引用计数为3.- 传入的是引用并不增加计数
-
connections_.erase(conn->name())会导致connections_中的那个shared_ptr销毁,减少1 -
loop_->queueInLoop(boost::bind(&TcpConnection::connectDestroyed, conn));,conn复制传入,增加1。
handleClose()运行结束,guardThis是局部变量,会销毁,因此引用计数减少1,变成2.handleEvent()运行结束,guard也是局部变量,也会销毁,因此引用计数变成1。- 在
TcpConnection::connectDestroyed()中:cpp void TcpConnection::connectDestroyed() { loop_->assertInLoopThread(); if (state_ == kConnected) { setState(kDisconnected); channel_->disableAll(); connectionCallback_(shared_from_this()); // 这里 } channel_->remove(); }当connectionCallback_(shared_from_this());也执行完毕,连接对象的引用计数就会变成0。到此,conn生命周期就会结束。
- 连接建立:
-
Channel的成员
cpp class Channel { private: ... std::weak_ptr<void> tie_; bool tied_; ... };- 为什么这里的
tie_数据类型是std::weak_ptr
weak_ptr 是为生命周期设计的如果这里是std::shared_ptr,那么这个接受的是std::shared_ptr<TcpConnection> tie_,这就会导致TcpConnection对象无法释放:即循环引用,因为:TcpConnection内含一个shared_ptr<Channel>Channel又内含一个shared_ptr<TcpConnection>
如此最后都无法释放,那么就会产生内存泄漏。解决办法就是使得其中一个为
std::weak_ptr。当需要使用所管理的对象时,再使用lock()函数,返回std::shared_ptr类型,并且使这个临时对象为局部变量,因此不会造就内存泄漏的问题。 - 为什么这里的