摘要這篇技術(shù)性文章討論了如何利用Microsoft Win32網(wǎng)絡(luò )函數創(chuàng )建一個(gè)
網(wǎng)絡(luò )瀏覽器。這篇文章的宗旨是讓讀者了解一些Win32網(wǎng)絡(luò )函數的作用、能力和使用范圍,而不是為這些功能給出一個(gè)詳細的文檔。這篇文章所配合的SurfBear樣本應用程序使用Win32網(wǎng)絡(luò )函數從網(wǎng)絡(luò )
服務(wù)器上讀取HTML文件,并把它們顯示成原始的、沒(méi)有經(jīng)過(guò)格式化的文本。介紹不通過(guò)網(wǎng)絡(luò ),你就無(wú)法了解我的一個(gè)朋友。計算機雜志已經(jīng)在internet上設置了電子期刊,而本地的報紙也已經(jīng)把整個(gè)段落都放到了網(wǎng)絡(luò )上。事實(shí)上,許多報紙都在聯(lián)機。每個(gè)人都有一個(gè)主頁(yè),甚至一些無(wú)家可歸的人都有一個(gè)主頁(yè)。雖然有許多關(guān)于網(wǎng)絡(luò )的消息難免言過(guò)其實(shí),但網(wǎng)絡(luò )正在變成計算機整體的一部分已經(jīng)是無(wú)庸置疑的了。Microsoft 已經(jīng)介紹了Microsoft Win32網(wǎng)絡(luò )函數來(lái)協(xié)助開(kāi)發(fā)者把網(wǎng)絡(luò )變成他們的應用程序的整體部分。這些新的功能簡(jiǎn)化了使用FTP(文件傳輸協(xié)議)、和HTTP(超文本傳輸協(xié)議)訪(fǎng)問(wèn)網(wǎng)絡(luò )。使用Win32網(wǎng)絡(luò )函數的開(kāi)發(fā)者不需要對TCP/IP或Windows 配件。對于一些最普通的操作,開(kāi)發(fā)者不需要知道他們正在使用的某個(gè)協(xié)議的細節。最終,Win32網(wǎng)絡(luò )函數將成為Win32應用程序接口的一部分并且與基于Windows的不同的平臺一起發(fā)布。最初,Win32網(wǎng)絡(luò )函數將安裝在一個(gè)叫做WININET.DLL的再分布式動(dòng)態(tài)鏈接庫里。網(wǎng)絡(luò )函數最好的探討Win32網(wǎng)絡(luò )函數的方法是直接進(jìn)入代碼。下面的代碼是樣本的代碼,為了方便閱讀,錯誤處理部分已經(jīng)被刪除掉了。HINTERNET hNet = ::InternetOpen("MSDN SurfBear",
PRE_CONFIG_INTERNET_ACCESS,
NULL,
INTERNET_INVALID_PORT_NUMBER,
0) ;HINTERNET hUrlFile = ::InternetOpenUrl(hNet,
"http://www.microsoft.com",
NULL,
0,
INTERNET_FLAG_RELOAD,
0) ;char buffer[10*1024] ;
DWORD dwBytesRead = 0;
BOOL bRead = ::InternetReadFile(hUrlFile,
buffer,
sizeof(buffer),
&dwBytesRead);::InternetCloseHandle(hUrlFile) ;::InternetCloseHandle(hNet) ;
上面列舉的代碼包括四個(gè)網(wǎng)絡(luò )函數:InternetOpen、InternetOpenOrl、InternetReadFile和InternetCloseHandle。下面我們依次對這些函數進(jìn)行分析。InternetOpenInternetOpen初始化WININET.DLL。它在其他的Win32網(wǎng)絡(luò )函數之前被調用。HINTERNET hNet = ::InternetOpen(
"MSDN SurfBear", // 1 LPCTSTR lpszCallerName
PRE_CONFIG_INTERNET_ACCESS, // 2 DWORD dwAccessType
"", // 3 LPCTSTR lpszProxyName
INTERNET_INVALID_PORT_NUMBER, // 4 INTERNET_PORT nProxyPort
0 // 5 DWORD dwFlags
) ;
InternetOpen返回一個(gè)類(lèi)型為HINTERNET的句柄。其他的Win32網(wǎng)絡(luò )函數把這個(gè)句柄當作一個(gè)參數?,F在你不能把一個(gè)HINTERNET句柄用在類(lèi)似于ReadFile之類(lèi)的其他Win32函數中。但是隨著(zhù)Microsoft Windows和Microsoft Windows NT網(wǎng)絡(luò )支持的成熟,這一點(diǎn)在將來(lái)不是不可能實(shí)現的。當你已經(jīng)結束使用Wein32網(wǎng)絡(luò )函數時(shí),你應該調用InternetCloseHandle釋放InternetOpen分配的資源。使用Microsoft基礎類(lèi)(MFC)的應用程序將從文件的構造程序里象征性地調用InternetOpen。絕大多數應用程序都將在每一進(jìn)程里調用InternetOpen。InternetOpen 的第一個(gè)參數lpszCallerName指定正在使用網(wǎng)絡(luò )函數的應用程序。當HTTP協(xié)議使用時(shí),這個(gè)名字將變成用戶(hù)
代理。第二個(gè)參數dwAccessType指定訪(fǎng)問(wèn)類(lèi)型。在上面的例子里,PRE_CONFIG_INTERNET_ACCESS訪(fǎng)問(wèn)類(lèi)型指示W(wǎng)in32網(wǎng)絡(luò )函數使用登記信息去發(fā)現一個(gè)服務(wù)器。使用PRE_CONFIG_INTERNET_ACCESS需要正確設定登記信息。這里我耍了一個(gè)小花招并讓網(wǎng)絡(luò )開(kāi)發(fā)者替我登記注冊。在登記注冊中,把AccessType設置為1,則意味著(zhù)“直接入網(wǎng)”,把AccessType 設置為2,意味著(zhù)“使用網(wǎng)關(guān)”。把DisableServiceLocation設置為1,將讓它使用一個(gè)已經(jīng)命名的服務(wù)器;否則將找到一個(gè)使用注冊信息和名字決議(RNR)應用程序接口的服務(wù)器,它是Windows接口的一部分。其他的訪(fǎng)問(wèn)類(lèi)型包括以下幾種:LOCAL_INTERNET_ACCESS只連接到當地Internet網(wǎng)站。例如,如果我使用SurfBear標志,我就只能訪(fǎng)問(wèn)Microsoft整體的Internet網(wǎng)站。
CERN_PROXY_INTERNET_ACCESS使用一個(gè)CERN代理去訪(fǎng)問(wèn)web。CERN代理是一個(gè)充當網(wǎng)關(guān)的web服務(wù)器并且能向要使用代理的服務(wù)器發(fā)送HTTP請求。
GATEWAY_INTERNET_ACCESS允許連接到World Wide Web。我可以用這個(gè)訪(fǎng)問(wèn)類(lèi)型去訪(fǎng)問(wèn)web上的任何站點(diǎn)。
GATEWAY_PROXY_INTERNET_ACCESS和CERN_PROXY_ACCESS訪(fǎng)問(wèn)類(lèi)型要求第三個(gè)參數給InternetOpen:服務(wù)器名(lpszProxyName)。PRE_CONFIG_INTERNET_ACCESS不要求服務(wù)器名,因為他可以為服務(wù)器搜索寄存信息。NProxyPort參數用在CERN_PROXY_INTERNET_ACCESS中用來(lái)指定使用的
端口數。使用INTERNET_INVALID_PORT_NUMBER相當于提供卻省的端口數。最后一個(gè)參數棗dwFlags,設置額外的選擇。你可以使用 INTERNET_FLAG_ASYNC標志去指示使用返回句句柄的將來(lái)的Internet函數將為回調函數發(fā)送狀態(tài)信息,使用InternetSetStatusCallback進(jìn)行此項設置?!nternetOpenUrl一旦你把Win32網(wǎng)絡(luò )函數初始化了,你就可以使用其他網(wǎng)絡(luò )函數。下一個(gè)要調用的Internet 函數是InternetOpenUrl。這個(gè)函數連接到一個(gè)網(wǎng)絡(luò )服務(wù)器上并且最被從服務(wù)器上讀取數據。InternetOpenUrl能對FTP,Gopher或HTTP協(xié)議起作用。在這篇文章中,我們只涉及HTTP協(xié)議。HINTERNET hUrlFile = ::InternetOpenUrl(
hNet, // 1 HINTERNET hInternetSession
"http://www.microsoft.com", // 2 LPCTSTR lpszUrl
NULL, // 3 LPCTSTR lpszHeaders
0, // 4 DWORD dwHeadersLength
INTERNET_FLAG_RELOAD, // 5 DWORD dwFlags
0 // 6 DWORD dwContext
) ;
InternetOpenUrl也返回一個(gè)HINTERNET,它被傳遞給在這個(gè)URL(統一資源定位)上操作的函數。你應該使用InternetClose來(lái)關(guān)閉這個(gè)句柄的處理。InternetOpenUrl的第一個(gè)參數hInternetSession是從InternetOpen返回的句柄。第二個(gè)參數lpszUrl是我們需要的資源的URL(統一資源定位)。在上面的例子中,我們想得到一個(gè)Microsoft的web主頁(yè)。下面兩個(gè)參數lpszHeaders和HeaderLength用來(lái)向服務(wù)器傳送額外的信息。使用這些參數要求具有正在使用的特定協(xié)議的知識。DwFlag是一個(gè)可以用幾種方式修改InternetOpenUrl行為的標志,InternetOpenUrl的行為包括關(guān)閉、隱藏,使原始數據可用和用存在的連接取代開(kāi)辟一個(gè)新的連接。最后一個(gè)參數dwContext是一個(gè) DWORD上下文值。如果有一個(gè)值已經(jīng)被指定,它將被送到狀態(tài)回調函數。如果這個(gè)值是0,信息將不會(huì )被送到狀態(tài)回調函數。InternetReadFile你打開(kāi)一個(gè)文件后,就要讀它,所以下一個(gè)函數是InternetReadFile是符合邏輯的:char buffer[10*1024] ;
DWORD dwBytesRead = 0;
BOOL bRead = ::InternetReadFile(
hUrlFile, // 1 HINTERNET hFile
buffer, // 2 LPVOID lpBuffer
sizeof(buffer), // 3 DWORD dwNumberOfBytesToRead
&dwBytesRead // 4 LPDWORD lpdwNumberOfBytesRead
);buffer[dwBytesRead] = 0 ;
pEditCtrl->SetWindowText(buffer) ;
InternetReadFile接收InternetOpenUrl返回的句柄。它也對其他Win32網(wǎng)絡(luò )函數,例如FtpOpenFile,FopherOpenFile和HttpOpenRequest返回的句柄有影響。剩下的InternetReadFile的三個(gè)參數也非常的明白直接。Inbuffer是指向保留數據的緩沖區的一個(gè)無(wú)返回值指針,dwNumberOfByteToRead以字節為單位指定緩沖區的尺寸。最后一個(gè)參數,lpdwNumberOfBytesRead是一個(gè)指向包含讀入緩沖區字節數的變量的指針。如果返回值是TRUE,而且lpdwNumberOfBytesRead指向0,則文件已經(jīng)讀到了文件的末尾。這個(gè)行為與Win32 Re3adFile的函數的行為是一致的。一個(gè)真正的web瀏覽器將在InternetReadFile上循環(huán) ,不停地從Internet上讀入數據塊。為了顯示緩沖區,向緩沖區添加一個(gè)0并把它送到編輯器控制。這樣,InternetOpen、InternetOpenUrl和InternetReadFile一起創(chuàng )建了Internet瀏覽器的基礎。他們使從Internet上讀取文件就象從你的本地硬盤(pán)驅動(dòng)器上讀取文件一樣容易?!TTP函數在一些例子中,InternetOpenUrl太普通了,所以你可能需要其他的Win32網(wǎng)絡(luò )函數。InternetOpenUrl相當與不同的FTP,GOPHER和HTTP函數的封皮。當使用HTTP時(shí),InternetOpenUrl調用InternetConnect,HttpOpenRequest以及HttpSendRequest,比如說(shuō)我們想要在下載一個(gè)HTML頁(yè)之前得到它的尺寸以便于我們在緩沖區中為其分配適當的尺寸,HttpQueryInfo將得到web頁(yè)的大小。警告:不是所有web 頁(yè)都支持得到頁(yè)尺寸。(例如:
www.toystory.com和
www.movielink.com不支持這個(gè)功能)另外,TCP/IP能傳遞的數據也比要求的要少。所以,你的應用程序應該處理著(zhù)兩種情況并且圍繞InternetReadFile循環(huán)直到結果為T(mén)RUE同時(shí)*lpdwNumberOfBytesRead為0。// Open Internet session.
HINTERNET hSession = ::InternetOpen("MSDN SurfBear",
PRE_CONFIG_INTERNET_ACCESS,
NULL,
INTERNET_INVALID_PORT_NUMBER,
0) ;// Connect to
www.microsoft.com.HINTERNET hConnect = ::InternetConnect(hSession,
"www.microsoft.com",
INTERNET_INVALID_PORT_NUMBER,
"",
"",
INTERNET_SERVICE_HTTP,
0,
0) ;// Request the file /MSDN/MSDNINFO/ from the server.
HINTERNET hHttpFile = ::HttpOpenRequest(hConnect,
"GET",
"/MSDN/MSDNINFO/",
HTTP_VERSION,
NULL,
0,
INTERNET_FLAG_DONT_CACHE,
0) ;// Send the request.
BOOL bSendRequest = ::HttpSendRequest(hHttpFile, NULL, 0, 0, 0);// Get the length of the file.
char bufQuery[32] ;
DWORD dwLengthBufQuery = sizeof(bufQuery);
BOOL bQuery = ::HttpQueryInfo(hHttpFile,
HTTP_QUERY_CONTENT_LENGTH,
bufQuery,
&dwLengthBufQuery) ;// Convert length from ASCII string to a DWORD.
DWORD dwFileSize = (DWORD)atol(bufQuery) ;// Allocate a buffer for the file.
char* buffer = new char[dwFileSize+1] ;// Read the file into the buffer.
DWORD dwBytesRead ;
BOOL bRead = ::InternetReadFile(hHttpFile,
buffer,
dwFileSize+1,
&dwBytesRead);
// Put a zero on the end of the buffer.
buffer[dwBytesRead] = 0 ;// Close all of the Internet handles.
::InternetCloseHandle(hHttpFile);
::InternetCloseHandle(hConnect) ;
::InternetCloseHandle(hSession) ;// Display the file in an edit control.
pEditCtrl->SetWindowText(buffer) ;InternetConnect
InternetConnet函數連接到一個(gè)HTTP,FTP或Gopher服務(wù)器:
HINTERNET hConnect = ::InternetConnect(
hSession, //1 HINTERNET hInternetSession
"www.microsoft.com", //2 LPCTSTR lpszServerName
INTERNET_INVALID_PORT_NUMBER, //3 INTERNET_PORT nServerPort
"", //4 LPCTSTR lpszUsername
"", //5 LPCTSTR lpszPassword
INTERNET_SERVICE_HTTP, //6 DWORD dwService
0, //7 DWORD dwFlags
O //8 DWORD dwContext
) ;
第六個(gè)參數dwService決定服務(wù)類(lèi)型(HTTP,FTP或Gopher)。在上面的例子中,InternetConnect連接到一個(gè)HTTP服務(wù)器上,因為dwService被設置成INTERNET_SERVICE_HTTP。第二個(gè)參數(設置成
www.microsoft.com)提供了服務(wù)器的地址。注意,HTTP地址必須為服務(wù)器名作語(yǔ)法分析,InternetOpenUrl為我們作語(yǔ)法分析。第一個(gè)參數hInternetSession是從InternetOpen返回的句柄。第四個(gè)、第五個(gè)參數提供一個(gè)用戶(hù)姓名和密碼 。這七個(gè)參數沒(méi)有控制任何標志影響HTTP操作。最后一個(gè)參數為狀態(tài)回調函數提供前后關(guān)系的信息。HttpOpenRequest
一旦和服務(wù)器的連接已經(jīng)建立,我們打開(kāi)了想要的文件。HttpOpenRequest和HttpSenRequest一起工作打開(kāi)文件。HttpOpenRequest去創(chuàng )建一個(gè)請求句柄并且把參數存儲在句柄中。HttpOpenRequest把請求參數送到HTTP服務(wù)器。
HINTERNET hHttpFile = ::HttpOpenRequest(
hConnect, // 1 HINTERNET hHttpSession
"GET", // 2 LPCTSTR lpszVerb
"/MSDN/MSDNINFO/", // 3 LPCTSTR lpszObjectName
HTTP_VERSION, // 4 LPCTSTR lpszVersion
NULL, // 5 LPCTSTR lpszReferer
0, // 6 LPCTSTR FAR * lplpszAcceptTypes
INTERNET_FLAG_DONT_CACHE, // 7 DWORD dwFlags
0 // 8 DWORD dwContext
) ;
到現在為止,網(wǎng)絡(luò )函數的許多參數看起來(lái)都類(lèi)似。HttpOpenResult的第一個(gè)參數是由InternetConnet返回的 HINTERNET。HttpOpenRequest的第七和第八個(gè)參數執行與InternetConnect中有相同名字的參數一樣的功能。
第二個(gè)參數(“GET”)指定我們想要得到由第三個(gè)參數(“/MSDN/MSDNINFO/”)命名的對象。HTTP版已經(jīng)傳遞第四個(gè)參數;現在,它肯定是HTTP棗VERSION。因為“GET”是最流行的動(dòng)詞類(lèi)型,HttpOpenRequest將為這個(gè)參數接收一個(gè)空指針。
第五個(gè)參數lpszReferer是一個(gè)網(wǎng)點(diǎn)的地址。在這個(gè)網(wǎng)點(diǎn)上我們發(fā)現了我們現在想要看見(jiàn)的URL(統一資源定位)。換而言之,如果你在
www.home.com上而且單擊了跳到
www.microsoft.com的一個(gè)連接,第五個(gè)參數就是
www.home.com。因為它使你指向了目標URL(統一資源定位)。這個(gè)值可以為空。第六個(gè)參數執行一個(gè)我們的程序接收的文件類(lèi)型列表。把空值傳遞給HttpOpenRequest即通知了服務(wù)器只有文本文件可以被接收?!ttpSendRequest除了傳送請求外,HttpSendRequest允許你傳送額外的HTTP標題給服務(wù)器。關(guān)于HTTP標題的信息可以在
http://www.w3.org/ 上的
最新的說(shuō)明上找到。在這個(gè)例子中,HttpSendRequest的所有參數都被傳遞為缺省值。BOOL bSendRequest = ::HttpSendRequest(
hHttpFile, // 1 HINTERNET hHttpRequest
NULL, // 2 LPCTSTR lpszHeaders
0, // 3 DWORD dwHeadersLength
0, // 4 LPVOID lpOptional
0 // 5 DWORD dwOptionalLength
);
HttpQueryInfo為了得到關(guān)于文件的信息,在調用HttpSendRequest后使用HttpQueryInfo函數:BOOL bQuery = ::HttpQueryInfo(
hHttpFile, // 1 HINTERNET hHttpRequest
HTTP_QUERY_CONTENT_LENGTH, // 2 DWORD dwInfoLevel
bufQuery, // 3 LPVOID lpvBuffer
&dwLengthBufQuery // 4 LPDWORD lpdwBufferLength
) ;
查詢(xún)的結構是字符串或lpvBuffer中的字符串列表。HTTP_QUERY_CONTENT_LENGTH查詢(xún)得到文件的長(cháng)度。你可以使用HttpQueryInfo查詢(xún)大范圍的信息。
總結
Win32網(wǎng)絡(luò )函數使從FTP,Gopher和HTTP服務(wù)器上讀取信息就想從你的硬盤(pán)驅動(dòng)器上讀取信息一樣容易。僅僅使用4個(gè)函數棗InternetOpen,InternetOpenUrl,InternetReadFile和InternetCloseHandle和很少的HTTP知識,你就可以寫(xiě)一個(gè)簡(jiǎn)單的網(wǎng)絡(luò )瀏覽器。把這個(gè)簡(jiǎn)單的瀏覽器變成一個(gè)工業(yè)性質(zhì)的瀏覽器將要花費很多工作,包括 一些對HTTP的了解,對如何顯示HTML文件的了解和以及使用多線(xiàn)程方式的能力。Win32網(wǎng)絡(luò )函數將開(kāi)發(fā)者從與TCP/IP,Windows Sockets和HTTP編程有關(guān)的大多數煩悶工作中解脫出來(lái)