【c++】muduo库(四)tcp服务器
TcpConnection
TcpConnection 类是整个网络库中最核心的组件之一,它代表了一个已建立的TCP连接,主要负责:
- 数据的收发
- 连接的生命周期管理
- 事件回调处理
- 缓冲区管理
// 创建TcpConnection对象
InetAddress peerAddr(addr);
TcpConnectionPtr conn(new TcpConnection(&loop,
"TestConn",
connfd,
listenAddr,
peerAddr));
// 设置回调函数
conn->setConnectionCallback(onConnection);
conn->setMessageCallback(onMessage);
conn->setWriteCompleteCallback(onWriteComplete);
conn->setHighWaterMarkCallback(onHighWaterMark, 64*1024); // 设置64KB的高水位标记
// 建立连接
conn->connectEstablished();
- 发送路径如何保证线程安全且尽量直写?
对外 send 根据线程判断,loop 线程直接 sendInLoop,跨线程用 runInLoop 转移执行,避免竞态;若当前未监听写且输出缓冲空,先尝试 write 直发,剩余再入输出缓冲并开启写事件。输出缓冲清空且仍在写态时关闭写事件,并通过 queueInLoop 触发 writeCompleteCallback_ - 连接建立/销毁的状态切换如何做?
初始 kConnecting,connectEstablished 切到 kConnected、tie 到 channel 并开始读;handleClose 将状态设为 kDisconnected,禁用事件并回调上层关闭逻辑。
当 TCP 三次握手完成(内核触发 socket 的 “可写事件”),框架会调用connectEstablished,完成关键操作:
| 操作 | 目的与底层意义 |
|---|---|
| 状态切为 kConnected | 标记连接 “已就绪”,上层逻辑(如业务代码)可通过状态判断连接是否可用,放心读写数据; |
| tie 到 channel | 用 weak_ptr 绑定 TcpConnection 到管理该连接的 Channel:→ 防止 Channel 触发回调时,TcpConnection 已被析构(野指针访问);→ 回调执行前会通过 weak_ptr 检查对象是否存活,保证线程安全; |
| 开始读(enableReading) | 启用 Channel 的 “可读事件”,让 EventLoop 监听该连接的可读事件:→ 客户端发数据时, EventLoop 能检测到并触发读回调(handleRead);→ 这是 “被动接收数据” 的核心开关。 |
当内核检测到连接关闭事件(如收到 FIN 包、RST 包,或调用 close (fd)),Channel 会触发 handleClose 回调。
正常关闭:shutdown 置 kDisconnecting,若无写待发则半关闭写端;
强制关闭:forceClose/forceCloseInLoop 直接走 handleClose,
| 操作 | 目的与底层意义 |
|---|---|
| 状态设为 kDisconnected | 标记连接 “已失效”,上层逻辑可通过状态判断连接不可用,停止向该连接写数据; |
| 禁用事件(disableAll) | 禁用 Channel 的所有事件(可读 / 可写),并从 EventLoop 中移除该 Channel:→ 避免 EventLoop 继续监听已失效的 socket 事件,减少无用开销;→ 防止后续触发无效的读 / 写回调; |
| 回调上层关闭逻辑 | 调用用户注册的 closeCallback_(关闭回调):→ 让上层业务逻辑处理连接关闭后的收尾工作(如释放业务资源、记录日志、通知其他模块、统计连接数等); → 实现 “框架层清理” 与 “业务层清理” 的解耦。 |
-
Channel 事件如何绑定到连接逻辑?用户回调何时触发?
构造时把读/写/关闭/错误事件分别绑定到对应成员函数,回调里均 assertInLoopThread 保证在所属 loop 执行。
读事件到达后交给 messageCallback_;状态变更(建立/关闭)触发 connectionCallback_;高水位与写完成分别在发送路径/写事件中通过 queueInLoop 调用 -
输入缓冲如何读取?输出缓冲何时启用/停用?
handleRead 调用 inputBuffer_.readFd 读 socket 数据,错误返回保存 errno,读到数据后转交 messageCallback_。
写未完或写阻塞时将剩余数据 append 到outputBuffer_并打开写事件;写事件消费至空时关闭写事件,必要时触发高水位/写完成或执行延迟的shutdownInLoop。
tcpserver
// TcpServer.h
class TcpServer : noncopyable {
private:
// 按照初始化顺序声明成员变量
EventLoop* loop_; // 事件循环
const std::string ipPort_; // 监听的IP和端口
const std::string name_; // 服务器名称
std::unique_ptr acceptor_; // 接收器
std::shared_ptr threadPool_; // 线程池
ConnectionCallback connectionCallback_; // 连接回调
MessageCallback messageCallback_; // 消息回调
WriteCompleteCallback writeCompleteCallback_; // 写完成回调
ThreadInitCallback threadInitCallback_; // 线程初始化回调
std::atomic_bool started_; // 服务器是否已启动
int nextConnId_; // 下一个连接ID
ConnectionMap connections_; // 连接表
};
-
为什么构造时要检查 mainLoop 非空?
通过 CheckLoopNotNull 在构造入口做硬校验,避免后续空指针崩溃 -
start() 如何做到幂等且线程安全?
用 started_ 的原子交换防重复启动;首次启动先拉起 EventLoopThreadPool,再把 Acceptor::listen 投递到主 loop 执行 -
新连接如何选择 IO 线程并生成名称?
getNextLoop() 采用 round-robin 取子 loop,连接名拼接 name-ipPort#id 并自增 nextConnId_ -
TcpConnection 是如何与业务回调串起来的?
创建 TcpConnection 后,把连接/消息/写完成回调下发给它,并设置关闭回调指向 TcpServer::removeConnection,最终在子 loop 中调用 connectEstablished 完成 Channel 注册。 -
关闭时如何保证在正确线程清理?服务器析构时怎么确保已有连接被正确销毁?
关闭回调把删除请求转发到主 loop 的 removeConnectionInLoop,从连接表移除后,再将 connectDestroyed 投递回所属 IO 线程执行,避免跨线程直接操作 Channel。
服务器析构时,遍历 connections_,将各连接的销毁逻辑投递到对应 loop 执行,防止悬挂回调或未移除的 Channel -
TcpServer 如何把新连接交给正确的事件循环?
主 loop 上的 Acceptor 接受连接后回调 TcpServer::newConnection,通过线程池 getNextLoop() 选出子 EventLoop,再在该 loop 上构造 TcpConnection 并投递 connectEstablished 注册 Channel 事件。 -
Channel/EventLoop 在连接生命周期里各司其职?
EventLoop 负责事件分发与线程串行;每个 TcpConnection 内部持有 Channel 监听套接字的读/写/错误/关闭事件,回调再调用用户注册的业务回调(连接、消息、写完成、关闭),实现从内核事件到业务逻辑的逐层传递。
tcpclient
一个tcp客户端由下面这些部分组成。
TcpClient: 主类,负责管理连接生命周期Connector: 负责建立TCP连接TcpConnection: 维护已建立的连接EventLoop: 事件循环,处理IO事件Channel: IO事件分发器
TcpClient 作为入口,负责连接的创建、重连与销毁;它持有 Connector 去发起非阻塞 TCP 连接,一旦连接成功就创建 TcpConnection 保存套接字、读写缓冲和业务回调;
每个 TcpConnection 运行在所属 EventLoop 上,EventLoop 用 Poller 等待内核事件并将就绪事件分发给 Channel;
Channel 绑定套接字的读/写/错误/关闭回调,把底层 IO 事件上抛到 TcpConnection,再由用户回调处理,实现从连接建立、事件驱动到生命周期收尾的闭环。
class TcpClient : noncopyable {
public:
TcpClient(EventLoop* loop,
const InetAddress& serverAddr,
const std::string& nameArg);
~TcpClient();
// 连接服务器
void connect();
// 断开连接
void disconnect();
// 停止客户端
void stop();
// 获取当前连接
TcpConnectionPtr connection() const {
MutexLockGuard lock(mutex_);
return connection_;
}
EventLoop* getLoop() const { return loop_; }
bool retry() const { return retry_; }
void enableRetry() { retry_ = true; }
const std::string& name() const { return name_; }
// 设置连接回调
void setConnectionCallback(ConnectionCallback cb) {
connectionCallback_ = std::move(cb);
}
// 设置消息回调
void setMessageCallback(MessageCallback cb) {
messageCallback_ = std::move(cb);
}
// 设置写完成回调
void setWriteCompleteCallback(WriteCompleteCallback cb) {
writeCompleteCallback_ = std::move(cb);
}
private:
// 连接成功的回调
void newConnection(int sockfd);
// 移除连接
void removeConnection(const TcpConnectionPtr& conn);
EventLoop* loop_; // 事件循环
ConnectorPtr connector_; // 连接器
const std::string name_; // 客户端名称
ConnectionCallback connectionCallback_; // 连接回调
MessageCallback messageCallback_; // 消息回调
WriteCompleteCallback writeCompleteCallback_; // 写完成回调
bool retry_; // 是否重试
bool connect_; // 是否连接
int nextConnId_; // 下一个连接ID
mutable MutexLock mutex_; // 互斥锁
TcpConnectionPtr connection_; // 当前连接
};
-
TcpClient 如何做到“自动重连 + 连接状态监控”?
构造时 connector_ 绑定新连接回调;removeConnection 里在连接销毁后若 retry_ && connect_ 为真就调用 connector_->restart() 发起重连,同时 connection_ 受互斥锁保护实现状态保存与重置 -
失败与异常如何处理并记录?
getpeername/getsockname 失败时立即记录错误并返回;连接销毁/重连路径都有日志提示;连接失败的自动重试由 connector_->restart() 驱动,并通过日志标记重连目标
- 单 Reactor:适合小规模并发,响应快但并发上限低
- 单 Reactor + 线程池:适合计算密集型,可以处理更多并发
- Main + Sub Reactors:适合 IO 密集型,可以处理高并发连接
- Main + Sub + 线程池:综合表现最好,可以处理最复杂的场景









