必须设置curl_easy_setopt(pcurl, CURLOPT_FOLLOWLOCATION, 1L);否则在某些网络环境下会出现函数返回成功,但是下载不到数据。

// 声明回调函数指针,在下载过程中显示进度信息
typedef void (WINAPI *XHTTPDOWNLOADPROGRESSCALLBACK)(	
	IN  size_t szTotalSize,                 // 待下载文件总字节大小
	IN  size_t szDownloadSize,              // 当前已下载字节大小
	IN  double dbDownloadSpeed,             // 下载速度,单位:K/S
	IN  LPVOID lpParameter                  // 传入参数(比如窗口重绘时需要传入窗口句柄等等)
	);

// 定义HTTP接口返回值类型
enum XHTTPRET_T
{
	XHTTPRET_SUCCESS = 0,                          // 操作成功
	XHTTPRET_ERROR_PARAMETER,                      // 函数参数错误
	XHTTPRET_ERROR_OPENFILE,                       // 打开文件错误
	XHTTPRET_ERROR_CURLEASYINIT,                   // curl_easy_init失败
	XHTTPRET_ERROR_CURLEASYPERFORM,                // curl_easy_perform失败	
	XHTTPRET_ERROR_PERFORMOPERATIONTIMEDOUT,       // curl_easy_perform失败,curlCode=CURLE_OPERATION_TIMEDOUT
	XHTTPRET_ERROR_READUPLOADFILE,                 // 读取上传文件失败
};

// 下载URL指定的文件
XHTTPLIB_API
XHTTPRET_T
XHTTP_DownloadUrlFile(
	IN  LPCWSTR lpURL,                                         // URL
	IN  LPCWSTR lpSaveFile,                                    // 下载文件保存路径
	OUT LONG *pCurlRetCode = NULL,                             // 输出CURL执行结果,出错时便于分析
	IN  XHTTPDOWNLOADPROGRESSCALLBACK lpProgressFunc = NULL,   // 进度回调函数
	IN  LPVOID lpParameter = NULL                              // 进度回调函数参数 	
	);

// 下载URL指定的文件数据到内存,此函数不适用于下载太大的文件
XHTTPLIB_API
XHTTPRET_T
XHTTP_DownloadUrlFileToBuf(
	IN  LPCWSTR lpURL,                                         // URL
	OUT string *pRetBuf,                                       // 输出返回结果数据,注意返回的是数据不是字符串  
	OUT LONG *pCurlRetCode = NULL                              // 输出CURL执行结果,出错时便于分析
	);


//////////////////////////////////////////////////////////////////////////
// 写数据到文件回调函数
static size_t XCURLIN_WriteDataToFileFunctionCallback(char *ptr, size_t size, size_t nmemb, FILE *pFile)  
{  
	// 文件有效性
	if (pFile == NULL)
	{
		return 0;
	}

	// 写入文件
	return fwrite(ptr, size, nmemb, pFile);  
} 

// 定义传入下载进度回调函数中的结构
struct XCURLINDownloadProgressData_t
{
	XCURLINDownloadProgressData_t()
	{
		lpProgressFunction = NULL;
		lpParameter = NULL;
		dwStartTics = 0;
		dwLastDoCallbackTics = 0;
		dbLastDownloadByteSize = 0;
		dwDoCallbackTimeInterval = 500;
	}

	XHTTPDOWNLOADPROGRESSCALLBACK lpProgressFunction;            // 自定义回调函数
	LPVOID lpParameter;                                          // 回调函数参数
	DWORD dwStartTics;                                           // 开始下载时刻
	DWORD dwLastDoCallbackTics;                                  // 上次执行回调函数的时刻
	double dbLastDownloadByteSize;                               // 上次执行回调函数时下载的字节数,用于解决下载完成后还会多次触发执行进度回调的问题
	DWORD dwDoCallbackTimeInterval;                              // 回调函数执行时间间隔,比如500表示500毫秒执行一次
};

// 下载进度回调函数
static int XCURLIN_DownloadProgressFunctionCallback(void *clientp, double dltotal, double dlnow, double ultotal, double ulnow)
{
	// 参数有效性
	XCURLINDownloadProgressData_t *pDownloadProgressData = (XCURLINDownloadProgressData_t*)clientp;
	if (   pDownloadProgressData == NULL
		|| pDownloadProgressData->lpProgressFunction == NULL
		)
	{
		return 0;
	}

	// Many times the callback will be called one or more times first, before it knows the data sizes so a program must be made to handle that. 
	// 此回调函数在libcurl中未获取到下载文件大小时会被多次调用,下载完成后仍然会被多次调用,这里需要处理这两种情况
	// 按指定间隔执行一次回调函数,当下载完成执行一次回调函数
	if (   SYSF_GetTimeBetweenTwoTics(pDownloadProgressData->dwLastDoCallbackTics, ::GetTickCount()) >= pDownloadProgressData->dwDoCallbackTimeInterval
		|| (dltotal>0 && (INT64)(dltotal-dlnow)==0 && dlnow>pDownloadProgressData->dbLastDownloadByteSize)		
		)
	{
		double dbDownloadSpeed = (dlnow/1024) / (double)(SYSF_GetTimeBetweenTwoTics(pDownloadProgressData->dwStartTics, ::GetTickCount())/1000 + 1);
		size_t szTotal = (dltotal >= 0) ? (size_t)dltotal : 0;
		size_t szDownload = (dlnow >= 0) ? (size_t)dlnow : 0;
		pDownloadProgressData->lpProgressFunction(szTotal, szDownload, dbDownloadSpeed, pDownloadProgressData->lpParameter);
		pDownloadProgressData->dbLastDownloadByteSize = dlnow;
		pDownloadProgressData->dwLastDoCallbackTics = ::GetTickCount();
	}

	// 只能返回0
	// Returning a non-zero value from this callback will cause libcurl to abort the transfer and return CURLE_ABORTED_BY_CALLBACK. 
	return 0;
}

// 下载URL指定的文件
XHTTPLIB_API
XHTTPRET_T
XHTTP_DownloadUrlFile(
	IN  LPCWSTR lpURL,                                         // URL
	IN  LPCWSTR lpSaveFile,                                    // 下载文件保存路径
	OUT LONG *pCurlRetCode,                                    // 输出CURL执行结果,出错时便于分析
	IN  XHTTPDOWNLOADPROGRESSCALLBACK lpProgressFunc,          // 进度回调函数
	IN  LPVOID lpParameter                                     // 进度回调函数参数 	
	)
{
	// 初始化输出参数
	if (pCurlRetCode != NULL)
	{
		*pCurlRetCode = CURLE_OK;
	}

	// 参数有效性
	string strUrlUtf8 = STRF_UnicodeToUtf8(lpURL);
	if (   strUrlUtf8.length() == 0
		|| lpSaveFile == NULL
		)
	{
		return XHTTPRET_ERROR_PARAMETER;
	}

	// 创建文件目录层次文件夹,打开文件
	FILEF_CreateAllDirInPath(lpSaveFile);
	FILE *pFile = _wfopen(lpSaveFile, L"wb");
	if (pFile == NULL)
	{
		return XHTTPRET_ERROR_OPENFILE;
	}

	// 申请句柄
	CURL *pcurl = curl_easy_init();
	if (pcurl == NULL)
	{
		return XHTTPRET_ERROR_CURLEASYINIT;
	}
	
	// 设置返回结果写入文件回调
	curl_easy_setopt(pcurl, CURLOPT_WRITEFUNCTION, XCURLIN_WriteDataToFileFunctionCallback); 
	curl_easy_setopt(pcurl, CURLOPT_WRITEDATA, pFile);  

	// 设置进度回调函数
	XCURLINDownloadProgressData_t xDownloadProgressData;
	if (lpProgressFunc != NULL)
	{
		curl_easy_setopt(pcurl, CURLOPT_NOPROGRESS, 0L);  
		curl_easy_setopt(pcurl, CURLOPT_PROGRESSFUNCTION, XCURLIN_DownloadProgressFunctionCallback);  
		xDownloadProgressData.lpProgressFunction = lpProgressFunc;
		xDownloadProgressData.lpParameter = lpParameter;	
		xDownloadProgressData.dwStartTics = ::GetTickCount();	
		curl_easy_setopt(pcurl, CURLOPT_PROGRESSDATA, &xDownloadProgressData);
	}

	// 设置其他参数
	curl_easy_setopt(pcurl, CURLOPT_NOSIGNAL, 1L);	
	curl_easy_setopt(pcurl, CURLOPT_FOLLOWLOCATION, 1L);
	curl_easy_setopt(pcurl, CURLOPT_SSL_VERIFYPEER, 0L);
	curl_easy_setopt(pcurl, CURLOPT_SSL_VERIFYHOST, 2L);
	curl_easy_setopt(pcurl, CURLOPT_URL, strUrlUtf8.c_str());
	
	// 发起请求
	CURLcode curlRetCode = curl_easy_perform(pcurl);

	// 释放句柄
	curl_easy_cleanup(pcurl); 

	// 关闭文件
	fclose(pFile);

	// 请求失败清理文件
	if (curlRetCode != CURLE_OK)
	{
		FILEFX_DelFile(lpSaveFile, TRUE);
	}	

	// 得到输出参数
	if (pCurlRetCode != NULL)
	{
		*pCurlRetCode = curlRetCode;
	}	

	// 返回是否下载成功
	return (curlRetCode != CURLE_OK) ? XHTTPRET_ERROR_CURLEASYPERFORM : XHTTPRET_SUCCESS;	
}

// 下载URL指定的文件数据到内存,此函数不适用于下载太大的文件
XHTTPLIB_API
XHTTPRET_T
XHTTP_DownloadUrlFileToBuf(
	IN  LPCWSTR lpURL,                                         // URL
	OUT string *pRetBuf,                                       // 输出返回结果数据,注意返回的是数据不是字符串  
	OUT LONG *pCurlRetCode                                     // 输出CURL执行结果,出错时便于分析
	)
{
	// 初始化输出参数
	if (pRetBuf != NULL)
	{
		pRetBuf->clear();
	}
	if (pCurlRetCode != NULL)
	{
		*pCurlRetCode = CURLE_OK;
	}

	// 参数有效性
	string strUrlUtf8 = STRF_UnicodeToUtf8(lpURL);
	if (strUrlUtf8.length() == 0)
	{
		return XHTTPRET_ERROR_PARAMETER;
	}

	// 申请句柄
	CURL *pcurl = curl_easy_init();
	if (pcurl == NULL)
	{
		return XHTTPRET_ERROR_CURLEASYINIT;
	}

	// 设置返回结果写入string
	if (pRetBuf != NULL)
	{
		curl_easy_setopt(pcurl, CURLOPT_WRITEFUNCTION, XCURLIN_WriteDataToStringFunctionCallback); 
		curl_easy_setopt(pcurl, CURLOPT_WRITEDATA, pRetBuf);  
	}	

	// 设置其他参数
	curl_easy_setopt(pcurl, CURLOPT_NOSIGNAL, 1L);	
	curl_easy_setopt(pcurl, CURLOPT_FOLLOWLOCATION, 1L);
	curl_easy_setopt(pcurl, CURLOPT_SSL_VERIFYPEER, 0L);
	curl_easy_setopt(pcurl, CURLOPT_SSL_VERIFYHOST, 2L);
	curl_easy_setopt(pcurl, CURLOPT_URL, strUrlUtf8.c_str());

	// 发起请求
	CURLcode curlRetCode = curl_easy_perform(pcurl);

	// 释放句柄
	curl_easy_cleanup(pcurl); 	

	// 得到输出参数
	if (pCurlRetCode != NULL)
	{
		*pCurlRetCode = curlRetCode;
	}	

	// 返回是否下载成功
	return (curlRetCode != CURLE_OK) ? XHTTPRET_ERROR_CURLEASYPERFORM : XHTTPRET_SUCCESS;	
}