分享

Windows下C++动态监测网络变化(对标Windows网络托盘图标)

 QomoIT 2020-09-17

背景

把Windows网络托盘小图标移植到应用程序中来。使得应用界面显示网络状态,状态包括无网络、有线网、无线网(需要显示WiFi信号强度)

方案

  • 动态监测托盘图标变化(没有采用)
  • 动态监测网络状态变化(利用Windows的APICOM接口,也可以采用轮询)

问题点

  • 获取Windows网络的活动连接列表
  • 获取活动连接的网络适配器类型
  • 注册网络状态变化的通知,避免轮询
  • 获取WiFi信号强度
  • 注册WiFi信号强度变化的通知,避免轮询

获取Windows网络的活动连接列表

使用的API头文件是<netlistmgr.h> https://docs.microsoft.com/en-us/windows/win32/api/netlistmgr/

先用GetNetworkConnectios拿到一个网络连接的枚举器IEnumNetworkConnections;然后进行枚举活动网络连接INetworkConnection https://docs.microsoft.com/en-us/windows/win32/api/netlistmgr/nn-netlistmgr-inetworkconnection

可以查看下这里面的方法,通过get_IsConnectedToInternet函数可以过滤掉没有连上网的连接;再通过GetAdapterId拿到网络适配器的GUID,这个很重要,因为要通过这个GUID来判断网络适配器的类型,同时在获取WiFi信号的时候也要用到这个GUID。

获取活动连接的网络适配器类型

使用的API头文件是<iphlpapi.h> https://docs.microsoft.com/en-us/windows/win32/api/iphlpapi/

使用GetAdaptersInfo https://docs.microsoft.com/zh-cn/windows/win32/api/iphlpapi/nf-iphlpapi-getadaptersinfo 可以拿到所有网络适配器的信息IP_ADAPTER_INFO https://docs.microsoft.com/en-us/windows/win32/api/iptypes/ns-iptypes-ip_adapter_info,这里面有个字段AdapterName,里面的信息就是之前提到的GUID,通过这个可以定位到活动网络连接对应的网络适配器;里面还有个字段Type,需要关心的类型含义如下:

  • MIB_IF_TYPE_ETHERNET 以太网,就是有线网
  • MIB_IF_TYPE_LOOPBACK 环回地址,过滤掉
  • IF_TYPE_IEEE80211 无线网

显示的规则如下:

同时存在有线和无线,优先显示有线;只存在其一则正常显示;如果都没有那么就显示断网。

注册网络状态变化的通知

使用事件INetworkConnectionEvents https://docs.microsoft.com/en-us/windows/win32/api/netlistmgr/nn-netlistmgr-inetworkconnectionevents;实现这个纯虚类,这里面有个接口NetworkConnectionConnectivityChanged,这个函数会在网络状态发生变化的时候回调。

注意,之前使用了INetworkListManagerEvents,里面有个函数ConnectivityChanged,这个函数在网络状态发生变化的时候也会回调,但是不敏感,举个例子:假设你有一个有线网卡和一个无线网卡,两个都连上网了,这个时候断开有线网,这个函数是不会回调的,但是NetworkConnectionConnectivityChanged会回调。因此优先使用上一个事件。

MSDN上有一个例子可以参考,https://docs.microsoft.com/zh-cn/previous-versions/windows/desktop/mpc/network-awareness-on-windows-vista-and-windows-7

在网络状态变化回调时获取一次活动网络连接,把结果进行显示即可。

获取WiFi信号强度

MSDN上有一个例子,https://docs.microsoft.com/zh-cn/windows/win32/api/wlanapi/nf-wlanapi-wlanqueryinterface

有可能存在很多WiFi网卡,因此上文中提到的GUID又起作用了。

获取wlanSignalQuality可线性转换为RSSI。这里参考了https://www./what-is-rssi-level.html,把信号强度分成了四个等级。

注册WiFi信号强度变化的通知

使用API WlanRegisterNotification https://docs.microsoft.com/en-us/windows/win32/api/wlanapi/nf-wlanapi-wlanregisternotification 注册回调 WLAN_NOTIFICATION_CALLBACK,里面有个参数NotificationSourceNotificationCode,只有当这两个参数满足NotificationSource == WLAN_NOTIFICATION_SOURCE_MSM && NotificationCode == wlan_notification_msm_signal_quality_change时,回调的内容才是WiFi信号发生了改变,可以把pData转成WLAN_SIGNAL_QUALITY,注意,这个值是不能用的,根本就不是WiFi信号强度,因此这里就改成回调时再获取一次WiFi信号强度即可。


整个流程就过完了,下面就上代码吧。有些细节需要自己揣摩下。

#include <string>
#include <iostream>
#include <memory>
#include <thread>
#include <vector>
#include <algorithm>
#include <locale>
#include <codecvt>

#include <Windows.h>
#include <netlistmgr.h>
#include <atlbase.h>
#include <objbase.h>
#include <wtypes.h>
#include <wlanapi.h>
#include <IPHlpApi.h>

#pragma comment(lib, "ole32.lib")
#pragma comment(lib, "wlanapi.lib")
#pragma comment(lib, "Iphlpapi.lib")

static std::wstring GUIDToString(const GUID & guid)
{
    OLECHAR guidString[40] = { 0 };
    ::StringFromGUID2(guid, guidString, sizeof(guidString));
    return guidString;
}

static std::string UnicodeToUTF8(const std::wstring & wstr)
{
    std::string ret;
    try
    {
        std::wstring_convert< std::codecvt_utf8<wchar_t> > wcv;
        ret = wcv.to_bytes(wstr);
    }
    catch (const std::exception & e)
    {
        std::cerr << e.what() << std::endl;
    }
    return ret;
}

class NetWorkEvent : public INetworkConnectionEvents
{
private:
    LONG _ref;
    std::function<void()> _callback;

public:
    NetWorkEvent(const std::function<void()> & cb);

    virtual HRESULT STDMETHODCALLTYPE NetworkConnectionConnectivityChanged(
        /* [in] */ GUID connectionId,
        /* [in] */ NLM_CONNECTIVITY newConnectivity);

    virtual HRESULT STDMETHODCALLTYPE NetworkConnectionPropertyChanged(
        /* [in] */ GUID connectionId,
        /* [in] */ NLM_CONNECTION_PROPERTY_CHANGE flags);

    STDMETHODIMP QueryInterface(REFIID refIID, void **pIFace);
    virtual ULONG __stdcall AddRef(void);
    virtual ULONG __stdcall Release(void);
};

NetWorkEvent::NetWorkEvent(const std::function<void()>& cb)
    : _callback(cb)
{
}

HRESULT NetWorkEvent::NetworkConnectionConnectivityChanged(GUID connectionId, NLM_CONNECTIVITY newConnectivity)
{
    std::wcout << GUIDToString(connectionId) << " | NUL_CONNECTIVITY : " << newConnectivity << std::endl;
    if (_callback)
    {
        _callback();
    }
    return S_OK;
}

HRESULT NetWorkEvent::NetworkConnectionPropertyChanged(GUID connectionId, NLM_CONNECTION_PROPERTY_CHANGE flags)
{
    return S_OK;
}

STDMETHODIMP NetWorkEvent::QueryInterface(REFIID refIID, void ** pIFace)
{
    HRESULT hr = S_OK;
    *pIFace = NULL;
    if (IsEqualIID(refIID, IID_IUnknown))
    {
        *pIFace = (IUnknown *)this;
        ((IUnknown *)*pIFace)->AddRef();
    }
    else if (IsEqualIID(refIID, IID_INetworkConnectionEvents))
    {
        *pIFace = (INetworkConnectionEvents *)this;
        ((IUnknown *)*pIFace)->AddRef();
    }
    else
    {
        hr = E_NOINTERFACE;
    }

    return hr;
}

ULONG NetWorkEvent::AddRef(void)
{
    return (ULONG)InterlockedIncrement(&_ref);
}

ULONG NetWorkEvent::Release(void)
{
    LONG Result = InterlockedDecrement(&_ref);
    if (Result == 0)
        delete this;
    return (ULONG)Result;
}

enum NetworkType
{
    NotNetwork,
    Ethernet,
    Wlan,
};

enum WiFiQuality
{
    Weak,             // (-INF, -70dBm)
    Fair,             // [-70dBm, -60dBm)
    Good,             // [-60dBm, -50dBm)
    Excellent         // [-50dBm, +INF)
};

struct ConnectionInfo
{
    std::wstring guid;
    NetworkType type;
};

class NetworkMonitor
{
    friend void OnNotificationCallback(PWLAN_NOTIFICATION_DATA Data, PVOID context);
public:
    NetworkMonitor();
    ~NetworkMonitor();

    std::vector<ConnectionInfo> GetNetworkConnections();
    NetworkType GetNetAdpaterType(const std::wstring &guid);
    void OnNetworkStatusChange();
    WiFiQuality GetWiFiSignalQuality(const std::wstring &guid);
    void OnWiFiQualityChange(const GUID & guid);

    void ShowNetworkStatus();

private:
    std::unique_ptr<NetWorkEvent> _networkEvent;

    DWORD _cookie;
    CComPtr<INetworkListManager> _pNLM;
    CComPtr<IConnectionPointContainer> _pCpc;
    CComPtr<IConnectionPoint> _pConnectionPoint;

    HANDLE _wlanHandle;
};

static NetworkMonitor * InstanceOfNetworkMonitor()
{
    static NetworkMonitor instance;
    return &instance;
}

NetworkMonitor::NetworkMonitor()
{
    CoInitializeEx(NULL, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE);

    HRESULT hr = CoCreateInstance(CLSID_NetworkListManager, NULL, CLSCTX_ALL, IID_INetworkListManager, (LPVOID *)&_pNLM);
    hr = _pNLM->QueryInterface(IID_IConnectionPointContainer, (void **)&_pCpc);
    hr = _pCpc->FindConnectionPoint(IID_INetworkConnectionEvents, &_pConnectionPoint);

    _networkEvent = std::make_unique<NetWorkEvent>(std::bind(&NetworkMonitor::OnNetworkStatusChange, this));
    hr = _pConnectionPoint->Advise((IUnknown *)_networkEvent.get(), &_cookie);
}

NetworkMonitor::~NetworkMonitor()
{
    if (_pConnectionPoint)
    {
        _pConnectionPoint->Unadvise(_cookie);
    }

    CloseHandle(_wlanHandle);

    CoUninitialize();
}

void OnNotificationCallback(PWLAN_NOTIFICATION_DATA Data, PVOID context)
{
    if (Data != NULL &&
        Data->NotificationSource == WLAN_NOTIFICATION_SOURCE_MSM &&
        Data->NotificationCode == wlan_notification_msm_signal_quality_change)
    {
        WLAN_SIGNAL_QUALITY Qality = (WLAN_SIGNAL_QUALITY)Data->pData;
        std::cout << "WiFi OnNotification Qality : " << Qality << std::endl;
        InstanceOfNetworkMonitor()->OnWiFiQualityChange(Data->InterfaceGuid);
    }
}

std::vector<ConnectionInfo> NetworkMonitor::GetNetworkConnections()
{
    std::vector<ConnectionInfo> result;

    CComPtr<IEnumNetworkConnections> enumConnectons;

    if (FAILED(_pNLM->GetNetworkConnections(&enumConnectons)))
    {
        std::cerr << "GetNetworkConnections error : " << GetLastError() << std::endl;
        return result;
    }

    if (enumConnectons)
    {
        ULONG lFetch;
        INetworkConnection *connection = nullptr;
        while (SUCCEEDED(enumConnectons->Next(1, &connection, &lFetch)) && nullptr != connection)
        {
            VARIANT_BOOL isConnectInternet = VARIANT_FALSE;
            connection->get_IsConnectedToInternet(&isConnectInternet);
            if (isConnectInternet == VARIANT_FALSE)
            {
                continue;
            }
            ConnectionInfo item;
            GUID guid;

            connection->GetAdapterId(&guid);
            item.guid = GUIDToString(guid);

            result.push_back(item);
        }
        if (connection)
        {
            connection->Release();
        }
    }

    for (auto it = result.begin(); it != result.end(); ++it)
    {
        it->type = GetNetAdpaterType(it->guid);
    }
    std::partition(result.begin(), std::partition(result.begin(), result.end(), [](const ConnectionInfo & info)
    {
        return info.type != NetworkType::NotNetwork;
    }), [](const ConnectionInfo & info)
    {
        return info.type == NetworkType::Ethernet;
    });
    for (auto it = result.begin(); it != result.end(); ++it)
    {
        std::wcout << "connect network guid : " << it->guid << " | type : " << it->type << std::endl;
    }
    return result;
}

NetworkType NetworkMonitor::GetNetAdpaterType(const std::wstring & guid)
{
    unsigned long unSize = sizeof(IP_ADAPTER_INFO);
    std::unique_ptr<uint8_t[]> data = std::make_unique<uint8_t[]>(unSize);
    bool find = false;
    unsigned long unResult = GetAdaptersInfo(reinterpret_cast<PIP_ADAPTER_INFO>(data.get()), &unSize);

    if (ERROR_BUFFER_OVERFLOW == unResult)
    {
        data = std::make_unique<uint8_t[]>(unSize);
        unResult = GetAdaptersInfo(reinterpret_cast<PIP_ADAPTER_INFO>(data.get()), &unSize);
    }
    if (ERROR_SUCCESS == unResult)
    {
        PIP_ADAPTER_INFO pIpAdapterInfo = reinterpret_cast<PIP_ADAPTER_INFO>(data.get());

        while (pIpAdapterInfo)
        {
            if (UnicodeToUTF8(guid) == pIpAdapterInfo->AdapterName)
            {
                return MIB_IF_TYPE_ETHERNET == pIpAdapterInfo->Type ? NetworkType::Ethernet :
                    IF_TYPE_IEEE80211 == pIpAdapterInfo->Type ? NetworkType::Wlan : NetworkType::NotNetwork;
            }
            pIpAdapterInfo = pIpAdapterInfo->Next;
        }
    }

    return NetworkType::NotNetwork;
}

void NetworkMonitor::OnNetworkStatusChange()
{
    ShowNetworkStatus();
}

WiFiQuality NetworkMonitor::GetWiFiSignalQuality(const std::wstring & guid)
{
    WiFiQuality result = WiFiQuality::Weak;
    DWORD dwMaxClient = 2;
    DWORD dwCurVersion = 0;
    DWORD dwResult = 0;

    unsigned int i, j;

    PWLAN_INTERFACE_INFO_LIST pIfList = NULL;
    PWLAN_INTERFACE_INFO pIfInfo = NULL;

    PWLAN_AVAILABLE_NETWORK_LIST pBssList = NULL;
    PWLAN_AVAILABLE_NETWORK pBssEntry = NULL;

    int iRSSI = 0;

    if (_wlanHandle == NULL)
    {
        dwResult = WlanOpenHandle(dwMaxClient, NULL, &dwCurVersion, &_wlanHandle);
        if (dwResult != ERROR_SUCCESS)
        {
            std::cerr << "WlanOpenHandle failed with error: " << dwResult << std::endl;
            return result;
        }
        dwResult = WlanRegisterNotification(_wlanHandle, WLAN_NOTIFICATION_SOURCE_ALL, TRUE, WLAN_NOTIFICATION_CALLBACK(OnNotificationCallback), NULL, NULL, NULL);
        if (dwResult != ERROR_SUCCESS)
        {
            std::cerr << "WlanRegisterNotification failed with error: " << dwResult << std::endl;
            return result;
        }
    }

    dwResult = WlanEnumInterfaces(_wlanHandle, NULL, &pIfList);
    if (dwResult != ERROR_SUCCESS)
    {
        std::cerr << "WlanEnumInterfaces failed with error: " << dwResult << std::endl;
        return result;
    }
    for (i = 0; i < (int)pIfList->dwNumberOfItems; i++)
    {
        pIfInfo = (WLAN_INTERFACE_INFO *)&pIfList->InterfaceInfo[i];
        if (guid != GUIDToString(pIfInfo->InterfaceGuid) || pIfInfo->isState != wlan_interface_state_connected)
        {
            continue;
        }

        dwResult = WlanGetAvailableNetworkList(_wlanHandle,
                                               &pIfInfo->InterfaceGuid,
                                               0,
                                               NULL,
                                               &pBssList);

        if (dwResult != ERROR_SUCCESS)
        {
            std::cerr << "WlanGetAvailableNetworkList failed with error:" << dwResult << std::endl;
            return result;
        }
        for (j = 0; j < pBssList->dwNumberOfItems; j++)
        {
            pBssEntry = (WLAN_AVAILABLE_NETWORK *)& pBssList->Network[j];

            if (pBssEntry->bNetworkConnectable && (pBssEntry->dwFlags & WLAN_AVAILABLE_NETWORK_CONNECTED))
            {
                if (pBssEntry->wlanSignalQuality == 0)
                    iRSSI = -100;
                else if (pBssEntry->wlanSignalQuality == 100)
                    iRSSI = -50;
                else
                    iRSSI = -100 + (pBssEntry->wlanSignalQuality / 2);

                std::cout << "Signal Quality:\t " << pBssEntry->wlanSignalQuality << " (RSSI: " << iRSSI << " dBm)" << std::endl;

                result = iRSSI < -70 ? WiFiQuality::Weak :
                    iRSSI < -60 ? WiFiQuality::Fair :
                    iRSSI < -50 ? WiFiQuality::Good : WiFiQuality::Excellent;
            }
        }
    }
    if (pBssList != NULL)
    {
        WlanFreeMemory(pBssList);
        pBssList = NULL;
    }

    if (pIfList != NULL)
    {
        WlanFreeMemory(pIfList);
        pIfList = NULL;
    }
    return result;
}

void NetworkMonitor::OnWiFiQualityChange(const GUID & guid)
{
    auto nowQuality = GetWiFiSignalQuality(GUIDToString(guid));
    std::cout << "WiFi signal quality now : " << nowQuality << std::endl;
    ShowNetworkStatus();
}

void NetworkMonitor::ShowNetworkStatus()
{
    NetworkType type;
    WiFiQuality quality;
    auto connections = GetNetworkConnections();
    if (connections.empty())
    {
        type = NetworkType::NotNetwork;
    }
    else
    {
        type = connections.front().type;
        if (type == NetworkType::Wlan)
        {
            quality = GetWiFiSignalQuality(connections.front().guid);
        }
    }

    std::cout << "====== Notify ======" << std::endl;
    std::cout << "* Type : " << (type == NetworkType::NotNetwork ? "NetworkError" : type == NetworkType::Ethernet ? "Ethernet" : "WiFi") << std::endl;
    if (type == NetworkType::Wlan)
    {
        std::cout << "* Signal : " << quality + 1 << std::endl;
    }
    std::cout << std::endl;
}

int main()
{
    InstanceOfNetworkMonitor()->ShowNetworkStatus();
    while (true) std::this_thread::sleep_for(std::chrono::hours(10));
    return 0;
}

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多