接着上文[muduo网络库]——EchoServer之消息读取(剖析muduo网络库核心部分、设计思想),整个Tcp网络编程还剩最后的消息断开部分,这一节我们来一步一步的剖析一下这个过程。
连接断开
连接断开分为主动断开与被动断开。
被动断开
- 在分析消息读取部分时,当
TcpConnection::handleRead()
中,n=0
说明客户端断开,就接着调用TcpConnection::handleClose( )
。
void TcpConnection::handleClose()
{
LOG_INFO("TcpConnection::handleClose fd=%d state=%d \n",channel_->fd(),(int)state_);
setState(kDisconnected);
channel_->disableAll();
TcpConnectionPtr connPtr(shared_from_this());
connectionCallback_(connPtr); //执行连接关闭的回调
closeCallback_(connPtr); //关闭连接的回调 TcpServer => TcpServer::removeConnection
}
- 设置当前Tcp的连接状态为已断开;
- 设置不关注事件,关闭掉
channel_
对事件的监听; - 执行连接关闭的回调
LOG_INFO("Connection DOWN : %s",conn->peerAddress().toIpPort().c_str());
- 关闭连接的回调,在
TcpServer::newConnection()
我们强调过这个回调:
conn->setCloseCallback(
std::bind(&TcpServer::removeConnection,this,std::placeholders::_1)
);
- 其实就是
TcpServer::removeConnection( )
函数
void TcpServer::removeConnection(const TcpConnectionPtr &conn)
{
loop_->runInLoop(
std::bind(&TcpServer::removeConnectionInLoop,this,conn)
);
}
- 调用
TcpServer::removeConnectionInLoop()
void TcpServer::removeConnectionInLoop(const TcpConnectionPtr &conn)
{
LOG_INFO("TcpServer::removeConnectionInLoop [%s] - connection \n",
name_.c_str(),conn->name().c_str());
connections_.erase(conn->name()); //从map表中删除
EventLoop *ioLoop = conn->getLoop();
ioLoop->queueInLoop(
std::bind(&TcpConnection::connectDestroyed,conn)
);
}
connections_
是using ConnectionMap = std::unordered_map<std::string,TcpConnectionPtr>;
,保存着Tcp连接的名字到TcpConnection对象的映射。当Tcp连接关闭,需要从map表中删掉。这一步是在mainLoop中执行的,因为connections_
是属于TcpServer
的,要删除他的数据,一定要在这个线程中运行。One Loop Per Thread。- 然后回到subLoop中,调用TcpConnection::connectDestroyed函数。
void TcpConnection::connectDestroyed()
{
if(state_ == kConnected)
{
setState(kDisconnected);
channel_->disableAll(); //把channel所有感兴趣的事件,从poller中del掉
connectionCallback_(shared_from_this());
}
channel_->remove();//把channel从poller中删除掉
}
- 将Tcp连接的监听描述符从事件监听器中移除,并将Poller类对象还保存的Tcp连接的channel对象从Poller内的数据结构中删除。
主动断开
当服务器主动断开连接时,会调用析构函数TcpServer::~TcpServer()
;
TcpServer::~TcpServer()
{
for(auto &item: connections_)
{
TcpConnectionPtr conn(item.second);
item.second.reset();
//销毁连接
conn->getLoop()->runInLoop(
std::bind(&TcpConnection::connectDestroyed,conn)
);
}
}
- 在
TcpServer::~TcpServer()
中,从connections_
取出,连接的对象,然后让TcpConnection
对象所属的subLoop线程执行TcpConnection::connectDestroyed()
函数; - 值得注意的是,在创建TcpConnection对象时,我们使用一个共享智能指针TcpConnetionPtr持有了对应的TcpConnection对象,执行
TcpConnectionPtr conn(item.second);
以后,这个TcpConnetion对象就被conn
和item.second
共同持有,conn
离开了当前for循环,conn就会被释放。 - 析构函数中使用
item.second.reset()
释放了保管TcpConnection对象的共享智能指针,也就释放了TcpConnection对象的堆内存空间,此时conn
还持有这个TcpConnection对象,因此当前TcpConnection对象还不会被析构。 - 执行
conn->getLoop()->runInLoop(std::bind(&TcpConnection::connectDestroyed,conn) );
,此时conn
所指向的资源的引用计数会加1(因为传给runInLoop的不只有函数,还有这个函数所属的对象conn)。 - 执行
TcpConnection::connectDestroyed
void TcpConnection::connectDestroyed()
{
if(state_ == kConnected)
{
setState(kDisconnected);
channel_->disableAll(); //把channel所有感兴趣的事件,从poller中del掉
connectionCallback_(shared_from_this());
}
channel_->remove();//把channel从poller中删除掉
}
- 当
conn
离开for循环,就被自动释放了。但TcpConnection对象的引用次数还有1,当这个函数执行完后,智能指针被释放。到此,TcpConnection对象被彻底析构。 - 在[muduo网络库]——muduo库TcpConnection类,万字总结(剖析muduo网络库核心部分、设计思想)我们也分析了TcpConnection的引用计数问题,这一点是很重要的,否则程序会崩溃!
在主动关闭的时候,自然会有疑问,如果TcpConnection中有正在发送的数据,怎么保证在触发TcpConnection关闭机制后,能先让TcpConnection先把数据发送完再释放TcpConnection对象的资源?
回到建立连接的时候:
void TcpConnection::connectEstablished()
{
setState(kConnected);
channel_->tie(shared_from_this());
channel_->enableReading(); //向poller注册channel的epollin事件
//新连接建立 执行回调
connectionCallback_(shared_from_this());
}
void Channel::tie(const std::shared_ptr<void> &obj)
{
tie_ = obj;
tied_ = true;
}
channel_->tie(shared_from_this());
,其传入了一个shared_ptr
(因为调用shared_from_this()
)指向这个Tcp对象,当有事件发生时,调用Channel::handleEvent()
void Channel::handleEvent(TimeStamp receiveTime)
{
if(tied_)
{
std::shared_ptr<void> guard;
guard = tie_.lock(); //提升
if(guard)
{
handleEventWithGuard(receiveTime);
}
}
else
{
handleEventWithGuard(receiveTime);
}
}
会先把tie_
这个弱指针提升为强共享智能指针,指向当前的TcpConnection对象,然后执行Channel::handleEventWithGuard()
,负责处理消息发送事件的逻辑,只要此函数没执行完,这个TcpConnetion对象都不会被析构释放堆内存。当函数调用完毕,这个guard智能指针就会被释放。这样也保证了即使触发TcpConnection关闭机制后,也可以先让TcpConnection先把数据发送完再释放TcpConnection对象的资源。
代码地址:https://github.com/Cheeron955/mymuduo/tree/master