陳碩 (giantchen_AT_gmail)
Blog.csdn.net/Solstice
Muduo 全系列文章列表: http://blog.csdn.net/Solstice/category/779646.aspx
今天收到一位網(wǎng)友來信:
在 simple 中的 daytime 示例中,服務(wù)端主動關(guān)閉時(shí)調(diào)用的是如下函數(shù)序列,這不是只是關(guān)閉了連接上的寫操作嗎,怎么是關(guān)閉了整個(gè)連接?
1: void DaytimeServer::onConnection(const muduo::net::TcpConnectionPtr& conn)
2: {
3: if (conn->connected())
4: {
5: conn->send(Timestamp::now().toFormattedString() + "\n");
6: conn->shutdown();
7: }
8: }
9:
10: void TcpConnection::shutdown()
11: {
12: if (state_ == kConnected)
13: {
14: setState(kDisconnecting);
15: loop_->runInLoop(boost::bind(&TcpConnection::shutdownInLoop, this));
16: }
17: }
18:
19: void TcpConnection::shutdownInLoop()
20: {
21: loop_->assertInLoopThread();
22: if (!channel_->isWriting())
23: {
24: // we are not writing
25: socket_->shutdownWrite();
26: }
27: }
28:
29: void Socket::shutdownWrite()
30: {
31: sockets::shutdownWrite(sockfd_);
32: }
33:
34: void sockets::shutdownWrite(int sockfd)
35: {
36: if (::shutdown(sockfd, SHUT_WR) < 0)
37: {
38: LOG_SYSERR << "sockets::shutdownWrite";
39: }
40: }
陳碩答復(fù)如下:
Muduo TcpConnection 沒有提供 close,而只提供 shutdown ,這么做是為了收發(fā)數(shù)據(jù)的完整性。
TCP 是一個(gè)全雙工協(xié)議,同一個(gè)文件描述符既可讀又可寫, shutdownWrite() 關(guān)閉了“寫”方向的連接,保留了“讀”方向,這稱為 TCP half-close。如果直接 close(socket_fd),那么 socket_fd 就不能讀或?qū)懥恕?/p>
用 shutdown 而不用 close 的效果是,如果對方已經(jīng)發(fā)送了數(shù)據(jù),這些數(shù)據(jù)還“在路上”,那么 muduo 不會漏收這些數(shù)據(jù)。換句話說,muduo 在 TCP 這一層面解決了“當(dāng)你打算關(guān)閉網(wǎng)絡(luò)連接的時(shí)候,如何得知對方有沒有發(fā)了一些數(shù)據(jù)而你還沒有收到?”這一問題。當(dāng)然,這個(gè)問題也可以在上面的協(xié)議層解決,雙方商量好不再互發(fā)數(shù)據(jù),就可以直接斷開連接。
等于說 muduo 把“主動關(guān)閉連接”這件事情分成兩步來做,如果要主動關(guān)閉連接,它會先關(guān)本地“寫”端,等對方關(guān)閉之后,再關(guān)本地“讀”端。練習(xí):閱讀代碼,回答“如果被動關(guān)閉連接,muduo 的行為如何?” 提示:muduo 在 read() 返回 0 的時(shí)候會回調(diào) connection callback,這樣客戶代碼就知道對方斷開連接了。
Muduo 這種關(guān)閉連接的方式對對方也有要求,那就是對方 read() 到 0 字節(jié)之后會主動關(guān)閉連接(無論 shutdownWrite() 還是 close()),一般的網(wǎng)絡(luò)程序都會這樣,不是什么問題。當(dāng)然,這么做有一個(gè)潛在的安全漏洞,萬一對方故意不不關(guān),那么 muduo 的連接就一直半開著,消耗系統(tǒng)資源。
完整的流程是:我們發(fā)完了數(shù)據(jù),于是 shutdownWrite,發(fā)送 TCP FIN 分節(jié),對方會讀到 0 字節(jié),然后對方通常會關(guān)閉連接,這樣 muduo 會讀到 0 字節(jié),然后 muduo 關(guān)閉連接。(思考題:在 shutdown() 之后,muduo 回調(diào) connection callback 的時(shí)間間隔大約是一個(gè) round-trip time,為什么?)
另外,如果有必要,對方可以在 read() 返回 0 之后繼續(xù)發(fā)送數(shù)據(jù),這是直接利用了 half-close TCP 連接。muduo 會收到這些數(shù)據(jù),通過 message callback 通知客戶代碼。
那么 muduo 什么時(shí)候真正 close socket 呢?在 TcpConnection 對象析構(gòu)的時(shí)候。TcpConnection 持有一個(gè) Socket 對象,Socket 是一個(gè) RAII handler,它的析構(gòu)函數(shù)會 close(sockfd_)。這樣,如果發(fā)生 TcpConnection 對象泄漏,那么我們從 /proc/pid/fd/ 就能找到?jīng)]有關(guān)閉的文件描述符,便于查錯(cuò)。
muduo 在 read() 返回 0 的時(shí)候會回調(diào) connection callback,然后把 TcpConnection 的引用計(jì)數(shù)減一,如果 TcpConnection 的引用計(jì)數(shù)降到零,它就會析構(gòu)了。
參考:
《TCP/IP 詳解》第一卷第 18.5 節(jié),TCP Half-Close。
《UNIX 網(wǎng)絡(luò)編程》第一卷第三版第 6.6 節(jié), shutdown() 函數(shù)。