Bootstrap

[muduo网络库]——EchoServer之连接断开(剖析muduo网络库核心部分、设计思想)

接着上文[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
}
  1. 设置当前Tcp的连接状态为已断开;
  2. 设置不关注事件,关闭掉channel_对事件的监听;
  3. 执行连接关闭的回调 LOG_INFO("Connection DOWN : %s",conn->peerAddress().toIpPort().c_str());
  4. 关闭连接的回调,在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)
        );
    }
}
  1. TcpServer::~TcpServer()中,从connections_取出,连接的对象,然后让TcpConnection对象所属的subLoop线程执行TcpConnection::connectDestroyed()函数;
  2. 值得注意的是,在创建TcpConnection对象时,我们使用一个共享智能指针TcpConnetionPtr持有了对应的TcpConnection对象,执行TcpConnectionPtr conn(item.second);以后,这个TcpConnetion对象就被connitem.second共同持有,conn离开了当前for循环,conn就会被释放。
  3. 析构函数中使用item.second.reset()释放了保管TcpConnection对象的共享智能指针,也就释放了TcpConnection对象的堆内存空间,此时conn还持有这个TcpConnection对象,因此当前TcpConnection对象还不会被析构。
  4. 执行conn->getLoop()->runInLoop(std::bind(&TcpConnection::connectDestroyed,conn) );,此时conn所指向的资源的引用计数会加1(因为传给runInLoop的不只有函数,还有这个函数所属的对象conn)。
  5. 执行TcpConnection::connectDestroyed
void TcpConnection::connectDestroyed()
{
    if(state_ == kConnected)
    {
        setState(kDisconnected);
        channel_->disableAll(); //把channel所有感兴趣的事件,从poller中del掉
        connectionCallback_(shared_from_this());
    }
    channel_->remove();//把channel从poller中删除掉
}

在主动关闭的时候,自然会有疑问,如果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

好了~ 有关于使用muduo库搭建EchoServer之连接断开就分析到这里了,设计到的资源的释放还是很重要的一部分,到此muduo库关于Tcp网络编程的三个半事件就分析完成了,也算是一个完结撒花了~ 接下来会对muduo库的精华部分在做一个梳理,我们下一节见~
;