一种更优的获取网络使用率的方案(a better solution to get network utilization)

这两天一直在研究如何去获取网络利用率(usage)和网卡线路速度(link speed)的问题,找到了一个比较好的方案,写出来跟大家分享一下。

记得我在以前的一篇博文中提到过这样一个问题,有时我们添加两个虚拟网卡时,两个网卡名称是一样的,这样的结果就是我们无法根据名称去匹配指定的网卡。

通常我们获取网卡的信息有两种方式:1. WMI的win32_networkAdapter类;2. IpHlpApi框架。

而获取网络使用率的方式也有两种:1. performance monitor编程接口;2. Win32_PerfFormattedData_Tcpip_NetworkInterface类。

但是我发现这些方式都没办法解决我以上提到的问题。因为无论是从performance monitor,还是Win32_PerfFormattedData_Tcpip_NetworkInterface来获取网络利用率都是依赖于网卡名。另外,我发现在Windows Task manager里面看的网络使用率和线路速度都匹配的很正常。所以,直觉是觉得应该有一种方式可以比较好的去获取这两个值,无论网卡名是否相同。通过研究发现,其实想要获取这两个值,并且建立匹配关系可以通过WMI和IpHlpApi框架来实现。顺便说一句,我的目标是该程序能运行在win2000以后的所有系统上,所以出于兼容性的考虑,我会放弃那些只支持vista之后操作系统的方案。下面我们具体来看一下,如何用代码来实现:

为了获得WMI和IpHlpApi框架的支持,我们需要包含下面几个头文件和库:

 

#include <Wbemidl.h>
#include 
<comdef.h>
#include 
<Iphlpapi.h>
#pragma comment(lib , "Iphlpapi.lib")

 

 

同样为了使用智能指针,我又做了以下声明

 

代码
_COM_SMARTPTR_TYPEDEF(IWbemLocator, __uuidof(IWbemLocator));
_COM_SMARTPTR_TYPEDEF(IWbemServices, __uuidof(IWbemServices));
_COM_SMARTPTR_TYPEDEF(IEnumWbemClassObject, __uuidof(IEnumWbemClassObject));
_COM_SMARTPTR_TYPEDEF(IWbemClassObject, __uuidof(IWbemClassObject));

 

 

对于WMI的具体操作,我就不在这里多说了。最主要的是介绍一下我的实现方式。首先我们需要用WMI去查询获取网卡的一些必要的信息,如MAC地址,Interface Index和线路速度

先定义一个结构体来存储网卡与使用率的映射关系

 

代码
typedef struct _tagNetworkUtilizationMapElement
{
    wchar_t MAC[
64];
    unsigned 
int  interfaceIndex;
    unsigned __int64 linkSpeed;
    DWORD preInBytes;
    DWORD preOutBytes;
    unsigned 
int  usage; 
}NetworkUtilization_Map_Element, 
*PNetworkUtilization_Map_Element;

 

 

之后通过一个WMI查询去获取MAC, InterfaceIndex和 linkSpeed。

 

代码
DWORD InitNetworkAdapterInfo(vector<NetworkUtilization_Map_Element> & refNUArray)
{
    HRESULT hres 
= S_OK;
    IWbemLocatorPtr pLocPtr 
= NULL;
    hres 
= CoCreateInstance(
        CLSID_WbemLocator,             
        
0
        CLSCTX_INPROC_SERVER, 
        IID_IWbemLocator, (LPVOID 
*&pLocPtr);

    
if(FAILED(hres)) return hres;

    IWbemServicesPtr pSvcPtr;
    hres 
= pLocPtr->ConnectServer(
        _bstr_t(L
"ROOT\\CIMV2"), // Object path of WMI namespace
        NULL,                    // User name. NULL = current user
        NULL,                    // User password. NULL = current
        0,                       // Locale. NULL indicates current
        NULL,                    // Security flags.
        0,                       // Authority (e.g. Kerberos)
        0,                       // Context object 
        &pSvcPtr                    // pointer to IWbemServices proxy
        );

    
if(FAILED(hres)) return hres;

    hres 
= CoSetProxyBlanket(
        pSvcPtr,                        
// Indicates the proxy to set
        RPC_C_AUTHN_WINNT,           // RPC_C_AUTHN_xxx
        RPC_C_AUTHZ_NONE,            // RPC_C_AUTHZ_xxx
        NULL,                        // Server principal name 
        RPC_C_AUTHN_LEVEL_CALL,      // RPC_C_AUTHN_LEVEL_xxx 
        RPC_C_IMP_LEVEL_IMPERSONATE, // RPC_C_IMP_LEVEL_xxx
        NULL,                        // client identity
        EOAC_NONE                    // proxy capabilities 
        );

    
if(FAILED(hres)) return hres;

    vector
<wstring> attributes;
    attributes.push_back(L
"MACAddress");
    attributes.push_back(L
"InterfaceIndex");
    attributes.push_back(L
"Speed");

    
// Query Network Adapter information from WMI
    IEnumWbemClassObjectPtr pEnumerator = NULL;
    hres 
= pSvcPtr->ExecQuery(
        bstr_t(
"WQL"), 
        L
"SELECT * FROM Win32_NetworkAdapter where adapterTypeId = 0",
        WBEM_FLAG_FORWARD_ONLY 
| WBEM_FLAG_RETURN_IMMEDIATELY, 
        NULL,
        
&pEnumerator);
    
if (FAILED(hres)) return hres;

    ULONG ulReturn 
= 0;
    IWbemClassObjectPtr pclsObj 
= NULL;
    
while (pEnumerator != NULL)
    {
        hres 
= pEnumerator->Next(WBEM_INFINITE, 1&pclsObj, &ulReturn);
        
if (hres == WBEM_S_FALSE || ulReturn == 0)
            
break;

        
if (hres == WBEM_S_NO_ERROR)
        {
            VARIANT vtProp;
            NetworkUtilization_Map_Element nui 
= {0};
            
            
// Populate the network adapter information and store it
            hres = pclsObj->Get(attributes[0].c_str(), 0,  &vtProp, 00);
            
if (SUCCEEDED(hres) && vtProp.vt == VT_BSTR)
            {
                swprintf_s(nui.MAC, _countof(nui.MAC), L
"%s", vtProp.bstrVal);      
            }

            hres 
= pclsObj->Get(attributes[1].c_str(), 0,  &vtProp, 00);
            
if (SUCCEEDED(hres) && vtProp.vt == VT_I4)
            {
                nui.interfaceIndex 
= vtProp.intVal;       
            }

            hres 
= pclsObj->Get(attributes[2].c_str(), 0,  &vtProp, 00);
            
if (SUCCEEDED(hres) && vtProp.vt == VT_BSTR)
            {
                nui.linkSpeed 
= _wtoi64(vtProp.bstrVal);       
            }

            
if (SUCCEEDED(hres))
            {
                refNUArray.push_back(nui);
            }
        }
    }

    
return hres;
}

 

 

为了方便,我并没有对这段代码进行优化,所以看起来比较冗长而且不通用,不过功能还是能work的。有了这些信息这后,我们就需要依靠这些信息去获取网络使用率了。因为没有办法是用PF和WMI这样现成的类来直接获取,那么我们就只能用一种间接的方式还获取。不知道大家是否还记得网卡使用率的计算公式:u = (send bytes + receive bytes) / link speed

也就是说,我们想得到u还必须得到send bytes和receive bytes。那么如何去获取这两个值呢?仔细查看了一下msdn关于IpHlpApi的信息得到

 

代码
typedef struct _MIB_IFROW {
  WCHAR wszName[MAX_INTERFACE_NAME_LEN];
  DWORD dwIndex;
  DWORD dwType;
  DWORD dwMtu;
  DWORD dwSpeed;
  DWORD dwPhysAddrLen;
  BYTE  bPhysAddr[MAXLEN_PHYSADDR];
  DWORD dwAdminStatus;
  DWORD dwOperStatus;
  DWORD dwLastChange;
  DWORD dwInOctets; 
--- 注意这个
  DWORD dwInUcastPkts;
  DWORD dwInNUcastPkts;
  DWORD dwInDiscards;
  DWORD dwInErrors;
  DWORD dwInUnknownProtos;
  DWORD dwOutOctets; 
--- 和这个
  DWORD dwOutUcastPkts;
  DWORD dwOutNUcastPkts;
  DWORD dwOutDiscards;
  DWORD dwOutErrors;
  DWORD dwOutQLen;
  DWORD dwDescrLen;
  BYTE  bDescr[MAXLEN_IFDESCR];
}MIB_IFROW, 
*PMIB_IFROW;

 

 

根据msdn的描述,看起来是我们要的两个值。但是经过测试发现这两个值是累计值而不是实时值,就是这两个值记载了网卡从启动开始传输的所有字节数。不过还好,不能直接用那就间接用,我们通过每隔一秒取一次变化还是能计算出我们需要的值。这个函数实现是这样:

 

代码

DWORD PopulateNetworkUtilization(vector
<NetworkUtilization_Map_Element> & refNUArray)
{
    PMIB_IFTABLE pIfTab;
    DWORD dwSize 
= 0;
    DWORD dwRetval 
= 0;

    pIfTab 
= (PMIB_IFTABLE)malloc(sizeof(PMIB_IFTABLE));
    
if (pIfTab == NULL)
        
return 0x01;

    
if (GetIfTable(pIfTab, &dwSize, 0== ERROR_INSUFFICIENT_BUFFER)
    {
        free(pIfTab);
        pIfTab 
= (PMIB_IFTABLE)malloc(dwSize);
    }

    
if ((dwRetval = GetIfTable(pIfTab, &dwSize, 0)) == NO_ERROR) 
    {
        
// Retrieve the transmission of each network card
        for (int i=0; i<(int)pIfTab->dwNumEntries; i++)
        {
            
for (int j=0; j<(int)refNUArray.size(); j++)
            {
                
if (refNUArray[j].interfaceIndex == pIfTab->table[i].dwIndex)
                {
                    DWORD inBytesDet 
= 0;
                    DWORD outBytesDet 
= 0;

                    
// if it isn't first call
                    if (refNUArray[j].preInBytes != 0 && refNUArray[j].preOutBytes != 0)
                    {
                        
// Deal with the overflow case
                        inBytesDet = (refNUArray[j].preInBytes > pIfTab->table[i].dwInOctets) 
                            
? 0xFFFFFFFF - refNUArray[j].preInBytes + pIfTab->table[i].dwInOctets
                            : pIfTab
->table[i].dwInOctets - refNUArray[j].preInBytes;

                        outBytesDet 
= (refNUArray[j].preOutBytes > pIfTab->table[i].dwOutOctets)
                            
?0xFFFFFFFF - refNUArray[j].preOutBytes + pIfTab->table[i].dwOutOctets
                            : pIfTab
->table[i].dwOutOctets - refNUArray[j].preOutBytes;
                    }

                    refNUArray[j].preInBytes 
= pIfTab->table[i].dwInOctets;
                    refNUArray[j].preOutBytes 
= pIfTab->table[i].dwOutOctets;

                    unsigned __int64 totalBytesDet 
= ((unsigned __int64)inBytesDet + (unsigned __int64)outBytesDet) * 8;
                    unsigned 
int usage = (unsigned int) ((totalBytesDet > refNUArray[j].linkSpeed) 
                        
? 100 : (totalBytesDet * 100 / refNUArray[j].linkSpeed));
                    refNUArray[j].usage 
= usage;
                    
break;
                }
            }
        }
    }

    free(pIfTab);
    
return 0x00;
}

 

因为这两个值是DWORD类型的,也就是说只能表达4G的数据大小。所以代码里还加了数值溢出处理。到这里我们的线路速度和网卡使用率就可以关联起来了。下面是测试代码

 

代码
int _tmain(int argc, _TCHAR* argv[])
{
    CoInitialize(NULL);
    vector
<NetworkUtilization_Map_Element> numeArray;
    
if (!SUCCEEDED(InitNetworkAdapterInfo(numeArray)))
        
return 1;

    
if (!SUCCEEDED(PopulateNetworkUtilization(numeArray)))
        
return 2;

    
for (int i=0; i<120; i++)
    {
        
if (!SUCCEEDED(PopulateNetworkUtilization(numeArray)))
            
return 2;

        
// test 
        vector<NetworkUtilization_Map_Element>::iterator it;
        
for (it = numeArray.begin(); it != numeArray.end(); it++)
        {
            cout 
<< "[" << endl;
            wcout 
<< "MAC: " << it->MAC << endl;
            cout 
<< "index: " << it->interfaceIndex << endl;
            cout 
<< "link speed : " << it->linkSpeed << endl;
            cout 
<< "usage : " << it->usage << endl;
            cout 
<< "]" << endl;
        }

        Sleep(
1000);
    }

    CoUninitialize();
    
return 0;
}

 

 

到这里这个方案基本就完成了,有心的朋友仔细查一下msdn可能会发现其实MIB_IFROW2的结构体里面就有linkSpeed字段,为什么不用呢?还是上面的话,因为这个结构体只在vista之后的系统中有效。
posted @ 2010-01-06 23:08  moonz-wu  阅读(4944)  评论(3编辑  收藏  举报