今天收到一位網友來信:
在 simple 中的 daytime 示例中,服務端主動關閉時調用的是如 下函數序列,這不是只是關閉了連接上的寫操作嗎,怎麼是關閉了整個連接?
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: }
陳碩答復如下:
Muduo TcpConnection 沒有提供 close,而只提供 shutdown ,這麼做是為了收發數據的完整 性。
TCP 是一個全雙工協議,同一個文件描述符既可讀又可寫, shutdownWrite() 關閉了“寫 ”方向的連接,保留了“讀”方向,這稱為 TCP half-close。如果直接 close(socket_fd),那麼 socket_fd 就不能讀或寫了。
用 shutdown 而不用 close 的效果是,如果對方已經發送了數據 ,這些數據還“在路上”,那麼 muduo 不會漏收這些數據。換句話說,muduo 在 TCP 這一層面解決了 “當你打算關閉網絡連接的時候,如何得知對方有沒有發了一些數據而你還沒有收到?”這一問題。當 然,這個問題也可以在上面的協議層解決,雙方商量好不再互發數據,就可以直接斷開連接。
等於說 muduo 把“主動關閉連接”這件事情分成兩步來做,如果要主動關閉連接,它會先關本地“寫 ”端,等對方關閉之後,再關本地“讀”端。練習:閱讀代碼,回答“如果被動關閉連接,muduo 的行 為如何?” 提示:muduo 在 read() 返回 0 的時候會回調 connection callback,這樣客戶代碼就知 道對方斷開連接了。
Muduo 這種關閉連接的方式對對方也有要求,那就是對方 read() 到 0 字 節之後會主動關閉連接(無論 shutdownWrite() 還是 close()),一般的網絡程序都會這樣,不是什 麼問題。當然,這麼做有一個潛在的安全漏洞,萬一對方故意不不關,那麼 muduo 的連接就一直半開 著,消耗系統資源。
完整的流程是:我們發完了數據,於是 shutdownWrite,發送 TCP FIN 分 節,對方會讀到 0 字節,然後對方通常會關閉連接,這樣 muduo 會讀到 0 字節,然後 muduo 關閉連 接。(思考題,在 shutdown() 之後,muduo 回調 connection callback 的時間間隔大約是一個 round-trip time,為什麼?)
另外,如果有必要,對方可以在 read() 返回 0 之後繼續發送 數據,這是直接利用了 half-close TCP 連接。muduo 會收到這些數據,通過 message callback 通知 客戶代碼。
那麼 muduo 什麼時候真正 close socket 呢?在 TcpConnection 對象析構的時候 。TcpConnection 持有一個 Socket 對象,Socket 是一個 RAII handler,它的析構函數會 close (sockfd_)。這樣,如果發生 TcpConnection 對象洩漏,那麼我們從 /proc/pid/fd/ 就能找到沒有關 閉的文件描述符,便於查錯。
muduo 在 read() 返回 0 的時候會回調 connection callback ,然後把 TcpConnection 的引用計數減一,如果 TcpConnection 的引用計數降到零,它就會析構了。
查看本欄目