今天收到一位网友来信:
在 simple 中的 daytime 示例中,服务端主动关闭时调用的是如下函数序列,这不是只是关闭了连接上的写操作吗,怎么是关闭了整个连接?
void DaytimeServer::onConnection(const muduo::net::TcpConnectionPtr& conn)2: {3: if (conn->connected())4: {5: conn->send(Timestamp::now().toFormattedString() &#43; "/n");6: conn->shutdown();7: }8: }9: 10: void TcpConnection::shutdown()11: {12: if (state_ &#61;&#61; 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 writing25: 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: }
陈硕答复如下&#xff1a;
Muduo TcpConnection 没有提供 close&#xff0c;而只提供 shutdown &#xff0c;这么做是为了收发数据的完整性。
TCP 是一个全双工协议&#xff0c;同一个文件描述符既可读又可写&#xff0c; shutdownWrite() 关闭了“写”方向的连接&#xff0c;保留了“读”方向&#xff0c;这称为 TCP half-close。如果直接 close(socket_fd)&#xff0c;那么 socket_fd 就不能读或写了。
用 shutdown 而不用 close 的效果是&#xff0c;如果对方已经发送了数据&#xff0c;这些数据还“在路上”&#xff0c;那么 muduo 不会漏收这些数据。换句话说&#xff0c;muduo 在 TCP 这一层面解决了“当你打算关闭网络连接的时候&#xff0c;如何得知对方有没有发了一些数据而你还没有收到&#xff1f;”这一问题。当然&#xff0c;这个问题也可以在上面的协议层解决&#xff0c;双方商量好不再互发数据&#xff0c;就可以直接断开连接。
等于说 muduo 把“主动关闭连接”这件事情分成两步来做&#xff0c;如果要主动关闭连接&#xff0c;它会先关本地“写”端&#xff0c;等对方关闭之后&#xff0c;再关本地“读”端。练习&#xff1a;阅读代码&#xff0c;回答“如果被动关闭连接&#xff0c;muduo 的行为如何&#xff1f;” 提示&#xff1a;muduo 在 read() 返回 0 的时候会回调 connection callback&#xff0c;这样客户代码就知道对方断开连接了。
Muduo 这种关闭连接的方式对对方也有要求&#xff0c;那就是对方 read() 到 0 字节之后会主动关闭连接&#xff08;无论 shutdownWrite() 还是 close()&#xff09;&#xff0c;一般的网络程序都会这样&#xff0c;不是什么问题。当然&#xff0c;这么做有一个潜在的安全漏洞&#xff0c;万一对方故意不不关&#xff0c;那么 muduo 的连接就一直半开着&#xff0c;消耗系统资源。
完整的流程是&#xff1a;我们发完了数据&#xff0c;于是 shutdownWrite&#xff0c;发送 TCP FIN 分节&#xff0c;对方会读到 0 字节&#xff0c;然后对方通常会关闭连接&#xff0c;这样 muduo 会读到 0 字节&#xff0c;然后 muduo 关闭连接。&#xff08;思考题&#xff0c;在 shutdown() 之后&#xff0c;muduo 回调 connection callback 的时间间隔大约是一个 round-trip time&#xff0c;为什么&#xff1f;&#xff09;
另外&#xff0c;如果有必要&#xff0c;对方可以在 read() 返回 0 之后继续发送数据&#xff0c;这是直接利用了 half-close TCP 连接。muduo 会收到这些数据&#xff0c;通过 message callback 通知客户代码。
那么 muduo 什么时候真正 close socket 呢&#xff1f;在 TcpConnection 对象析构的时候。TcpConnection 持有一个 Socket 对象&#xff0c;Socket 是一个 RAII handler&#xff0c;它的析构函数会 close(sockfd_)。这样&#xff0c;如果发生 TcpConnection 对象泄漏&#xff0c;那么我们从 /proc/pid/fd/ 就能找到没有关闭的文件描述符&#xff0c;便于查错。
muduo 在 read() 返回 0 的时候会回调 connection callback&#xff0c;然后把 TcpConnection 的引用计数减一&#xff0c;如果 TcpConnection 的引用计数降到零&#xff0c;它就会析构了。