程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> 獲取網絡使用率的方案(a better solution to get network utilization)

獲取網絡使用率的方案(a better solution to get network utilization)

編輯:關於.NET

這兩天一直在研究如何去獲取網絡利用率(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之後的系統中有效。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved