第三卷 Internet編程

Internet/Intranet是一項飛速發展的技術,特別是現在,隨著環球網(WWW--World Wide Web)的推廣,Internet/Intranet已經成為了當前最熱門的話題之一。用戶開發的程序或許就需要支持Internet訪問,本章就來介紹MFC中與Internet有關的一些內容。

Microsoft提供了大量的API函數以支持用戶開發Internet服務器或者客戶端應用程序。客戶端的應用程序需要提供訪問Internet的能力,而服務器端的應用程序需要支持HTTP、FTP或Gopher等類型的服務。

就個人計算機用戶而言,更重要的是客戶端的應用程序。因此在本章中主要介紹客戶端應用程序的實現,如果需要了解如何開發服務器端應用程序,請參考有關專著,或者直接從VC6.0的聯機幫助中獲取信息。

直接使用API函數開發Internet程序是令人煩惱的,MFC對其中的一部分進行了封裝。使用MFC進行開發還可以進一步分為幾種類型:

■ 使用Windows插口:Winsock標準定義了一個DLL接口,MFC使用CAsyncSocket和CSocket類對插口進行了封裝。即使是使用MFC提供的類,直接通過插口實現Internet訪問也是困難的。

■ 使用消息收發API(MAPI--Message API):使用MAPI可以方便地向其他應用程序發送電子郵件、語音郵件或者傳真。AppWizard為應用程序獲得MAPI支持提供了捷徑,用戶隻需要在AppWizard的第四步中選中"Message API"選項即可。

■ 使用WinInet類:MFC建立了許多新的類免除用戶學習插口編程之苦,這些新的WinInet類能夠幫助用戶建立客戶端的應用程序。WinInet類支持HTTP、FTP和Gopher等標準的協議。

■ 使用ISAPI類(ISAPI--Internet Server API):ISAPI類幫助用戶擴展HTTP服務器的功能。ISAPI可以用來創建Internet服務器擴展器和過濾器。用戶可以通過使用ISAPI Extension Wizard建立擴展器或者過濾器的框架。

本章中主要介紹如何使用MFC提供的WinInet類完成客戶端的應用程序。

第一章 使用WinInet類

使用WinInet類使得用戶可以在相當高級的層次上實現對Internet的訪問,根本不需要考慮有關插口和協議的詳細內容,MFC包辦了大部分的工作。

WinInet類隻是一個總稱,包括大約10個具體的類。其中最重要的是CInternetSession類,該類用來建立與某個Internet服務器的會話。隻有首先建立起與其他服務器的會話,才能夠進行下一步的操作。

其次是CInternetConnection類及其派生類CHttpConnection、CFtpConnection和CGopherConnection類等。這些類幫助用戶管理與Internet服務器的連接,同時,還提供了大量的成員函數用來與相應的HTTP、FTP或Gopher服務器通信。

最後,CInternetFile類及其派生類CHttpFile、CGopherFile實現了對遠程係統上的文件的存取工作。而CFindFile類及其派生類CFtpFindFile、CGopherFindFile類實現了對本地係統及遠程係統上文件的搜索和定位工作。

如果應用程序隻是作為一個瀏覽器訪問Internet上的資源,那麼很簡單,隻要首先建立一個CInternetSession對象,即建立一個會話。然後就可以調用會話的OpenURL()函數打開指定位置的資源,當然,用戶必須提供正確的URL。

如果應用程序需要進一步的操作,比如說,需要向FTP服務器上傳文件,那麼可以在建立CinternetSession會話後調用GetFtpConnection()等函數建立一個連接。通過連接可以進行一些"高級"的操作。

本節中將建立的"Query"程序隻作為一個瀏覽器使用,而且是一個很粗糙的瀏覽器,隻能返回本次連接的一些基本信息。本例子程序的目的隻在於說明如何建立與Internet服務器的會話與連接,建立連接之後進一步的完善工作用戶可以自己完成。

10.1.1 建立應用程序框架

"Query"程序是基於對話框界麵的,因此在AppWizard的第一步中選擇"Dialog Based"程序類型,其他步驟中接受缺省設置即可。

1.修改"Query"程序的界麵

由於"Query"程序是一個基於對話框的應用程序,首先需要確定界麵對話框資源。圖10-1顯示了最終完成的IDD_QUERY_DIALOG資源。

圖10-1 "Query"程序的主界麵對話框

在IDD_QUERY_DIALOG對話框中,提供了一組單選按鈕供用戶選擇服務類型,用戶向編輯框中輸入目標服務器的地址後,單擊"Connect"按鈕開始連接過程。連接的結果,無論成功與否,都顯示在下方的編輯框中。

表10-1中列出了IDD_QUERY_DIALOG對話框中各控件的屬性。

表10-1 IDD_QUERY_DIALOG對話框的控件

跳轉順序

控件類型

命令ID

標題文本

1

靜態文本

IDC_STATIC

Site:

2

編輯框

IDC_EDIT_SITE

3

按鈕

IDC_BTN_CONNECT

Connect

4

成組框

IDC_STATIC

Server Type

5

單選按鈕

IDC_RAD_HTTP

HTTP

6

單選按鈕

IDC_RAD_FTP

FTP

7

單選按鈕

IDC_RAD_GOPHER

Gopher

8

編輯框

IDC_EDIT_RESULT

在這些控件中,需要修改IDC_RAD_HTTP單選按鈕和IDC_EDIT_RESULT編輯框的屬性。對IDC_RAD_HTTP單選按鈕,確定選中了"Group"選項;對IDC_EDIT_RESULT編輯框,選中"Read-only"、"Auto HScroll"、"Auto VScroll"和"MultiLine"等屬性,該編輯框將用來顯示連接的結果。

用戶還必須向CQueryDlg類添加幾個成員變量,表10-2中列出了這些成員變量。

表10-2 CQueryDlg類的成員變量

控件ID

變量類型

變量名稱

IDC_EDIT_SITE

CString

m_szSite

IDC_EDIT_RESULT

CString

m_szResult

IDC_RAD_HTTP

int

m_nType

2.響應按鈕單擊

最後,需要響應"Connect"按鈕的BN_CLICKED通知,並按照程序清單10-1所示修改OnBtnConnect()函數的代碼。

程序清單10-1 CQueryDlg::OnBtnConnect()

void CQueryDlg::OnBtnConnect()

{

// TODO: Add your control notification handler code here

UpdateData(TRUE);

switch(m_nType)

{

case 0:

TryHttp();

break;

case 1:

TryFtp();

break;

case 2:

TryGopher();

break;

}

}

在OnBtnConncet()函數中,通過對當前選中的服務類型的判斷,調用不同的輔助函數完成連接工作。其中TryHTTP()、TryFTP()和TryGopher()等輔助函數將在下一小節中完成。

10.1.2 與Internet連接

"Query"程序提供了三種連接Internet的方式:HTTP、FTP和Gopher,但所有的操作都必須以用戶提供正確的Internet服務器地址為前提。當然,"Query"程序的目的隻是演示如何與指定的Internet服務器進行連接,輸出的結果也很簡單:如果沒有連接上,則顯示"Sorry";如果連接上了,除了顯示"OK"之外,還附帶了一些基本的信息。

1.HTTP連接

如果用戶已經輸入了正確的HTTP站點的地址或者域名,並且選中"HTTP"服務類型,單擊"Connect"按鈕,那麼程序運行的結果就完全由TryHttp()函數決定。首先在程序清單10-2中列出了TryHttp()函數的代碼。

程序清單10-2 CQueryDlg::TryHttp()

void CQueryDlg::TryHttp()

{

// 初始化數據

if(m_szSite.Left(7)!="http://")

{

AfxMessageBox("HTTP site needed!");

return;

}

m_szResult.Empty();

m_szResult+="Please wait while trying to open "+m_szSite+"\r

";

UpdateData(FALSE);

// 嚐試連接

CInternetSession session;

CHttpFile* pFile=NULL;

try

{

pFile=(CHttpFile*) session.OpenURL(m_szSite);

}

catch(CInternetException* pEx)

{

pFile=NULL;

pEx->Delete();

}

// 讀取文件

if(pFile)

{

m_szResult+="OK! HTTP Site found! \r

";

m_szResult+="---------------------------------\r

";

CString szLine;

while(pFile->ReadString(szLine))

{

m_szResult+=szLine+"\r

";

}

pFile->Close();

delete pFile;

}

else

{

m_szResult+="Sorry! HTTP Site not found! \r

";

}

// 關閉會話

session.Close();

UpdateData(FALSE);

}

在TryHttp()函數中用到了許多MFC提供的WinInet類,為了使編譯器能夠尋找到這些類的聲明與定義,用戶需要在QueryDlg.cpp文件的頂部添加如下一行:#include "afxinet.h"

在TryHttp()函數中,首先構造了一個CInternetSession類的會話對象。在任何使用WinInet類開發的Internet客戶端程序中,都必須首先構造一個會話對象,在對象的構造函數將完成內部數據結構的初始化並為應用程序後麵的調用做好準備。

為了創建與HTTP站點的一個連接,調用了會話對象的OpenURL()函數,該函數總是返回一個文件類型的指針,具體類型與指定站點提供的服務有關。OpenURL()函數支持四種協議:

■ file:// 打開一個本地文件。函數創建一個CStdioFile對象並返回其指針。

■ ftp:// 試圖連接一個FTP站點,如果連接成功,返回一個指向CInternetFile對象的指針。

■ http:// 試圖連接一個HTTP站點,如果連接成功,返回一個指向CHttpFile對象的指針。

■ gopher:// 試圖連接一個Gopher站點,如果連接成功返回一個指向CGopherFile對象的指針。

由於在TryHttp()函數的最開頭確保了本函數中隻處理HTTP連接,因此最終將OpenURL()函數返回的文件指針強製轉換為CHttpFile類型。

讀取文件數據的工作由ReadString()函數完成,該函數每次從文件中讀取連續的字符串,當遇到新的行標誌時便停止。如果遇到文件尾導致讀取失敗,則返回值為FALSE。

如果用戶想現在就嚐試一下"Query"程序訪問HTTP站點的功能,那麼可以編譯並運行"Query"程序,在"Site"編輯框中輸入某個HTTP站點的網址,單擊"Connect"按鈕就可以等待結果出現了。等待的時間取決於用戶的計算機與Internet網連接的速度。

可以輸入一個HTTP 站點的域名進行檢驗,比如說輸入"http://www. tsinghua.edu.cn","Query"程序應該能夠找到它,圖10-2就是連接至該站點時的結果。

圖10-2 "Query"連接到HTTP站點

有點遺憾的是,"Query"程序隻能以字符串的方式來顯示網頁的內容。但是這已經足夠了,"Query"程序從來都不希望成為一個漂亮的瀏覽器。在本章的下一節中,將向用戶介紹如何建立一個自己的瀏覽器程序,當然,這需要MFC提供類的支持。

2.FTP連接

訪問FTP站點的工作與訪問HTTP站點是類似的,程序清單10-3中列出了TryFTP()函數的代碼。

程序清單10-3 CQueryDlg::TryFtp()

void CQueryDlg::TryFtp()

{

// 初始化數據

if(m_szSite.Left(6)!="ftp://")

{

AfxMessageBox("FTP site needed!");

return;

}

m_szResult.Empty();

m_szResult+="Please wait while trying to open "+m_szSite+"\r

";

UpdateData(FALSE);

// 嚐試連接

CInternetSession session;

CFtpConnection* pFtp;

try

{

pFtp=session.GetFtpConnection(m_szSite);

}

catch(CInternetException* pEx)

{

pFtp=NULL;

pEx->Delete();

}

// 讀取FTP站點的當前目錄

if(pFtp)

{

m_szResult+="OK! FTP Site found! \r

";

m_szResult+="----------------------------------\r

";

CString szLine;

pFtp->GetCurrentDirectory(szLine);

m_szResult+="Current directory: "+ szLine +"\r

";

pFtp->Close();

delete pFtp;

}

else

{

m_szResult+=" Sorry! FTP Site not found! \r

";

}

// 關閉會話

session.Close();

UpdateData(FALSE);

}

可以看到,TryFtp()函數和TryHttp()函數的大體結構是類似的。不同的是在TryFtp()函數中沒有使用OpenURL()函數以打開一個FTP站點,而是調用了GetFtpConnection()函數建立一個FTP連接。最後,調用GetCurrenDirectory()函數獲取遠程站點上的當前目錄。

TryFtp()函數並沒有做更多的事情,用戶可以通過CFindFile類對象從當前目錄開始進行搜索與定位,這又有些類似於本書第四章中的"NewCtrl"程序,不同的是"Query"程序操作的對象是遠程係統,而"NewCtrl"程序隻搜索本地係統上的文件。

3.Gopher連接

訪問Gopher站點的工作由TryGopher()函數完成,程序清單10-4中是該函數的代碼。

程序清單10-4 CQueryDlg::TryGopher()

void CQueryDlg::TryGopher()

{

// 初始化數據

m_szResult.Empty();

m_szResult+="Please wait while trying to open "+m_szSite+"\r

";

UpdateData(FALSE);

// 嚐試連接

CInternetSession session;

CGopherConnection* pGopher;

try

{

pGopher=session.GetGopherConnection(m_szSite);

}

catch(CInternetException* pEx)

{

pGopher=NULL;

pEx->Delete();

}

// 返回Gopher站點信息

if(pGopher)

{

m_szResult+="OK! FTP Site found! \r

";

m_szResult+="----------------------------------\r

";

CString szLine;

CGopherLocator locator=

pGopher->CreateLocator(NULL,NULL,GOPHER_TYPE_DIRECTORY);

szLine=locator;

m_szResult+="Found locator: "+szLine+" \r

";

pGopher->Close();

delete pGopher;

}

else

{

m_szResult+=" Sorry! Gopher Site not found! \r

";

}

// 關閉會話

session.Close();

UpdateData(FALSE);

}

TryGopher()函數的代碼與TryFtp()函數是完全類似的,讀者可以自行分析。

現在讀者已經完成了"Query"程序的所有代碼,可以對程序的功能進行檢驗了。這裏需要提醒讀者的是,"Query"程序並沒有對用戶輸入的Internet服務器的域名或地址進行轉換與嚐試,因此,如果用戶輸入的網址不存在或者是錯誤的,就無法得到正確的結果。

第二章 製作Web瀏覽器

互聯網是目前最流行的技術之一,互聯網上眾多的站點提供了大量的信息,許多計算機用戶已經視互聯網是自己生活不可分割的一部分。Microsoft公司提供的Internet Explorer和Netscape公司提供的Navigator成為了大部分PC用戶瀏覽網上信息時首選的工具。

然而,利用VC6.0開發一個用戶自己的Web瀏覽器也不是一項很困難的工作。使用MFC提供的CHtmlView類,再加上一些努力,很快就能夠看到自己的豐碩成果。本節中就將通過"Browser"例子程序介紹如何創建自己的Web瀏覽器。

"Browser"程序的視圖類從CHtmlView類派生,這使得"Browser"程序很容易就獲得了瀏覽Web頁麵的能力。CHtmlView類是VC6.0中新近提供的MFC類,通過封裝WebBrowser ActiveX控件實現其功能,而隻有安裝了Internet Explorer 4.0的計算機才擁有該控件。因此,必須確保安裝了Internet Explorer 4.0,否則可能無法完成"Browser"程序。