为什么muduo中的shutdown没有直接关闭TCP连接?

本文解析了Muduo库中TCP连接管理机制,重点介绍了使用shutdown而非close的方法及其背后的原因,包括确保数据完整性和半关闭连接的工作原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

今天收到一位网友来信:

在 simple 中的 daytime 示例中,服务端主动关闭时调用的是如下函数序列,这不是只是关闭了连接上的写操作吗,怎么是关闭了整个连接?


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 的引用计数降到零,它就会析构了。


### 关于 `muduo` 库中 `tcpserver.h` 文件缺失的原因分析 在使用 `muduo` 网络库的过程中,如果遇到 `tcpserver.h` 文件缺失的情况,可能是由于以下几个原因造成的: #### 1. **未正确下载完整的源码包** 如果从不完整的资源或者错误的版本获取了 `muduo` 的源码,则可能导致某些头文件(如 `tcpserver.h`)不存在。建议重新确认是否已下载官方发布的完整版源码[^1]。 #### 2. **路径配置问题** 即使已经拥有完整的源码,在构建项目时如果没有正确设置包含路径 (`include path`) 或者链接器选项,也可能导致编译器无法找到该头文件。可以通过调整 CMakeLists.txt 中的相关配置来解决问题[^3]。 #### 3. **版本差异引起的兼容性问题** 不同版本之间可能存在 API 变更或结构调整,比如某个特定功能被移到其他模块里去了。因此需要仔细核对所使用的具体分支/标签号以及对应的文档说明[^2]。 --- ### 解决方案 以下是针对上述可能原因提出的几种解决办法: #### 方法一:验证并更新至最新稳定版本 确保当前操作基于最新的发布版本而非开发中的不稳定状态;访问 GitHub 官方仓库页面查看 Release Notes 并按照指示升级代码基线。 ```bash git clone https://github.com/chenshuo/muduo.git cd muduo git checkout tags/vX.Y.Z # 替换 X.Y.Z 表示期望的目标发行编号 ``` #### 方法二:检查依赖关系与环境变量设定 对于跨平台工具链而言,有时缺少必要的外部库支持也会间接影响内部组件可用性。例如 Boost 是一个常见的前提条件之一,需事先完成其本地部署后再继续尝试安装 Muduo。 - 下载 Boost 源码压缩包解压后执行如下命令序列: ```bash ./bootstrap.sh --prefix=/usr/local/ sudo ./b2 install export BOOST_ROOT=/usr/local/include/boost ``` 随后修改 Makefile.am 添加额外标志 `-I${BOOST_ROOT}` 来告知预处理器查找位置。 #### 方法三:手动复制遗漏项回原目录结构内 假如经过排查发现确实存在个别重要单元遗失现象的话,可以从网上搜索相似主题帖子里面附带补丁附件直接应用上去即可恢复正常使用状况[^4]。 --- ### 示例代码片段展示如何定义回调处理程序 下面给出一段简单的例子演示怎样利用 TcpServer 类型创建服务器端实例对象的同时注册消息到达事件监听逻辑部分: ```cpp #include "muduo/net/TcpServer.h" #include "muduo/net/EventLoop.h" using namespace muduo; using namespace muduo::net; void onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp time){ std::string msg(buf->retrieveAllAsString()); LOG_INFO << conn->name() << " receives " << msg.size() << " bytes at " << time.toString(); } int main(){ EventLoop loop; InetAddress listenAddr(9981); TcpServer server(&loop,"TestServer",listenAddr); server.setThreadNum(3); // 设置多线程模式下的工作线程数量 server.setMessageCallback(onMessage); server.start(); loop.loop(); return 0; } ``` --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值