HTTP 客户端下载

来源:互联网 发布:淘宝股份占比例图 编辑:程序博客网 时间:2024/05/02 08:12

参考《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);

}