Skip to content

Latest commit

 

History

History
46 lines (38 loc) · 5.25 KB

File metadata and controls

46 lines (38 loc) · 5.25 KB

IPRegionMon 插件项目文档

1. 功能需求

本插件 (IPRegionMon) 专为 TrafficMonitor 设计,主要功能如下:

  1. IP 归属地与网络类型检测
    • 支持通过第三方 API 接口获取当前的公网 IP 地址。
    • 解析并显示 IP 对应的地理位置(国家、省份、城市)及运营商(ISP)。
    • 对 IP 进行风险或威胁类型检测(如:移动网络、数据中心、宽带、是否为代理/VPN/Tor 节点、是否存在滥用黑名单记录)。
  2. 多链路(直连与代理)并行检测
    • 插件支持双轨并行查询。一条链路直连(绕过系统代理)以获取本地真实外网 IP;另一条链路走代理(如果配置了代理),以获取代理节点的出口 IP。
    • 在任务栏主界面同时显示两者的概况(如:直:中国福建 | 代:日本Tokyo)。
  3. 节点延迟测速
    • 支持配置多个测速目标 URL(如百度、Google、Github 等)。
    • 使用 WinHTTP 对这些目标进行 HTTP 延迟测量(记录建立连接及首字节响应时间),并分别测量直连延迟和代理延迟。
  4. 悬浮提示 (Tooltip)
    • 鼠标悬停在任务栏文本时,提供详细的报告,包含直连和代理的具体 IP、完整位置、ISP、网络纯净度(类型、状态、风险分数)以及所有测速目标的延迟详情和最后更新时间。
  5. 无 UI 纯后台运行
    • (当前版本调整)插件移除了所有 MFC 弹窗配置界面,以换取极高的运行稳定性和零前台打扰。所有配置项(包括更新间隔、API 接口、测速目标等)均通过读取 IPRegionMon.ini 配置文件实现。用户需要手动修改 ini 并重启 TrafficMonitor 或重载插件来使配置生效。

2. 错误与踩坑记录 (UI 相关的历史 Bug)

在项目早期的 UI 界面开发过程中,遇到了多次致命卡死和死锁问题,特此记录以避免未来重蹈覆辙:

2.1 TrafficMonitor 全局鼠标钩子导致的拖拽卡死

  • 现象:在设置界面包含 SysListView32 列表控件时,如果用户尝试拖拽表头(Column Header)调整列宽,整个 TrafficMonitor 主程序瞬间卡死,且永远无法恢复,窗口标题变为 (未响应)
  • 原因:TrafficMonitor 作为悬浮窗软件,在底层安装了全局的鼠标钩子(Mouse Hook)来实现“按住任意位置拖动窗口”的功能。当用户在列表表头上按下鼠标并拖动时,原生的表头控件会进入一个内部的 GetMessage 死循环,等待 WM_LBUTTONUP 消息来结束拖拽。但 TrafficMonitor 的全局钩子误将该松开动作识别为主窗口的拖拽结束事件,从而拦截并吞噬了 WM_LBUTTONUP 消息。这导致表头控件永远等不到释放信号,主线程彻底死锁在内部循环中。
  • 临时解决方案:曾在底层拦截表头控件的 HDN_BEGINTRACK 通知消息,并返回 TRUE 强制禁止改变列宽。

2.2 CDialogEx 与 SysListView32 重绘死循环

  • 现象:当改变列表列宽时,即便没有全局钩子的死锁,有时也会引发界面的无限重绘风暴,导致 CPU 满载并卡死。
  • 原因CDialogEx 拥有复杂的背景重绘和主题(Theme)管理逻辑。当嵌套包含 LVS_EX_FULLROWSELECTLVS_REPORT 样式的 ListCtrl 时,控件的调整会触发父窗口背景重绘,而父窗口的重绘又再次触发子控件的刷新,形成 WM_PAINTWM_NCPAINT 的死循环。
  • 临时解决方案:必须将对话框基类降级为 CDialog,并为所有的对话框模板(Dialog Template)强行加上 WS_CLIPCHILDREN 窗口样式,以切断父子窗口间的无效重绘链。

2.3 std::wstring 多线程数据竞争 (Data Race) 引发的堆死锁

  • 现象:在点击“立即刷新”按钮或定时后台刷新触发时,整个界面偶尔或极易出现瞬间卡死 (未响应),且无法恢复。
  • 原因
    • TrafficMonitor 官方的插件架构采用了主线程和刷新线程分离的模型。其中 DataRequired()(数据准备)由独立工作线程调用,而 GetItemText() / GetTooltipInfo() 由主线程(UI线程)调用读取字符串的 c_str()
    • 早期代码在弹窗定时器中或后台测速线程中,强行使用了 SwapBuffers() 来赋值和重新分配双缓冲的 std::wstring 变量。
    • 结果造成了经典的数据竞争:UI 线程正在读取 std::wstring::c_str() 指针进行绘制,而另一个线程同时对该字符串重新赋值,导致旧内存被释放。Windows 堆分配器在处理这种并发野指针访问时发生崩溃,进而引发了 RtlAllocateHeap 内部关键节死锁,导致程序硬卡死。
  • 最终解决方案
    • 彻底理清并遵守 TrafficMonitor 的线程模型。后台测速线程在长达数秒的网络请求期间,必须绝对只使用函数局部变量
    • 只有在所有网络请求结束的最后一刻,才加锁并一次性安全地赋予全局背景变量(_bg)。
    • 取消了在 UI 层面的所有强制同步操作。
    • 考虑到 MFC UI 在 TrafficMonitor 插件生态下存在过多未知冲突和同步风险,最终决定整体切除 UI 层,将程序架构简化为纯后台执行引擎,以获得极致的稳定性。