直接用socket實現HTTP下載

從HTTP服務器上下載一個文件有很多方法,"熱心"的微軟提供了WinInet類,用起來也很方便.當然,我們也可以自己實現這些功能,通過格式化請求頭很容易就能實現斷點續傳和檢查更新等等功能.本文附帶的工程中有一個支持HTTP1.1協議,直接用Socket實現下載功能的DLL,實現了以下功能:
1.連接主機
2.格式化請求頭
3.設置接收,發送超時
4.接收並分析迴應頭
連接,發送,設置超時,接收數據等我就不細說了,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"的值就可以了.
一個完整的使用過程由以下幾個步驟組成:
1.調用AfxParseURL()分析URL得到Server和下載路徑.
2.調用CHttpSocket::Socket()創建套接字.
3.調用CHttpSocket::Connect()連接服務器.
4.調用CHttpSocket::FormatRequestHeader()格式化請求頭.
5.調用CHttpSocket::SendRequest()向服務器發送請求頭.
6.調用CHttpSocket::GetServerState()得到迴應狀態碼.
7.調用CHttpSocket::GetField("Content-Length")得到下載文件的大小.
8.調用CHttpSocket::Receive()接收數據直到數據接收完成.
    本文附帶源代碼還包括了一個使用CHttpSocket實現下載功能的例子工程.注意,所有的調用都是阻塞的,所以最好爲一個下載任務創建一個線程,否則會導致界面無法響應用戶輸入. 程序運行界面如上圖,顯示了請求頭,迴應頭以及下載進度. 
    當然,要真正實現多任務多線程下載還有很多工作要做.本文僅僅討論了自己實現下載的一種可能性,希望對讀者有所幫助

發佈了29 篇原創文章 · 獲贊 34 · 訪問量 17萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章