概述:Windows 网卡事件监控技术,包括网络状态变化检测、连接事件通知等。
0x01、网络监控概述
Windows 提供了多种监控网络状态变化的 API,主要包括:
| API | 说明 | 需要权限 |
|---|---|---|
| NLA(Network Location Awareness) | 网络位置感知,监控网络连接变化 | 无 |
| NotifyAddrChange | 监控 IP 地址变化 | 无 |
| WMI | Windows 管理规范,全面监控 | 无 |
| IP Helper API | 网络相关 API | 无 |
| 注册表通知 | 监控注册表网络项变化 | 无 |
0x02、NLA Network List Manager
Network List Manager API 是监控网络状态变化的标准方式。
核心接口
| 接口 | 说明 |
|---|---|
INetworkListManager | 网络列表管理器主接口 |
INetworkListManagerEvents | 网络列表事件接口 |
INetworkEvents | 网络事件接口 |
IConnectionPointContainer | 连接点容器 |
基本使用流程
- CoCreateInstance 创建 NetworkListManager
- QueryInterface 获取 INetworkListManager
- 提供事件监听接口(COM 回调)
- 注册事件通知
- 处理网络变化事件
- 注销通知
0x03、完整代码实现
头文件部分
#pragma once
#include <netlistmgr.h>
#include <atomic>
#include <functional>
// 全局网络状态标志
std::atomic_bool g_bNetworkOnline(true);
// 网络列表管理器事件类
class CNetworkListManagerEvent : public INetworkListManagerEvents
{
public:
CNetworkListManagerEvent(std::function<void(bool)> handler)
: m_ref(1)
, changeHandler_(handler)
{
}
~CNetworkListManagerEvent() {}
// IUnknown 接口实现
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject)
{
if (IsEqualIID(riid, IID_IUnknown) ||
IsEqualIID(riid, IID_INetworkListManagerEvents)) {
*ppvObject = static_cast<INetworkListManagerEvents*>(this);
AddRef();
return S_OK;
}
*ppvObject = nullptr;
return E_NOINTERFACE;
}
ULONG STDMETHODCALLTYPE AddRef()
{
return (ULONG)InterlockedIncrement(&m_ref);
}
ULONG STDMETHODCALLTYPE Release()
{
LONG ref = InterlockedDecrement(&m_ref);
if (ref == 0) delete this;
return (ULONG)ref;
}
// INetworkListManagerEvents 接口实现
HRESULT STDMETHODCALLTYPE ConnectivityChanged(NLM_CONNECTIVITY newConnectivity)
{
if (newConnectivity == NLM_CONNECTIVITY_DISCONNECTED)
{
// 网络断开
if (changeHandler_) changeHandler_(false);
}
else if ((newConnectivity & NLM_CONNECTIVITY_IPV4_INTERNET) ||
(newConnectivity & NLM_CONNECTIVITY_IPV6_INTERNET))
{
// 网络连接到互联网
if (changeHandler_) changeHandler_(true);
}
return S_OK;
}
private:
LONG m_ref;
std::function<void(bool)> changeHandler_;
};网络监控器类
#include <windows.h>
#include <netlistmgr.h>
#include <thread>
#include <functional>
#include <iostream>
class CNetworkMonitor
{
public:
CNetworkMonitor() = default;
~CNetworkMonitor() = default;
// 启动监控线程
void StartMonitor()
{
std::thread monitorThread(&CNetworkMonitor::InitNetworkMonitor, this);
monitorThread.detach();
}
// 停止监控
void StopMonitor()
{
// 实际实现需要设置退出标志并通知消息循环
}
private:
// 网络状态变化回调
void NetworkStatusChangedCallback(bool online)
{
g_bNetworkOnline = online;
if (online) {
std::cout << "Network connected" << std::endl;
OnNetworkConnected();
} else {
std::cout << "Network disconnected" << std::endl;
OnNetworkDisconnected();
}
}
// 网络连接处理
void OnNetworkConnected()
{
// 实现网络连接后的业务逻辑
// 如:重连服务器、发送心跳等
}
// 网络断开处理
void OnNetworkDisconnected()
{
// 实现网络断开后的业务逻辑
// 如:清理连接、等待重连等
}
// 初始化网络监控
void InitNetworkMonitor()
{
CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
IUnknown* pUnknown = nullptr;
HRESULT hr = CoCreateInstance(CLSID_NetworkListManager, nullptr,
CLSCTX_ALL, IID_IUnknown,
reinterpret_cast<void**>(&pUnknown));
if (SUCCEEDED(hr))
{
INetworkListManager* pNLM = nullptr;
hr = pUnknown->QueryInterface(IID_INetworkListManager,
reinterpret_cast<void**>(&pNLM));
if (SUCCEEDED(hr))
{
// 检查当前网络连接状态
VARIANT_BOOL isConnected = VARIANT_FALSE;
hr = pNLM->get_IsConnectedToInternet(&isConnected);
if (SUCCEEDED(hr))
{
std::cout << "Current internet status: "
<< (isConnected ? "Connected" : "Disconnected")
<< std::endl;
}
// 注册网络变化事件
IConnectionPointContainer* pCPC = nullptr;
hr = pNLM->QueryInterface(IID_IConnectionPointContainer,
reinterpret_cast<void**>(&pCPC));
if (SUCCEEDED(hr))
{
IConnectionPoint* pCP = nullptr;
hr = pCPC->FindConnectionPoint(IID_INetworkListManagerEvents, &pCP);
if (SUCCEEDED(hr))
{
DWORD cookie = 0;
// 创建事件处理对象
auto callback = std::bind(&CNetworkMonitor::NetworkStatusChangedCallback,
this, std::placeholders::_1);
CNetworkListManagerEvent* pEvent = new CNetworkListManagerEvent(callback);
if (pEvent)
{
hr = pCP->Advise(static_cast<IUnknown*>(pEvent), &cookie);
if (SUCCEEDED(hr))
{
std::cout << "Network monitor started successfully" << std::endl;
// 消息循环处理事件
MSG msg;
while (GetMessage(&msg, nullptr, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// 注销事件
pCP->Unadvise(cookie);
} else {
std::cerr << "Failed to advise event: 0x" << std::hex << hr << std::endl;
}
pEvent->Release();
}
pCP->Release();
} else {
std::cerr << "Failed to find connection point: 0x" << std::hex << hr << std::endl;
}
pCPC->Release();
} else {
std::cerr << "Failed to query connection point container: 0x" << std::hex << hr << std::endl;
}
pNLM->Release();
} else {
std::cerr << "Failed to query NetworkListManager: 0x" << std::hex << hr << std::endl;
}
pUnknown->Release();
} else {
std::cerr << "Failed to create NetworkListManager: 0x" << std::hex << hr << std::endl;
}
CoUninitialize();
}
};0x04、使用示例
int main()
{
CNetworkMonitor monitor;
std::cout << "Starting network monitor..." << std::endl;
monitor.StartMonitor();
// 模拟程序运行
std::this_thread::sleep_for(std::chrono::minutes(5));
return 0;
}0x05、其他监控方法
NotifyAddrChange
监控 IP 地址变化的简单方法:
#include <winsock2.h>
#include <iphlpapi.h>
void MonitorIPAddressChange()
{
HANDLE hEvent = WSACreateEvent();
while (true)
{
DWORD ret = NotifyAddrChange(&hEvent, nullptr);
if (ret == WAIT_OBJECT_0 + 1)
{
// 地址列表已更改,获取新的地址
PIP_ADAPTER_INFO pAdapterInfo = nullptr;
ULONG ulOutBufLen = 0;
GetAdaptersInfo(pAdapterInfo, &ulOutBufLen);
pAdapterInfo = reinterpret_cast<PIP_ADAPTER_INFO>(new BYTE[ulOutBufLen]);
if (GetAdaptersInfo(pAdapterInfo, &ulOutBufLen) == ERROR_SUCCESS)
{
PIP_ADAPTER_INFO pAdapter = pAdapterInfo;
while (pAdapter)
{
std::cout << "Adapter: " << pAdapter->Description << std::endl;
std::cout << "IP: " << pAdapter->IpAddressList.IpAddress.String << std::endl;
pAdapter = pAdapter->Next;
}
}
delete[] pAdapterInfo;
}
else if (ret == WAIT_OBJECT_0)
{
// 线程退出
break;
}
}
WSACloseEvent(hEvent);
}WMI 监控
使用 WMI Monitoring 更全面地监控网络:
#include <wbemidl.h>
#include <comdef.h>
#include <wmiutils.h>
void MonitorNetworkWMI()
{
CoInitializeEx(0, COINIT_MULTITHREADED);
IWbemLocator* pLocator = nullptr;
HRESULT hr = CoCreateInstance(CLSID_WbemLocator, 0,
CLSCTX_INPROC_SERVER,
IID_IWbemLocator,
reinterpret_cast<void**>(&pLocator));
if (SUCCEEDED(hr))
{
IWbemServices* pService = nullptr;
hr = pLocator->ConnectServer(_bstr_t(L"ROOT\\CIMV2"), nullptr,
nullptr, nullptr, 0, nullptr, nullptr,
&pService);
if (SUCCEEDED(hr))
{
// 查询网络适配器
IEnumWbemClassObject* pEnumerator = nullptr;
hr = pService->ExecQuery(_bstr_t(L"WQL"),
_bstr_t(L"SELECT * FROM Win32_NetworkAdapterConfiguration WHERE IPEnabled = TRUE"),
WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
nullptr, &pEnumerator);
if (SUCCEEDED(hr))
{
IWbemClassObject* pObject = nullptr;
ULONG uReturned = 0;
while (pEnumerator->Next(WBEM_INFINITE, 1, &pObject, &uReturned) == S_OK)
{
VARIANT vtProp;
// 获取适配器描述
hr = pObject->Get(L"Description", 0, &vtProp, nullptr, nullptr);
if (SUCCEEDED(hr) && vtProp.vt == VT_BSTR)
{
std::wcout << L"Adapter: " << vtProp.bstrVal << std::endl;
VariantClear(&vtProp);
}
// 获取 IP 地址
hr = pObject->Get(L"IPAddress", 0, &vtProp, nullptr, nullptr);
if (SUCCEEDED(hr) && vtProp.vt == (VT_ARRAY | VT_BSTR))
{
// 处理 IP 地址数组
VariantClear(&vtProp);
}
pObject->Release();
}
pEnumerator->Release();
}
pService->Release();
}
pLocator->Release();
}
CoUninitialize();
}0x06、注意事项
COM 初始化
- 必须在主线程或新线程中调用 CoInitializeEx
- 使用 COM 接口时需要正确处理引用计数
- 记得在退出前调用 CoUninitialize
线程模型
- Network List Manager 事件需要在消息循环中处理
- 建议在单独线程中运行监控逻辑
- 不要在事件回调中执行耗时操作
错误处理
- 所有 COM 接口调用都需要检查 HRESULT
- 记得释放所有 COM 接口
- 处理网络状态不确定的情况
0x07、相关 API 参考
NLM_CONNECTIVITY 标志
| 标志 | 说明 |
|---|---|
NLM_CONNECTIVITY_DISCONNECTED | 未连接 |
NLM_CONNECTIVITY_IPV4_NOTRAFFIC | IPv4 连接但无流量 |
NLM_CONNECTIVITY_IPV4_SUBNET | IPv4 子网连接 |
NLM_CONNECTIVITY_IPV4_LOCALNETWORK | IPv4 本地网络 |
NLM_CONNECTIVITY_IPV4_INTERNET | IPv4 互联网连接 |
NLM_CONNECTIVITY_IPV6_* | IPv6 对应标志 |
链接库
# 在 .pro 项目文件中添加
LIBS += -liphlpapi -lnetapi320x08、性能优化
减少事件频率
// 使用防抖机制
class DebounceTimer
{
public:
void Trigger(std::function<void()> callback, int delayMs)
{
callback_ = callback;
if (!timer_.running) {
timer_.delay = delayMs;
timer_.Start([this]() {
if (callback_) callback_();
});
}
}
private:
SimpleTimer timer_;
std::function<void()> callback_;
};批量处理事件
将多个网络事件合并处理,避免频繁的状态改变通知
更新时间: 2026-03-30