TcpServer & TcpClient — 网络通信的两端

第 12 课:TcpServer & TcpClient — 网络通信的两端 对应源文件: trantor/net/TcpServer.h / TcpServer.cc — TCP 服务器 trantor/net/TcpClient.h / TcpClient.cc — TCP 客户端 一、两个类的定位 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ┌──────────────────────────────────────────┐ │ TcpServer │ │ loop_(Accept 线程) │ │ Acceptor(监听 socket) │ │ connSet_(所有连接的生命周期管理) │ │ ioLoops_(I/O 线程池) │ │ timingWheelMap_(每个 I/O 线程一个时间轮)│ └──────────────────────────────────────────┘ │ newConnection() ▼ TcpConnectionImpl(每个连接一个) 运行在 ioLoops_ 中的某个 EventLoop ┌──────────────────────────────────────────┐ │ TcpClient │ │ loop_(单一 EventLoop) │ │ connector_(发起连接) │ │ connection_(当前连接,mutex_ 保护) │ └──────────────────────────────────────────┘ TcpServer 是一对多:管理一个监听端口和大量并发连接。 TcpClient 是一对一:管理一条到服务器的连接(可断线重连)。 ...

March 25, 2025 · 11 min · 2242 words

TcpConnection — 连接生命周期

第 11 课:TcpConnection — 连接生命周期 对应源文件: trantor/net/TcpConnection.h — 公共抽象接口(用户使用) trantor/net/inner/TcpConnectionImpl.h / TcpConnectionImpl.cc — 内部实现 一、设计:接口与实现分离 1 2 3 4 5 6 7 8 9 10 11 12 TcpConnection(纯虚基类) │ 定义公共 API:send/sendFile/shutdown/forceClose/setContext... │ 存储回调:recvMsgCallback_/connectionCallback_/closeCallback_... │ └── TcpConnectionImpl(具体实现) 继承 TcpConnection + NonCopyable + enable_shared_from_this │ ├── Channel(ioChannelPtr_)— fd 事件分发 ├── Socket(socketPtr_) — RAII fd 管理 ├── MsgBuffer readBuffer_ — 接收缓冲区 ├── list<BufferNodePtr> writeBufferList_ — 发送队列 └── TLSProvider(可选) — 透明 TLS 加密层 为什么分离接口和实现? ...

March 22, 2025 · 10 min · 1966 words

Acceptor & Connector — 连接的两端

第 10 课:Acceptor & Connector — 连接的两端 对应源文件: trantor/net/inner/Acceptor.h / Acceptor.cc — 服务端:监听并接受连接 trantor/net/inner/Connector.h / Connector.cc — 客户端:主动发起连接(含重连) 一、两个类在架构中的角色 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ┌──────────────────────────┐ │ TcpServer │ │ ┌─────────────────────┐ │ │ │ Acceptor │ │ │ │ Socket(listenFd) │ │ │ │ Channel │ │ │ └─────────────────────┘ │ └──────────────────────────┘ ↑ listen 客户端发起 connect ↓ accept → 回调 newConnectionCallback_ ┌──────────────────────────┐ │ TcpClient │ │ ┌─────────────────────┐ │ │ │ Connector │ │ │ │ (非阻塞 connect) │ │ │ │ 指数退避重连 │ │ │ └─────────────────────┘ │ └──────────────────────────┘ Acceptor 和 Connector 都不拥有连接——它们的职责是把一个已就绪的 fd 交给上层(TcpServer/TcpClient),由上层创建 TcpConnection 对象来管理该 fd 的后续生命周期。 ...

March 21, 2025 · 11 min · 2276 words

网络地址与 Socket 封装

第 9 课:网络地址与 Socket 封装 对应源文件: trantor/net/InetAddress.h / InetAddress.cc — IPv4/IPv6 地址封装 trantor/net/inner/Socket.h / Socket.cc — 跨平台 Socket RAII 封装 一、两个类在架构中的位置 1 2 3 4 5 6 7 TcpServer / TcpClient │ ▼ Acceptor / Connector │ ├─ InetAddress ← 描述"连谁/绑哪里" └─ Socket ← 持有实际的系统 fd,负责创建/配置/关闭 这两个类是"最底层的 C++ 包装": InetAddress:把 struct sockaddr_in/in6 包成一个类型安全的 C++ 对象 Socket:RAII 管理 socket fd,把 setsockopt/bind/listen/accept 包成成员函数 二、InetAddress — 双协议地址封装 2.1 核心存储 1 2 3 4 5 6 7 // InetAddress.h(精简) union { struct sockaddr_in addr_; // IPv4:16 字节 struct sockaddr_in6 addr6_; // IPv6:28 字节 }; bool isIpV6_; // 区分当前存的是哪种 bool isUnspecified_; // 是否是"未指定地址"(0.0.0.0 / ::) 为什么用 union? ...

March 20, 2025 · 8 min · 1617 words

定时器系统(Timer + TimerQueue + TimingWheel)

第 8 课:定时器系统(Timer + TimerQueue + TimingWheel) 对应源文件: trantor/net/inner/Timer.h/.cc — 单个定时器对象 trantor/net/inner/TimerQueue.h/.cc — 定时器优先队列(最小堆) trantor/utils/TimingWheel.h/.cc — 分级时间轮(高并发连接超时) 一、三个类的分工 1 2 3 4 5 6 Timer — 单个定时器的数据:到期时间、回调、是否重复 │ TimerQueue — 管理所有定时器,最小堆,驱动到期回调 │ TimingWheel — 用于大量连接的超时检测,O(1) 插入/删除 基于 TimerQueue 的 runEvery 驱动 两套定时器,用途不同: TimerQueue:精确定时,适合少量定时任务(heartbeat 广播、延迟关闭等) TimingWheel:粗粒度超时,适合万级连接的空闲超时检测 二、Timer:单个定时器 2.1 数据成员 1 2 3 4 5 6 TimerCallback callback_; // 到期执行的函数 TimePoint when_; // 到期时间(steady_clock,单调时钟) TimeInterval interval_; // 重复间隔(microseconds,为 0 表示一次性) bool repeat_; // = (interval_.count() > 0) TimerId id_; // 全局唯一 ID(原子递增) static std::atomic<TimerId> timersCreated_; // 全局计数器 2.2 ID 生成 1 2 3 4 5 // Timer.cc 第 21 行 std::atomic<TimerId> Timer::timersCreated_ = ATOMIC_VAR_INIT(InvalidTimerId); // 初始值 0 // 构造时(Timer.cc 第 29 行) id_(++timersCreated_) // 原子前自增,从 1 开始 每个 Timer 对象构造时自动分配唯一 ID,多线程安全,不需要锁。InvalidTimerId = 0 作为哨兵值。 ...

March 18, 2025 · 10 min · 2008 words

Poller — I/O 多路复用

第 7 课:Poller — I/O 多路复用 对应源文件: trantor/net/inner/Poller.h / Poller.cc — 抽象基类 + 工厂函数 trantor/net/inner/poller/EpollPoller.h/.cc — Linux/Windows 实现 trantor/net/inner/poller/KQueue.h/.cc — macOS/BSD 实现 trantor/net/inner/poller/PollPoller.h/.cc — 其他 Unix 兜底实现 一、Poller 在架构中的位置 1 2 3 4 5 6 7 EventLoop │ poll(timeoutMs, &activeChannels) ▼ Poller(抽象基类) ├── EpollPoller ← Linux / Windows(wepoll) ├── KQueue ← macOS / FreeBSD / OpenBSD └── PollPoller ← 其他 Unix(兜底) Poller 是桥接模式的经典应用:上层 EventLoop 只依赖抽象基类 Poller,底层平台差异完全被屏蔽。EventLoop 的代码里看不到任何 epoll_wait 或 kevent。 ...

March 15, 2025 · 10 min · 2076 words

Channel — 事件通道

第 6 课:Channel — 事件通道 对应源文件: trantor/net/Channel.h — 公共接口 trantor/net/Channel.cc — 实现(仅 112 行) 一、Channel 是什么? Channel 是 trantor Reactor 模式中的中间层,它把一个文件描述符(fd)的事件管理和回调分发封装在一起。 1 2 fd ──── Channel ──── Poller(注册/更新感兴趣的事件) └────── EventLoop(事件就绪后,回调分发) 三个关键概念: events_:当前感兴趣的事件(告诉 Poller 我想监听什么) revents_:Poller 返回的实际发生的事件(poll/epoll 填写) 回调函数:事件发生后调用哪个函数 Channel 不拥有 fd——fd 的生命周期由 Socket 对象管理,Channel 只是"贴在" fd 上的事件管理标签。 二、事件标志位 1 2 3 4 // Channel.cc 第 31-34 行 const int Channel::kNoneEvent = 0; const int Channel::kReadEvent = POLLIN | POLLPRI; // 可读 + 紧急数据 const int Channel::kWriteEvent = POLLOUT; // 可写 为什么用 POLLIN/POLLOUT 而不是 EPOLLIN/EPOLLOUT? ...

March 12, 2025 · 8 min · 1684 words

EventLoop — 事件循环

第 5 课:EventLoop — 事件循环 对应源文件: trantor/net/EventLoop.h — 公共接口 trantor/net/EventLoop.cc — 实现 trantor/utils/LockFreeQueue.h — 无锁任务队列(MpscQueue) 一、EventLoop 是什么? EventLoop 是 trantor 整个框架的心脏,也是 Reactor 模式的核心。 一句话定义:一个线程,一个循环,监听所有 I/O 事件和定时器,串行处理所有回调。 1 2 3 4 5 6 7 8 9 ┌─────────────────────────────────────────┐ │ EventLoop::loop() │ │ │ │ while (!quit_) { │ │ ① poller_->poll(timeout) ←阻塞等事件│ │ ② 处理所有就绪的 Channel 回调 │ │ ③ doRunInLoopFuncs() ← 执行投递任务 │ │ } │ └─────────────────────────────────────────┘ 三个核心原则: One loop per thread:一个 EventLoop 只属于一个线程,且一个线程最多一个 EventLoop 所有 I/O 操作在 Loop 线程执行:不跨线程操作 socket 跨线程操作必须通过 runInLoop/queueInLoop:把任务投递进去,由 Loop 线程执行 二、核心主循环 loop() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 // EventLoop.cc 第 204-266 行(精简版) void EventLoop::loop() { assert(!looping_); assertInLoopThread(); // 必须在 Loop 线程调用 looping_.store(true, std::memory_order_release); auto loopFlagCleaner = makeScopeExit( // RAII:确保退出时清 looping_ [this]() { looping_.store(false, ...); }); while (!quit_.load(std::memory_order_acquire)) { activeChannels_.clear(); // ① 阻塞等待 I/O 事件(最长 10 秒) #ifdef __linux__ poller_->poll(kPollTimeMs, &activeChannels_); // Linux: epoll #else poller_->poll(timerQueue_->getTimeout(), &activeChannels_); timerQueue_->processTimers(); // 非 Linux: 手动处理定时器 #endif // ② 处理所有就绪 Channel 的回调 eventHandling_ = true; for (auto *channel : activeChannels_) { currentActiveChannel_ = channel; channel->handleEvent(); // 分发读/写/错误回调 } currentActiveChannel_ = nullptr; eventHandling_ = false; // ③ 执行跨线程投递进来的任务 doRunInLoopFuncs(); } // 退出后执行 runOnQuit 注册的清理函数 Func f; while (funcsOnQuit_.dequeue(f)) f(); } 主循环的三个阶段 1 2 3 4 5 6 7 8 9 10 11 12 Phase 1: poller_->poll() └─ 调用 epoll_wait(最多等 10 秒) └─ 返回:活跃的 Channel 列表(有读/写事件的 fd) Phase 2: handleEvent() └─ 遍历活跃 Channel,各自回调 └─ 例如:socket 可读 → RecvMessageCallback socket 可写 → WriteCompleteCallback Phase 3: doRunInLoopFuncs() └─ 消费 funcs_ 队列(MpscQueue),执行所有投递进来的任务 └─ 例如:其他线程调用了 queueInLoop(f) 三、wakeupFd:打破阻塞的关键 问题:epoll_wait 在等待时是阻塞的,如果其他线程此时投递了一个任务(queueInLoop),Loop 线程要等最多 10 秒才能执行。 ...

March 10, 2025 · 9 min · 1848 words

回调类型定义(callbacks.h)

第 4 课:回调类型定义(callbacks.h) 对应源文件: trantor/net/callbacks.h — 所有网络回调 typedef trantor/net/TcpConnection.h — 回调的注册接口与存储位置(辅助理解) 一、为什么要先读这个文件? callbacks.h 只有 46 行,却是后续所有网络模块的词汇表。 从第 5 课开始,你会在 EventLoop、TcpServer、TcpClient 里频繁看到这些类型名。如果不先建立印象,每次碰到 RecvMessageCallback 都要回头查,打断阅读节奏。 先把这张表背熟,后面读代码会顺畅得多。 二、完整回调类型表 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // callbacks.h 全文(去掉注释) using TimerCallback = std::function<void()>; using TcpConnectionPtr = std::shared_ptr<TcpConnection>; using RecvMessageCallback = std::function<void(const TcpConnectionPtr &, MsgBuffer *)>; using ConnectionErrorCallback = std::function<void()>; using ConnectionCallback = std::function<void(const TcpConnectionPtr &)>; using CloseCallback = std::function<void(const TcpConnectionPtr &)>; using WriteCompleteCallback = std::function<void(const TcpConnectionPtr &)>; using HighWaterMarkCallback = std::function<void(const TcpConnectionPtr &, const size_t)>; using SSLErrorCallback = std::function<void(SSLError)>; using SockOptCallback = std::function<void(int)>; 三、逐一解析 3.1 TcpConnectionPtr — 连接的生命线 1 using TcpConnectionPtr = std::shared_ptr<TcpConnection>; 这不是回调,是基础类型,但几乎所有回调都把它作为第一个参数。 ...

March 9, 2025 · 7 min · 1402 words

日期时间 & 工具函数

第 3 课:日期时间 & 工具函数 对应源文件: trantor/utils/Date.h / Date.cc — 微秒精度时间点 trantor/utils/Funcs.h — 通用辅助函数(字节序 + 字符串分割) trantor/utils/NonCopyable.h — 禁拷贝基类 一、Date:微秒精度时间点 1.1 设计思路 Date 本质上只是一个 int64_t 的包装: 1 2 3 4 5 6 class Date { private: int64_t microSecondsSinceEpoch_{0}; // 从 1970-01-01 00:00:00 UTC 至今的微秒数 public: static constexpr long MICRO_SECONDS_PER_SEC = 1000000LL; }; 为什么用微秒而不是毫秒/秒? 定时器精度要求微秒级(定时器误差 < 1ms) 日志时间戳需要显示到微秒(高频事件排查) int64_t 表示微秒可以撑到 292471年,溢出不是问题 1.2 获取当前时间:Date::now() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // Date.cc 第 52-65 行 const Date Date::date() { #ifndef _WIN32 struct timeval tv; gettimeofday(&tv, NULL); // Linux: 系统调用,精度 ~1 微秒 int64_t seconds = tv.tv_sec; return Date(seconds * MICRO_SECONDS_PER_SEC + tv.tv_usec); #else // Windows: GetLocalTime → mktime → 精度只到毫秒(wMilliseconds * 1000) timeval tv; gettimeofday(&tv, NULL); // Windows 版本在同文件里实现 ... #endif } Linux 路径:gettimeofday 是 VDSO 调用(Virtual Dynamic Shared Object),在用户空间直接读取内核映射的时钟,不陷入内核,速度极快(几十纳秒)。 ...

March 8, 2025 · 8 min · 1603 words