参考《Visual+C++网络高级编程》简化修改了代码:
// HTTPDownload.cpp: implementation of the CHTTPDownload class.
//
//
#include "stdafx.h"
//#include "Download.h"
#include "HTTPDownload.h"
#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif
//
// Construction/Destruction
//
CHTTPDownload::CHTTPDownload()
{
AfxOleInit();
if (!AfxSocketInit())
{
AfxMessageBox("AfxSocketInit");
}
for(int i = 0; i < 4; i++){
m_bTerminate[i] = FALSE;
}
m_bSupportResume = FALSE;
m_bResume = FALSE;
}
CHTTPDownload::~CHTTPDownload()
{
m_lsTask.RemoveAll();
}
BOOL CHTTPDownload::StartTask(CString remoteurl, CString localfile)
{
if(remoteurl.IsEmpty())
return FALSE;
if(!ParseURL(remoteurl)){
remoteurl = _T("http://") + remoteurl;
if(!ParseURL(remoteurl)){
TRACE("Requested URL is invalid!\n");
return FALSE;
}
}
m_strSavePath = localfile;
m_strSavePath.TrimLeft();
m_strSavePath.TrimRight();
if(m_strSavePath.IsEmpty() )
return FALSE;
m_strTempSavePath = m_strSavePath;
m_strTempSavePath += ".down";
FILE* fp = fopen(m_strTempSavePath, "r") ;
if(fp == NULL)
{
m_state.range[0] = -1;
m_bResume = FALSE;
}
else{
m_bResume = TRUE;
char* str = new char[1024];
memset(str, 0, 1024);
fgets(str, 1024, fp);
m_state.url.Empty();
m_state.url += str;
m_state.url = m_state.url.Left(m_state.url.GetLength() - 2);
memset(str, 0, 1024);
fgets(str, 1024, fp);
m_state.localfile.Empty();
m_state.localfile += str;
m_state.localfile = m_state.localfile.Left(m_state.localfile.GetLength() - 2);
delete [] str;
fread(&m_state.length, sizeof(LONG), 1, fp);
fread(&m_state.time, sizeof(CTime), 1, fp);
fread(m_state.range, sizeof(LONG), 8, fp);
fclose(fp);
}
if(SendRequest() != SENDREQUEST_SUCCESS){
TRACE("Remote Web Server is not reachable,or somthing else error occured!\n");
return FALSE;
}
if(m_state.range[0] == -1){
m_state.localfile = localfile;
m_state.url = remoteurl;
m_state.time = m_TimeLastModified;
m_state.length = m_dwFileSize;
for(int i = 0; i < 4; i++){
m_state.range[i * 2] = i * (m_dwFileSize / 4);
m_state.range[i * 2 + 1] = (i + 1) * (m_dwFileSize / 4) - 1;
}
m_state.range[7] = m_dwFileSize - 1;
}
else{
if(m_state.url != remoteurl){
AfxMessageBox("Maybe the download file is not you want,you can try to save as another file!");
return FALSE;
}
if(m_state.time < m_TimeLastModified || m_state.range[7] != (LONG)(m_dwFileSize - 1)){
m_state.time = m_TimeLastModified;
m_state.length = m_dwFileSize;
for(int i = 0; i < 4; i++){
m_state.range[i * 2] = i * (m_dwFileSize / 4);
m_state.range[i * 2 + 1] = (i + 1) * (m_dwFileSize / 4);
}
m_state.range[7] = m_dwFileSize - 1;
}
}
return TRUE;
}
BOOL CHTTPDownload::ParseURL(CString str)
{
str.TrimLeft();
if(str.IsEmpty())
return FALSE;
CString strURL = str;
// 清除数据
m_strServer = _T("");
m_strObject = _T("");
m_nPort = 0;
int nPos = strURL.Find("://");
if( nPos == -1 )
return FALSE;
// 进一步验证是否为http://
CString strTemp = strURL.Left( nPos+lstrlen("://") );
strTemp.MakeLower();
if( strTemp.Compare("http://") != 0 )
return FALSE;
strURL = strURL.Mid( strTemp.GetLength() );
nPos = strURL.Find('/');
if ( nPos == -1 )
return FALSE;
m_strObject = strURL.Mid(nPos);
strTemp = strURL.Left(nPos);
///
/// 注意:并没有考虑URL中有用户名和口令的情形和最后有#的情形
/// 例如:http://abc@def:www.yahoo.com:81/index.html#link1
///
//
// 查找是否有端口号
nPos = strTemp.Find(":");
if( nPos == -1 )
{
m_strServer = strTemp;
m_nPort = DEFAULT_HTTP_PORT;
}
else
{
m_strServer = strTemp.Left( nPos );
strTemp = strTemp.Mid( nPos+1 );
m_nPort = (USHORT)_ttoi((LPCTSTR)strTemp);
}
return TRUE;
}
UINT CHTTPDownload::SendRequest(BOOL bHead)
{
CString strVerb;
if( bHead )
strVerb = _T("HEAD ");
else
strVerb = _T("GET ");
CString strSend,strHeader,strRange;
int iStatus = 0,nRet;
char szReadBuf[1025];
DWORD dwContentLength,dwStatusCode;
while (TRUE)
{
if(m_pSocket.m_hSocket != NULL)
m_pSocket.Close();
m_pSocket.Create();
m_pSocket.Connect(m_strServer, m_nPort);
strSend = strVerb + m_strObject + " HTTP/1.1\r\n";
strSend += "Host: " + m_strServer + "\r\n";
strSend += "Accept: */*\r\n";
strSend += "Pragma: no-cache\r\n";
strSend += "Cache-Control: no-cache\r\n";
if( !m_strReferer.IsEmpty() )
strSend += "Referer: " + m_strReferer + "\r\n";
strSend += "Connection: close\r\n";
strRange = "Range: bytes=100-\r\n";
strSend += strRange;
//必须要加一个空行,否则Http服务器将不会应答
strSend += "\r\n";
int ret = m_pSocket.Send(strSend.GetBuffer(0), strSend.GetLength());
strSend.ReleaseBuffer();
strHeader.Empty();
while( TRUE )
{
ZeroMemory(szReadBuf,1025);
ret = m_pSocket.Receive(szReadBuf, 1025);
if( szReadBuf[0] == '\0' ) // We have encountered "\r\n\r\n"
break;
strHeader += szReadBuf;
if( iStatus == 0)
strHeader += "\r\n";
}
nRet = GetInfo(strHeader,dwContentLength,
dwStatusCode,m_TimeLastModified);
switch ( nRet )
{
case HTTP_FAIL:
return SENDREQUEST_FAIL;
break;
case HTTP_ERROR:
return SENDREQUEST_ERROR;
break;
case HTTP_REDIRECT:
continue;
break;
case HTTP_OK:
m_dwDownloadSize = dwContentLength + 100;
// 应该判断一下服务器是否支持断点续传
if( strRange.IsEmpty() )
m_dwFileSize = dwContentLength + 100; // 整个文件的长度
else
{
if ( dwStatusCode == 206 ) //支持断点续传
{
m_bSupportResume = TRUE;
m_dwFileSize = dwContentLength + 100;
}
else //不支持断点续传
{
m_bSupportResume = FALSE;
m_dwFileSize = dwContentLength + 100;
}
}
return SENDREQUEST_SUCCESS;
break;
default:
return SENDREQUEST_FAIL;
break;
}
}
m_pSocket.Close();
return SENDREQUEST_SUCCESS;
}
UINT CHTTPDownload::GetInfo(LPCTSTR lpszHeader, DWORD &dwContentLength,
DWORD &dwStatusCode, CTime &TimeLastModified)
{
dwContentLength = 0;
dwStatusCode = 0;
TimeLastModified= CTime::GetCurrentTime();
CString strHeader = lpszHeader;
strHeader.MakeLower();
//拆分出HTTP应答的头信息的第一行
int nPos = strHeader.Find("\r\n");
if (nPos == -1)
return HTTP_FAIL;
CString strFirstLine = strHeader.Left(nPos);
// 获得返回码: Status Code
strFirstLine.TrimLeft();
strFirstLine.TrimRight();
nPos = strFirstLine.Find(' ');
if( nPos == -1 )
return HTTP_FAIL;
strFirstLine = strFirstLine.Mid(nPos+1);
nPos = strFirstLine.Find(' ');
if( nPos == -1 )
return HTTP_FAIL;
strFirstLine = strFirstLine.Left(nPos);
dwStatusCode = (DWORD)_ttoi((LPCTSTR)strFirstLine);
// 检查返回码
if( dwStatusCode >= 300 && dwStatusCode < 400 ) //首先检测一下服务器的应答是否为重定向
{
nPos = strHeader.Find("location:");
if (nPos == -1)
return HTTP_FAIL;
CString strRedirectFileName = strHeader.Mid(nPos + strlen("location:"));
nPos = strRedirectFileName.Find("\r\n");
if (nPos == -1)
return HTTP_FAIL;
strRedirectFileName = strRedirectFileName.Left(nPos);
strRedirectFileName.TrimLeft();
strRedirectFileName.TrimRight();
// 设置Referer
m_strReferer = m_strDownloadUrl;
// 判断是否重定向到其他的服务器
nPos = strRedirectFileName.Find("http://");
if( nPos != -1 )
{
m_strDownloadUrl = strRedirectFileName;
// 检验要下载的URL是否有效
if ( !ParseURL(m_strDownloadUrl))
return HTTP_FAIL;
return HTTP_REDIRECT;
}
// 重定向到本服务器的其他地方
strRedirectFileName.Replace("");
// 是相对于根目录
if( strRedirectFileName[0] == '/' )
{
m_strObject = strRedirectFileName;
return HTTP_REDIRECT;
}
// 是相对当前目录
int nParentDirCount = 0;
nPos = strRedirectFileName.Find("../");
while (nPos != -1)
{
strRedirectFileName = strRedirectFileName.Mid(nPos+3);
nParentDirCount++;
nPos = strRedirectFileName.Find("../");
}
for (int i=0; i<=nParentDirCount; i++)
{
nPos = m_strDownloadUrl.ReverseFind('/');
if (nPos != -1)
m_strDownloadUrl = m_strDownloadUrl.Left(nPos);
}
m_strDownloadUrl = m_strDownloadUrl+"/"+strRedirectFileName;
if ( !ParseURL(m_strDownloadUrl))
return HTTP_FAIL;
return HTTP_REDIRECT;
}
// 服务器错误,可以重试
if( dwStatusCode >= 500 )
return HTTP_ERROR;
// 客户端错误,重试无用
if( dwStatusCode >=400 && dwStatusCode <500 )
return HTTP_FAIL;
// 获取ContentLength
nPos = strHeader.Find("content-length:");
if (nPos == -1)
return HTTP_FAIL;
CString strDownFileLen = strHeader.Mid(nPos + strlen("content-length:"));
nPos = strDownFileLen.Find("\r\n");
if (nPos == -1)
return HTTP_FAIL;
strDownFileLen = strDownFileLen.Left(nPos);
strDownFileLen.TrimLeft();
strDownFileLen.TrimRight();
// Content-Length:
dwContentLength = (DWORD) _ttoi( (LPCTSTR)strDownFileLen );
// 获取Last-Modified:
nPos = strHeader.Find("last-modified:");
if (nPos != -1)
{
CString strTime = strHeader.Mid(nPos + strlen("last-modified:"));
nPos = strTime.Find("\r\n");
if (nPos != -1)
{
strTime = strTime.Left(nPos);
strTime.TrimLeft();
strTime.TrimRight();
TimeLastModified = GetTime(strTime);
}
}
return HTTP_OK;
}
CTime CHTTPDownload::GetTime(LPCTSTR lpszTime)
{
int nDay,nMonth,nYear,nHour,nMinute,nSecond;
CString strTime = lpszTime;
int nPos = strTime.Find(',');
if (nPos != -1)
{
strTime = strTime.Mid(nPos+1);
strTime.TrimLeft();
CString strDay,strMonth,strYear,strHour,strMinute,strSecond;
CString strAllMonth = "jan,feb,mar,apr,may,jan,jul,aug,sep,oct,nov,dec";
strDay = strTime.Left(2);
nDay = atoi(strDay);
strMonth = strTime.Mid(3,3);
strMonth.MakeLower();
nPos = strAllMonth.Find(strMonth);
if (nPos != -1)
{
strMonth.Format("%d",((nPos/4)+1));
nMonth = atoi(strMonth);
}
else
nMonth = 1;
strTime = strTime.Mid(6);
strTime.TrimLeft();
nPos = strTime.FindOneOf(" \t");
if (nPos != -1)
{
strYear = strTime.Left(nPos);
nYear = atoi(strYear);
}
else
nYear = 2000;
strTime = strTime.Mid(nPos+1);
strHour = strTime.Left(2);
nHour = atoi(strHour);
strMinute = strTime.Mid(3,2);
nMinute = atoi(strMinute);
strSecond = strTime.Mid(6,2);
nSecond = atoi(strSecond);
}
CTime time(nYear,nMonth,nDay,nHour,nMinute,nSecond);
return time;
}
UINT CHTTPDownload::ThreadFunc(int index )
{
CSocket pSocket;
pSocket.Create();
pSocket.Connect(m_strServer, m_nPort);
CString strSend, strRange, strHeader;
char szReadBuf[1025];
strSend = "GET " + m_strObject + " HTTP/1.1\r\n";
strSend += "Host: " + m_strServer + "\r\n";
strSend += "Accept: */*\r\n";
strSend += "Pragma: no-cache\r\n";
strSend += "Cache-Control: no-cache\r\n";
if( !m_strReferer.IsEmpty() )
strSend += "Referer: " + m_strReferer + "\r\n";
strSend += "Connection: close\r\n";
strRange.Format("Range: bytes=%d-%d\r\n", m_state.range[2 * index], m_state.range[2 * index + 1]);
if(m_bSupportResume)
strSend += strRange;
//必须要加一个空行,否则Http服务器将不会应答
strSend += "\r\n";
int ret = pSocket.Send(strSend.GetBuffer(0), strSend.GetLength());
strSend.ReleaseBuffer();
DWORD lpArgument;
if(!pSocket.AsyncSelect(0) && !pSocket.IOCtl(FIONBIO, &lpArgument))
return -1;
strHeader.Empty();
int iStatus = 0;
ZeroMemory(szReadBuf,1025);
ret = pSocket.Receive(szReadBuf, 1025);
int n = GetHeadLength(szReadBuf);
CFile file;
CString name;
name.Format("%d", index);
name = m_strTempSavePath /*+ name*/ ; name.Replace(".down","");
int bOpen = 0;
if(!m_bResume)
bOpen = file.Open(name, CFile::modeCreate | CFile::modeWrite);
else
bOpen = file.Open(name, CFile::modeWrite);
if(bOpen == 0){
TRACE("Error in file open!\n");
return -10;
}
file.SeekToEnd();
TRACE("Entering the key section!\n");
file.Write(szReadBuf + n, ret - n);
int sum = ret - n, num = 0;
while(1){
if(m_bTerminate[index]){
m_state.range[2 * index] = m_state.range[2 * index] + sum;
return -5;
}
ZeroMemory(szReadBuf,1025);
if(!(num = pSocket.Receive(szReadBuf, 1025))
|| num == SOCKET_ERROR)
break; // (EOF||network error)
else{
file.Write(szReadBuf, num);
sum += num;
}
}
file.Close();
return 0;
}
int CHTTPDownload::GetHeadLength(char *lpData)
{
int ndx = 0;
CString str;
while(1){
str.Empty();
str = GetLine(lpData, ndx);
if(str.IsEmpty())
break;
}
return (ndx);
}
CString CHTTPDownload::GetLine(char *lpData, int& ndx)
{
BOOL bLine = FALSE;
CString str;
while ( bLine == FALSE && ndx < 1025 )
{
char ch = (char)(lpData[ndx]);
switch( ch )
{
case '\r': // ignore
break;
case '\n': // end-of-line
bLine = TRUE;
break;
default: // other....
str += ch;
break;
}
++ndx;
}
return str;
}
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
// HTTPDownload.h: interface for the CHTTPDownload class.
//
//
#if !defined(AFX_HTTPDOWNLOAD_H__BA195238_6DC5_4491_9173_FEC73B994BDC__INCLUDED_)
#define AFX_HTTPDOWNLOAD_H__BA195238_6DC5_4491_9173_FEC73B994BDC__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#include <afxtempl.h>
#include <afxmt.h>
#include <afxsock.h>
//This struct is used to save the uncomplished task's info
//Now this program can only start a task once a time.
typedef struct tagDOWNLOADSTATE{
CString url;
CString localfile;
CTime time;
LONG length;
LONG range[8];
}DOWNLOADSTATE,*LPDOWNLOADSTATE;
#define DEFAULT_HTTP_PORT 80
// HTTP STATUS CODE分类
const UINT HTTP_OK = 0;
const UINT HTTP_ERROR = 1;
const UINT HTTP_REDIRECT = 2;
const UINT HTTP_FAIL = 3;
// 发送请求
const UINT SENDREQUEST_SUCCESS = 0; // 成功
const UINT SENDREQUEST_ERROR = 1; // 一般网络错误,可以重试
const UINT SENDREQUEST_STOP = 2; // 中途停止(用户中断) (不用重试)
const UINT SENDREQUEST_FAIL = 3; // 失败 (不用重试)
class CHTTPDownload
{
public:
UINT ThreadFunc(int index = 0 );
CTime GetTime(LPCTSTR lpszTime);
UINT GetInfo(LPCTSTR lpszHeader, DWORD &dwContentLength, DWORD &dwStatusCode, CTime &TimeLastModified);
BOOL StartTask(CString remoteurl, CString localfile);
CHTTPDownload();
virtual ~CHTTPDownload();
public:
CString GetLine(char* lpData, int& ndx);
int GetHeadLength(char* lpData);
UINT SendRequest(BOOL bHead = FALSE);
BOOL ParseURL(CString str);
CTypedPtrList<CPtrList, CAsyncSocket*> m_lsTask;
CString m_strSavePath;
CString m_strTempSavePath;
DOWNLOADSTATE m_state;
BOOL m_bTerminate[4];
CString m_strHead;
CSocket m_pSocket;
CString m_strServer;
CString m_strObject;
CString m_strReferer;
CString m_strDownloadUrl;
CTime m_TimeLastModified;
DWORD m_dwDownloadSize;
BOOL m_bSupportResume;
BOOL m_bResume;
DWORD m_dwFileSize;
UINT m_nPort;
CCriticalSection m_cs;
LONG m_index;
};
#endif // !defined(AFX_HTTPDOWNLOAD_H__BA195238_6DC5_4491_9173_FEC73B994BDC__INCLUDED_)
//
应用:添加到VC工程后:
void DownloadFile(char * pSeverFilename, char * pLcoalFilenam)
{
CHTTPDownload httpdownload;
httpdownload.StartTask( pSeverFilename , pLcoalFilenam );
httpdownload.ThreadFunc(0);
}