TcpConnection

TcpConnection 的设计精髓在于 事件驱动回调机制。它将底层的socket读写、连接状态变化等事件都封装好,然后通过回调函数通知给用户,用户只需要关心如何处理这些事件(比如收到消息后该做什么业务处理),而不需要关心具体的网络I/O细节。

下面我将 TcpConnection.h 中定义的API分为几类来介绍:

1. 生命周期管理 (Lifecycle Management)

这类API负责连接的建立、状态变更和销毁。

  • 构造函数 TcpConnection(...) 和析构函数 ~TcpConnection()
    • 构造函数通常不是由用户直接调用的,而是由 TcpServer (当接受一个新连接时) 或 TcpClient (当成功连接到服务器时) 在内部创建 TcpConnection 对象。它需要一个 EventLoop 对象、连接名称、已连接的socket文件描述符 sockfd 以及本地和对端的地址。
  • connectEstablished()connectDestroyed()
    • 这两个是内部接口,TcpServer 在接受新连接并创建好 TcpConnection 后会调用 connectEstablished()。这个函数会把连接状态设为 kConnected,启用读事件,并触发用户设置的 ConnectionCallback
    • connectDestroyed() 则在连接彻底销毁前被调用,用于清理工作。
  • shutdown()
    • 优雅关闭。调用后,muduo 会确保 outputBuffer_ 中待发送的数据全部发送完毕后,再关闭写端(发送FIN包),但此时仍然可以接收数据。这是TCP的"半关闭"(half-close)功能。它是非线程安全的,需要在IO线程中调用。
  • forceClose()forceCloseWithDelay()
    • 强制关闭。立即或延迟一段时间后,直接关闭连接,outputBuffer_ 中未发送的数据可能会丢失。

2. 数据收发 (Data Transfer)

用户通过这类API来发送数据和获取接收到的数据。

  • send() 系列函数
    • 这是用户发送数据的核心API。它有多个重载版本,可以发送 const void* + lenStringPieceBuffer*
    • 关键特性:线程安全send() 可以在任何线程中调用。如果当前在 TcpConnection 所属的I/O线程,它会直接尝试发送;如果不在,它会把发送任务派发(runInLoop)到那个I/O线程去执行,从而避免了多线程并发写socket的问题。
  • inputBuffer()outputBuffer()
    • 分别返回指向内部输入/输出缓冲区的指针。用户在 MessageCallback 中通过 inputBuffer() 来读取和处理收到的数据。outputBuffer() 主要供库内部使用,用户一般不需要直接操作它。

3. 回调函数设置 (Callback Setup)

这是 TcpConnection 用法的核心,用户通过设置不同的回调函数来注入自己的业务逻辑。

  • setConnectionCallback(const ConnectionCallback& cb)
    • 设置连接建立和断开时的回调。当一个连接成功建立或断开时,这个回调函数会被调用。
  • setMessageCallback(const MessageCallback& cb)
    • 设置消息到达时的回调。当TcpConnection从socket读到数据并存入inputBuffer_后,会调用这个回调。这是处理业务逻辑最主要的地方。
  • setWriteCompleteCallback(const WriteCompleteCallback& cb)
    • 设置发送完成回调。当 outputBuffer_ 中的数据全部被发送到内核缓冲区后,这个回调会被触发。
  • setHighWaterMarkCallback(const HighWaterMarkCallback& cb, size_t highWaterMark)
    • 设置"高水位"回调。用于防止发送速度过快,导致outputBuffer_积压过多数据耗尽内存。当待发送数据量超过 highWaterMark 时,会触发此回调,用户可以在回调中暂停写入,等 WriteCompleteCallback 被触发(说明缓冲区数据量下降了)后再恢复写入,实现流量控制。

4. 状态查询与控制 (State Query & Control)

  • connected()disconnected()
    • 检查当前连接的状态。
  • getLoop(), name(), localAddress(), peerAddress()
    • 获取连接所属的EventLoop、连接名、本地地址和对端地址等信息。
  • setTcpNoDelay(bool on)
    • 开启或关闭 TCP_NODELAY 选项(Nagle算法),默认关闭。开启后可以降低小数据包的延迟。
  • startRead() / stopRead()
    • 在连接上开启或停止读数据。这也常用于实现上层应用的流量控制。

内部工作流程 (TcpConnection.cc 实现)

TcpConnection 内部通过一个 Channel 对象来关注socket上的IO事件。

  1. handleRead() : 当Channel检测到socket可读时被调用。它会从socket读取数据到inputBuffer_,然后调用用户设置的MessageCallback。如果read返回0,表示对端关闭连接,就会调用handleClose()
  2. handleWrite() : 当outputBuffer_中有数据且socket可写时被调用。它会把outputBuffer_中的数据写入socket。如果数据全部写完,它会停止关注可写事件(节省CPU),并调用WriteCompleteCallback
  3. handleClose() : 当对端关闭连接、本端主动关闭连接或发生错误时被调用。它会清理资源,禁用Channel上的所有事件,并依次调用ConnectionCallback和内部的CloseCallback(通知TcpServer将自己移除)。
  4. handleError(): 当socket发生错误时被调用,用于记录日志。

好的,没问题。时序图(Sequence Diagram)确实能更清晰地展示对象之间的交互和时间顺序。
User Thread IO Thread / EventLoop TcpConnection Socket / OS 1. 连接建立 new TcpConnection(loop, fd, ...) connectEstablished() setState(kConnected) enableReading() via Channel ConnectionCallback (连接成功) 2. 数据接收 数据到达, socket可读 handleRead() read(fd, inputBuffer) MessageCallback(conn, inputBuffer) 在回调中处理业务逻辑 3. 跨线程发送数据 send(data) 判断出调用者不在IO线程 runInLoop(bind(&sendInLoop, ...)) sendInLoop(data) write(fd, data) 尝试直接写入socket append data to outputBuffer alt outputBuffer 为空 outputBuffer 不为空 将剩余数据存入outputBuffer enableWriting() via Channel WriteCompleteCallback alt 数据未完全写入 数据写完且outputBuffer为空 4. 处理待发送数据 Socket 变为可写 handleWrite() write(fd, outputBuffer.peek()) disableWriting() via Channel WriteCompleteCallback alt outputBuffer 被清空 5. 优雅关闭 shutdown() runInLoop(bind(&shutdownInLoop, ...)) shutdownInLoop() shutdownWrite() 发送 FIN 将在handleWrite写完数据后, 再调用shutdownInLoop alt outputBuffer 已清空 outputBuffer 仍有数据 收到对端的FIN, socket可读 handleRead() read(fd) 返回 0 handleClose() setState(kDisconnected) disableAll() via Channel ConnectionCallback (连接断开) closeCallback() (通知TcpServer移除自己) 析构TcpConnection对象 User Thread IO Thread / EventLoop TcpConnection Socket / OS

时序图解读:

  1. 连接建立 : TcpServer 在其所在的 IO Thread / EventLoop 中创建 TcpConnection 对象,并调用 connectEstablished 初始化连接,最终通过 ConnectionCallback 通知用户。
  2. 数据接收 : 物理socket收到数据后,EventLoop 监听到可读事件,并调用 TcpConnectionhandleRead 方法。handleRead 负责读取数据到缓冲区,然后通过 MessageCallback 将数据交给用户处理。
  3. 跨线程发送数据 : 当 User Thread 调用 send 时,TcpConnection 会将发送任务 sendInLoop 抛给 IO Thread 去执行,以保证线程安全。sendInLoop 会尝试直接发送,如果发送不完,就把剩余数据放入 outputBuffer_
  4. 处理待发送数据 : 当 outputBuffer_ 中有数据时,TcpConnection 会监听socket的可写事件。一旦socket可写,EventLoop 就会调用 handleWrite,将缓冲区的数据发送出去。
  5. 优雅关闭 : 用户调用 shutdown 发起半关闭。TcpConnection 会确保 outputBuffer_ 的数据发送完毕后,再向对端发送FIN包。当收到对端的FIN后(体现为read返回0),handleClose 被调用,执行最后的清理工作,并再次通过 ConnectionCallback 通知用户连接已断开。

1. 精巧的线程模型:one loop per thread + 跨线程调用

  • 核心思想 : 每个 I/O 线程(EventLoop 所在的线程)独立管理自己的一组 TcpConnection。一个 TcpConnection 的所有 I/O 操作(读、写、关闭)都必须在它所属的那个 EventLoop 线程中执行。这彻底避免了多线程访问 socket 和相关数据结构的竞态条件,因此完全不需要使用锁 来保护 TcpConnection 内部的状态(如 state_, inputBuffer_, outputBuffer_)。

  • 精巧之处 : 如何让用户在任何线程 都能安全地调用 send()shutdown() 呢?

    • TcpConnection.ccsend 方法里,你会看到这样的判断:

      cpp 复制代码
      if (loop_->isInLoopThread())
      {
        sendInLoop(message);
      }
      else
      {
        loop_->runInLoop(
            std::bind(fp,
                      this,
                      message.as_string()));
      }
    • 这就是关键:isInLoopThread() 判断当前线程是否就是管理此 TcpConnection 的 I/O 线程。

      • 如果是 ,就直接执行 sendInLoop
      • 如果不是 (即用户在自己的工作线程中调用),则通过 loop_->runInLoop()sendInLoop 这个任务打包,发送给目标 I/O 线程,让 I/O 线程在未来的某个时间点(通常是下一次事件循环)来执行它。
    • 优点 : 这种"任务派发"机制,既保证了 send 接口的线程安全性,又避免了锁带来的性能开销和死锁风险,是现代高性能网络库的典范设计。

2. 精巧的生命周期管理:shared_ptr + enable_shared_from_this + Channel::tie

在异步回调的环境中,对象的生命周期管理是个大难题。比如,一个 TcpConnection 可能在它的一个回调函数还没来得及执行时就被销毁了,导致野指针访问。muduo 用一套组合拳完美解决了这个问题。

  • shared_ptr<TcpConnection> : TcpServer 中用一个 map 持有所有 TcpConnectionshared_ptr,确保了只要连接存在,对象就不会被销毁。
  • enable_shared_from_this : 这让 TcpConnection 对象内部能安全地获取到自身的 shared_ptr(通过 shared_from_this())。这在注册回调函数时至关重要,因为回调函数需要持有 TcpConnectionshared_ptr,以延长其生命周期,确保回调执行时对象依然有效。例如 handleClose 中的 TcpConnectionPtr guardThis(shared_from_this()); 就是一个经典的用法。
  • 最精巧的一点:Channel::tie
    • Channel 对象是 TcpConnectionEventLoop 之间的桥梁,它直接和 Poller 交互。Channel 的生命周期和 TcpConnection 绑定,但 Channel 的回调函数是在 EventLoop 中被调用的。
    • 考虑一个场景:TcpConnection 析构了,但 Poller 中可能还有该连接的残留事件,EventLoop 稍后处理这个事件时,会通过 Channel 调用 handleRead 等成员函数,此时 TcpConnection 对象已经销毁,程序崩溃。
    • muduo 的解决方案是:在 connectEstablished 中调用 channel_->tie(shared_from_this());tie 内部存储了一个 weak_ptr<void> 指向 TcpConnection
    • Channel 准备执行回调(如 handleEvent)时,它会先尝试将这个 weak_ptr提升(lock())为 shared_ptr
      • 如果提升成功 ,说明 TcpConnection 对象还活着,就安全地执行回调。
      • 如果提升失败 ,说明 TcpConnection 对象已经被销毁了,那就放弃执行回调。
    • 这个 tie 机制像一根安全绳,优雅地解决了跨对象的生命周期依赖问题,防止了 use-after-free。

3. 精巧的缓冲区设计:Buffer

  • 解决 TCP 粘包/半包问题 : Buffer 是处理 TCP 字节流的基础。handleRead 一次性将 socket 中所有可读数据读入 Buffer,然后 MessageCallbackBuffer 中按照应用层协议解析出完整的消息包,解决了 TCP 本身不提供消息边界的问题。
  • 内存使用效率 : Buffer 内部维护了 prependable bytesreadable byteswritable bytes 三个区域。当已读数据(readable bytes)被消耗后,这部分空间并不会立即释放,而是变成了 prependable bytes。这样可以避免频繁的内存移动。当 writable bytes 不够用时,才会考虑移动数据或重新分配内存。
  • 零拷贝操作 : Buffer::readFd 内部使用了 readv 这个 scatter/gather I/O 操作。它可以直接将数据从内核缓冲区读到 Bufferwritable 空间,同时还可以利用一个栈上的临时缓冲区,避免了"内核 -> 用户临时buffer -> Buffer"的两次拷贝,提升了读取效率。

4. 精巧的流量控制与关闭机制

  • 高水位回调 (HighWaterMarkCallback) : 这是防止发送速度远大于接收速度导致内存耗尽的关键机制。当 outputBuffer_ 中积压的数据超过一个阈值(highWaterMark_)时,会触发回调。用户可以在这个回调中暂停产生数据(比如停止读取上游数据源)。当 outputBuffer_ 的数据量降下来后,WriteCompleteCallback 会被触发,用户此时可以恢复产生数据。这是一个简单的应用层反压/流控机制。
  • 优雅关闭 (shutdown) : shutdown 并非直接 close 套接字,而是调用 socket->shutdownWrite(),这对应于 TCP 的半关闭(half-close)。它会向对端发送 FIN 包,表示"我这边的数据已经发完了",但仍然可以接收来自对端的数据。这对于需要确保所有待发数据都成功送达的协议至关重要。
相关推荐
pixcarp21 小时前
知识库系统的内容资产闭环怎么设计
服务器·数据库·后端·golang
江畔柳前堤21 小时前
github实战指南01-账号配置与 SSH 密钥
运维·人工智能·深度学习·ssh·github·pyqt·信号处理
Moshow郑锴1 天前
Ubuntu 26.04 中文输入法 : fcitx5+Rime中州韵引擎
linux·运维·ubuntu
祺风挽楠1 天前
ansible编辑
网络·ansible
莫名的好感°1 天前
手机RAR解压怎么选?2026年二季度四款产品问答
服务器·网络·智能手机
小赖同学啊1 天前
智能连接器集群化高可用生产方案
linux·运维·人工智能
wanghao6664551 天前
DevOps 从入门到实践:构建高效交付流水线
运维·devops
qq_546937271 天前
从“能用”到“超神”,DeepSeek++给网页版装上“大脑”和“手脚”,支持长期记忆、MCP工具与自动化任务!
运维·自动化
ZStack开发者社区1 天前
基于AI Agent的ZCF API文档全链路自动化
运维·人工智能·自动化
Cinema KI1 天前
Linux第一个系统程序-进度条
linux·服务器