最近项目中需要实现一个网络测速的功能,把尝试到的几种方式总结一下。 开发环境是Visual Studio 2010。
一开始对网络测速这个功能有点误解,和当前网卡流量搞混了。毫不犹豫的找到了一个非常简单的方式,利用pdh.lib。这个库可以实现的功能可以参考Windows自带的工具perfmon.exe。在“Start”里搜索perfmon.exe可以看到。常见的CPU利用率,剩余内存,网络流量等都可以查到。
#include
#include
#include
#pragma comment(lib,"pdh.lib")
int main()
{
HQUERY query;
PDH_STATUS status = PdhOpenQuery(NULL, NULL, &query);
if (status != ERROR_SUCCESS)
std::cout << "Open Query Error" << std::endl;
HCOUNTER counter;
counter = (HCOUNTER *)GlobalAlloc(GPTR, sizeof(HCOUNTER));
WCHAR CounterPathBuffer[PDH_MAX_COUNTER_PATH]=
L"\\Network Interface(Intel[R] Ethernet Connection [2] I219-V)\\Bytes Received/Sec"; //Use perfMon.exe, add monitor can get the right counter name.
status = PdhAddCounter(query, CounterPathBuffer, NULL, &counter);
if (status != ERROR_SUCCESS)
std::cout << "Add Counter Error" << std::endl;
PdhCollectQueryData(query);
Sleep(1000);
PdhCollectQueryData(query);
PDH_FMT_COUNTERVALUE pdhValue;
DWORD dwValue;
status = PdhGetFormattedCounterValue(counter, PDH_FMT_DOUBLE, &dwValue, &pdhValue);
if (status != ERROR_SUCCESS)
std::cout << "Get Value Error" << std::endl;
std::cout << pdhValue.doubleValue << std::endl;
PdhCloseQuery(query);
return 0;
}
实现很简单,可惜是错的,看到的当前经过网卡的流量,不是网速。
网速的测试只有利用文件下载,文件大小 / 下载时间 = 网络速度。偷懒又想走捷径,利用了Urlmon.lib里的URLDownloadToFile函数。
#include
#include
#include
#pragma comment(lib,"URlmon")
int main()
{
char buffer[MAX_PATH];
_getcwd(buffer, MAX_PATH);
strcat_s(buffer, "//test.zip");
//Clear cache first
DeleteUrlCacheEntryA("http://www.nirsoft.net/utils/nircmd.zip");
double end, cost;
long Kbytes;
double start = GetTickCount();
HRESULT Result = URLDownloadToFileA(NULL, "http://www.nirsoft.net/utils/nircmd.zip", buffer, 0, NULL);
switch (Result)
{
case S_OK:
end=GetTickCount();
cost = (end - start)/1000;
Kbytes = file_size/ cost /1024;
std::cout << cost << " speed is " <
简单,但是不对。URLDownloadToFileA封装了下载文件的全过程,网络连接,下载,写入文件。没有只提取下载部分的时间,导致网络测速的结果很不准确。如果希望每次都重新下载,记得要调用DeleteUrlCacheEntry函数。值得一提的是URLDownloadToFileA的最后一个参数,LPBINDSTATUSCALLBACK lpfnCB,根据MSDN的介绍:
lpfnCBA pointer to the IBindStatusCallback interface of the caller. By using IBindStatusCallback::OnProgress, a caller can receive download status. URLDownloadToFile calls the IBindStatusCallback::OnProgress and IBindStatusCallback::OnDataAvailable methods as data is received. The download operation can be canceled by returning E_ABORT from any callback. This parameter can be set to NULL if status is not required.
看起来不错,也看到网上有人介绍利用这个参数在MFC中实现进度条的下载。但是我的体会是如果下载文件很小就完全没什么用,不准确。
之后尝试了CInternetSession, 需要#include
#define RECVPACK_SIZE 2048
bool DownloadSaveFiles(const char* url,const char* strSaveFile) {
bool ret=false;
CInternetSession Sess(_T("lpload"));
Sess.SetOption(INTERNET_OPTION_CONNECT_TIMEOUT , 5000); //5 seconds time out
Sess.SetOption(INTERNET_OPTION_SEND_TIMEOUT , 5000); //5 seconds time out
Sess.SetOption(INTERNET_OPTION_RECEIVE_TIMEOUT , 5000); //5 seconds time out
Sess.SetOption(INTERNET_OPTION_DATA_SEND_TIMEOUT , 5000); //5 seconds time out
Sess.SetOption(INTERNET_OPTION_DATA_RECEIVE_TIMEOUT, 5000); //5 seconds time out
DWORD dwFlag = INTERNET_FLAG_TRANSFER_BINARY|INTERNET_FLAG_DONT_CACHE|INTERNET_FLAG_RELOAD;
CHttpFile* cFile = NULL;
char *pBuf = NULL;
int nBufLen = 0;
do {
try{
cFile = (CHttpFile*)Sess.OpenURL((CString)url,1,dwFlag);
DWORD dwStatusCode;
cFile->QueryInfoStatusCode(dwStatusCode);
if (dwStatusCode == HTTP_STATUS_OK) {
DWORD nLen=0;
cFile->QueryInfo(HTTP_QUERY_CONTENT_LENGTH, nLen);
nBufLen=nLen;
if (nLen <= 0) break;
pBuf = (char*)malloc(nLen+8);
ZeroMemory(pBuf,nLen+8);
char *p=pBuf;
double end, cost;
double start = GetTickCount();
while (nLen>0) {
int n = cFile->Read(p,(nLenClose();
Sess.Close();
delete cFile;
}
return ret;
}
这次看起来都对了,测试的时间也是比较准确的。问题出在引用的文件,afxinet.h,属于MFC的类。如果用在我们开放的软件中,需要打包整个MFC库,这肯定是不能接受的。
由于afxinet其实使用的是wininet.lib,可以直接考虑利用这个库。这个库属于Microsoft SDKs,对于我们的软件来说可以接受。在网上找到了这篇文章,点击打开链接,里面的代码很完整,非常好,稍加修改用来做网络测速也很准确。谢谢原博主。
还尝试了用socket来实现文件下载。代码太长就不贴了。整理了一个代码包,把列的几种方式都包含了进去。点击打开链接