在VC++ 5.0中,MFC的WinInet類封裝了相關的用於internet 客戶機程式編程的win32 API函數。這樣,無需瞭解winsock或TCP/IP的細節就可以編製出internet客戶機程式。本文中,我們將探討如何使用WinInet類來編寫一個internet查詢程式,該程式起名為“探路者”。該程式能使用各種協議來查詢網路,包括古老的FINGER和WHOIS。
顧名思義,“探路者”用於探查一部internet伺服器的情況,查看它能提供哪些服務。例如說,你知道某人的e-mail地址,假設是wang@eop.com,我們就可以通過該程式,嘗試用各種辦法建立到eop.com的連結,隨後對該指定域依次採用WWW查詢、FTP查詢、GOPHER查詢、FINGER查詢和WHOIS查詢。根據返回的結果,可以判斷該域能提供哪些服務。此外,如果你想給自己的伺服器起某個網域名稱,你也可以通過本程式知道該網域名稱是否已被採用。
一、查詢程式介面的實現
本程式的使用者介面將基於對話方塊來實現。建立介面的步驟如下:
在developer studio中,
1、選擇file-new…,高亮MFC appwizard(exe),在Project name欄內給程式取名為query ,單擊ok。
2、在step-1中,單擊dialog based,選English做為資源語言。單擊next。
3、在step-2中,取消ActiveX Controls的選中標記。不要選擇windows socket(該程式不直接調用socket函數),在對話方塊標題列中鍵入“探路者”。
4、在step-3中,選擇yes,please和As a ststically linked library
5、在step-4中,不要改變appwiard所產生的任何類名,appwizard結束後,我們準備著手編寫程式的核心部分。
appwizard建立了一個空的對話方塊,我們將從它的基礎上開始工作。下面編輯對話方塊:
選resource view ,展開query resources-dialogs,雙擊IDD-QUERY-DIALOG 資源,下面編輯對話方塊:
1、將OK按鈕改為“查詢主機”,右擊該按鈕,選擇properties,將該按鈕的ID名改為id-query。
2、將CANCEL按鈕改為“結束查詢”。
3、刪除to do等靜態文本。
4、將對話方塊增大到300像素寬
5、在對話方塊頂端增加一個編輯框,資源id名為IDC-HOST,將編輯框展開到最寬。
6、給編輯框增加一個名為“地址名”的標籤。
7、將對話方塊展開到150像素高。
8、在對話方塊底部增加另一個編輯框,id名為IDC-OUT,將它展開到儘可能的大,填滿對話方塊的剩餘空間。
9、給該編輯框增加Multi-line,Horizontal scroll,Vertical scroll,Border,Read-only等特性。
當使用者單擊“查詢主機”按鈕時,程式將使用各種方法查詢網址。因此,需使用classwizard,把實現查詢功能的代碼連結到query按鈕。
1、選view-classwizard。
2、選CQueryDlg類,主機名稱將由該類接受。
3、高亮ID-query,高亮右邊列表欄裡的BN-CLICKED。
4、按Add Fuction按鈕,增加一個函數。
5、Class Wizard建議的名字為OnOk,將它改為OnQuery ,,單擊ok
6、單擊成員變數欄,準備將edit控制串連到對話方塊類的變數
7、高亮IDC-HOST並單擊Add Variable。將把EDIT控制串連到對話方塊類的Cstring成員變數,名為m_host
8、類似以上步驟,將IDC-OUT連結到CString變數m_out
點擊ok關閉classwizard,剩下的工作是編寫CQueryDlg::OnQuery()函數,它將使用m_host值進行查詢並將結果輸出到m_out變數。
二、編寫代碼,進行http查詢
查詢一個internet地址時,首先應嘗試建立http連結。這是因為大量的地址都含有web頁面。使用http建立連結的最簡單的辦法是使用winlnet類CInternetSession,並調用其成員函數OpenURL()。該函數將返回一個檔案,我們在m_out中顯示該檔案的頭幾行文字。首先,在QueryDLG.CPP的開始加入以下行:
#include‘afxinet.h’
這將定義訪問Winlnet類的代碼。由於本程式將訪問大量的URLS,在CQueryDlg中增加一個名為TryURL的函數,它含有一個CString類的參數,名為URL,傳回值為void。右擊CQueryDLG類,選擇Add Member function…。增加函數TryURL(),將它定義為protected類型。這個新函數將從CQueryDlg::OnQuery()中調用。在OnQuery()中增加以下代碼:
void CQueryDlg::OnQuery()
{ const CString http = "http://";
UpdateData(TRUE);
m_out = "";
UpdateData(FALSE);
TryURL(http + m_host);
TryURL(http + "www." + m_host);
}
UpdateData(TRUE)的調用將給m_host賦予使用者定義的值。UpdateData(FALSE)語句將清空輸出編輯框變數m_out的內容。接下來調用兩次TryURL(),例如說,當使用者輸入microsoft.com,程式將首先試一試http://microsoft.com,然後試一試http://www.microsoft.com。以下是TryURL的代碼。
void CQueryDlg::TryURL(CString URL)
{
CInternetSession session;
m_out+="正在連結"+URL+"/r/n";
UpdateData(FALSE);
CInternetFile* file=NULL;
try
{
file=(CInternetFile*)session.OpenURL(URL);
}
catch(CInternetException* pEx)
{
file=NULL;
pEx->Delete();
}
if(file)
{
m_out+="已建立連結。/r/n";
CString line;
for(int i=0;i<20&&file->ReadString(line);i++)
{
m_out+=line+"/r/n";
}
file->Close();
delete file;
}
else
{
m_out+="本地址沒有發現http主機/r/n";
}
m_out+="------------------------------------------------------/r/n";
UpdateData(FALSE);
}
下面,我們對以上代碼進行逐段分析。首先,建立一個internet區,這要定義一個CInternetSession的對象。其建構函式的原形如下:
CInternetSession( LPCTSTR pstrAgent = NULL, DWORD dwContext = 1, DWORD dwAccessType = INTERNET_OPEN_TYPE_PRECONFIG, LPCTSTR pstrProxyName = NULL, LPCTSTR pstrProxyBypass = NULL, DWORD dwFlags = 0 );這需要定義很多參數,但是,本程式都使用預設值,即“=”號後的值。CInternetSession建構函式參數說明如下:
LPCTSTR pstrAgent-應用程式名稱,如果為NULL,它將替你填入你在AppWizard中給定的程式名。
DWORD dwContext-本操作的裝置關聯符定義。
DWORD dwAccessType-訪問類型,為以下參數之一,INTERNET_OPEN_TYPE_PRECONFIG (default), INTERNET_OPEN_TYPE_DIRECT, 或 INTERNET_OPEN_TYPE_PROXY
LPCTSTR pstrProxyName-如訪問類型為INTERNET_OPEN_TYPE_PROXY,則給該參數賦予協議名稱。
LPCTSTR pstrProxyBypass-如訪問類型為INTERNET_OPEN_TYPE_PROXY,則該參數為不通過協議伺服器而直接連結的一系列地址。
DWORD dwFlags-可為以下參數,INTERNET_FLAG_DONT_CACHE, INTERNET_FLAG_ASYNC, 和INTERNET_FLAG_OFFLINE.
dwAccessType值預設時將使用系統註冊簿定義的值。顯然,程式允許使用者定義訪問類型將比由程式內部直接定義要好。因此,要正確使用本程式,必需先在windows系統中定義好網路訪問類型,步驟如下:
1、雙擊案頭上“my computer"表徵圖。
2、點擊“contyol panel".
3、點擊“internet ”。
4、在隨後彈出的對話方塊中,選“connection"欄,然後填寫網路連接屬性。如果你是撥接,選中“dial”選擇項,並填寫相關屬性。如果你是通過proxy伺服器上網,選中“proxy”選項,點擊“setting”按鈕,設定proxy伺服器位址和連接埠號碼。如果你是直接連入internet,應使所有的選項均為非選中狀態。
本程式在構造CInternetSession對象時使用預設值,因此,建構函式將不帶任何參數。如下所示:
CInternetSession session;
在構造對象session後,我們需寫兩行程式作一些輸出,表示程式已開始工作。
m_out+="正在連結"+URL+"/r/n";
UpdateData(FALSE);
接下來我們使用session對象的成員函數OpenURL()來開啟一個URL資源。該函數返回一個檔案的指標,檔案類型為以下四種之一:
// 如果訪問的是本地機器,函數返回一個CStudioFile類對象的指標。
ftp:// 如果訪問的是一ftp地址,函數返回一個CInternetFile類對象的指標。
gopher:// 如果訪問的是一gopher地址,函數返回一個CGopherFile類對象的指標。
http:// 如果訪問的是一http地址,函數返回一個CHttpFile類對象的指標。
本程式用於訪問遠程機器,因此,函數不會返回一個//類型的本地檔案。而CGopherFile和CHttpFile均派生自CInternetFile,所以將該函數傳回值賦給CInternetFile類的指標是安全的。當OpenURL()不能正常開啟URL資源時,該函數將會拋出一個異常(exception),從而導致程式執行階段錯誤。由於本程式用於探查未知的網址,該網址可能不提供相應服務或根本不存在,因此OpenURL()有時可能不會正常運行。為了避免程式異常終止,我們需使用try-catch結構來處理異常。本段程式碼如下:
CInternetFile* file=NULL;
try
{
file=(CInternetFile*)session.OpenURL(URL);
}
catch(CInternetException* pEx)
{
file=NULL;//如果發生執行階段錯誤,給file賦空值,程式將繼續運行
pEx->Delete();
}
通過以上代碼,程式將使用使用者指定的地址來試圖開啟Http網址,如果失敗,返回的檔案為空白,程式將繼續運行,嘗試用其它協議開啟網址。隨後,無論網址是否成功開啟,我們都應作相應輸出以提示使用者。如果成功,我們用一個for迴圈讀出返迴文件的頭20句並輸出,如果失敗,也要給出相應的提示。程式如下:
if(file) //判定連結是否成功
{
m_out+="已建立連結。/r/n";
CString line;
for(int i=0;i<20&&file->ReadString(line);i++)
{
m_out+=line+"/r/n";
}
file->Close();
delete file;
}
else
{
m_out+="本地址沒有發現http主機/r/n";
}
m_out+="------------------------------------------------------/r/n";
UpdateData(FALSE);
現在我們可以編譯該程式並運行,鍵入地址microsof.com並查詢,輸出的資訊表明,程式在http://microsoft.com和http://www.microsoft.com處均發現了www網頁,並輸出了首頁的HTML檔案的頭20行。
三、ftp查詢的實現
現在我們繼續編程,來探查使用者輸入的網址是否提供ftp服務。ftp是internet上的一種檔案傳輸通訊協定,主要用於在伺服器和客戶機之間傳送檔案。重新調用TryURL()來達到目的是不可能的,因為TryURL()假設該網址指向一個檔案,而一個ftp網址指向一系列檔案。必需重新編寫一個函數,仿照以前步驟,給CQueryDlg類增加另一個成員函數void TryFtp(CString host)。如果所探查的網址提供ftp服務,該函數將找出ftp預設目錄並顯示出來。代碼如下:
void CQueryDlg::TryFTP(CString host)
{
CInternetSession session;
m_out += "正在連結FTP地址 " + host + "/r/n";
UpdateData(FALSE);
CFtpConnection* connection = NULL;
try
{
connection = session.GetFtpConnection(host);
}
catch (CInternetException* pEx)
{
connection = NULL;
pEx->Delete();
}
if (connection)
{
m_out += "已建立連結。 /r/n";
CString line;
connection->GetCurrentDirectory(line);
m_out += "預設目錄為 " + line + "/r/n";
connection->Close();
delete connection;
}
else
{
m_out += "本地址沒有發現ftp主機 。/r/n";
}
m_out += "------------------------------------------------------/r/n";
UpdateData(FALSE);
}
本函數和TryURL()很相似,不過,它不使用OpenURL()來開啟一個檔案,而是用GetFtpConnection()來與ftp伺服器建立連結。如果連結成功,將使用GetCurrentDirectory()來得到伺服器的預設目錄。
大多數ftp地址前有ftp.的首碼,但一些老的地址沒有此首碼。我們可以在OnQuery()函數的末尾增加兩行來調用這個新的函數:
TryFTP(m_host);
TryFTP("ftp."+m_host);
重新編譯器並運行,可以發現,在ftp.microsoft.com處提供ftp服務。在開始查詢到出結果將有一些延遲,因為程式等結果全部探查到後再一次顯示,如果我們希望結果即時地顯示,必需使用非同步通訊端(asynchronous sockets)或多線程編程。
四、gopher查詢的實現
GOPHER是一種基於文本的協議,它和WWW相似,可以通過點擊文字內容,實現網路內的連結和瀏覽。但是,GOPHER是通過逐級文字菜單來組織連結和內容的,它不象WWW那樣有豐富的多媒體頁面。要實現GOPHER查詢,我們應給CQueryDlg類增加另一個成員函數void TryGopher(CString host)。如果查詢成功,該函數將返回查詢地址的第一個Gopher位置(locator)。位置是gopher協議的概念,任何gopher客戶程式必需先得到一個gopher位置,然後才能進行相應的gopher操作。TryGopher()函數如下:
void CQueryDlg::TryGopher(CString host)
{
CInternetSession session;
m_out += "正在連結gopher地址 " + host + "/r/n";
UpdateData(FALSE);
CGopherConnection* connection = NULL;
try
{
connection = session.GetGopherConnection(host);
}
catch (CInternetException* pEx)
{
connection = NULL;
pEx->Delete();
}
if (connection)
{
m_out += "已建立連結。 /r/n";
CString line;
CGopherLocator locator = connection->CreateLocator(NULL, NULL, GOPHER_TYPE_DIRECTORY);
line = locator;
m_out += "第一個Gopher位置是" + line + "/r/n";
connection->Close();
delete connection;
}
else
{
m_out += "本地址沒有發現gopher主機 。 /r/n";
}
m_out += "------------------------------------------------------/r/n";
UpdateData(FALSE);
}
本函數和前兩個函數大致相似,在通過調用connection = session.GetGopher Connection(host);來建立Gopher連結後,通過語句CGopherLocator locator = connection-> CreateLocator(NULL, NULL, GOPHER_TYPE_DIRECTORY);來建立一個Gopher位置(locator),函數CreateLocator()有多個版本,現在我們使用的是其含三個參數的版本。其原形為CGopherLocator CreateLocator( LPCTSTR pstrDisplayString, LPCTSTR pstrSelectorString, DWORD dwGopherType );。其中參數pstrDisplayString指明要查詢的伺服器上的具體檔案或目錄名,如果它為NULL,則返回伺服器預設目錄名;參數pstrSelectorString是發送給伺服器的字元命令,以便檢索某個項目,它可設為空白;參數dwGopherType指明Gopher訪問類型,本例中定義為GOPHER_TYPE_DIRECTORY,指明要訪問的是目錄。它的其它可取值請參考VC++5.0文檔。Gopher位置(locator)建立後,我們把它強制轉換為Cstring類型,並把該位置顯示出來。在OnQuery()函數的末尾加入以下行:
TryGopher(m_host);
TryGopher("gopher." + m_host);
重新編譯器,輸入地址harvard.edu,程式將會探查出它是一個Gopher地址,並顯示出第一個Gopher位置。
五、使用Gopher來實現Finger查詢
Finger協議的作用是給你提供一個網址的具體情況,它是Internet上最古老的協議之一。在一個Finger伺服器上,你可以查詢它的某一個使用者或整個網址的情況。當然,這對網路的安全是不利的,實際上,有經驗的駭客們在攻擊一個未知網路時,第一步就是向它發送Finger和Whois查詢,這也是駭客網址上的駭客教程中建議的步驟。為了安全,許多網路伺服器不提供Finger服務,然而,當它接受到Finger查詢請求時,仍然會返回一些其它的有用資訊。
在MFC和WIN32 API中,沒有提供直接實現Finger查詢的函數,但是,我們仍有變通的辦法來實現它。所有的internet連結都需要一個宿主名和連接埠號碼,所有的著名的服務都有其特定的連接埠號碼,例如:http服務使用遠程宿主機上的連接埠80,ftp服務使用連接埠21,gopher服務使用連接埠70。對於finger服務來說,它使用連接埠79。finger是一種簡單的協議,如果你向遠程宿主機的連接埠79發送字串,finger伺服器在連接埠79偵聽到後,將會發送出一個finger回答。如果你發送的字串僅僅包含/r/n,伺服器通常將會把本伺服器上所有使用者的列表及相關資訊(如使用者真實姓名等)做為應答返回。因此,如果我們不使用預設的連接埠70,而是使用連接埠79來建立gopher連結,我們就能發出finger查詢。給CQueryDlg類增加一個成員函數void TryFinger(CString host)如下:
void CQueryDlg::TryFinger(CString host)
{
CInternetSession session;
m_out += "正在連結finger地址 " + host + "/r/n";
UpdateData(FALSE);
CGopherConnection* connection = NULL;
try
{
connection = session.GetGopherConnection(host,NULL,NULL,79);
}
catch (CInternetException* pEx)
{
connection = NULL;
pEx->Delete();
}
if (connection)
{
m_out += "已建立連結。 /r/n";
CGopherLocator locator = connection->CreateLocator(NULL, NULL, GOPHER_TYPE_TEXT_FILE);
CGopherFile* file =NULL;
try
{
file = connection->OpenFile(locator);
}
catch (CInternetException* pEx)
{
file = NULL;
pEx->Delete();
}
if (file)
{
CString line;
for (int i=0; i < 20 && file->ReadString(line); i++)
{
m_out += line + "/r/n";
}
file->Close();
delete file;
}
else
{
m_out+="finger查詢失敗。/r/n";
}
connection->Close();
delete connection;
}
else
{
m_out += "本地址沒有發現finger主機 。 /r/n";
}
m_out += "------------------------------------------------------/r/n";
UpdateData(FALSE);
}
本函數中,語句connection = session.GetGopherConnection(host,NULL,NULL,79);用於建立finger連結。隨後,我們創立一個文字檔類型的Gopher位置用來動作伺服器返回的資訊:CGopherLocator locator = connection->CreateLocator(NULL, NULL, GOPHER_TYPE_TEXT_FILE);。使用該Gopher位置開啟檔案並使用一個for迴圈來讀出該檔案的頭20行,隨後將它顯示出來。
在OnQuery()函數的末尾加入以下行:TryFinger(m_host);。編譯器,輸入地址whitehouse.gov,程式將會返回該伺服器的e-mail地址,從返回的資訊可知,出於安全考慮,該伺服器的其它finger服務已被取消了。如果你輸入的網址沒有提供finger服務,程式將有較長一段時間的延遲,最後彈出一個訊息框,通知使用者連結出現逾時錯誤,單擊ok按鈕即可。
六、使用gopher協議發送whois查詢
還有一個協議也能提供網址的相關資訊,它也是一種古老的協議,MFC並不直接支援它,這就是whois協議。在整個internet上,只有少數伺服器提供whois服務。whois服務建立了internet上的網域名稱資料庫,如果對某個網域名稱進行whois查詢,伺服器將會返回擁有該域的機構或個人的真實姓名、地址、電話號碼等資訊。國際上的網域名稱註冊機構擁有whois伺服器,例如,網域名稱結尾為.com的域都在一個稱為InterNIC的機構中註冊,該機構擁有一個whois伺服器稱為rs.internic.net。whois協議和finger協議一樣,是一種簡單的協議,它使用連接埠43,如果向whois伺服器的連接埠43發送包含網域名稱的字串,則whois伺服器將會返回該域擁有者的情況。給CQueryDlg類增加一個成員函數void TryWhois(CString host)如下:
void CQueryDlg::TryWhois(CString host)
{
CInternetSession session;
m_out += "正在連結Whois地址 " + host + "/r/n";
UpdateData(FALSE);
CGopherConnection* connection = NULL;
try
{
connection = session.GetGopherConnection("rs.internic.net",NULL,NULL,43);
}
catch (CInternetException* pEx)
{
connection = NULL;
pEx->Delete();
}
if (connection)
{
m_out += "已建立連結。 /r/n";
CGopherLocator locator = connection->CreateLocator(NULL, host, GOPHER_TYPE_TEXT_FILE);
CGopherFile* file = NULL;
try
{
file = connection->OpenFile(locator);
}
catch (CInternetException* pEx)
{
file = NULL;
pEx->Delete();
}
if (file)
{
CString line;
for (int i=0; i < 20 && file->ReadString(line); i++)
{
m_out += line + "/r/n";
}
file->Close();
delete file;
}
else
{
m_out+="Whois查詢失敗。/r/n";
}
connection->Close();
delete connection;
}
else
{
m_out += "Whois查詢失敗。/r/n";
}
m_out += "------------------------------------------------------/r/n";
UpdateData(FALSE);
}
在本函數中,語句connection = session.GetGopherConnection("rs.internic.net",NULL, NULL,43);使程式連結到了whois伺服器rs.internic.net。隨後,我們建立一個gopher位置來查詢使用者輸入的域:CGopherLocator locator = connection->CreateLocator(NULL, host, GOPHER_TYPE_TEXT_FILE);由於連結的域為rs.internic.net,所以,本函數只能查詢結尾為.com的域。讀者可以對本段程式進行擴充,連結到其它相關whois伺服器,以查詢其它類型的域。
在OnQuery()函數的末尾加入以下行:TryFinger(m_host);。重新編譯器,現在,我們可以對結尾為.com的域進行whois查詢了。
至此,本程式已全部結束。當然,有興趣的讀者可以對它進一步擴充,以完成更多的功能。可以對WinInet類進行一些小小的擴充,以便能通過特定的連接埠訪問e-mail和news服務。此外,也可以連結到一些著名的網路搜尋引擎上並提交查詢,使程式具有搜尋功能。這一切,就取決於你的創造力了。