转自:http://blog.csdn.net/huzy204/archive/2007/12/12/1932109.aspx
很久之前用VC做了一个基于http/https的项目,当时把用wininet开发http/https程序的各种问题都搞得十分清楚。由于当时没有总结,以至于现在又出现相同的问题,又得重新查资料,但还好代码在那里。一些问题看看代码也就明白。
后来由于工作变迁,离开了原来的公司。那部分代码现在也没有了。所以又得从头来过。
现在又做一个http的项目,将一些问题总结一下,为以后再遇到类似的问题节省时间。
1. 几个wininet函数。
URLDownloadToFile:给一个文件的url,就可以把文件下载下来,需要差数可以只有两个,url和文件保存的路径。该函数封装了http,ftp,gppher协议的函数,只要是正确的url都可以下载。
URLDownloadToFile拆分为三个函数:InternetOpen,InternetOpenUrl和InternetReadFile,InternetOpenUrl也是一个封装好了的函数,它也不管具体的协议内容。InternetOpenUrl返回一个HINTERNET句柄,该句柄作为HttpQueryInfo的入参,可以得到相关的信息,如文件的长度,文件修改的日期等。该句柄同时作为InternetReadFile的入参,InternetReadFile将文件下载到一个申请好的缓冲区中。
如果是http协议,InternetOpenUrl可以分解为InternetConnect,HttpOpenRequest,HttpSendRequest。InternetConnect负责连接服务器,HttpOpenRequest去创建一个请求句柄并且把参数存储在句柄中。HttpSendRequest把请求参数送到HTTP服务器。
几个可能会遇到,并且比较郁闷的问题。
1. HttpOpenRequest的问题
HttpOpenRequest的时候如果代码像下面这样写,一般不会出现什么问题。
HINTERNET hRequest = HttpOpenRequest(hConnect,
"GET",
pszLoctionFilePath,
HTTP_VERSION,
NULL,
(const char **)p,
0,
1);
倒数第二个参数:IN DWORD dwFlags,msdn上说明:dwFlags Internet flag values. Can be any of the following values: ……
看上去如果是0的话,应该没有什么问题。但我的问题就出在这里。
参数为0的话,就会在第二次访问同一个url(不同的url不会有问题)的时候,HttpQueryInfo会失败, GetLastError()为12150:Header Not Found。这个时候如果打开IE选项,general->delete->delete files。就好了。我用程序删除缓冲区里的全部文件,没有用。非得手动点击一下delete files。这个问题产生的原因还没有找到,如果有谁遇到过同样的问题,麻烦告诉我一声。
倒数第三个参数,我的写法是:
char szHead[] = "Accept: */*/r/n/r/n";
char **p = new char*[2];*p = szHead;*(p+1) = NULL;
之后倒数第三个参数为:(const char **)p,这样写不会有错。
如果写成(const char **)&szHead,程序不会报错,但debug调试的话,会有First-chance exception in HttpAndFtpTest.exe (KERNEL32.DLL): 0xC0000005: Access Violation.的警告,这是因为强制把一个1维数组变成2维数组,它的第二个数组没有'/0'结尾所导致的访问冲突。
2. 122错误
HttpOpenRequest之后报122:The data area passed to a system call is too small. 错误,原因没有找到。但程序不会有错误,也没有警告。
3. 各个过程花费时间
InternetOpen,InternetConnect,HttpOpenRequest,HttpQueryInfo基本上不花时间。HttpSendRequest和InternetReadFile,占用整个下载过程的绝大部分时间。
4. IE请求对应的程序的写法
http://www.abc.com/123/edf.asp?Key=login&login=2&password=1
对应的IE请求
// 连接服务器
HINTERNET hConnect = InternetConnect(hSession,
ServerName,
INTERNET_DEFAULT_HTTP_PORT,
NULL,
NULL,
INTERNET_SERVICE_HTTP,
0,
1);
ServerName为www.abc.com
// 创建一个请求
HINTERNET hRequest = HttpOpenRequest(hConnect,
Method,
FormAction,
HTTP_VERSION,
NULL,
(const char**)&accept,
0,
1);
Method为"GET"
FormAction为/123/edf.asp
BOOL bSeccuss = HttpSendRequest( hRequest, hdrs, strlen(hdrs), frmdata, strlen(frmdata))
frmdata为Key=login&login=2&password=1。
5. 删除IE缓存的函数
void ClearInternetCache()
{
DWORD dwNeeded = 0;
FindFirstUrlCacheEntry(NULL, NULL, &dwNeeded);
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
{
unsigned char *buffer = new unsigned char[dwNeeded];
try
{
LPINTERNET_CACHE_ENTRY_INFO lpicei =
reinterpret_cast<LPINTERNET_CACHE_ENTRY_INFO>(buffer);
HANDLE HFind = FindFirstUrlCacheEntry(NULL, lpicei, &dwNeeded);
DeleteUrlCacheEntry(lpicei->lpszSourceUrlName);
bool no_more_files = false;
while (!no_more_files)
{
if (FindNextUrlCacheEntry(HFind, lpicei, &dwNeeded))
{
DeleteUrlCacheEntry(lpicei->lpszSourceUrlName);
}
else switch (GetLastError())
{
case ERROR_INSUFFICIENT_BUFFER:
{
delete [] buffer;
buffer = new unsigned char[dwNeeded];
lpicei = reinterpret_cast<LPINTERNET_CACHE_ENTRY_INFO>(buffer);
break;
}
default:
{
no_more_files = true;
break;
}
}
}
FindCloseUrlCache(HFind);
}
catch (...)
{
delete [] buffer;
}
delete [] buffer;
}
}
6. http和https的请求
InternetConnect 的第三个参数,INTERNET_DEFAULT_HTTP_PORT改为INTERNET_DEFAULT_HTTPS_PORT
HttpOpenRequest 的第七个参数 多了一个INTERNET_FLAG_SECURE 选项
还有一个自动安装证书的代码以前做过,现在找不到了。
7. 更好的InternetOpen方法
DWORD dwFlags = 1;
InternetGetConnectedState(&dwFlags, 0);
if(!(dwFlags & INTERNET_CONNECTION_PROXY))
*hSession = InternetOpenA("MyAgent", INTERNET_OPEN_TYPE_PRECONFIG_WITH_NO_AUTOPROXY, NULL, NULL, 0);
else
*hSession = InternetOpenA("MyAgent", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
if (*hSession)
return TRUE;
else
return FALSE;
8. 下载文件的函数
UINT InternetGetFile (HINTERNET IN hConnect, // Handle from InternetOpen()
LPCSTR szFileName)
{
FILE * pFile;
if ( !(pFile = fopen (szFileName, "wb" ) ) )
{
return INTERNET_ERROR_FILEOPEN;
}
VOID* szTemp[16384];
DWORD dwSize;
while (TRUE)
{
// Keep coping in 16 KB chunks, while file has any data left.
// Note: bigger buffer will greatly improve performance.
if (!InternetReadFile (hConnect, szTemp, 16384, &dwSize) )
{
fclose (pFile);
return INTERNET_ERROR_READFILE;
}
if (!dwSize)
break; // Condition of dwSize=0 indicate EOF. Stop.
else
fwrite(szTemp, sizeof (char), dwSize , pFile);
} // do
fflush (pFile);
fclose (pFile);
return 0;
}
9. 设置internet session
如果要设置internet session及其他参数,可以调用InternetSetOption。