構(gòu)造.NET環(huán)境下的網(wǎng)頁下載器 (1)
發(fā)布時間:2008-09-07 閱讀數(shù): 次 來源:網(wǎng)樂原科技
一.前言:
微軟的.Net框架為我們進(jìn)行網(wǎng)絡(luò)編程提供了以下兩個名字空間:System.Net以及System.Net.Sockets。通過合理運用其中的類和方法,我們可以很容易地編寫出各種網(wǎng)絡(luò)應(yīng)用程序。這種網(wǎng)絡(luò)應(yīng)用程序既可以是基于流套接字的,也可以是基于數(shù)據(jù)報套接字的。而基于流套接字的通訊中采用最廣泛的協(xié)議就是TCP協(xié)議,基于數(shù)據(jù)報套接字的通訊中采用最廣泛的自然就是UDP協(xié)議了。下面我將重點向大家介紹.NET網(wǎng)絡(luò)編程中的一些類:Dns類、IPHostEntry類、IPEndPoint類以及Socket類,并會在最后給出一個網(wǎng)頁下載器的實例以加深讀者對.NET網(wǎng)絡(luò)編程的理解。
二.網(wǎng)絡(luò)編程類介紹:
Dns 類:
向使用 TCP/IP Internet 服務(wù)的應(yīng)用程序提供域名服務(wù)。其Resolve()方法查詢DNS服務(wù)器以將用戶友好的域名(如"www.google.com")映射到數(shù)字形式的 Internet 地址(如 192.168.1.1)。Resolve()方法返回一個IPHostEnty實例,該實例包含所請求名稱的地址和別名的列表。大多數(shù)情況下,可以使用 AddressList 數(shù)組中返回的第一個地址。
Resolve()方法的函數(shù)原型如下:
public static IPHostEntry Resolve(string hostName);
下面的代碼獲取一個 IPAddress 實例,該實例包含服務(wù)器 www.google.com 的IP地址:
IPHostEntry ipHostInfo = Dns.Resolve("www.google.com");
IPAddress ipAddress = ipHostInfo.AddressList[0];
不過在Dns類中,除了通過Resolve()方法,你還可以通過GetHostByAddress()方法以及GetHostByName()方法來得到相應(yīng)的IPHostEntry實例,函數(shù)原型如下:
public static IPHostEntry GetHostByAddress(string IPAddress);
public static IPHostEntry GetHostByName(string hostName);
下面的代碼顯示了如何分別運用以上兩種方法獲得包含服務(wù)器www.google.com的相關(guān)信息的IPHostEntry實例:
IPHostEntry hostInfo=Dns.GetHostByAddress("192.168.1.1");
IPHostEntry hostInfo=Dns.GetHostByName("www.google.com");
在使用以上方法時,你將可能需要處理以下幾種異常:
SocketException異常:訪問Socket時操作系統(tǒng)發(fā)生錯誤引發(fā)
ArgumentNullException異常:參數(shù)為空引用引發(fā)
ObjectDisposedException異常:Socket已經(jīng)關(guān)閉引發(fā)
以上,我向大家簡要地介紹了Dns類中一些方法以及其用法,并列舉出了可能出現(xiàn)的異常,下面就讓我們轉(zhuǎn)到和Dns類密切相關(guān)的IPHostEntry類。
IPHostEntry類:
該類的實例對象中包含了Internet主機(jī)的地址相關(guān)信息。此類型的所有公共靜態(tài)成員對多線程操作而言都是安全的,但不保證任何實例成員是線程安全的。其中主要的一些屬性有:AddressList屬性、Aliases屬性以及HostName屬性。
AddressList屬性和Aliases屬性的作用分別是獲取或設(shè)置與主機(jī)關(guān)聯(lián)的IP地址列表以及獲取或設(shè)置與主機(jī)關(guān)聯(lián)的別名列表。其中AddressList屬性值是一個IPAddress類型的數(shù)組,包含解析為Aliases屬性中包含的主機(jī)名的IP地址;Aliases屬性值是一組字符串,包含解析為AddressList 屬性中的IP地址的DNS名。而HostName屬性比較好理解,它包含了服務(wù)器的主要主機(jī)名,這光從名稱上就可以知道了。如果服務(wù)器的DNS項定義了附加別名,則可在Aliases屬性中使用這些別名。
下面的代碼列出了服務(wù)器www.google.com的相關(guān)別名列表以及IP地址列表的長度并將所有的IP地址列出:
IPHostEntry IPHost = Dns.Resolve("www.google.com/");
string[] aliases = IPHost.Aliases;
Console.WriteLine(aliases.Length);
IPAddress[] addr = IPHost.AddressList;
Console.WriteLine(addr.Length);
for(int i= 0; i < addr.Length ; i++)
{
Console.WriteLine(addr[i]);
}
介紹完IPHostEntry類,我們能獲得了所要連接的主機(jī)的相關(guān)IP地址以及別名列表,但是真正要和主機(jī)取得連接還需要一個很重要的類-IPEndPoint類。
IPEndPoint類:
在Internet中,TCP/IP使用一個網(wǎng)絡(luò)地址和一個服務(wù)端口號來唯一標(biāo)識設(shè)備。網(wǎng)絡(luò)地址標(biāo)識網(wǎng)絡(luò)上的特定設(shè)備;端口號標(biāo)識要連接到的該設(shè)備上的特定服務(wù)。網(wǎng)絡(luò)地址和服務(wù)端口的組合稱為終結(jié)點,在.NET框架中正是由EndPoint類表示這個終結(jié)點,它提供表示網(wǎng)絡(luò)資源或服務(wù)的抽象,用以標(biāo)志網(wǎng)絡(luò)地址等信息。.Net同時也為每個受支持的地址族定義了 EndPoint的子代;對于IP地址族,該類為IPEndPoint。IPEndPoint類包含應(yīng)用程序連接到主機(jī)上的服務(wù)所需的主機(jī)和端口信息,通過組合服務(wù)的主機(jī)IP地址和端口號,IPEndPoint類形成到服務(wù)的連接點。
在IPEndPoint類中有兩個很有用的構(gòu)造函數(shù):
public IPEndPoint(long, int);
public IPEndPoint(IPAddress, int);
它們的作用就是用指定的地址和端口號初始化 IPEndPoint 類的新實例。該類中的屬性有:Address屬性、AddressFamily屬性以及Port屬性,這些屬性相對比較容易理解,這里就不作多介紹。下面的代碼顯示了如何取得服務(wù)器www.google.com的終結(jié)點:
IPHostEntry IPHost = Dns.Resolve("www.google.com");
IPAddress[] addr = IPHost.AddressList;
IPEndPoint ep = new IPEndPoint(addr[0],80);
這樣,我們已經(jīng)了解了和主機(jī)取得連接的一些必要的基本類,有了這些知識,我們就可以運用下面的Socket類真正地和主機(jī)取得連接并進(jìn)行通訊了。
Socket類:
Socket類是包含在System.Net.Sockets名字空間中的一個非常重要的類。一個Socket實例包含了一個本地以及一個遠(yuǎn)程的終結(jié)點,就像上面介紹的那樣,該終結(jié)點包含了該Socket實例的一些相關(guān)信息。
需要知道的是Socket 類支持兩種基本模式:同步和異步。其區(qū)別在于:在同步模式中,對執(zhí)行網(wǎng)絡(luò)操作的函數(shù)(如Send和Receive)的調(diào)用一直等到操作完成后才將控制返回給調(diào)用程序。在異步模式中,這些調(diào)用立即返回。
下面我們重點討論同步模式的Socket編程。首先,同步模式的Socket編程的基本過程如下:
1.創(chuàng)建一個Socket實例對象。
2.將上述實例對象連接到一個具體的終結(jié)點(EndPoint)。
3.連接完畢,就可以和服務(wù)器進(jìn)行通訊:接收并發(fā)送信息。
4.通訊完畢,用ShutDown()方法來禁用Socket。
5.最后用Close()方法來關(guān)閉Socket。
知道了以上基本過程,我們就開始進(jìn)一步實現(xiàn)連接并通訊了。在使用之前,你需要首先創(chuàng)建Socket對象的實例,這可以通過Socket類的構(gòu)造方法來實現(xiàn):
public Socket(AddressFamily addressFamily,SocketType
socketType,ProtocolType protocolType);
其中,addressFamily 參數(shù)指定Socket使用的尋址方案,比如AddressFamily.InterNetwork表明為IP版本4的地址;socketType參數(shù)指定Socket的類型,比如SocketType.Stream表明連接是基于流套接字的,而SocketType.Dgram表示連接是基于數(shù)據(jù)報套接字的。protocolType參數(shù)指定Socket使用的協(xié)議,比如ProtocolType.Tcp表明連接協(xié)議是運用TCP協(xié)議的,而Protocol.Udp則表明連接協(xié)議是運用UDP協(xié)議的。
在創(chuàng)建了Socket實例后,我們就可以通過一個遠(yuǎn)程主機(jī)的終結(jié)點和它取得連接,運用的方法就是Connect()方法:
public Connect (EndPoint ep);
該方法只可以被運用在客戶端。進(jìn)行連接后,我們可以運用套接字的Connected屬性來驗證連接是否成功。如果返回的值為true,則表示連接成功,否則就是失敗。下面的代碼就顯示了如何創(chuàng)建Socket實例并通過終結(jié)點與之取得連接的過程:
IPHostEntry IPHost = Dns.Resolve("http://www.google.com/");
string []aliases = IPHost.Aliases;
IPAddress[] addr = IPHost.AddressList;
EndPoint ep = new IPEndPoint(addr[0],80);
Socket sock = newSocket(AddressFamily.InterNetwork,
SocketType.Stream,ProtocolType.Tcp);
sock.Connect(ep);
if(sock.Connected)
Console.WriteLine("OK");
一旦連接成功,我們就可以運用Send()和Receive()方法來進(jìn)行通訊。
Send()方法的函數(shù)原型如下:
public int Send (byte[] buffer, int size, SocketFlags flags);
其中,參數(shù)buffer包含了要發(fā)送的數(shù)據(jù),參數(shù)size表示要發(fā)送數(shù)據(jù)的大小,而參數(shù)flags則可以是以下一些值:SocketFlags.None、SocketFlags.DontRoute、SocketFlags.OutOfBnd。
該方法返回的是一個System.Int32類型的值,它表明了已發(fā)送數(shù)據(jù)的大小。同時,該方法還有以下幾種已被重載了的函數(shù)實現(xiàn):
public int Send (byte[] buffer);
public int Send (byte[] buffer, SocketFlags flags);
public int Send (byte[] buffer,int offset, int size,
SocketFlags flags);
介紹完Send()方法,下面是Receive()方法,其函數(shù)原型如下:
public int Receive(byte[] buffer, int size, SocketFlags flags);
其中的參數(shù)和Send()方法的參數(shù)類似,在這里就不再贅述。
同樣,該方法還有以下一些已被重載了的函數(shù)實現(xiàn):
public int Receive (byte[] buffer);
public int Receive (byte[] buffer, SocketFlags flags);
public int Receive (byte[] buffer,int offset, int size,
SocketFlags flags);
在通訊完成后,我們就通過ShutDown()方法來禁用Socket,函數(shù)原型如下:
public void ShutDown(SocketShutdown how);
其中的參數(shù)how表明了禁用的類型,SoketShutdown.Send表明關(guān)閉用于發(fā)送的套接字;SoketShutdown.Receive表明關(guān)閉用于接收的套接字;而SoketShutdown.Both則表明發(fā)送和接收的套接字同時被關(guān)閉。
應(yīng)該注意的是在調(diào)用Close()方法以前必須調(diào)用ShutDown()方法以確保在Socket關(guān)閉之前已發(fā)送或接收所有掛起的數(shù)據(jù)。一旦ShutDown()調(diào)用完畢,就調(diào)用Close()方法來關(guān)閉Socket,其函數(shù)原型如下:
public void Close();
該方法強制關(guān)閉一個Socket連接并釋放所有托管資源和非托管資源。該方法在內(nèi)部其實是調(diào)用了方法Dispose(),該函數(shù)是受保護(hù)類型的,其函數(shù)原型如下:
protected virtual void Dispose(bool disposing);
其中,參數(shù)disposing為true或是false,如果為true,則同時釋放托管資源和非托管資源;如果為false,則僅釋放非托管資源。因為Close()方法調(diào)用Dispose()方法時的參數(shù)是true,所以它釋放了所有托管資源和非托管資源。
這樣,一個Socket從創(chuàng)建到連接到通訊最后的關(guān)閉的過程就完成了。雖然整個過程比較復(fù)雜,但相對以前在SDK或是其他環(huán)境下進(jìn)行Socket編程,這個過程就顯得相當(dāng)輕松了。