muduo網路庫學習之EventLoop(五):TcpConnection生存期管理(連線關閉)

s1mba發表於2013-11-09

監聽套接字可讀事件是POLLIN; 已連線套接字正常可讀是POLLIN; 正常可寫是POLLOUT; 對等方close/shutdown關閉連線,已連線套接字可讀是POLLIN | POLLHUP;

時序圖分析:


注意:將TcpConnectionPtr 在connections_ 中 erase 掉,時並不會馬上 析構TcpConnection 物件(引用計數不為0),

因為此時正處於Channel::handleEvent() 中,如果析構了TcpConnection,那麼它的成員channel_ 也會被析構,即導致

core dump.

也就是說TcpConnection 物件生存期要長於handleEvent() 函式,直到執行完connectDestroyed() 後才會析構。


在EventLoop(三)的基礎上,在TcpConnection 建構函式中再新增:

 C++ Code 
1
2
3
4
5
6
 
// 連線關閉,回撥TcpConnection::handleClose
channel_->setCloseCallback(
    boost::bind(&TcpConnection::handleClose, this));
// 發生錯誤,回撥TcpConnection::handleError
channel_->setErrorCallback(
    boost::bind(&TcpConnection::handleError, this));

在 TcpServer::newConnection() 中再新增:

 C++ Code 
1
2
3
4
5
6
 
void TcpServer::newConnection(int sockfd, const InetAddress &peerAddr)
{
    .....
    conn->setCloseCallback(
        boost::bind(&TcpServer::removeConnection, this, _1));
}

在TcpConnection::handleRead() 中再新增:

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 

void TcpConnection::handleRead(Timestamp receiveTime)
{
    ssize_t n = ::read(channel_->fd(), buf, sizeof buf);
    if (n > 0)
    {
        messageCallback_(shared_from_this(), buf, n);
    }
    else if (n == 0)
    {
        handleClose();
    }
    else
    {
        errno = savedErrno;
        LOG_SYSERR << "TcpConnection::handleRead";
        handleError();
    }
}

假設現在已經建立了一個新連線,經過幾次收發資料後,對等方關閉close套接字,TcpConnection::channel_ 可讀事件發生,poll返

回,呼叫Channel::handleEvent()處理活動通道,呼叫TcpConnection::handleRead(),::read() 返回0,進而調

TcpConnection::handleClose()

 C++ Code 
1
2
3
4
5
6
7
8
9
10
 
void TcpConnection::handleClose()
{
    setState(kDisconnected);
    channel_->disableAll();

    TcpConnectionPtr guardThis(shared_from_this());
     connectionCallback_(guardThis);      

    // must be the last line
    closeCallback_(guardThis);  // 呼叫TcpServer::removeConnection
}

這裡需要注意的是有關shared_from_this() 的使用:

 C++ Code 
1
2
 
class TcpConnection : boost::noncopyable,
    public boost::enable_shared_from_this<TcpConnection>

shared_from_this()  會用當前物件的裸指標構造一個臨時智慧指標物件,引用計數加1,但馬上會被析構,又減1,故無論呼叫多少

次,對引用計數都沒有影響。

TcpConnectionPtr guardThis(shared_from_this()); 為什麼不能直接寫成TcpConnectionPtr guardThis(this); ?

因為這樣寫的話,guardThis的引用計數就為1,而不是2,如下例所示:

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
 
#include<boost/enable_shared_from_this.hpp>
#include<boost/shared_ptr.hpp>
#include<cassert>

class Y: public boost::enable_shared_from_this<Y>
{
public:
    boost::shared_ptr<Y> f()
    {
        return shared_from_this();
    }

    Y *f2()
    {
        return this;
    }
};

int main(void)
{
    boost::shared_ptr<Y> p(new Y);
    boost::shared_ptr<Y> q = p->f();

    Y *r = p->f2();
    assert(p == q);
    assert(p.get() == r);

    std::cout << p.use_count() << std::endl; //2
    boost::shared_ptr<Y> s(r);
    std::cout << s.use_count() << std::endl; //1
    assert(p == s); //斷言失敗

    return 0;
}

直接用裸指標生成智慧指標物件s後,s的引用計數只是為1,而不會將p引用計數提升為3;如前所述,TcpConnection的生存期就會

成為問題,不能在恰當的時候被釋放。


進而呼叫TcpServer::removeConnection(), 

 C++ Code 
1
2
3
4
5
6
7
8
 
void TcpServer::removeConnection(const TcpConnectionPtr &conn)
{
    size_t n = connections_.erase(conn->name());

    loop_->queueInLoop(
        boost::bind(&TcpConnection::connectDestroyed, conn));

}


handleEvent() 處理完畢後,當前IO執行緒繼續執行doPendingFunctors() 函式,取出 TcpConnection::connectDestroyed() 執行:

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
 
void TcpConnection::connectDestroyed()
{
    loop_->assertInLoopThread();
    if (state_ == kConnected)
    {
        setState(kDisconnected);
        channel_->disableAll();

        connectionCallback_(shared_from_this());
    }
    channel_->remove(); //poll 不再關注此通道
}


參考:
《UNP》
muduo manual.pdf
《linux 多執行緒伺服器程式設計:使用muduo c++網路庫》


相關文章