c#網絡編程基礎

C#網絡編程概述
 
微軟下一代互聯網開發工具VS.Net已於三月份在全國範圍推出,其中的一門新興語言C#正被越來越多的開發者所接受並運用。
  C#作爲一門集衆家之長的語言,在各個方面尤其是網絡編程方面有着很大的優勢。本文就向大家介紹一下用C#進行網絡編程的一些基本知識和方法。
  微軟的.Net框架爲我們進行網絡編程提供了以下兩個名字空間:System.Net以及System.Net.Sockets。通過合理運用其中的類和方法,我們可以很容易地編寫出各種網絡應用程序。這種網絡應用程序既可以是基於流套接字的,也可以是基於數據報套接字的。而基於流套接字的通訊中採用最廣泛的協議就是TCP協議,基於數據報套接字的通訊中採用最廣泛的自然就是UDP協議了。

  下面我重點向大家介紹C#網絡編程中的一些類:Dns類、IPHostEntry類、IPEndPoint類以及Socket類,最後我會給出相應的實例以加深讀者的理解。
Dns 類:

  向使用 TCP/IP Internet 服務的應用程序提供域名服務。其Resolve()方法查詢DNS服務器以將用戶友好的域名(如"www.google.com")映射到數字形式的 Internet 地址(如 192.168.1.1)。Resolve()方法返回一個IPHostEnty實例,該實例包含所請求名稱的地址和別名的列表。大多數情況下,可以使用 AddressList 數組中返回的第一個地址。

Resolve()方法的函數原型如下:

public static IPHostEntry Resolve(string hostName);

下面的代碼獲取一個 IPAddress 實例,該實例包含服務器 www.google.com 的IP地址:

IPHostEntry ipHostInfo = Dns.Resolve("www.google.com");

IPAddress ipAddress = ipHostInfo.AddressList[0];

不過在Dns類中,除了通過Resolve()方法,你還可以通過GetHostByAddress()方法以及GetHostByName()方法來得到相應的IPHostEntry實例,函數原型如下:

public static IPHostEntry GetHostByAddress(string IPAddress);

public static IPHostEntry GetHostByName(string hostName);

下面的代碼顯示瞭如何分別運用以上兩種方法獲得包含服務器www.google.com的相關信息的IPHostEntry實例:

IPHostEntry hostInfo=Dns.GetHostByAddress(“192.168.1.1”);

IPHostEntry hostInfo=Dns.GetHostByName("www.google.com");

在使用以上方法時,你將可能需要處理以下幾種異常:

SocketException異常:訪問Socket時操作系統發生錯誤引發

ArgumentNullException異常:參數爲空引用引發

ObjectDisposedException異常:Socket已經關閉引發

以上,我向大家簡要地介紹了Dns類中一些方法以及其用法,並列舉出了可能出現的異常,下面就讓我們轉到和Dns類密切相關的IPHostEntry類。
IPHostEntry類:

該類的實例對象中包含了Internet主機的地址相關信息。此類型的所有公共靜態成員對多線程操作而言都是安全的,但不保證任何實例成員是線程安全的。其中主要的一些屬性有:AddressList屬性、Aliases屬性以及HostName屬性。

AddressList屬性和Aliases屬性的作用分別是獲取或設置與主機關聯的IP地址列表以及獲取或設置與主機關聯的別名列表。其中AddressList屬性值是一個IPAddress類型的數組,包含解析爲Aliases屬性中包含的主機名的IP地址;Aliases屬性值是一組字符串,包含解析爲AddressList 屬性中的IP地址的DNS名。而HostName屬性比較好理解,它包含了服務器的主要主機名,這光從名稱上就可以知道了。如果服務器的DNS項定義了附加別名,則可在Aliases屬性中使用這些別名。

下面的代碼列出了服務器www.google.com的相關別名列表以及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類,我們能獲得了所要連接的主機的相關IP地址以及別名列表,但是真正要和主機取得連接還需要一個很重要的類-IPEndPoint類。

IPEndPoint類:

在Internet中,TCP/IP使用一個網絡地址和一個服務端口號來唯一標識設備。網絡地址標識網絡上的特定設備;端口號標識要連接到的該設備上的特定服務。網絡地址和服務端口的組合稱爲終結點,在.NET框架中正是由EndPoint類表示這個終結點,它提供表示網絡資源或服務的抽象,用以標誌網絡地址等信息。.Net同時也爲每個受支持的地址族定義了 EndPoint的子代;對於IP地址族,該類爲IPEndPoint。IPEndPoint類包含應用程序連接到主機上的服務所需的主機和端口信息,通過組合服務的主機IP地址和端口號,IPEndPoint類形成到服務的連接點。

在IPEndPoint類中有兩個很有用的構造函數:

public IPEndPoint(long, int);

public IPEndPoint(IPAddress, int);

它們的作用就是用指定的地址和端口號初始化 IPEndPoint 類的新實例。該類中的屬性有:Address屬性、AddressFamily屬性以及Port屬性,這些屬性相對比較容易理解,這裏就不作多介紹。下面的代碼顯示瞭如何取得服務器www.google.com的終結點:

IPHostEntry IPHost = Dns.Resolve("www.google.com");

IPAddress[] addr = IPHost.AddressList;

IPEndPoint ep = new IPEndPoint(addr[0],80);

這樣,我們已經瞭解了和主機取得連接的一些必要的基本類,有了這些知識,我們就可以運用下面的Socket類真正地和主機取得連接並進行通訊了。

Socket類:

Socket類是包含在System.Net.Sockets名字空間中的一個非常重要的類。一個Socket實例包含了一個本地以及一個遠程的終結點,就像上面介紹的那樣,該終結點包含了該Socket實例的一些相關信息。

需要知道的是Socket 類支持兩種基本模式:同步和異步。其區別在於:在同步模式中,對執行網絡操作的函數(如Send和Receive)的調用一直等到操作完成後纔將控制返回給調用程序。在異步模式中,這些調用立即返回。

下面我們重點討論同步模式的Socket編程。首先,同步模式的Socket編程的基本過程如下:

1. 創建一個Socket實例對象。

2. 將上述實例對象連接到一個具體的終結點(EndPoint)。

3. 連接完畢,就可以和服務器進行通訊:接收併發送信息。

4. 通訊完畢,用ShutDown()方法來禁用Socket。

5. 最後用Close()方法來關閉Socket。

知道了以上基本過程,我們就開始進一步實現連接並通訊了。在使用之前,你需要首先創建Socket對象的實例,這可以通過Socket類的構造方法來實現:

public Socket(AddressFamily addressFamily,SocketType socketType,ProtocolType protocolType);

其中,addressFamily 參數指定Socket使用的尋址方案,比如AddressFamily.InterNetwork表明爲IP版本4的地址;socketType參數指定Socket的類型,比如SocketType.Stream表明連接是基於流套接字的,而SocketType.Dgram表示連接是基於數據報套接字的。protocolType參數指定Socket使用的協議,比如ProtocolType.Tcp表明連接協議是運用TCP協議的,而Protocol.Udp則表明連接協議是運用UDP協議的。

在創建了Socket實例後,我們就可以通過一個遠程主機的終結點和它取得連接,運用的方法就是Connect()方法:

public Connect (EndPoint ep);

該方法只可以被運用在客戶端。進行連接後,我們可以運用套接字的Connected屬性來驗證連接是否成功。如果返回的值爲true,則表示連接成功,否則就是失敗。下面的代碼就顯示瞭如何創建Socket實例並通過終結點與之取得連接的過程:

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 = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);

sock.Connect(ep);

if(sock.Connected)

Console.WriteLine("OK");
一旦連接成功,我們就可以運用Send()和Receive()方法來進行通訊。

Send()方法的函數原型如下:

public int Send (byte[] buffer, int size, SocketFlags flags);

其中,參數buffer包含了要發送的數據,參數size表示要發送數據的大小,而參數flags則可以是以下一些值:SocketFlags.None、SocketFlags.DontRoute、SocketFlags.OutOfBnd。

該方法返回的是一個System.Int32類型的值,它表明了已發送數據的大小。同時,該方法還有以下幾種已被重載了的函數實現:

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()方法,其函數原型如下:

public int Receive(byte[] buffer, int size, SocketFlags flags);

其中的參數和Send()方法的參數類似,在這裏就不再贅述。

同樣,該方法還有以下一些已被重載了的函數實現:

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,函數原型如下:

public void ShutDown(SocketShutdown how);

其中的參數how表明了禁用的類型,SoketShutdown.Send表明關閉用於發送的套接字;SoketShutdown.Receive表明關閉用於接收的套接字;而SoketShutdown.Both則表明發送和接收的套接字同時被關閉。

應該注意的是在調用Close()方法以前必須調用ShutDown()方法以確保在Socket關閉之前已發送或接收所有掛起的數據。一旦ShutDown()調用完畢,就調用Close()方法來關閉Socket,其函數原型如下:

public void Close();

該方法強制關閉一個Socket連接並釋放所有託管資源和非託管資源。該方法在內部其實是調用了方法Dispose(),該函數是受保護類型的,其函數原型如下:

protected virtual void Dispose(bool disposing);

其中,參數disposing爲true或是false,如果爲true,則同時釋放託管資源和非託管資源;如果爲false,則僅釋放非託管資源。因爲Close()方法調用Dispose()方法時的參數是true,所以它釋放了所有託管資源和非託管資源。

這樣,一個Socket從創建到連接到通訊最後的關閉的過程就完成了。雖然整個過程比較複雜,但相對以前在SDK或是其他環境下進行Socket編程,這個過程就顯得相當輕鬆了。

最後,我就綜合以上C#網絡編程的一些知識,向大家展示一個很好的實例。該實例是一個運用Socket的基於同步模式的客戶端應用程序,它首先通過解析服務器的IP地址建立一個終結點,同時創建一個基於流套接字的Socket連接,其運用的協議是TCP協議。通過該Socket就可以發送獲取網頁的命令,再通過該Socket獲得服務器上默認的網頁,最後通過文件流將獲得的數據寫入本機文件。這樣就完成了網頁的下載工作了,程序運行的效果如下所示:


源代碼如下:(其中主要的函數爲DoSocketGet())

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.IO;

namespace SocketSample
{

///
/// Form1 的摘要說明。
///

public class Form1 : System.Windows.Forms.Form
{

private System.Windows.Forms.Label label1;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.Button Download;
private System.Windows.Forms.TextBox ServerAddress;
private System.Windows.Forms.TextBox Filename;

///
/// 必需的設計器變量。
///

private System.ComponentModel.Container components = null;

public Form1()
{
//
// Windows 窗體設計器支持所必需的
//

InitializeComponent();

//
// TODO: 在 InitializeComponent 調用後添加任何構造函數代碼
//

}


///
/// 清理所有正在使用的資源。
///
protected override void Dispose( bool disposing )
{

if( disposing )

{
if (components != null)
{

components.Dispose();

}

}

base.Dispose( disposing );

}


#region Windows Form Designer generated code

///

/// 設計器支持所需的方法 - 不要使用代碼編輯器修改

/// 此方法的內容。

///

private void InitializeComponent()

{

this.label1 = new System.Windows.Forms.Label();

this.label2 = new System.Windows.Forms.Label();

this.Download = new System.Windows.Forms.Button();

this.ServerAddress = new System.Windows.Forms.TextBox();

this.Filename = new System.Windows.Forms.TextBox();

this.SuspendLayout();

//

// label1

//

this.label1.Location = new System.Drawing.Point(16, 24);

this.label1.Name = "label1";

this.label1.Size = new System.Drawing.Size(80, 23);

this.label1.TabIndex = 0;

this.label1.Text = "服務器地址:";

this.label1.TextAlign = System.Drawing.ContentAlignment.MiddleRight;

//

// label2

//

this.label2.Location = new System.Drawing.Point(16, 64);

this.label2.Name = "label2";

this.label2.Size = new System.Drawing.Size(80, 23);

this.label2.TabIndex = 1;

this.label2.Text = "本地文件名:";

this.label2.TextAlign = System.Drawing.ContentAlignment.MiddleRight;

//

// Download

//

this.Download.Location = new System.Drawing.Point(288, 24);

this.Download.Name = "Download";

this.Download.TabIndex = 2;

this.Download.Text = "開始下載";

this.Download.Click += new System.EventHandler(this.Download_Click);

//

// ServerAddress

//

this.ServerAddress.Location = new System.Drawing.Point(96, 24);

this.ServerAddress.Name = "ServerAddress";

this.ServerAddress.Size = new System.Drawing.Size(176, 21);

this.ServerAddress.TabIndex = 3;

this.ServerAddress.Text = "";

//

// Filename

//

this.Filename.Location = new System.Drawing.Point(96, 64);

this.Filename.Name = "Filename";

this.Filename.Size = new System.Drawing.Size(176, 21);

this.Filename.TabIndex = 4;

this.Filename.Text = "";

//

// Form1

//

this.AutoScaleBaseSize = new System.Drawing.Size(6, 14);

this.ClientSize = new System.Drawing.Size(376, 117);

this.Controls.AddRange(new System.Windows.Forms.Control[] {

this.Filename,

this.ServerAddress,

this.Download,

this.label2,

this.label1});

this.Name = "Form1";

this.Text = "網頁下載器";

this.ResumeLayout(false);


}

#endregion


///

/// 應用程序的主入口點。

///

[STAThread]

static void Main()

{

Application.Run(new Form1());

}


private string DoSocketGet(string server)

{

//定義一些必要的變量以及一條要發送到服務器的字符串

Encoding ASCII = Encoding.ASCII;

string Get = "GET / HTTP/1.1/r/nHost: " + server +

"/r/nConnection: Close/r/n/r/n";

Byte[] ByteGet = ASCII.GetBytes(Get);

Byte[] RecvBytes = new Byte[256];

String strRetPage = null;


//獲取服務器相關的IP地址列表,其中第一項即爲我們所需的

IPAddress hostadd = Dns.Resolve(server).AddressList[0];


//根據獲得的服務器的IP地址創建一個終結點,端口爲默認的80

IPEndPoint EPhost = new IPEndPoint(hostadd, 80);


//創建一個Socket實例

Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream,

ProtocolType.Tcp );


try

{

//用上面所取得的終結點連接到服務器

s.Connect(EPhost);

}

catch(Exception se)

{

MessageBox.Show("連接錯誤:"+se.Message,"提示信息",

MessageBoxButtons.RetryCancel,MessageBoxIcon.Information);

}


if (!s.Connected)

{

strRetPage = "不能連接到服務器!";

return strRetPage;

}


try

{

//向服務器發送GET命令

s.Send(ByteGet, ByteGet.Length, SocketFlags.None);

}

catch(Exception ce)

{

MessageBox.Show("發送錯誤:"+ce.Message,"提示信息",

MessageBoxButtons.RetryCancel,MessageBoxIcon.Information);

}


//接收頁面數據,直到所有字節接收完畢

Int32 bytes = s.Receive(RecvBytes, RecvBytes.Length, 0);

strRetPage = "以下是在服務器" + server + "上的默認網頁:/r/n";

strRetPage = strRetPage + ASCII.GetString(RecvBytes, 0, bytes);


while (bytes > 0)

{

bytes = s.Receive(RecvBytes, RecvBytes.Length, SocketFlags.None);

strRetPage = strRetPage + ASCII.GetString(RecvBytes, 0, bytes);

}


//禁用並關閉Socket實例

s.Shutdown(SocketShutdown.Both);

s.Close();


return strRetPage;

}


private void Download_Click(object sender, System.EventArgs e)

{

//將所讀取的字符串轉換爲字節數組

byte[] content=Encoding.ASCII.GetBytes(DoSocketGet(ServerAddress.Text));

try

{

//創建文件流對象實例

FileStream fs=new FileStream(Filename.Text,FileMode.OpenOrCreate,FileAccess.ReadWrite);

//寫入文件

fs.Write(content,0,content.Length);

}

catch(Exception fe)

{

MessageBox.Show("文件創建/寫入錯誤:"+fe.Message,"提示信息",MessageBoxButtons.RetryCancel,MessageBoxIcon.Information);

}

}

}

}


以上程序在Windows 2000服務器版、Visual Studio.Net中文正式版下調試通過

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章