不能直接用裸指针管理重传定时器,因其无生命周期语义,易致悬垂指针或重复释放;应结合weak_ptr、类型安全转换与双重检查保障内存安全与定时器有效性。

为什么不能直接用裸指针管理重传定时器
裸指针(timer_struct*)本身不携带生命周期语义,在协议栈这种多状态、异步回调密集的场景里,极易引发悬垂指针或重复释放——比如TCP超时回调触发时,连接可能已被上层主动关闭,但定时器回调仍拿着旧指针去访问已析构的tcpcb对象。这不是设计疏漏,而是C++原生指针在资源归属上完全沉默。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 所有定时器结构体必须绑定明确的所有者(如
tcp_sock实例),且所有权转移需同步更新定时器中的反向指针 - 避免在定时器回调函数内直接解引用传入的
void*参数——它大概率是裸指针,应先通过static_cast转为带类型安全的句柄(如tcp_sock*),再检查if (sock && sock->is_valid()) - Linux内核协议栈用
struct timer_list配合from_timer()宏做类型安全转换;用户态可仿照实现,例如定义container_of_timer辅助函数
如何用std::unique_ptr配合定时器队列
std::unique_ptr能强制表达“单点拥有权”,但它不能直接用于回调上下文——因为定时器到期时,你无法保证unique_ptr还在作用域中。真正可行的是把它作为定时器载体的成员,而非定时器句柄本身。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 定义
struct retrans_timer { std::unique_ptr<tcp_sock> owner; int seq; ... };</tcp_sock>,把owner设为weak_ptr更稳妥(见下节) - 注册定时器时,用
std::make_unique<retrans_timer>(shared_from_this(), ...)</retrans_timer>构造,并将原始指针传给底层定时器API(如setitimer或libevent的evtimer_add) - 回调函数里收到该指针后,立即用
static_cast<retrans_timer>(arg)</retrans_timer>还原,再通过owner.lock()尝试升级为shared_ptr——失败即说明连接已销毁,直接return
为什么std::weak_ptr比std::shared_ptr更适合做定时器里的连接引用
shared_ptr会延长tcp_sock生命周期,导致连接无法被及时回收:即使应用层调用了close(),只要还有未触发的重传定时器持有shared_ptr,对象就一直存活,内存泄漏+状态滞留。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 在
tcp_sock类中声明std::shared_ptr<tcp_sock> self_;</tcp_sock>,构造时用self_ = shared_from_this(); - 定时器结构体内存持
std::weak_ptr<tcp_sock> weak_owner_;</tcp_sock>,注册前赋值为self_ - 回调中调用
auto locked = weak_owner_.lock(); if (!locked) return;—— 这一步天然规避了use-after-free - 注意:
weak_ptr::lock()不是原子操作,若协议栈运行在多线程环境(如DPDK用户态栈),需确保tcp_sock析构是线程安全的(例如析构前调用cancel_all_timers())
libevent或muduo中重传定时器的实际写法
以muduo为例:TimerId本质是int64_t序列号,底层用std::map索引Timer*,而Timer内部存储的是std::function<void></void>。问题在于:这个lambda捕获this时若用[this],就是裸指针;必须改用[ptr = shared_from_this()]()。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- muduo用户应在创建重传定时器时显式捕获
shared_from_this(),例如:loop_->runAfter(timeout, [ptr = shared_from_this(), seq] { ptr->onRetransmit(seq); }); - libevent用户若用
event_set(),回调函数签名固定为void(*)(int, short, void*),此时void*应指向一个堆分配的struct { weak_ptr<tcp_sock> w; int seq; }</tcp_sock>,而非tcp_sock* - 不要依赖定时器取消函数(如
event_del())的返回值判断是否成功——它只表示是否从事件队列移除,不保证回调未执行。真正安全的做法是在回调开头加双重检查
最易被忽略的一点:重传定时器的超时时间不是固定值,而是随RTT动态变化的。这意味着每次重置定时器时,你不仅要更新时间值,还要确保旧定时器已彻底失效——否则可能两个定时器同时触发,造成重复重传。这要求对定时器句柄的管理比内存管理更严格。


















