跳转至

关闭时序

关闭连接

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();
    }
}
因为客户端请求关闭时,服务器端读取到的字节数为0,因此会再次调用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,与sp2,尽管都是引用同一个对象指针,但是由于是通过构造函数完成的,因此他们的引用计数都是1,并不是共享计数,这就导致同一个对象会被析构两次。但sp3是通过赋值操作=完成,就和sp1共享引用,引用计数为2,sp2的引用计数为1。

    同理,`fun1`会增加引用计数,而`fun2`不会增加。
  • conn的生命周期
    由于conn是通过share_ptr管理,那么它的生命周期也可通过引用计数来追踪。
    • 连接建立:
      • 建立conn时,引用计数为1
      • connections_[connName] = conn;引用计数加1
      • conn->connectEstablished(); 执行函数时,内部会使得引用计数变为3,执行完恢复2.
        cpp shared_from_this() // 创建的是一个临时std::shared_ptr对象
      • TcpServer::newConnection执行完毕,创建的局部变量conn会被销毁,只是剩下一个connections_中的指向connshared_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类型,并且使这个临时对象为局部变量,因此不会造就内存泄漏的问题。