WinINet不是給服務(wù)端用的,服務(wù)端用Microsoft Windows HTTP Services (WinHTTP)WinINet抽象了Gopher,F(xiàn)TP,HTTP協(xié)議的一些細(xì)節(jié)。
WinINet函數(shù)創(chuàng)建、使用的句柄都是HINTERNET類型的,這種類型的句柄無法被轉(zhuǎn)換成其 它類型的句柄。換句話說,最好別用ReadFile、CloseHandle之類的函數(shù)來操作這些句柄。同樣的,也別用WinINet函數(shù)來訪問、操作其 他類型的句柄。比如,用InternetReadFile訪問CreateFile創(chuàng)建句柄是無法得到你想要的結(jié)果的。想關(guān)閉HINTERNET句柄要使 用InternetCloseHandle函數(shù)。
3.句柄架構(gòu)
InternetOpen 創(chuàng)建的句柄在頂層,由接下來的一層的 InternetOpenUrl 和 InternetConnect 使用,而 InternetConnect 創(chuàng)建的句柄又被之后的幾個函數(shù)使用。
下面這張圖是依賴 InternetOpenUrl 創(chuàng)建的句柄的幾個函數(shù),灰色的方框是返回 HINTERNET 句柄的函數(shù),而白色的框就是使用被創(chuàng)建的 HINTERNET 句柄的函數(shù)
FTP Hierarchy
HTTP Hierarchy
注意這張圖,這張圖的意思是 HttpSendRequestEx 先訪問 HttpOpenRequest 創(chuàng)建的句柄之后, HttpEndRequest, InternetReadFileEx 和 InternetWriteFile 才能訪問這個句柄。 HttpEndRequest 被調(diào)用之后,才輪的到InternetReadFile, InternetSetFilePointer 和 InternetQueryDataAvailable 來訪問這個句柄。
4.內(nèi)容編碼
HTTP 協(xié)議 (RFC 2616) 規(guī)定了應(yīng)用程序可以要求服務(wù)器用編碼的方式(encoded format)返回HTTP響應(yīng)。在Windows Server 2008 與 Windows Vista之前,發(fā)送給應(yīng)用程序的內(nèi)容編碼了的請求需要應(yīng)用程序自己處理,從Windows Server 2008 and Windows Vista開始, 應(yīng)用程序可以讓 WinINet 來解碼了(gzip與deflate)。有三種方式開啟解碼選項(基于會話、請求、連接),它們的作用域不同。可以使用InternetOpen(基于會話), InternetConnect(基于連接), HttpOpenRequest(基于請求)返回的句柄調(diào)用InternetSetOption 來打開或關(guān)閉解碼選項,打開則將 dwOption 參數(shù)中INTERNET_OPTION_HTTP_DECODING 選項打開, 令 lpBuffer 指向一個為true的boolean變量. 關(guān)閉則dwOption 參數(shù)中INTERNET_OPTION_HTTP_DECODING 選項打開, 令 lpBuffer 指向一個為false的boolean變量 .
設(shè)置解碼選項之后, WinInet 在你調(diào)用 InternetReadFile 是就會執(zhí)行一次解碼。不過就算你打開了解碼選項,它也不一定就為你解碼……當(dāng)這種情況發(fā)生時, InternetReadFile 函數(shù)會失敗并返回ERROR_INTERNET_DECODING_FAILED.這個時候你可以選擇去掉Accept-Encoding頭重新發(fā)送一次請求,或者把解碼關(guān)掉然后自己來解碼(這時你就得檢查Content-Encoding頭來判斷編碼方式了)。
5.協(xié)議無關(guān)函數(shù)
- 從Internet下載文件 (InternetReadFile, InternetSetFilePointer, InternetFindNextFile, InternetQueryDataAvailable).
- 設(shè)置同步操作 (InternetSetStatusCallback).
- 查詢、修改設(shè)置 (InternetSetOption , InternetQueryOption).
- 關(guān)閉 HINTERNET 句柄 (InternetCloseHandle).
- 鎖定、解鎖資源文件 (InternetLockRequestFile , InternetUnlockRequestFile).
Function | Description |
InternetFindNextFile | 繼續(xù)文件的枚舉或搜索. 需要以下函數(shù)創(chuàng)建的句柄 FtpFindFirstFile, GopherFindFirstFile, InternetOpenUrl |
InternetLockRequestFile | 允許用戶鎖定文件. 需要以下函數(shù)創(chuàng)建的句柄 FtpOpenFile, GopherOpenFile, HttpOpenRequest, InternetOpenUrl . |
InternetQueryDataAvailable | 查詢可用數(shù)據(jù)的數(shù)量. 需要以下函數(shù)創(chuàng)建的句柄 FtpOpenFile, GopherOpenFile, HttpOpenRequest . |
InternetQueryOption | 查詢 Internet 設(shè)置. |
InternetReadFile | 讀取 URL 數(shù)據(jù). 需要以下函數(shù)創(chuàng)建的句柄 InternetOpenUrl, FtpOpenFile, GopherOpenFile, HttpOpenRequest . |
InternetSetFilePointer | 設(shè)置文件指針. 需要以下函數(shù)創(chuàng)建的句柄 InternetOpenUrl ( HTTP URL only) HttpOpenRequest (GET 方法). |
InternetSetOption | 配置 Internet 設(shè)置. |
InternetSetStatusCallback | 設(shè)置一個接收狀態(tài)信息的回調(diào)函數(shù). 分配一個回調(diào)函數(shù)給指定的 HINTERNET 句柄及從其演化而來的句柄. |
InternetUnlockRequestFile | 解鎖被 InternetLockRequestFile 鎖定的文件. |
讀文件
函數(shù) InternetReadFile 用來從一個由函數(shù) InternetOpenUrl, FtpOpenFile, GopherOpenFile, HttpOpenRequest 返回的 HINTERNET 句柄下載資源.
WinINet 提供了兩種方法來下載整個資源
- InternetQueryDataAvailable 函數(shù).
- 利用 InternetReadFile 的返回值
InternetQueryDataAvailable 使用 InternetOpenUrl, FtpOpenFile, GopherOpenFile, HttpOpenRequest (還記得上面的那個圖么,這個函數(shù)需要在 HttpSendRequest 操作該句柄之后才能訪問這個句柄) 創(chuàng)建的句柄。返回有效數(shù)據(jù)的字節(jié)數(shù),據(jù)此,我們就可以分配足夠的內(nèi)存并進(jìn)行讀取,但是這種方法不保險,包頭里的長度可能是過時的,而且包頭也可能會丟。
當(dāng)所有數(shù)據(jù)都被讀完的時候, InternetReadFile 會返回0字節(jié)被讀取(lpdwNumberOfBytesRead 參數(shù)保存了讀取的字節(jié)數(shù)). 據(jù)此,我們就可以循環(huán)調(diào)用 InternetReadFile 直到讀取結(jié)束。
尋找文件
先使用 FtpFindFirstFile, GopherFindFirstFile, 或 InternetOpenUrl ,然后將其返回的句柄作為參數(shù) 傳遞給 InternetFindNextFile 進(jìn)行繼續(xù)查找,持續(xù)調(diào)用 InternetFindNextFile 知道返回擴展的錯誤信息 ERROR_NO_MORE_FILES 來完成整個搜索,調(diào)用 GetLastError 來獲取最后的錯誤信息.
6.HTTP 會話
使用 WinINet 函數(shù)訪問WWW資源
(1)初始化 WWW 連接
將服務(wù)類型設(shè)為 INTERNET_SERVICE_HTTP 調(diào)用 InternetConnect 來建立一個 HTTP 會話
HINTERNET InternetConnect(
__in HINTERNET hInternet, //InternetOpen 返回的句柄
__in LPCTSTR lpszServerName, //可以描述目標(biāo)服務(wù)器的字符串
__in INTERNET_PORT nServerPort,//目標(biāo)服務(wù)器的端口
__in LPCTSTR lpszUsername,//用戶名
__in LPCTSTR lpszPassword,//密碼
__in DWORD dwService,//使用的服務(wù)類型
__in DWORD dwFlags,
__in DWORD_PTR dwContext
);nServerPort
Value Meaning INTERNET_DEFAULT_FTP_PORT
Uses the default port for FTP servers (port 21).
INTERNET_DEFAULT_GOPHER_PORT
Uses the default port for Gopher servers (port 70).
INTERNET_DEFAULT_HTTP_PORT
Uses the default port for HTTP servers (port 80).
INTERNET_DEFAULT_HTTPS_PORT
Uses the default port for Secure Hypertext Transfer Protocol (HTTPS) servers (port 443).
INTERNET_DEFAULT_SOCKS_PORT
Uses the default port for SOCKS firewall servers (port 1080).
INTERNET_INVALID_PORT_NUMBER
Uses the default port for the service specified by dwService.
ldwService
Value Meaning INTERNET_SERVICE_FTP
FTP service.
INTERNET_SERVICE_GOPHER
Gopher service.
INTERNET_SERVICE_HTTP
HTTP service.
(2)建立請求
調(diào)用 HttpOpenRequest 來建立一個 HTTP 請求,不過這個函數(shù)不會自動把請求發(fā)送出去,要發(fā)送請求需要調(diào)用 HttpSendRequest
HINTERNET HttpOpenRequest(
__in HINTERNET hConnect, // InternetConnect 函數(shù)返回的句柄
__in LPCTSTR lpszVerb, // 動作,有GET, PUT, POST。也可以設(shè)置為 NULL ,會被當(dāng)成默認(rèn)的 GET 來用
__in LPCTSTR lpszObjectName, // 一個描述你請求資源的字符串,當(dāng)請求一個默認(rèn)頁面時令這個參數(shù)指向一個空串
__in LPCTSTR lpszVersion, // HTTP 版本,這個參數(shù)為 NULL 時,默認(rèn)使用""HTTP/1.1""
__in LPCTSTR lpszReferer, // 說明了lpszObjectName是取自哪個文件,可以設(shè)為NULL
__in LPCTSTR *lplpszAcceptTypes, //是一個指向LPCTSTR數(shù)組的指針!數(shù)組以一個NULL指針結(jié)束。指定了程序接受的內(nèi)容的類型,設(shè)為空則不接受 任何類型的內(nèi)容,設(shè)為空串則等價于""text/*"",即不接受文本文件以外的圖片等文件,只接受某種特定的文件可以用類似"image/gif, image/jpeg"的方式。關(guān)于更多內(nèi)容類型 請看這里
__in DWORD dwFlags, // 一般都可以設(shè)置為 0
__in DWORD_PTR dwContext // 一般都可以設(shè)置為 0
);(3)添加請求
BOOL HttpAddRequestHeaders(__in HINTERNET hConnect,//HttpOpenRequest 返回的句柄__in LPCTSTR lpszHeaders,//包含要添加到請求中的頭的字符串的指針,每個頭都要以一個 CR/LF ( "r"n"r"n ) 對結(jié)束__in DWORD dwHeadersLength,//lpszHeaders指向的字符串的長度(以TCHAR類型記). 如果這個參數(shù)被設(shè)為-1,則字符串被當(dāng)作以0結(jié)尾的字符串處理,自動計算該字符串的長度__in DWORD dwModifiers);dwModifiers可以是下面這些值的組合
Value Meaning HTTP_ADDREQ_FLAG_ADD
Adds the header if it does not exist. Used with HTTP_ADDREQ_FLAG_REPLACE.
HTTP_ADDREQ_FLAG_ADD_IF_NEW
Adds the header only if it does not already exist; otherwise, an error is returned.
HTTP_ADDREQ_FLAG_COALESCE
Coalesces headers of the same name.
HTTP_ADDREQ_FLAG_COALESCE_WITH_COMMA
Coalesces headers of the same name. For example, adding "Accept: text/*" followed by "Accept: audio/*" with this flag results in the formation of the single header "Accept: text/*, audio/*". This causes the first header found to be coalesced. It is up to the calling application to ensure a cohesive scheme with respect to coalesced/separate headers.
HTTP_ADDREQ_FLAG_COALESCE_WITH_SEMICOLON
Coalesces headers of the same name using a semicolon.
HTTP_ADDREQ_FLAG_REPLACE
Replaces or removes a header. If the header value is empty and the header is found, it is removed. If not empty, the header value is replaced.
(4)發(fā)送一個請求
BOOL HttpSendRequest(
__in HINTERNET hRequest, //HttpOpenRequst 返回的句柄
__in LPCTSTR lpszHeaders, //附加到請求上的頭,可以為NULL
__in DWORD dwHeadersLength, //lpszHeaders指向的字符串的長度(以TCHAR類型記). 如果這個參數(shù)被設(shè)為-1,當(dāng)調(diào)用的是HttpSendRequestA時則字符串被當(dāng)作以0結(jié)尾的字符串處理,自動計算該字符串的長度。當(dāng)調(diào)用的是 HttpSendRequestW時就會產(chǎn)生一個錯誤
__in LPVOID lpOptional, //當(dāng)使用POST或PUT方法時,這個參數(shù)指向的數(shù)據(jù)會緊接著請求被發(fā)送出去。沒有需要發(fā)送的數(shù)據(jù)則可以設(shè)置為NULL
__in DWORD dwOptionalLength //lpOptional數(shù)據(jù)的字節(jié)長度,無數(shù)據(jù)時設(shè)置為0
);(5)向服務(wù)器發(fā)送數(shù)據(jù)
方法一:參見上面HttpSentRequest的lpOptional參數(shù)的說明
方法二:使用 InternetWriteFile 向一個句柄里發(fā)送數(shù)據(jù),然后使用 HttpSendRequestEx 發(fā)送
(6)查詢一個請求的信息
BOOL HttpQueryInfo(
__in HINTERNET hRequest, /*由 HttpOpenRequest 或 InternetOpenUrl 返回的句柄*/
__in DWORD dwInfoLevel,/* Query Info Flags.*/__inout LPVOID lpvBuffer, /*用于存儲查詢結(jié)果的緩沖區(qū),不可為NULL*/
__inout LPDWORD lpdwBufferLength,/*lpvBuffer指向的緩沖區(qū)的字節(jié)長度
若函數(shù)執(zhí)行成功,這個變量存儲的是寫到緩沖區(qū)里的數(shù)據(jù)長度。如果數(shù)據(jù)是字符串則返回的長度不包括字符串的結(jié)束字符
如果函數(shù)發(fā)生 ERROR_INSUFFICIENT_BUFFER 錯誤, 則這個變量里保存的是數(shù)據(jù)的實際字節(jié)長度程序需要根據(jù)這個長度重新分配內(nèi)存,再執(zhí)行一次這個函數(shù)*/
__inout LPDWORD lpdwIndex /*沒看大明白,不過似乎可以設(shè)置為NULL
Pointer to a zero-based header index used to enumerate multiple headers with the same name.
When calling the function, this parameter is the index of the specified header to return.
When the function returns, this parameter is the index of the next header.
If the next index cannot be found, ERROR_HTTP_HEADER_NOT_FOUND is returned.*/
);(7)從 WWW 上下載資源
在調(diào)用 HttpOpenRequest 和 HttpSendRequest 之后, 程序可以使用 InternetReadFile, InternetQueryDataAvailable, InternetSetFilePointer 來下載HTTP服務(wù)器上的資源了。
BOOL InternetQueryDataAvailable(//查詢數(shù)據(jù)的長度
__in HINTERNET hFile, //由 InternetOpenUrl, FtpOpenFile, GopherOpenFile, 或 HttpOpenRequest
返回的句柄
__out LPDWORD lpdwNumberOfBytesAvailable, //用于存放數(shù)據(jù)長度的指針
__in DWORD dwFlags, //保留參數(shù),置0
__in DWORD_PTR dwContext //保留參數(shù),置0
);BOOL InternetReadFile( //讀取句柄的數(shù)據(jù)__in HINTERNET hFile, // InternetOpenUrl, FtpOpenFile, GopherOpenFile, HttpOpenRequest 創(chuàng)建的句柄__out LPVOID lpBuffer, // 存放數(shù)據(jù)的緩沖區(qū)__in DWORD dwNumberOfBytesToRead, // 準(zhǔn)備讀取的字節(jié)數(shù)__out LPDWORD lpdwNumberOfBytesRead // 讀取了的字節(jié)數(shù));
DWORD InternetSetFilePointer( //設(shè)置InternetReadFile的文件位置(莫非多線程下載就是用這個實現(xiàn)的?),服務(wù)器不支持隨機訪問的話函數(shù)調(diào)用會失敗,如果 InternetReadFile已經(jīng)讀取到了文件的末尾,這個函數(shù)的調(diào)用也會失敗。
__in HINTERNET hFile, //由 InternetOpenUrl (on an HTTP or HTTPS URL)創(chuàng)建。 或由HttpOpenRequest 創(chuàng)建(使用 GET 或 HEAD方法,而且句柄已經(jīng)被 HttpSendRequest 訪問過了).
這個句柄也不可以使用 INTERNET_FLAG_DONT_CACHE 或 INTERNET_FLAG_NO_CACHE_WRITE 標(biāo)志
__in LONG lDistanceToMove, //移動的字節(jié)數(shù)。正數(shù)向后移動,負(fù)數(shù)向前移動
__in PVOID pReserved, //保留參數(shù),為NULL
__in DWORD dwMoveMethod, //指定了移動指針時的參考點。FILE_BEGIN(使用這個標(biāo)志時,移動的字節(jié)數(shù)被當(dāng)作無符號數(shù)處理)、FILE_CURRENT、FILE_END(如果內(nèi)容的長度無法獲得,則使用這個標(biāo)志時會失敗)
__in DWORD_PTR dwContext //保留參數(shù),為NULL
);