c#網(wǎng)絡(luò)編程概述
微軟下一代互聯(lián)網(wǎng)開發(fā)工具vs.net已于三月份在全國范圍推出,其中的一門新興語言c#正被越來越多的開發(fā)者所接受并運(yùn)用。
c#作為一門集眾家之長的語言,在各個(gè)方面尤其是網(wǎng)絡(luò)編程方面有著很大的優(yōu)勢。本文就向大家介紹一下用c#進(jìn)行網(wǎng)絡(luò)編程的一些基本知識和方法。
微軟的.net框架為我們進(jìn)行網(wǎng)絡(luò)編程提供了以下兩個(gè)名字空間:system.net以及system.net.sockets。通過合理運(yùn)用其中的類和方法,我們可以很容易地編寫出各種網(wǎng)絡(luò)應(yīng)用程序。這種網(wǎng)絡(luò)應(yīng)用程序既可以是基于流套接字的,也可以是基于數(shù)據(jù)報(bào)套接字的。而基于流套接字的通訊中采用最廣泛的協(xié)議就是tcp協(xié)議,基于數(shù)據(jù)報(bào)套接字的通訊中采用最廣泛的自然就是udp協(xié)議了。
下面我重點(diǎn)向大家介紹c#網(wǎng)絡(luò)編程中的一些類:dns類、iphostentry類、ipendpoint類以及socket類,最后我會給出相應(yīng)的實(shí)例以加深讀者的理解。
dns 類:
向使用 tcp/ip internet 服務(wù)的應(yīng)用程序提供域名服務(wù)。其resolve()方法查詢dns服務(wù)器以將用戶友好的域名(如"www.google.com")映射到數(shù)字形式的 internet 地址(如 192.168.1.1)。resolve()方法返回一個(gè)iphostenty實(shí)例,該實(shí)例包含所請求名稱的地址和別名的列表。大多數(shù)情況下,可以使用 addresslist 數(shù)組中返回的第一個(gè)地址。
resolve()方法的函數(shù)原型如下:
public static iphostentry resolve(string hostname);
下面的代碼獲取一個(gè) ipaddress 實(shí)例,該實(shí)例包含服務(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í)例,函數(shù)原型如下:
public static iphostentry gethostbyaddress(string ipaddress);
public static iphostentry gethostbyname(string hostname);
下面的代碼顯示了如何分別運(yùn)用以上兩種方法獲得包含服務(wù)器www.google.com的相關(guān)信息的iphostentry實(shí)例:
iphostentry hostinfo=dns.gethostbyaddress(“192.168.1.1”);
iphostentry hostinfo=dns.gethostbyname("www.google.com");
在使用以上方法時(shí),你將可能需要處理以下幾種異常:
socketexception異常:訪問socket時(shí)操作系統(tǒng)發(fā)生錯(cuò)誤引發(fā)
argumentnullexception異常:參數(shù)為空引用引發(fā)
objectdisposedexception異常:socket已經(jīng)關(guān)閉引發(fā)
以上,我向大家簡要地介紹了dns類中一些方法以及其用法,并列舉出了可能出現(xiàn)的異常,下面就讓我們轉(zhuǎn)到和dns類密切相關(guān)的iphostentry類。
iphostentry類:
該類的實(shí)例對象中包含了internet主機(jī)的地址相關(guān)信息。此類型的所有公共靜態(tài)成員對多線程操作而言都是安全的,但不保證任何實(shí)例成員是線程安全的。其中主要的一些屬性有:addresslist屬性、aliases屬性以及hostname屬性。
addresslist屬性和aliases屬性的作用分別是獲取或設(shè)置與主機(jī)關(guān)聯(lián)的ip地址列表以及獲取或設(shè)置與主機(jī)關(guān)聯(lián)的別名列表。其中addresslist屬性值是一個(gè)ipaddress類型的數(shù)組,包含解析為aliases屬性中包含的主機(jī)名的ip地址;aliases屬性值是一組字符串,包含解析為addresslist 屬性中的ip地址的dns名。而hostname屬性比較好理解,它包含了服務(wù)器的主要主機(jī)名,這光從名稱上就可以知道了。如果服務(wù)器的dns項(xiàng)定義了附加別名,則可在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ī)取得連接還需要一個(gè)很重要的類-ipendpoint類。
ipendpoint類:
在internet中,tcp/ip使用一個(gè)網(wǎng)絡(luò)地址和一個(gè)服務(wù)端口號來唯一標(biāo)識設(shè)備。網(wǎng)絡(luò)地址標(biāo)識網(wǎng)絡(luò)上的特定設(shè)備;端口號標(biāo)識要連接到的該設(shè)備上的特定服務(wù)。網(wǎng)絡(luò)地址和服務(wù)端口的組合稱為終結(jié)點(diǎn),在.net框架中正是由endpoint類表示這個(gè)終結(jié)點(diǎn),它提供表示網(wǎng)絡(luò)資源或服務(wù)的抽象,用以標(biāo)志網(wǎng)絡(luò)地址等信息。.net同時(shí)也為每個(gè)受支持的地址族定義了 endpoint的子代;對于ip地址族,該類為ipendpoint。ipendpoint類包含應(yīng)用程序連接到主機(jī)上的服務(wù)所需的主機(jī)和端口信息,通過組合服務(wù)的主機(jī)ip地址和端口號,ipendpoint類形成到服務(wù)的連接點(diǎn)。
在ipendpoint類中有兩個(gè)很有用的構(gòu)造函數(shù):
public ipendpoint(long, int);
public ipendpoint(ipaddress, int);
它們的作用就是用指定的地址和端口號初始化 ipendpoint 類的新實(shí)例。該類中的屬性有:address屬性、addressfamily屬性以及port屬性,這些屬性相對比較容易理解,這里就不作多介紹。下面的代碼顯示了如何取得服務(wù)器www.google.com的終結(jié)點(diǎn):
iphostentry iphost = dns.resolve("www.google.com");
ipaddress[] addr = iphost.addresslist;
ipendpoint ep = new ipendpoint(addr[0],80);
這樣,我們已經(jīng)了解了和主機(jī)取得連接的一些必要的基本類,有了這些知識,我們就可以運(yùn)用下面的socket類真正地和主機(jī)取得連接并進(jìn)行通訊了。
socket類:
socket類是包含在system.net.sockets名字空間中的一個(gè)非常重要的類。一個(gè)socket實(shí)例包含了一個(gè)本地以及一個(gè)遠(yuǎn)程的終結(jié)點(diǎn),就像上面介紹的那樣,該終結(jié)點(diǎn)包含了該socket實(shí)例的一些相關(guān)信息。
需要知道的是socket 類支持兩種基本模式:同步和異步。其區(qū)別在于:在同步模式中,對執(zhí)行網(wǎng)絡(luò)操作的函數(shù)(如send和receive)的調(diào)用一直等到操作完成后才將控制返回給調(diào)用程序。在異步模式中,這些調(diào)用立即返回。
下面我們重點(diǎn)討論同步模式的socket編程。首先,同步模式的socket編程的基本過程如下:
1. 創(chuàng)建一個(gè)socket實(shí)例對象。
2. 將上述實(shí)例對象連接到一個(gè)具體的終結(jié)點(diǎn)(endpoint)。
3. 連接完畢,就可以和服務(wù)器進(jìn)行通訊:接收并發(fā)送信息。
4. 通訊完畢,用shutdown()方法來禁用socket。
5. 最后用close()方法來關(guān)閉socket。
知道了以上基本過程,我們就開始進(jìn)一步實(shí)現(xiàn)連接并通訊了。在使用之前,你需要首先創(chuàng)建socket對象的實(shí)例,這可以通過socket類的構(gòu)造方法來實(shí)現(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ù)報(bào)套接字的。protocoltype參數(shù)指定socket使用的協(xié)議,比如protocoltype.tcp表明連接協(xié)議是運(yùn)用tcp協(xié)議的,而protocol.udp則表明連接協(xié)議是運(yùn)用udp協(xié)議的。
在創(chuàng)建了socket實(shí)例后,我們就可以通過一個(gè)遠(yuǎn)程主機(jī)的終結(jié)點(diǎn)和它取得連接,運(yùn)用的方法就是connect()方法:
public connect (endpoint ep);
該方法只可以被運(yùn)用在客戶端。進(jìn)行連接后,我們可以運(yùn)用套接字的connected屬性來驗(yàn)證連接是否成功。如果返回的值為true,則表示連接成功,否則就是失敗。下面的代碼就顯示了如何創(chuàng)建socket實(shí)例并通過終結(jié)點(diǎn)與之取得連接的過程:
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");
一旦連接成功,我們就可以運(yùn)用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。
該方法返回的是一個(gè)system.int32類型的值,它表明了已發(fā)送數(shù)據(jù)的大小。同時(shí),該方法還有以下幾種已被重載了的函數(shù)實(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ù)實(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ā)送和接收的套接字同時(shí)被關(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();
該方法強(qiáng)制關(guān)閉一個(gè)socket連接并釋放所有托管資源和非托管資源。該方法在內(nèi)部其實(shí)是調(diào)用了方法dispose(),該函數(shù)是受保護(hù)類型的,其函數(shù)原型如下:
protected virtual void dispose(bool disposing);
其中,參數(shù)disposing為true或是false,如果為true,則同時(shí)釋放托管資源和非托管資源;如果為false,則僅釋放非托管資源。因?yàn)閏lose()方法調(diào)用dispose()方法時(shí)的參數(shù)是true,所以它釋放了所有托管資源和非托管資源。
這樣,一個(gè)socket從創(chuàng)建到連接到通訊最后的關(guān)閉的過程就完成了。雖然整個(gè)過程比較復(fù)雜,但相對以前在sdk或是其他環(huán)境下進(jìn)行socket編程,這個(gè)過程就顯得相當(dāng)輕松了。
最后,我就綜合以上c#網(wǎng)絡(luò)編程的一些知識,向大家展示一個(gè)很好的實(shí)例。該實(shí)例是一個(gè)運(yùn)用socket的基于同步模式的客戶端應(yīng)用程序,它首先通過解析服務(wù)器的ip地址建立一個(gè)終結(jié)點(diǎn),同時(shí)創(chuàng)建一個(gè)基于流套接字的socket連接,其運(yùn)用的協(xié)議是tcp協(xié)議。通過該socket就可以發(fā)送獲取網(wǎng)頁的命令,再通過該socket獲得服務(wù)器上默認(rèn)的網(wǎng)頁,最后通過文件流將獲得的數(shù)據(jù)寫入本機(jī)文件。這樣就完成了網(wǎng)頁的下載工作了,程序運(yùn)行的效果如下所示:
源代碼如下:(其中主要的函數(shù)為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;
///
/// 必需的設(shè)計(jì)器變量。
///
private system.componentmodel.container components = null;
public form1()
{
//
// windows 窗體設(shè)計(jì)器支持所必需的
//
initializecomponent();
//
// todo: 在 initializecomponent 調(diào)用后添加任何構(gòu)造函數(shù)代碼
//
}
///
/// 清理所有正在使用的資源。
///
protected override void dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.dispose();
}
}
base.dispose( disposing );
}
#region windows form designer generated code
///
/// 設(shè)計(jì)器支持所需的方法 - 不要使用代碼編輯器修改
/// 此方法的內(nèi)容。
///
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 = "服務(wù)器地址:";
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 = "網(wǎng)頁下載器";
this.resumelayout(false);
}
#endregion
///
/// 應(yīng)用程序的主入口點(diǎn)。
///
[stathread]
static void main()
{
application.run(new form1());
}
private string dosocketget(string server)
{
//定義一些必要的變量以及一條要發(fā)送到服務(wù)器的字符串
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;
//獲取服務(wù)器相關(guān)的ip地址列表,其中第一項(xiàng)即為我們所需的
ipaddress hostadd = dns.resolve(server).addresslist[0];
//根據(jù)獲得的服務(wù)器的ip地址創(chuàng)建一個(gè)終結(jié)點(diǎn),端口為默認(rèn)的80
ipendpoint ephost = new ipendpoint(hostadd, 80);
//創(chuàng)建一個(gè)socket實(shí)例
socket s = new socket(addressfamily.internetwork, sockettype.stream,
protocoltype.tcp );
try
{
//用上面所取得的終結(jié)點(diǎn)連接到服務(wù)器
s.connect(ephost);
}
catch(exception se)
{
messagebox.show("連接錯(cuò)誤:"+se.message,"提示信息",
messageboxbuttons.retrycancel,messageboxicon.information);
}
if (!s.connected)
{
strretpage = "不能連接到服務(wù)器!";
return strretpage;
}
try
{
//向服務(wù)器發(fā)送get命令
s.send(byteget, byteget.length, socketflags.none);
}
catch(exception ce)
{
messagebox.show("發(fā)送錯(cuò)誤:"+ce.message,"提示信息",
messageboxbuttons.retrycancel,messageboxicon.information);
}
//接收頁面數(shù)據(jù),直到所有字節(jié)接收完畢
int32 bytes = s.receive(recvbytes, recvbytes.length, 0);
strretpage = "以下是在服務(wù)器" + server + "上的默認(rèn)網(wǎng)頁:/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);
}
//禁用并關(guān)閉socket實(shí)例
s.shutdown(socketshutdown.both);
s.close();
return strretpage;
}
private void download_click(object sender, system.eventargs e)
{
//將所讀取的字符串轉(zhuǎn)換為字節(jié)數(shù)組
byte[] content=encoding.ascii.getbytes(dosocketget(serveraddress.text));
try
{
//創(chuàng)建文件流對象實(shí)例
filestream fs=new filestream(filename.text,filemode.openorcreate,fileaccess.readwrite);
//寫入文件
fs.write(content,0,content.length);
}
catch(exception fe)
{
messagebox.show("文件創(chuàng)建/寫入錯(cuò)誤:"+fe.message,"提示信息",messageboxbuttons.retrycancel,messageboxicon.information);
}
}
}
}
以上程序在windows 2000服務(wù)器版、visual studio.net中文正式版下調(diào)試通過