概述:Windows 网卡事件监控技术,包括网络状态变化检测、连接事件通知等。

本文部分内容由 AI 生成,经人工修订。

0x01、网络监控概述

Windows 提供了多种监控网络状态变化的 API,主要包括:

API说明需要权限
NLA(Network Location Awareness)网络位置感知,监控网络连接变化
NotifyAddrChange监控 IP 地址变化
WMIWindows 管理规范,全面监控
IP Helper API网络相关 API
注册表通知监控注册表网络项变化

0x02、NLA Network List Manager

Network List Manager API 是监控网络状态变化的标准方式。

核心接口

接口说明
INetworkListManager网络列表管理器主接口
INetworkListManagerEvents网络列表事件接口
INetworkEvents网络事件接口
IConnectionPointContainer连接点容器

基本使用流程

  1. CoCreateInstance 创建 NetworkListManager
  2. QueryInterface 获取 INetworkListManager
  3. 提供事件监听接口(COM 回调)
  4. 注册事件通知
  5. 处理网络变化事件
  6. 注销通知

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 初始化

  1. 必须在主线程或新线程中调用 CoInitializeEx
  2. 使用 COM 接口时需要正确处理引用计数
  3. 记得在退出前调用 CoUninitialize

线程模型

  1. Network List Manager 事件需要在消息循环中处理
  2. 建议在单独线程中运行监控逻辑
  3. 不要在事件回调中执行耗时操作

错误处理

  1. 所有 COM 接口调用都需要检查 HRESULT
  2. 记得释放所有 COM 接口
  3. 处理网络状态不确定的情况

0x07、相关 API 参考

NLM_CONNECTIVITY 标志

标志说明
NLM_CONNECTIVITY_DISCONNECTED未连接
NLM_CONNECTIVITY_IPV4_NOTRAFFICIPv4 连接但无流量
NLM_CONNECTIVITY_IPV4_SUBNETIPv4 子网连接
NLM_CONNECTIVITY_IPV4_LOCALNETWORKIPv4 本地网络
NLM_CONNECTIVITY_IPV4_INTERNETIPv4 互联网连接
NLM_CONNECTIVITY_IPV6_*IPv6 对应标志

链接库

# 在 .pro 项目文件中添加
LIBS += -liphlpapi -lnetapi32

0x08、性能优化

减少事件频率

// 使用防抖机制
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

参考文档