一种更优的获取网络使用率的方案(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,  0 0 );
            
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,  0 0 );
            
if  (SUCCEEDED(hres)  &&  vtProp.vt  ==  VT_I4)
            {
                nui.interfaceIndex 
=  vtProp.intVal;       
            }

            hres 
=  pclsObj -> Get(attributes[ 2 ].c_str(),  0 ,   & vtProp,  0 0 );
            
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之后的系统中有效。

你可能感兴趣的:(NetWork)