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

IO 与无锁序列:cppcoro 的网络文件 I/O,以及 LMAX Disruptor 的协程化

IO 与无锁序列:cppcoro 的网络文件 I/O,以及 LMAX Disruptor 的协程化 本专栏文章:拆开 cppcoro 给你看 · 第 6 篇(完结篇) 这是系列的最后一篇,也是最"硬"的一篇。要搞懂两件事:① cppcoro 怎么把 Windows IOCP 封装成协程友好的文件/网络 I/O 接口;② cppcoro 借鉴 LMAX Disruptor 的无锁序列怎么协程化,让消费者不是忙等而是挂起等待。 注:IOCP 基础、win32_overlapped_operation CRTP 模式、四态取消状态机、cancellation_token 三层模型、io_service 和 async_scope 在上一篇 Layer 3 中已经讲过了。本篇聚焦文件 I/O 类型层次、socket 的协程封装,以及无锁序列原语的设计推导。 1. 文件 I/O 类型体系 1.1 类层次结构 1 2 3 4 5 6 file (基类: size(), 持有 Windows HANDLE) ├── readable_file (抽象: read(offset, buffer, size)) │ └── read_only_file (具体类: open() 工厂方法) ├── writable_file (抽象: write(offset, buffer, size), set_size()) │ └── write_only_file (具体类: open() 工厂方法) └── read_write_file (多继承: readable_file + writable_file) 1.2 为什么用静态工厂? 1 2 3 4 5 static read_only_file open( io_service& ioService, const path& path, file_share_mode shareMode = file_share_mode::read, file_buffering_mode bufferingMode = file_buffering_mode::default_); Windows 上打开文件涉及多个系统调用(CreateFile + CreateIoCompletionPort),且可能失败。工厂方法把全部设置逻辑封装在一处,返回值语义对象,调用者负责生命周期。 ...

November 30, 2024 · 6 min · 1080 words

协程调度器到底在调度什么?从 inline_scheduler 到工作窃取线程池

协程调度器到底在调度什么?从 inline_scheduler 到工作窃取线程池 本专栏文章:拆开 cppcoro 给你看 · 第 5 篇 前几篇我们一直在协程"内部"兜圈子——task 怎么设计、同步原语怎么写、并发怎么编排。但有一个问题一直没认真回答:协程在哪个线程上执行? co_await 之后你可能在任何线程上恢复——这取决于谁调了 handle.resume()。大部分时候这不是问题,但有时你确实想控制恢复的线程。这就是调度器的概念。 本文分三层递进: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Layer 1: 调度器概念(30 分钟能读完) ├─ inline_scheduler — 22 行,不调度也是调度 ├─ round_robin_scheduler — 125 行,对称转移调度的杰作 └─ schedule_on vs resume_on Layer 2: 工作窃取线程池(核心) ├─ 本地 LIFO 队列 ├─ 全局 MPSC 队列 ├─ 偷取 (work-stealing) └─ 睡眠/唤醒协议 Layer 3: IOCP + 取消 + 文件/网络 I/O ├─ OVERLAPPED 嵌入 awaiter (CRTP) ├─ 可取消操作的四态状态机 ├─ cancellation_token/source/registration ├─ io_service + io_work_scope └─ async_scope Layer 1:调度器概念 问题:协程在哪个线程上执行? 1 2 3 4 5 6 task<> my_coro() { std::cout << "I'm on thread " << std::this_thread::get_id() << std::endl; co_await some_io(); std::cout << "Now I'm on thread " << std::this_thread::get_id() << std::endl; // ↑ 恢复后可能在完全不同的线程上! } 1.1 inline_scheduler —— “不调度"也是调度 1 2 3 4 5 6 class inline_scheduler { public: std::suspend_never schedule() const noexcept { return {}; // 不挂起,原地继续 } }; 返回 suspend_never 意味着 co_await scheduler.schedule() 等价于什么都不做。但它在泛型代码中有实际意义——你的函数接受一个调度器参数,inline_scheduler 就是"不需要调度的调度器”。 ...

November 25, 2024 · 6 min · 1067 words