trantor 网络库学习总结

trantor 网络库学习总结 学习周期:一个多月 覆盖范围:trantor 全部核心模块,共 18 课 一、整体架构鸟瞰 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 ┌─────────────────────────────────────────────────────────┐ │ 用户代码 / Drogon 框架 │ ├─────────────────────────────────────────────────────────┤ │ TcpServer / TcpClient │ │ • 连接管理(connSet_) • Round-Robin 分配 │ │ • TimingWheel 超时 • promise/future 优雅停止 │ ├────────────────┬────────────────────────────────────────┤ │ TcpConnection │ TaskQueue(Serial / Concurrent) │ │ • 状态机 │ • 卸载阻塞操作 │ │ • 发送队列 │ • SerialTaskQueue = EventLoopThread │ │ • TLS透明层 │ • ConcurrentTaskQueue = 线程池 │ ├────────────────┴────────────────────────────────────────┤ │ EventLoopThread / EventLoopThreadPool │ │ • 3阶段 promise/future 启动协议 │ │ • atomic round-robin 无锁分配 │ ├──────────┬──────────────┬──────────────────────────────┤ │ Acceptor │ Connector │ Resolver(DNS 异步解析) │ │ idleFd_ │ EINPROGRESS │ • NormalResolver(线程池) │ │ EMFILE │ 指数退避 │ • AresResolver(c-ares) │ ├──────────┴──────────────┴──────────────────────────────┤ │ EventLoop(Reactor 核心) │ │ loop() ← Channel ← Poller(epoll/kqueue/IOCP) │ │ runInLoop / queueInLoop / runAfter / runEvery │ │ MpscQueue<Func>:无锁任务投递 │ ├──────────────────────────┬──────────────────────────────┤ │ 定时器系统 │ 工具层 │ │ TimerQueue(最小堆) │ MsgBuffer / Logger │ │ TimingWheel(O(1) 超时) │ ObjectPool / MpscQueue │ │ timerfd / wakeupFd 驱动 │ Hash / secureRandomBytes │ └──────────────────────────┴──────────────────────────────┘ ↓ OS:epoll / kqueue / IOCP / wepoll 二、18 课核心知识点速查 阶段一:基础工具层(第 1-4 课) 第 1 课 — 日志系统 LOG_INFO << "msg" 展开为 Logger(__FILE__, __LINE__).stream(),析构时刷出 FixedBuffer<N>:栈上固定缓冲,避免日志路径的堆分配 AsyncFileLogger:前台线程写入内存队列,后台线程批量刷盘(异步、不阻塞 I/O) 自定义输出:Logger::setOutputFunction(),可对接 ELK、syslog 等 第 2 课 — 消息缓冲区 MsgBuffer 双指针设计:_readIndex / _writeIndex,中间是可读数据,右侧是可写空间 prepend 区域(8字节):预留报头空间,避免插入时移动数据 readFd():readv + 栈上 65536 字节备用缓冲,单次 syscall 读取大量数据 BufferNode 4种子类:MemBufferNode、FileBufferNodeUnix、FileBufferNodeWin、AsyncStreamBufferNode 第 3 课 — 日期时间与工具函数 Date:微秒精度时间点(int64_t microSecondsSinceEpoch_),可作定时器 key Date::now() → gettimeofday / GetSystemTimeAsFileTime NonCopyable:= delete 拷贝构造和赋值,所有核心类的基类 第 4 课 — 回调类型定义 ConnectionCallback:连接建立/断开 RecvMessageCallback:收到数据(TcpConnectionPtr + MsgBuffer*) WriteCompleteCallback:发送缓冲区清空 TimerCallback:定时器触发 阶段二:Reactor 核心(第 5-8 课) 第 5 课 — EventLoop 核心循环:epoll_wait → 分发 Channel 事件 → 执行 pendingFunctors_ wakeupFd_(eventfd/pipe):跨线程唤醒阻塞的 epoll_wait runInLoop(f):当前线程直接执行;其他线程 → queueInLoop → 唤醒 → 下轮执行 MpscQueue<Func> funcs_:任务队列用无锁 MPSC 队列,多线程投递无锁 关键不变量:EventLoop 是单线程的,所有网络操作必须在其线程执行。 ...

April 15, 2025 · 6 min · 1152 words

密码学工具 — 哈希函数 & 安全随机数

第 18 课:密码学工具 — 哈希函数 & 安全随机数 对应源文件: trantor/utils/Utilities.h — 公开 API(Hash128/160/256、所有哈希函数、secureRandomBytes) trantor/utils/Utilities.cc — 无 TLS 后端时的纯 C 实现 trantor/utils/crypto/openssl.cc — OpenSSL 后端实现 trantor/utils/crypto/botan.cc — Botan 后端实现 trantor/utils/crypto/md5.h/cc — 内置 MD5(纯 C) trantor/utils/crypto/sha1.h/cc — 内置 SHA1(纯 C,公有域) trantor/utils/crypto/sha256.h/cc — 内置 SHA256(纯 C) trantor/utils/crypto/sha3.h/cc — 内置 SHA3-256(Keccak,纯 C) trantor/utils/crypto/blake2.h/cc — 内置 BLAKE2b-256(纯 C) 一、整体架构:三层后端选择 trantor 的密码学工具采用编译期后端切换设计,同一套 API 在三种环境下对应不同实现: 1 2 3 4 5 6 7 8 9 10 11 12 13 用户代码 │ ▼ trantor::utils::md5(data, len) ← 统一 API(Utilities.h) │ ├─ USE_OPENSSL 定义时 ──→ crypto/openssl.cc (OpenSSL EVP API) ├─ USE_BOTAN 定义时 ──→ crypto/botan.cc (Botan HashFunction) └─ 两者均无时 ──────────→ Utilities.cc (内置纯 C 实现) ├─ crypto/md5.cc ├─ crypto/sha1.cc ├─ crypto/sha256.cc ├─ crypto/sha3.cc └─ crypto/blake2.cc 设计原理: ...

April 12, 2025 · 11 min · 2189 words

并发工具 — MpscQueue 无锁队列 & ObjectPool 对象池

第17 课:并发工具 — MpscQueue 无锁队列 & ObjectPool 对象池 对应源文件: trantor/utils/LockFreeQueue.h — MpscQueue<T>:无锁多生产者单消费者队列 trantor/utils/ObjectPool.h — ObjectPool<T>:共享指针驱动的对象池 一、为什么需要这两个工具? 在高性能网络库中,有两类性能瓶颈反复出现: 瓶颈 1:跨线程任务投递 EventLoop::runInLoop() 每秒可能被调用数十万次(每个连接的读写完成都要回调)。如果用 mutex 保护投递队列,大量线程争锁会造成显著延迟。 → 解决方案:MpscQueue<T> 无锁队列,多线程投递不需要 mutex。 瓶颈 2:高频内存分配 HTTP 框架每个请求都要 new HttpRequest、new HttpResponse,请求完成立刻 delete。频繁的堆分配/释放不仅慢,还会造成内存碎片。 → 解决方案:ObjectPool<T> 对象池,释放时归还而不销毁,下次直接复用。 二、MpscQueue — 无锁多生产者单消费者队列 2.1 完整实现(共 ~115 行) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 template <typename T> class MpscQueue : public NonCopyable { private: struct BufferNode { BufferNode() = default; BufferNode(const T &data) : dataPtr_(new T(data)) {} BufferNode(T &&data) : dataPtr_(new T(std::move(data))) {} T *dataPtr_; // 堆上的数据 std::atomic<BufferNode *> next_{nullptr}; }; std::atomic<BufferNode *> head_; // 指向最新插入的节点 std::atomic<BufferNode *> tail_; // 指向最旧的哨兵节点(消费端) }; 2.2 初始状态 1 2 3 4 MpscQueue() : head_(new BufferNode), // 创建哨兵节点 tail_(head_.load(std::memory_order_relaxed)) // tail = head = 哨兵 {} 初始状态: ...

April 10, 2025 · 10 min · 1947 words

DNS 解析 — Resolver、NormalResolver、AresResolver

第 16 课:DNS 解析 — Resolver、NormalResolver、AresResolver 对应源文件: trantor/net/Resolver.h — 异步 DNS 解析抽象接口 trantor/net/inner/NormalResolver.h / NormalResolver.cc — 基于 getaddrinfo 的线程池实现 trantor/net/inner/AresResolver.h — 基于 c-ares 的真异步实现 一、为什么 DNS 解析需要异步? getaddrinfo() 是系统标准 DNS 解析函数,但它是阻塞的——在 DNS 服务器响应之前,调用线程会一直挂起。 在 EventLoop 单线程模型中,如果直接调用 getaddrinfo(): EventLoop 线程被阻塞 该线程上所有其他连接的 I/O 事件、定时器全部停止响应 哪怕只是 100ms 的 DNS 查询,对游戏服务器来说都是灾难性的抖动 trantor 提供两种解决方案: 1 2 3 4 5 6 7 8 9 10 11 12 DNS 解析请求 │ ├── NormalResolver(默认) │ → 投递到 ConcurrentTaskQueue(阻塞线程池) │ → getaddrinfo() 在工作线程里阻塞 │ → 完成后回调(在工作线程里直接调用) │ └── AresResolver(c-ares 可用时) → 在 EventLoop 线程里全程非阻塞 → c-ares 管理 DNS socket,注册到 epoll → EventLoop 处理 DNS socket 的读写事件 → 完成后在 EventLoop 线程里调用回调 二、Resolver — 统一抽象接口 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Resolver { public: using Callback = std::function<void(const trantor::InetAddress&)>; using ResolverResultsCallback = std::function<void(const std::vector<trantor::InetAddress>&)>; // 工厂函数:根据编译配置选择实现 static std::shared_ptr<Resolver> newResolver( EventLoop *loop = nullptr, size_t timeout = 60); // timeout:DNS 缓存有效期(秒) // 解析单个地址 virtual void resolve(const std::string& hostname, const Callback& callback) = 0; // 解析所有地址(A/AAAA 记录,可能有多个 IP) virtual void resolve(const std::string& hostname, const ResolverResultsCallback& callback) = 0; static bool isCAresUsed(); // 当前是否在用 c-ares }; 两个回调的区别: ...

April 5, 2025 · 10 min · 1921 words

TLS 安全通信

第 15 课:TLS 安全通信 对应源文件: trantor/net/TLSPolicy.h — TLS 策略配置(证书、验证规则、ALPN 等) trantor/net/Certificate.h — 证书抽象接口 trantor/net/inner/TLSProvider.h — TLS 提供者抽象基类(策略模式) 具体实现(未深入):OpenSSLProvider.cc(OpenSSL 后端)、BotanTLSProvider.cc(Botan 后端) 一、TLS 在 trantor 中的架构 trantor 的 TLS 是完全透明的——插入到 TcpConnectionImpl 和用户代码之间,用户几乎感知不到加密的存在: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 用户代码(send/recvMsgCallback) │ ▲ │ 明文数据 │ 解密后的明文 ▼ │ ┌───────────────────────────┐ │ TLSProvider │ ← 透明加密/解密层 │ startEncryption() │ │ sendData(明文) → 密文 │ │ recvData(密文) → 明文 │ └───────────────────────────┘ │ ▲ │ TLS 密文 │ 从 socket 读到的密文 ▼ │ TcpConnectionImpl(writeRaw / readBuffer_) │ ▼ TCP socket(内核) 策略模式(Strategy Pattern) TLSProvider 是一个纯虚接口,具体的 TLS 实现(OpenSSL、Botan)是策略类: ...

April 1, 2025 · 9 min · 1902 words

任务队列 — TaskQueue、SerialTaskQueue、ConcurrentTaskQueue

第 14 课:任务队列 — TaskQueue、SerialTaskQueue、ConcurrentTaskQueue 对应源文件: trantor/utils/TaskQueue.h — 抽象基类 trantor/utils/SerialTaskQueue.h / SerialTaskQueue.cc — 串行执行队列 trantor/utils/ConcurrentTaskQueue.h / ConcurrentTaskQueue.cc — 并发线程池队列 一、为什么需要 TaskQueue? EventLoop 是单线程的,其中不能执行任何阻塞操作(数据库查询、文件 I/O、耗时计算),否则整条链路的 I/O 响应都会被拖慢。 TaskQueue 提供了一个"卸载阻塞任务"的机制: 1 2 3 4 5 6 [EventLoop 线程] [TaskQueue 线程] 收到玩家请求 │ → 投递到 TaskQueue ────────────►│ 执行 DB 查询(可阻塞) → 立即返回,处理下一个事件 │ 查询完成 │ → 回调投递回 EventLoop ◄── 收到结果,发送响应 ────────────┘ 这是异步编程的基本模式:不阻塞事件循环,把耗时操作委托给专用线程。 二、TaskQueue — 抽象基类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class TaskQueue : public NonCopyable { public: // 纯虚:子类实现具体的投递方式 virtual void runTaskInQueue(const std::function<void()> &task) = 0; virtual void runTaskInQueue(std::function<void()> &&task) = 0; virtual std::string getName() const { return ""; } // 同步执行:投递任务并阻塞等待完成(基类实现,子类免费获得) void syncTaskInQueue(const std::function<void()> &task) { std::promise<int> prom; std::future<int> fut = prom.get_future(); runTaskInQueue([&]() { task(); prom.set_value(1); // 任务完成,解锁调用方 }); fut.get(); // 阻塞等待 } }; syncTaskInQueue 的精妙之处: ...

March 29, 2025 · 8 min · 1504 words

多线程 EventLoop — EventLoopThread & EventLoopThreadPool

第 13 课:多线程 EventLoop — EventLoopThread & EventLoopThreadPool 对应源文件: trantor/net/EventLoopThread.h / EventLoopThread.cc — 在独立线程运行一个 EventLoop trantor/net/EventLoopThreadPool.h / EventLoopThreadPool.cc — EventLoop 线程池 一、为什么需要这两个类? 在第 12 课里,我们看到 TcpServer::setIoLoopNum(4) 内部创建了一个 EventLoopThreadPool。它们解决的核心问题是: 如何安全地在新线程里创建 EventLoop,并确保 EventLoop 真正开始运行后再返回给调用者? 这看起来简单,实际上有一个微妙的同步问题: EventLoop 对象必须在它将要运行的线程里创建(t_loopInThisThread 线程局部变量) 调用者拿到 EventLoop * 之前,要保证该指针有效(对象已创建) run() 返回之前,要保证 EventLoop 确实进入了 loop() 主循环(否则第一个 runInLoop 可能无法立刻执行) trantor 用三个 std::promise 精确解决了这个三阶段同步问题。 二、EventLoopThread 的三阶段启动协议 2.1 成员变量一览 1 2 3 4 5 6 7 8 std::shared_ptr<EventLoop> loop_; // EventLoop 对象(新线程里创建) std::mutex loopMutex_; // 保护 loop_ 的读写(析构时用) std::string loopThreadName_; // 线程名(prctl 设置) std::promise<std::shared_ptr<EventLoop>> promiseForLoopPointer_; // ① EventLoop 指针就绪 std::promise<int> promiseForRun_; // ② "请开始循环"信号 std::promise<int> promiseForLoop_; // ③ "循环已在运行"确认 std::once_flag once_; // 保证 run() 只执行一次 std::thread thread_; // 实际的 OS 线程 2.2 三阶段时序图 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 主线程 新线程(loopFuncs) │ │ │ EventLoopThread(name) │ │ → thread_ = std::thread(loopFuncs) │ 线程启动 │ → f = promiseForLoopPointer_.get_future() │ │ ↓ 阻塞等待 ① │ prctl(PR_SET_NAME) │ │ loop = make_shared<EventLoop>() │ 【①】│ promiseForLoopPointer_.set_value(loop) │ ← f.get() 返回 loop 指针 │ │ → this->loop_ = loop │ promiseForLoop_: queueInLoop 注册回调 │ │ f2 = promiseForRun_.get_future() │ (构造完成,loop_ 有效,但循环未开始) │ ↓ 阻塞等待 ② │ │ │ run() │ │ → std::call_once { │ │ f3 = promiseForLoop_.get_future() │ │ 【②】promiseForRun_.set_value(1) │ │ ↓ 阻塞等待 ③ │ ← f2.get() 返回 │ │ loop->loop() 开始 │ │ → 第一次 poll │ │ → doRunInLoopFuncs() │ 【③】│ → promiseForLoop_.set_value(1) │ ← f3.get() 返回 │ (循环持续运行...) │ } │ (run() 返回,EventLoop 确保在运行中) 三个 promise 的职责: ...

March 28, 2025 · 9 min · 1757 words

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