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*+len、StringPiece或Buffer*。 - 关键特性:线程安全。
send()可以在任何线程中调用。如果当前在TcpConnection所属的I/O线程,它会直接尝试发送;如果不在,它会把发送任务派发(runInLoop)到那个I/O线程去执行,从而避免了多线程并发写socket的问题。 
- 这是用户发送数据的核心API。它有多个重载版本,可以发送 
 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事件。
handleRead(): 当Channel检测到socket可读时被调用。它会从socket读取数据到inputBuffer_,然后调用用户设置的MessageCallback。如果read返回0,表示对端关闭连接,就会调用handleClose()。handleWrite(): 当outputBuffer_中有数据且socket可写时被调用。它会把outputBuffer_中的数据写入socket。如果数据全部写完,它会停止关注可写事件(节省CPU),并调用WriteCompleteCallback。handleClose(): 当对端关闭连接、本端主动关闭连接或发生错误时被调用。它会清理资源,禁用Channel上的所有事件,并依次调用ConnectionCallback和内部的CloseCallback(通知TcpServer将自己移除)。handleError(): 当socket发生错误时被调用,用于记录日志。
好的,没问题。时序图(Sequence Diagram)确实能更清晰地展示对象之间的交互和时间顺序。
时序图解读:
- 连接建立: 
TcpServer在其所在的IO Thread / EventLoop中创建TcpConnection对象,并调用connectEstablished初始化连接,最终通过ConnectionCallback通知用户。 - 数据接收: 物理socket收到数据后,
EventLoop监听到可读事件,并调用TcpConnection的handleRead方法。handleRead负责读取数据到缓冲区,然后通过MessageCallback将数据交给用户处理。 - 跨线程发送数据: 当 
User Thread调用send时,TcpConnection会将发送任务sendInLoop抛给IO Thread去执行,以保证线程安全。sendInLoop会尝试直接发送,如果发送不完,就把剩余数据放入outputBuffer_。 - 处理待发送数据: 当 
outputBuffer_中有数据时,TcpConnection会监听socket的可写事件。一旦socket可写,EventLoop就会调用handleWrite,将缓冲区的数据发送出去。 - 优雅关闭: 用户调用 
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.cc的send方法里,你会看到这样的判断: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持有所有TcpConnection的shared_ptr,确保了只要连接存在,对象就不会被销毁。enable_shared_from_this: 这让TcpConnection对象内部能安全地获取到自身的shared_ptr(通过shared_from_this())。这在注册回调函数时至关重要,因为回调函数需要持有TcpConnection的shared_ptr,以延长其生命周期,确保回调执行时对象依然有效。例如handleClose中的TcpConnectionPtr guardThis(shared_from_this());就是一个经典的用法。- 最精巧的一点:
Channel::tieChannel对象是TcpConnection和EventLoop之间的桥梁,它直接和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,然后MessageCallback从Buffer中按照应用层协议解析出完整的消息包,解决了 TCP 本身不提供消息边界的问题。 - 内存使用效率: 
Buffer内部维护了prependable bytes、readable bytes和writable bytes三个区域。当已读数据(readable bytes)被消耗后,这部分空间并不会立即释放,而是变成了prependable bytes。这样可以避免频繁的内存移动。当writable bytes不够用时,才会考虑移动数据或重新分配内存。 - 零拷贝操作: 
Buffer::readFd内部使用了readv这个 scatter/gather I/O 操作。它可以直接将数据从内核缓冲区读到Buffer的writable空间,同时还可以利用一个栈上的临时缓冲区,避免了“内核 -> 用户临时buffer -> Buffer”的两次拷贝,提升了读取效率。 
4. 精巧的流量控制与关闭机制
- 高水位回调 (
HighWaterMarkCallback): 这是防止发送速度远大于接收速度导致内存耗尽的关键机制。当outputBuffer_中积压的数据超过一个阈值(highWaterMark_)时,会触发回调。用户可以在这个回调中暂停产生数据(比如停止读取上游数据源)。当outputBuffer_的数据量降下来后,WriteCompleteCallback会被触发,用户此时可以恢复产生数据。这是一个简单的应用层反压/流控机制。 - 优雅关闭 (
shutdown):shutdown并非直接close套接字,而是调用socket->shutdownWrite(),这对应于 TCP 的半关闭(half-close)。它会向对端发送FIN包,表示“我这边的数据已经发完了”,但仍然可以接收来自对端的数据。这对于需要确保所有待发数据都成功送达的协议至关重要。 
