sockets C#
Microsoft.Net?Framework為應(yīng)用程序訪問Internet提供了分層的、可擴(kuò)展的以及受管轄的網(wǎng)絡(luò)服務(wù),其名字空間System.Net和System.Net.Sockets包含豐富的類可以開發(fā)多種網(wǎng)絡(luò)應(yīng)用程序。.Net類采用的分層結(jié)構(gòu)允許應(yīng)用程序在不同的控制級別上訪問網(wǎng)絡(luò),開發(fā)人員可以根據(jù)需要選擇針對不同的級別編制程序,這些級別幾乎囊括了Internet的所有需要--從socket套接字到普通的請求/響應(yīng),更重要的是,這種分層是可以擴(kuò)展的,能夠適應(yīng)Internet不斷擴(kuò)展的需要。拋開ISO/OSI模型的7層構(gòu)架,單從TCP/IP模型上的邏輯層面上看,.Net類可以視為包含3個層次:請求/響應(yīng)層、應(yīng)用協(xié)議層、傳輸層。WebReqeust和WebResponse?代表了請求/響應(yīng)層,支持Http、Tcp和Udp的類組成了應(yīng)用協(xié)議層,而Socket類處于傳輸層。?傳輸層位于這個結(jié)構(gòu)的最底層,當(dāng)其上面的應(yīng)用協(xié)議層和請求/響應(yīng)層不能滿足應(yīng)用程序的特殊需要時,就需要使用這一層進(jìn)行Socket套接字編程。而在.Net中,System.Net.Sockets?命名空間為需要嚴(yán)密控制網(wǎng)絡(luò)訪問的開發(fā)人員提供了?Windows?Sockets?(Winsock)?接口的托管實(shí)現(xiàn)。System.Net?命名空間中的所有其他網(wǎng)絡(luò)訪問類都建立在該套接字Socket實(shí)現(xiàn)之上,如TCPClient、TCPListener?和?UDPClient?類封裝有關(guān)創(chuàng)建到?Internet?的?TCP?和?UDP?連接的詳細(xì)信息;NetworkStream類則提供用于網(wǎng)絡(luò)訪問的基礎(chǔ)數(shù)據(jù)流等,常見的許多Internet服務(wù)都可以見到Socket的蹤影,如Telnet、Http、Email、Echo等,這些服務(wù)盡管通訊協(xié)議Protocol的定義不同,但是其基礎(chǔ)的傳輸都是采用的Socket。?????其實(shí),Socket可以象流Stream一樣被視為一個數(shù)據(jù)通道,這個通道架設(shè)在應(yīng)用程序端(客戶端)和遠(yuǎn)程服務(wù)器端之間,而后,數(shù)據(jù)的讀取(接收)和寫入(發(fā)送)均針對這個通道來進(jìn)行。可見,在應(yīng)用程序端或者服務(wù)器端創(chuàng)建了Socket對象之后,就可以使用Send/SentTo方法將數(shù)據(jù)發(fā)送到連接的Socket,或者使用Receive/ReceiveFrom方法接收來自連接Socket的數(shù)據(jù);?針對Socket編程,.NET?框架的?Socket?類是?Winsock32?API?提供的套接字服務(wù)的托管代碼版本。其中為實(shí)現(xiàn)網(wǎng)絡(luò)編程提供了大量的方法,大多數(shù)情況下,Socket?類方法只是將數(shù)據(jù)封送到它們的本機(jī)?Win32?副本中并處理任何必要的安全檢查。如果你熟悉Winsock?API函數(shù),那么用Socket類編寫網(wǎng)絡(luò)程序會非常容易,當(dāng)然,如果你不曾接觸過,也不會太困難,跟隨下面的解說,你會發(fā)覺使用Socket類開發(fā)windows?網(wǎng)絡(luò)應(yīng)用程序原來有規(guī)可尋,它們在大多數(shù)情況下遵循大致相同的步驟。?
在使用之前,你需要首先創(chuàng)建Socket對象的實(shí)例,這可以通過Socket類的構(gòu)造方法來實(shí)現(xiàn):?
public?Socket(AddressFamily?addressFamily,SocketType?socketType,ProtocolType?protocolType);?
其中,addressFamily?參數(shù)指定?Socket?使用的尋址方案,socketType?參數(shù)指定?Socket?的類型,protocolType?參數(shù)指定?Socket?使用的協(xié)議。?下面的示例語句創(chuàng)建一個?Socket,它可用于在基于?TCP/IP?的網(wǎng)絡(luò)(如?Internet)上通訊。?
Socket?s?=?new?Socket(AddressFamily.InterNetwork,?SocketType.Stream,?ProtocolType.Tcp);?
若要使用?UDP?而不是?TCP,需要更改協(xié)議類型,如下面的示例所示:?
Socket?s?=?new?Socket(AddressFamily.InterNetwork,?SocketType.Dgram,?ProtocolType.Udp);?
一旦創(chuàng)建?Socket,在客戶端,你將可以通過Connect方法連接到指定的服務(wù)器,并通過Send/SendTo方法向遠(yuǎn)程服務(wù)器發(fā)送數(shù)據(jù),而后可以通過Receive/ReceiveFrom從服務(wù)端接收數(shù)據(jù);而在服務(wù)器端,你需要使用Bind方法綁定所指定的接口使Socket與一個本地終結(jié)點(diǎn)相聯(lián),并通過Listen方法偵聽該接口上的請求,當(dāng)偵聽到用戶端的連接時,調(diào)用Accept完成連接的操作,創(chuàng)建新的Socket以處理傳入的連接請求。使用完?Socket?后,記住使用?Shutdown?方法禁用?Socket,并使用?Close?方法關(guān)閉?Socket。其間用到的方法/函數(shù)有: Socket.Connect方法:建立到遠(yuǎn)程設(shè)備的連接?
public?void?Connect(EndPoint?remoteEP)(有重載方法)?
Socket.Send?方法:從數(shù)據(jù)中的指示位置開始將數(shù)據(jù)發(fā)送到連接的?Socket。?
public?int?Send(byte[],?int,?SocketFlags);(有重載方法)?
Socket.SendTo?方法?將數(shù)據(jù)發(fā)送到特定終結(jié)點(diǎn)。?
public?int?SendTo(byte[],?EndPoint);(有重載方法)?
Socket.Receive方法:將數(shù)據(jù)從連接的?Socket?接收到接收緩沖區(qū)的特定位置。?
public?int?Receive(byte[],int,SocketFlags);?
Socket.ReceiveFrom方法:接收數(shù)據(jù)緩沖區(qū)中特定位置的數(shù)據(jù)并存儲終結(jié)點(diǎn)。?
public?int?ReceiveFrom(byte[],?int,?SocketFlags,?ref?EndPoint);?
Socket.Bind?方法:使?Socket?與一個本地終結(jié)點(diǎn)相關(guān)聯(lián):?
public?void?Bind(?EndPoint?localEP?);?
Socket.Listen方法:將?Socket?置于偵聽狀態(tài)。?
public?void?Listen(?int?backlog?);?
Socket.Accept方法:創(chuàng)建新的?Socket?以處理傳入的連接請求。?
public?Socket?Accept();?
Socket.Shutdown方法:禁用某?Socket?上的發(fā)送和接收?
public?void?Shutdown(?SocketShutdown?how?);?
Socket.Close方法:強(qiáng)制?Socket?連接關(guān)閉?
public?void?Close();?
可以看出,以上許多方法包含EndPoint類型的參數(shù),在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é)點(diǎn),在?.NET?框架中正是由?EndPoint?類表示這個終結(jié)點(diǎn),它提供表示網(wǎng)絡(luò)資源或服務(wù)的抽象,用以標(biāo)志網(wǎng)絡(luò)地址等信息。.Net同時也為每個受支持的地址族定義了?EndPoint?的子代;對于?IP?地址族,該類為?IPEndPoint。IPEndPoint?類包含應(yīng)用程序連接到主機(jī)上的服務(wù)所需的主機(jī)和端口信息,通過組合服務(wù)的主機(jī)IP地址和端口號,IPEndPoint?類形成到服務(wù)的連接點(diǎn)。?用到IPEndPoint類的時候就不可避免地涉及到計(jì)算機(jī)IP地址,.Net中有兩種類可以得到IP地址實(shí)例:?IPAddress類:IPAddress?類包含計(jì)算機(jī)在?IP?網(wǎng)絡(luò)上的地址。其Parse方法可將?IP?地址字符串轉(zhuǎn)換為?IPAddress?實(shí)例。下面的語句創(chuàng)建一個?IPAddress?實(shí)例:?IPAddress?myIP?=?IPAddress.Parse("192.168.1.2");?
Dns?類:向使用?TCP/IP?Internet?服務(wù)的應(yīng)用程序提供域名服務(wù)。其Resolve?方法查詢?DNS?服務(wù)器以將用戶友好的域名(如"host.contoso.com")映射到數(shù)字形式的?Internet?地址(如?192.168.1.1)。Resolve方法?返回一個?IPHostEnty?實(shí)例,該實(shí)例包含所請求名稱的地址和別名的列表。大多數(shù)情況下,可以使用?AddressList?數(shù)組中返回的第一個地址。下面的代碼獲取一個?IPAddress?實(shí)例,該實(shí)例包含服務(wù)器?host.contoso.com?的?IP?地址。?
IPHostEntry?ipHostInfo?=?Dns.Resolve("host.contoso.com");?
IPAddress?ipAddress?=?ipHostInfo.AddressList[0];?
你也可以使用GetHostName方法得到IPHostEntry實(shí)例:?
IPHosntEntry?hostInfo=Dns.GetHostByName("host.contoso.com")?
在使用以上方法時,你將可能需要處理以下幾種異常:?
SocketException異常:訪問Socket時操作系統(tǒng)發(fā)生錯誤引發(fā)?
ArgumentNullException異常:參數(shù)為空引用引發(fā)?
ObjectDisposedException異常:Socket已經(jīng)關(guān)閉引發(fā)?
在掌握上面得知識后,下面的代碼將該服務(wù)器主機(jī)(?host.contoso.com的?IP?地址與端口號組合,以便為連接創(chuàng)建遠(yuǎn)程終結(jié)點(diǎn):?
IPEndPoint?ipe?=?new?IPEndPoint(ipAddress,11000);?
確定了遠(yuǎn)程設(shè)備的地址并選擇了用于連接的端口后,應(yīng)用程序可以嘗試建立與遠(yuǎn)程設(shè)備的連接。下面的示例使用現(xiàn)有的?IPEndPoint?實(shí)例與遠(yuǎn)程設(shè)備連接,并捕獲可能引發(fā)的異常:?
try?...{?
s.Connect(ipe);//嘗試連接?
??}?
//處理參數(shù)為空引用異常?
???catch(ArgumentNullException?ae)?...{?
Console.WriteLine("ArgumentNullException?:?{0}",?ae.ToString());?
}?
//處理操作系統(tǒng)異常?
???catch(SocketException?se)?...{?
Console.WriteLine("SocketException?:?{0}",?se.ToString());?
}?
catch(Exception?e)?...{?
Console.WriteLine("Unexpected?exception?:?{0}",?e.ToString());?
}?
需要知道的是:Socket?類支持兩種基本模式:同步和異步。其區(qū)別在于:在同步模式中,對執(zhí)行網(wǎng)絡(luò)操作的函數(shù)(如?Send?和?Receive)的調(diào)用一直等到操作完成后才將控制返回給調(diào)用程序。在異步模式中,這些調(diào)用立即返回。???另外,很多時候,Socket編程視情況不同需要在客戶端和服務(wù)器端分別予以實(shí)現(xiàn),在客戶端編制應(yīng)用程序向服務(wù)端指定端口發(fā)送請求,同時編制服務(wù)端應(yīng)用程序處理該請求,這個過程在上面的闡述中已經(jīng)提及;當(dāng)然,并非所有的Socket編程都需要你嚴(yán)格編寫這兩端程序;視應(yīng)用情況不同,你可以在客戶端構(gòu)造出請求字符串,服務(wù)器相應(yīng)端口捕獲這個請求,交由其公用服務(wù)程序進(jìn)行處理。以下事例語句中的字符串就向遠(yuǎn)程主機(jī)提出頁面請求:?string?Get?=?"GET?/?HTTP/1.1 Host:?"?+?server?+?" Connection:?Close ";??遠(yuǎn)程主機(jī)指定端口接受到這一請求后,就可利用其公用服務(wù)程序進(jìn)行處理而不需要另行編制服務(wù)器端應(yīng)用程序。???綜合運(yùn)用以上闡述的使用Visual?C#進(jìn)行Socket網(wǎng)絡(luò)程序開發(fā)的知識,下面的程序段完整地實(shí)現(xiàn)了Web頁面下載功能。用戶只需在窗體上輸入遠(yuǎn)程主機(jī)名(Dns?主機(jī)名或以點(diǎn)分隔的四部分表示法格式的?IP?地址)和預(yù)保存的本地文件名,并利用專門提供Http服務(wù)的80端口,就可以獲取遠(yuǎn)程主機(jī)頁面并保存在本地機(jī)指定文件中。如果保存格式是.htm格式,你就可以在Internet瀏覽器中打開該頁面。適當(dāng)添加代碼,你甚至可以實(shí)現(xiàn)一個簡單的瀏覽器程序。?
實(shí)現(xiàn)此功能的主要源代碼如下:?
//"開始"按鈕事件?
??private?void?button1_Click(object?sender,?System.EventArgs?e)?...{?
//取得預(yù)保存的文件名?
???string?fileName=textBox3.Text.Trim();?
//遠(yuǎn)程主機(jī)?
???string?hostName=textBox1.Text.Trim();?
//端口?
???int?port=Int32.Parse(textBox2.Text.Trim());?
//得到主機(jī)信息?
???IPHostEntry?ipInfo=Dns.GetHostByName(hostName);?
//取得IPAddress[]?
???IPAddress[]?ipAddr=ipInfo.AddressList;?
//得到ip?
???IPAddress?ip=ipAddr[0];?
//組合出遠(yuǎn)程終結(jié)點(diǎn)?
???IPEndPoint?hostEP=new?IPEndPoint(ip,port);?
//創(chuàng)建Socket?實(shí)例?
???Socket?socket=new?Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);?
try?
...{?
//嘗試連接?
???socket.Connect(hostEP);?
}?
catch(Exception?se)?
...{?
MessageBox.Show("連接錯誤"+se.Message,"提示信息?
,MessageBoxButtons.RetryCancel,MessageBoxIcon.Information);?
}?
//發(fā)送給遠(yuǎn)程主機(jī)的請求內(nèi)容串?
??string?sendStr="GET?/?HTTP/1.1 Host:?"?+?hostName?+?
" Connection:?Close ";?
//創(chuàng)建bytes字節(jié)數(shù)組以轉(zhuǎn)換發(fā)送串?
???byte[]?bytesSendStr=new?byte[1024];?
//將發(fā)送內(nèi)容字符串轉(zhuǎn)換成字節(jié)byte數(shù)組?
???bytesSendStr=Encoding.ASCII.GetBytes(sendStr);?
try?
...{?
//向主機(jī)發(fā)送請求?
??socket.Send(bytesSendStr,bytesSendStr.Length,0);?
}?
catch(Exception?ce)?
...{?
MessageBox.Show("發(fā)送錯誤:"+ce.Message,"提示信息?
,MessageBoxButtons.RetryCancel,MessageBoxIcon.Information);?
}?
//聲明接收返回內(nèi)容的字符串?
???string?recvStr="";?
//聲明字節(jié)數(shù)組,一次接收數(shù)據(jù)的長度為1024字節(jié)?
???byte[]?recvBytes=new?byte[1024];?
//返回實(shí)際接收內(nèi)容的字節(jié)數(shù)?
???int?bytes=0;?
//循環(huán)讀取,直到接收完所有數(shù)據(jù)?
??while(true)?
...{?
bytes=socket.Receive(recvBytes,recvBytes.Length,0);?
//讀取完成后退出循環(huán)?
??if(bytes〈=0)?
break;?
//將讀取的字節(jié)數(shù)轉(zhuǎn)換為字符串?
??recvStr+=Encoding.ASCII.GetString(recvBytes,0,bytes);?
}?
//將所讀取的字符串轉(zhuǎn)換為字節(jié)數(shù)組?
??byte[]?content=Encoding.ASCII.GetBytes(recvStr);?
try?
...{?
//創(chuàng)建文件流對象實(shí)例?
???FileStream?fs=new?FileStream(fileName,FileMode.OpenOrCreate,FileAccess.ReadWrite);?
//寫入文件?
??fs.Write(content,0,content.Length);?
}?
catch(Exception?fe)?
...{?
MessageBox.Show("文件創(chuàng)建/寫入錯誤:"+fe.Message,"提示信息",MessageBoxButtons.RetryCancel,MessageBoxIcon.Information);?
}?
//禁用Socket?
???socket.Shutdown(SocketShutdown.Both);?
//關(guān)閉Socket?
???socket.Close();?
}?
}?
Visual?C#.Net網(wǎng)絡(luò)程序開發(fā)-Tcp篇
前一篇《Visual?C#.Net網(wǎng)絡(luò)程序開發(fā)-Socket篇》中說到:支持Http、Tcp和Udp的類組成了TCP/IP三層模型(請求響應(yīng)層、應(yīng)用協(xié)議層、傳輸層)的中間層-應(yīng)用協(xié)議層,該層的類比位于最底層的Socket類提供了更高層次的抽象,它們封裝?TCP?和?UDP?套接字的創(chuàng)建,不需要處理連接的細(xì)節(jié),這使得我們在編寫套接字級別的協(xié)議時,可以更多地嘗試使用?TCPClient?、?UDPClient和TcpListener,而不是直接向?Socket?中寫。它們之間的這種層次關(guān)系示意如下:???可見,?TcpClient?類基于?Socket?類構(gòu)建,這是它能夠以更高的抽象程度提供?TCP?服務(wù)的基礎(chǔ)。正因?yàn)檫@樣,許多應(yīng)用層上的通訊協(xié)議,比如FTP(File?Transfers?Protocol)文件傳輸協(xié)議、HTTP(Hypertext?Transfers?Protocol)超文本傳輸協(xié)議等都直接創(chuàng)建在TcpClient等類之上。?TCPClient?類使用?TCP?從?Internet?資源請求數(shù)據(jù)。TCP?協(xié)議建立與遠(yuǎn)程終結(jié)點(diǎn)的連接,然后使用此連接發(fā)送和接收數(shù)據(jù)包。TCP?負(fù)責(zé)確保將數(shù)據(jù)包發(fā)送到終結(jié)點(diǎn)并在數(shù)據(jù)包到達(dá)時以正確的順序?qū)ζ溥M(jìn)行組合。?
從名字上就可以看出,TcpClient類專為客戶端設(shè)計(jì),它為?TCP?網(wǎng)絡(luò)服務(wù)提供客戶端連接。TcpClient?提供了通過網(wǎng)絡(luò)連接、發(fā)送和接收數(shù)據(jù)的簡單方法。?若要建立?TCP?連接,必須知道承載所需服務(wù)的網(wǎng)絡(luò)設(shè)備的地址(IPAddress)以及該服務(wù)用于通訊的?TCP?端口?(Port)。Internet?分配號碼機(jī)構(gòu)?(Internet?Assigned?Numbers?Authority,?IANA)?定義公共服務(wù)的端口號(你可以訪問?http://www.iana.org/assignments/port-numbers獲得這方面更詳細(xì)的資料)。IANA?列表中所沒有的服務(wù)可使用?1,024?到?65,535?這一范圍中的端口號。要創(chuàng)建這種連接,你可以選用TcpClient類的三種構(gòu)造函數(shù)之一:?
1、public?TcpClient()當(dāng)使用這種不帶任何參數(shù)的構(gòu)造函數(shù)時,將使用本機(jī)默認(rèn)的ip地址并將使用默認(rèn)的通信端口號0。這樣情況下,如果本機(jī)不止一個ip地址,將無法選擇使用。以下語句示例了如何使用默認(rèn)構(gòu)造函數(shù)來創(chuàng)建新的?TcpClient:?
TcpClient?tcpClientC?=?new?TcpClient();?
2、public?TcpClient(IPEndPoint)使用本機(jī)IPEndPoint創(chuàng)建TcpClient的實(shí)例對象。上一篇介紹過了,IPEndPoint將網(wǎng)絡(luò)端點(diǎn)表示為IP地址和端口號,在這里它用于指定在建立遠(yuǎn)程主機(jī)連接時所使用的本地網(wǎng)絡(luò)接口(IP?地址)和端口號,這個構(gòu)造方法為使用本機(jī)IPAddress和Port提供了選擇余地。下面的語句示例了如何使用本地終結(jié)點(diǎn)創(chuàng)建?TcpClient?類的實(shí)例:?
IPHostEntry?ipInfo=Dns.GetHostByName("www.tuha.net");//主機(jī)信息?
???IPAddressList[]?ipList=ipInfo.AddressList;//IP地址數(shù)組?
???IPAddress?ip=ipList[0];//多IP地址時一般用第一個?
???IPEndPoint?ipEP=new?IPEndPoint(ip,4088);//得到網(wǎng)絡(luò)終結(jié)點(diǎn)?
???try...{?
TcpClient?tcpClientA?=?new?TcpClient(ipLocalEndPoint);?
}?
catch?(Exception?e?)?...{?
Console.WriteLine(e.ToString());?
}?
到這里,你可能會感到困惑,客戶端要和服務(wù)端創(chuàng)建連接,所指定的IP地址及通信端口號應(yīng)該是遠(yuǎn)程服務(wù)器的呀!事實(shí)上的確如此,使用以上兩種構(gòu)造函數(shù),你所實(shí)現(xiàn)的只是TcpClient實(shí)例對象與IP地址和Port端口的綁定,要完成連接,你還需要顯式指定與遠(yuǎn)程主機(jī)的連接,這可以通過TcpClient類的Connect方法來實(shí)現(xiàn),?Connet方法使用指定的主機(jī)名和端口號將客戶端連接到?遠(yuǎn)程主機(jī):?
1)、public?void?Connect(IPEndPoint);?使用指定的遠(yuǎn)程網(wǎng)絡(luò)終結(jié)點(diǎn)將客戶端連接到遠(yuǎn)程?TCP?主機(jī)。?
public?void?Connect(IPAddress,?int);?使用指定的?IP?地址和端口號將客戶端連接到?TCP?主機(jī)。?
public?void?Connect(string,?int);?將客戶端連接到指定主機(jī)上的指定端口。?
需要指出的是,Connect方法的所有重載形式中的參數(shù)IPEndPoint網(wǎng)絡(luò)終?
結(jié)點(diǎn)、IPAddress以及表現(xiàn)為string的Dns主機(jī)名和int指出的Port端口均指的是遠(yuǎn)程服務(wù)器。?
以下示例語句使用主機(jī)默認(rèn)IP和Port端口號0與遠(yuǎn)程主機(jī)建立連接:?
TcpClient?tcpClient?=?new?TcpClient();//創(chuàng)建TcpClient對象實(shí)例?
???try...{?
tcpClient.Connect("www.contoso.com",11002);//建立連接?
???}?
catch?(Exception?e?)...{?
Console.WriteLine(e.ToString());?
}?
3、public?TcpClient(string,?int);初始化?TcpClient?類的新實(shí)例并連接到指定主機(jī)上的指定端口。與前兩個構(gòu)造函數(shù)不一樣,這個構(gòu)造函數(shù)將自動建立連接,你不再需要額外調(diào)用Connect方法,其中string類型的參數(shù)表示遠(yuǎn)程主機(jī)的Dns名,如:www.tuha.net。?
以下示例語句調(diào)用這一方法實(shí)現(xiàn)與指定主機(jī)名和端口號的主機(jī)相連:?
try...{?
TcpClient?tcpClientB?=?new?TcpClient("www.tuha.net",?4088);?
}?
catch?(Exception?e?)?...{?
Console.WriteLine(e.ToString());?
}?
前面我們說,TcpClient類創(chuàng)建在Socket之上,在Tcp服務(wù)方面提供了更高層次的抽象,體現(xiàn)在網(wǎng)絡(luò)數(shù)據(jù)的發(fā)送和接受方面,是TcpClient使用標(biāo)準(zhǔn)的Stream流處理技術(shù),使得它讀寫數(shù)據(jù)更加方便直觀,同時,.Net框架負(fù)責(zé)提供更豐富的結(jié)構(gòu)來處理流,貫穿于整個.Net框架中的流具有更廣泛的兼容性,構(gòu)建在更一般化的流操作上的通用方法使我們不再需要困惑于文件的實(shí)際內(nèi)容(HTML、XML?或其他任何內(nèi)容),應(yīng)用程序都將使用一致的方法(Stream.Write、Stream.Read)?發(fā)送和接收數(shù)據(jù)。另外,流在數(shù)據(jù)從?Internet?下載的過程中提供對數(shù)據(jù)的即時訪問,可以在部分?jǐn)?shù)據(jù)到達(dá)時立即開始處理,而不需要等待應(yīng)用程序下載完整個數(shù)據(jù)集。.Net中通過NetworkStream類實(shí)現(xiàn)了這些處理技術(shù)。?
NetworkStream?類包含在.Net框架的System.Net.Sockets?命名空間里,該類專門提供用于網(wǎng)絡(luò)訪問的基礎(chǔ)數(shù)據(jù)流。NetworkStream?實(shí)現(xiàn)通過網(wǎng)絡(luò)套接字發(fā)送和接收數(shù)據(jù)的標(biāo)準(zhǔn).Net?框架流機(jī)制。NetworkStream?支持對網(wǎng)絡(luò)數(shù)據(jù)流的同步和異步訪問。NetworkStream?從?Stream?繼承,后者提供了一組豐富的用于方便網(wǎng)絡(luò)通訊的方法和屬性。?
同其它繼承自抽象基類Stream的所有流一樣,NetworkStream網(wǎng)絡(luò)流也可以被視為一個數(shù)據(jù)通道,架設(shè)在數(shù)據(jù)來源端(客戶Client)和接收端(服務(wù)Server)之間,而后的數(shù)據(jù)讀取及寫入均針對這個通道來進(jìn)行。?
.Net框架中,NetworkStream流支持兩方面的操作:?
1、?寫入流。寫入是從數(shù)據(jù)結(jié)構(gòu)到流的數(shù)據(jù)傳輸。?2、讀取流。讀取是從流到數(shù)據(jù)結(jié)構(gòu)(如字節(jié)數(shù)組)的數(shù)據(jù)傳輸。與普通流Stream不同的是,網(wǎng)絡(luò)流沒有當(dāng)前位置的統(tǒng)一概念,因此不支持查找和對數(shù)據(jù)流的隨機(jī)訪問。相應(yīng)屬性CanSeek?始終返回?false,而?Seek?和?Position?方法也將引發(fā)?NotSupportedException。???基于Socket上的應(yīng)用協(xié)議方面,你可以通過以下兩種方式獲取NetworkStream網(wǎng)絡(luò)數(shù)據(jù)流:?
1、使用NetworkStream構(gòu)造函數(shù):public?NetworkStream(Socket,?FileAccess,?bool);(有重載方法),它用指定的訪問權(quán)限和指定的?Socket?所屬權(quán)為指定的?Socket?創(chuàng)建?NetworkStream?類的新實(shí)例,使用前你需要創(chuàng)建Socket對象實(shí)例,并通過Socket.Connect方法建立與遠(yuǎn)程服務(wù)端的連接,而后才可以使用該方法得到網(wǎng)絡(luò)傳輸流。示例如下:?
Socket?s=new?Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);//創(chuàng)建客戶端Socket對象實(shí)例?
???try...{?
s.Connect("www.tuha.net",4088);//建立與遠(yuǎn)程主機(jī)的連接?
???}?
catch(Exception?e)...{?
MessageBox.show("連接錯誤:"?+e.Message);?
}?
try...{?
NetworkStream?stream=new?NetworkStream(s,FileAccess.ReadWrite,false);//取得網(wǎng)絡(luò)傳輸流?
???}?
2、通過TcpClient.GetStream方法:public?NetworkStream?etStream();它返回用于發(fā)送和接收數(shù)據(jù)的基礎(chǔ)網(wǎng)絡(luò)流NetworkStream。GetStream?通過將基礎(chǔ)?Socket?用作它的構(gòu)造函數(shù)參數(shù)來創(chuàng)建?NetworkStream?類的實(shí)例。使用前你需要先創(chuàng)TcpClient對象實(shí)例并建立與遠(yuǎn)程主機(jī)的連接,示例如下:?
TcpClient?tcpClient?=?new?TcpClient();//創(chuàng)建TcpClient對象實(shí)例?
???Try...{?
tcpClient.Connect("www.tuha.net",4088);//嘗試與遠(yuǎn)程主機(jī)相連?
???}?
catch(Exception?e)...{?
MessageBox.Show("連接錯誤:"+e.Message);?
}?
try...{?
NetworkStream?stream=tcpClient.GetStream();//獲取網(wǎng)絡(luò)傳輸流?
???}?
catch(Exception?e)?
...{?
MessageBox.Show("TcpClient錯誤:"+e.Message);?
}?
通過以上方法得到NetworkStream網(wǎng)絡(luò)流之后,你就可以使用標(biāo)準(zhǔn)流讀寫方法Write和Read來發(fā)送和接受數(shù)據(jù)了。?
以上是.Net下使用TcpClient類實(shí)現(xiàn)客戶端編程的技術(shù)資料,為了向客戶端提供這些服務(wù),我們還需要編制相應(yīng)的服務(wù)端程序,前一篇《Visual?C#.Net網(wǎng)絡(luò)程序開發(fā)-Socket篇》上曾經(jīng)提到,?Socket作為其他網(wǎng)絡(luò)協(xié)議的基礎(chǔ),既可以面向客戶端開發(fā),也可以面向服務(wù)端開發(fā),在傳輸層面上使用較多,而在應(yīng)用協(xié)議層面上,客戶端我們采用構(gòu)建于Socket類之上的TcpClient取代Socket;相應(yīng)地,構(gòu)建于Socket之上的TcpListener提供了更高理念級別的?TCP?服務(wù),使得我們能更方便地編寫服務(wù)端應(yīng)用程序。正是因?yàn)檫@樣的原因,像FTP?和?HTTP?這樣的應(yīng)用層協(xié)議都是在?TcpListener?類的基礎(chǔ)上建立的。?.Net中的TCPListener?用于監(jiān)視TCP?端口上的傳入請求,通過綁定本機(jī)IP地址和相應(yīng)端口(這兩者應(yīng)與客戶端的請求一致)創(chuàng)建TcpListener對象實(shí)例,并由Start方法啟動偵聽;當(dāng)TcpListener偵聽到用戶端的連接后,視客戶端的不同請求方式,通過AcceptTcpClient?方法接受傳入的連接請求并創(chuàng)建?TcpClient?以處理請求,或者通過AcceptSocket?方法接受傳入的連接請求并創(chuàng)建?Socket?以處理請求。最后,你需要使用?Stop?關(guān)閉用于偵聽傳入連接的?Socket,你必須也關(guān)閉從?AcceptSocket?或?AcceptTcpClient?返回的任何實(shí)例。這個過程詳細(xì)解說如下:?
首先,創(chuàng)建TcpListener對象實(shí)例,這通過TcpListener類的構(gòu)造方法來實(shí)現(xiàn):?
public?TcpListener(port);//指定本機(jī)端口?
??public?TcpListener(IPEndPoint)//指定本機(jī)終結(jié)點(diǎn)?
??public?TcpListener(IPAddress,port)//指定本機(jī)IP地址及端口?
以上方法中的參數(shù)在前面多次提到,這里不再細(xì)述,唯一需要提醒的是,這些參數(shù)均針對服務(wù)端主機(jī)。下面的示例演示創(chuàng)建?TcpListener?類的實(shí)例:?
IPHostEntry?ipInfo=Dns.Resolve("127.0.0.1");//主機(jī)信息?
???IPAddressList[]?ipList=ipInfo.IPAddressList;//IP數(shù)組?
???IPAddress?ip=ipList[0];//IP?
???try...{?
TcpListener?tcpListener?=?new?TcpListener(ipAddress,?4088);//創(chuàng)建TcpListener對象實(shí)例以偵聽用戶端連接?
???}?
catch?(?Exception?e)...{?
MessageBox.Show("TcpListener錯誤:"+e.Message);?
}?
隨后,你需要調(diào)用Start方法啟動偵聽:?public?void?Start();?其次,當(dāng)偵聽到有用戶端連接時,需要接受掛起的連接請求,這通過調(diào)用以下兩方法之一來完成連接:?public?Socket?AcceptSocket();?public?TcpClient?AcceptTcpClient();?
前一個方法返回代表客戶端的Socket對象,隨后可以通過Socket?類的?Send?和?Receive?方法與遠(yuǎn)程計(jì)算機(jī)通訊;后一個方法返回代表客戶端的TcpClient對象,隨后使用上面介紹的?TcpClient.GetStream?方法獲取?TcpClient?的基礎(chǔ)網(wǎng)絡(luò)流?NetworkStream,并使用流讀寫Read/Write方法與遠(yuǎn)程計(jì)算機(jī)通訊。?最后,請記住關(guān)閉偵聽器:public?void?Stop();?同時關(guān)閉其他連接實(shí)例:public?void?Close();?
下面的示例完整體現(xiàn)了上面的過程:?
bool?done?=?false;?
TcpListener?listener?=?new?TcpListener(13);//?創(chuàng)建TcpListener對象實(shí)例(13號端口提供時間服務(wù))?
???listener.Start();//啟動偵聽?
???while?(!done)?...{//進(jìn)入無限循環(huán)以偵聽用戶連接?
???TcpClient?client?=?listener.AcceptTcpClient();//偵聽到連接后創(chuàng)建客戶端連接TcpClient?
???NetworkStream?ns?=?client.GetStream();//得到網(wǎng)絡(luò)傳輸流?
???byte[]?byteTime?=?Encoding.ASCII.GetBytes(DateTime.Now.ToString());//預(yù)發(fā)送的內(nèi)容(此為服務(wù)端時間)轉(zhuǎn)換為字節(jié)數(shù)組以便寫入流?
???try?...{?
ns.Write(byteTime,?0,?byteTime.Length);//寫入流?
???ns.Close();//關(guān)閉流?
???client.Close();//關(guān)閉客戶端連接?
???}?
catch?(Exception?e)?...{?
MessageBox.Show("流錯誤:"+e.Message)?
}?
綜合運(yùn)用上面的知識,下面的實(shí)例實(shí)現(xiàn)了簡單的網(wǎng)絡(luò)通訊-雙機(jī)互連,針對客戶端和服務(wù)端分別編制了應(yīng)用程序。客戶端創(chuàng)建到服務(wù)端的連接,向遠(yuǎn)程主機(jī)發(fā)送連接請求連接信號,并發(fā)送交談內(nèi)容;遠(yuǎn)程主機(jī)端接收來自客戶的連接,向客戶端發(fā)回確認(rèn)連接的信號,同時接收并顯示客戶端的交談內(nèi)容。在這個基礎(chǔ)上,發(fā)揮你的創(chuàng)造力,你完全可以開發(fā)出一個基于程序語言(C#)級的聊天室!?
客戶端主要源代碼:?
public?void?SendMeg()//發(fā)送信息?
???...{?
try?
...{?
int?port=Int32.Parse(textBox3.Text.ToString());//遠(yuǎn)程主機(jī)端口?
???try?
...{?
tcpClient=new?TcpClient(textBox1.Text,port);//創(chuàng)建TcpClient對象實(shí)例?}?
???catch(Exception?le)?
...{?
MessageBox.Show("TcpClient?Error:"+le.Message);?
}?
string?strDateLine=DateTime.Now.ToShortDateString()+"?"+DateTime.Now.ToLongTimeString();//得到發(fā)送時客戶端時間?
???netStream=tcpClient.GetStream();//得到網(wǎng)絡(luò)流?
???sw=new?StreamWriter(netStream);//創(chuàng)建TextWriter,向流中寫字符?
???string?words=textBox4.Text;//待發(fā)送的話?
???string?content=strDateLine+words;//待發(fā)送內(nèi)容?
???sw.Write(content);//寫入流?
???sw.Close();//關(guān)閉流寫入器?
???netStream.Close();//關(guān)閉網(wǎng)絡(luò)流?
???tcpClient.Close();//關(guān)閉客戶端連接?
??}?
catch(Exception?ex)?
...{?
MessageBox.Show("Sending?Message?Failed!"+ex.Message);?
}?
textBox4.Text="";//清空?
???}?
服務(wù)器端主要源代碼:?
public?void?StartListen()//偵聽特定端口的用戶請求?
???...{?
//ReceiveMeg();?
??isLinked=false;?//連接標(biāo)志?
???try?
...{?
int?port=Int32.Parse(textBox1.Text.ToString());//本地待偵聽端口?
???serverListener=new?TcpListener(port);//創(chuàng)建TcpListener對象實(shí)例?
???serverListener.Start();?//啟動偵聽?
???}?
catch(Exception?ex)?
...{?
MessageBox.Show("Can‘t?Start?Server"+ex.Message);?
return;?
}?
isLinked=true;?
while(true)//進(jìn)入無限循環(huán)等待用戶端連接?
???...{?
try?
...{?
tcpClient=serverListener.AcceptTcpClient();//創(chuàng)建客戶端連接對象?
???netStream=tcpClient.GetStream();//得到網(wǎng)絡(luò)流?
???sr=new?StreamReader(netStream);//流讀寫器?
???}?
catch(Exception?re)?
...{?
MessageBox.Show(re.Message);?
}?
string?buffer="";?
string?received="";?
received+=sr.ReadLine();//讀流中一行?
???while(received.Length!=0)?
...{?
buffer+=received;?
buffer+=" ";?
//received="";?
???received=sr.ReadLine();?
}?
listBox1.Items.Add(buffer);//顯示?
//關(guān)閉?
??sr.Close();?
netStream.Close();?
tcpClient.Close();?
}?
}??
總結(jié)
以上是生活随笔為你收集整理的sockets C#的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: oracle的连接字符串
- 下一篇: 发送邮件 的类 C# .net