程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> VC >> 關於VC++ >> 直接用socket實現HTTP協議(下載專用)

直接用socket實現HTTP協議(下載專用)

編輯:關於VC++

從HTTP服務器上下載一個文件有很多方法,“熱心”的微軟提供了 WinInet 類,用起來也很方便。當然,我們也可以自己實現這些功能,通過格式化請求頭很容易就能實現斷點續傳和檢查更新等等功能 。本文附帶的工程中有一個支持 HTTP1.1 協議,直接用 Socket 實現下載功能的 DLL,實現了以下功能:

連接主機

格式化請求頭

設置接收,發送超時

接收並分析回應頭

連接,發送,設置超時,接收數據等我就不細說了,windows socket早就做好了,調用相應的函數就OK了。

要想從服務器下載文件,首先要向服務器發送一個請求。HTTP 請求頭由若干行字符串組成。下面結合實例說說 HTTP 請求頭的格式。假設要下載 http://www.sina.com.cn/index.html 這個網頁 ,那麼請求頭的寫法如下:

第1行:方法,請求的內容,HTTP協議的版本

下載一般可以用GET方法,請求的內容是“/index.html”,HTTP協議的版本是指浏覽器支持的版本,對於下載軟件來說無所謂,所以用1.1版 “HTTP/1.1”;

“GET /index.html HTTP/1.1”

第2行:主機名,格式為“Host:主機”

在這個例子中是:“Host:www.sina.com.cn”

第3行:接受的數據類型,下載軟件當然要接收所有的數據類型,所以:

“Accept:*/*”

第4行:指定浏覽器的類型

有些服務器會根據客戶服務器種類的不同會增加或減少一些內容,在這個例子中可以這樣寫:

“User-Agent:Mozilla/4.0 (compatible; MSIE 5.00; Windows 98)”

第5行:連接設置

設定為一直保持連接:“Connection:Keep-Alive”

第6行:若要實現斷點續傳則要指定從什麼位置起接收數據,格式如下:

“Range: bytes=起始位置 - 終止位置”

比如要讀前500個字節可以這樣寫:“Range: bytes=0 - 499”;從第 1000 個字節起開始下載:

“Range: bytes=999 -”

最後,別忘了加上一行空行,表示請求頭結束。整個請求頭如下:

GET /index.html HTTP/1.1
Host:www.sina.com.cn
Accept:*/*
User-Agent:Mozilla/4.0 (compatible; MSIE 5.00; Windows 98)
Connection:Keep-Alive

CHttpSocket 提供了 FormatRequestHeader()函數,用以格式化輸出HTTP請求頭。代碼如下:

///根據請求的相對URL輸出HTTP請求頭
const char *CHttpSocket::FormatRequestHeader(char *pServer,char *pObject, long &Length,
char *pCookie,char *pReferer,long nFrom,
long nTo,int nServerType)
{
  char szPort[10];
  char szTemp[20];
  sprintf(szPort,"%d",m_port);
  memset(m_requestheader,''\0'',1024);
  ///第1行:方法,請求的路徑,版本
  strcat(m_requestheader,"GET ");
  strcat(m_requestheader,pObject);
  strcat(m_requestheader," HTTP/1.1");
     strcat(m_requestheader,"\r\n");
  ///第2行:主機
  strcat(m_requestheader,"Host:");
  strcat(m_requestheader,pServer);
  strcat(m_requestheader,"\r\n");
  ///第3行:
  if(pReferer != NULL)
  {
    strcat(m_requestheader,"Referer:");
    strcat(m_requestheader,pReferer);
    strcat(m_requestheader,"\r\n");
  }
  ///第4行:接收的數據類型
  strcat(m_requestheader,"Accept:*/*");
  strcat(m_requestheader,"\r\n");
  ///第5行:浏覽器類型
  strcat(m_requestheader,"User-Agent:Mozilla/4.0 (compatible; MSIE 5.00; Windows 98)");
  strcat(m_requestheader,"\r\n");
  ///第6行:連接設置,保持
  strcat(m_requestheader,"Connection:Keep-Alive");
  strcat(m_requestheader,"\r\n");
  ///第7行:Cookie.
  if(pCookie != NULL)
  {
    strcat(m_requestheader,"Set Cookie:0");
    strcat(m_requestheader,pCookie);
    strcat(m_requestheader,"\r\n");
  }
  ///第8行:請求的數據起始字節位置(斷點續傳的關鍵)
  if(nFrom > 0)
  {
    strcat(m_requestheader,"Range: bytes=");
    _ltoa(nFrom,szTemp,10);
    strcat(m_requestheader,szTemp);
    strcat(m_requestheader,"-");
    if(nTo > nFrom)
    {
      _ltoa(nTo,szTemp,10);
      strcat(m_requestheader,szTemp);
    }
    strcat(m_requestheader,"\r\n");
  }

  ///最後一行:空行
  strcat(m_requestheader,"\r\n");
  ///返回結果
  Length=strlen(m_requestheader);
  return m_requestheader;
}

請求頭發送給服務器後就可以接收來自服務器的回應頭了。回應頭也是由若干行字符串組成,除了第一行和最後一個空行以外,每一行都由一個域和一個值組成。第一行包括了服務器的回應狀態 ,從 2XX 到 5XX,每個狀態碼都有不同的意思,詳細內容可以查看RFC文檔下載需要關心的有 :2XX表示成功,可以繼續讀取數據;3XX表示目標已經轉移,新的地址在“Location”域中;4XX表示客戶端錯,可能是下載地址不對,等等;5XX表示服務器端錯 。回應頭中的域有“Content-Length”,“Accept-Ranges”,“Content-Type”,“Date”,“Last-Modified”,“Location”等等內容 ,下載比較關心的域有“Content-Length”域和“Location”域。“Content-Length”表示下載文件的大小 ,“Location”表示目標的實際存放位置,當回應碼為3XX時就要用該域中的值重新連接。

附帶源碼中的 CHttpSocket 類提供了以下幾個方法,分別用來讀取服務器狀態碼,某個域的值,回應頭中的一行以及整個回應頭:

int  GetServerState();  //返回服務器狀態碼 -1表示不成功
int  GetField(const char* szSession,char *szValue,int nMaxLength);//返回某個域值,-1表示不成功
int  GetResponseLine(char *pLine,int nMaxLength);//獲取返回頭的一行
const char*  GetResponseHeader(int &Length);

取得回應頭後,如果回應碼為2XX並且“Content-Length”的值不等於0就表示可以接收下載文件數據了,接下來的工作就很簡單了,調用 CHttpSocket::Recevie()直到接收的數據長度等於“Content-Length”的值就可以了 。

一個完整的使用過程由以下幾個步驟組成:

調用AfxParseURL()分析URL得到Server和下載路徑;

調用CHttpSocket::Socket()創建套接字;

調用CHttpSocket::Connect()連接服務器;

調用CHttpSocket::FormatRequestHeader()格式化請求頭;

調用CHttpSocket::SendRequest()向服務器發送請求頭;

調用CHttpSocket::GetServerState()得到回應狀態碼;

調用CHttpSocket::GetField("Content-Length")得到下載文件的大小;

調用CHttpSocket::Receive()接收數據直到數據接收完成;

本文附帶源代碼還包括了一個使用 CHttpSocket 實現下載功能的例子工程。注意,所有的調用都是阻塞的,所以最好為一個下載任務創建一個線程 ,否則會導致界面無法響應用戶輸入。程序運行界面如下圖所示:

該圖顯示了請求頭,回應頭以及下載進度。

當然,要真正實現多任務多線程下載還有很多工作要做。本文僅僅討論了自己實現下載的一種可能性,希望對讀者有所幫助。

本文配套源碼

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved