Visual C#.Net網(wǎng)絡(luò)程序開發(fā)-Tcp篇
發(fā)布時間:2008-08-07 閱讀數(shù): 次 來源:網(wǎng)樂原科技
前一篇《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)建,不需要處理連接的細節(jié),這使得我們在編寫套接字級別的協(xié)議時,可以更多地嘗試使用 TCPClient 、 UDPClient和TcpListener,而不是直接向 Socket 中寫。它們之間的這種層次關(guān)系示意如下:
可見, TcpClient 類基于 Socket 類構(gòu)建,這是它能夠以更高的抽象程度提供 TCP 服務(wù)的基礎(chǔ)。正因為這樣,許多應(yīng)用層上的通訊協(xié)議,比如FTP(File Transfers Protocol)文件傳輸協(xié)議、HTTP(Hypertext Transfers Protocol)超文本傳輸協(xié)議等都直接創(chuàng)建在TcpClient等類之上。
TCPClient 類使用 TCP 從 Internet 資源請求數(shù)據(jù)。TCP 協(xié)議建立與遠程終結(jié)點的連接,然后使用此連接發(fā)送和接收數(shù)據(jù)包。TCP 負責(zé)確保將數(shù)據(jù)包發(fā)送到終結(jié)點并在數(shù)據(jù)包到達時以正確的順序?qū)ζ溥M行組合。
從名字上就可以看出,TcpClient類專為客戶端設(shè)計,它為 TCP 網(wǎng)絡(luò)服務(wù)提供客戶端連接。TcpClient 提供了通過網(wǎng)絡(luò)連接、發(fā)送和接收數(shù)據(jù)的簡單方法。
若要建立 TCP 連接,必須知道承載所需服務(wù)的網(wǎng)絡(luò)設(shè)備的地址(IPAddress)以及該服務(wù)用于通訊的 TCP 端口 (Port)。Internet 分配號碼機構(gòu) (Internet Assigned Numbers Authority, IANA) 定義公共服務(wù)的端口號(你可以訪問 http://www.iana.org/assignments/port-numbers獲得這方面更詳細的資料)。IANA 列表中所沒有的服務(wù)可使用 1,024 到 65,535 這一范圍中的端口號。要創(chuàng)建這種連接,你可以選用TcpClient類的三種構(gòu)造函數(shù)之一:
1、public TcpClient()當使用這種不帶任何參數(shù)的構(gòu)造函數(shù)時,將使用本機默認的ip地址并將使用默認的通信端口號0。這樣情況下,如果本機不止一個ip地址,將無法選擇使用。以下語句示例了如何使用默認構(gòu)造函數(shù)來創(chuàng)建新的 TcpClient:
TcpClient tcpClientC = new TcpClient();
2、public TcpClient(IPEndPoint)使用本機IPEndPoint創(chuàng)建TcpClient的實例對象。上一篇介紹過了,IPEndPoint將網(wǎng)絡(luò)端點表示為IP地址和端口號,在這里它用于指定在建立遠程主機連接時所使用的本地網(wǎng)絡(luò)接口(IP 地址)和端口號,這個構(gòu)造方法為使用本機IPAddress和Port提供了選擇余地。下面的語句示例了如何使用本地終結(jié)點創(chuàng)建 TcpClient 類的實例:
IPHostEntry ipInfo=Dns.GetHostByName("www.tuha.net");//主機信息
IPAddressList[] ipList=ipInfo.AddressList;//IP地址數(shù)組
IPAddress ip=ipList[0];//多IP地址時一般用第一個
IPEndPoint ipEP=new IPEndPoint(ip,4088);//得到網(wǎng)絡(luò)終結(jié)點
try{
TcpClient tcpClientA = new TcpClient(ipLocalEndPoint);
}
catch (Exception e ) {
Console.WriteLine(e.ToString());
}
到這里,你可能會感到困惑,客戶端要和服務(wù)端創(chuàng)建連接,所指定的IP地址及通信端口號應(yīng)該是遠程服務(wù)器的呀!事實上的確如此,使用以上兩種構(gòu)造函數(shù),你所實現(xiàn)的只是TcpClient實例對象與IP地址和Port端口的綁定,要完成連接,你還需要顯式指定與遠程主機的連接,這可以通過TcpClient類的Connect方法來實現(xiàn), Connet方法使用指定的主機名和端口號將客戶端連接到 遠程主機:
1)、public void Connect(IPEndPoint); 使用指定的遠程網(wǎng)絡(luò)終結(jié)點將客戶端連接到遠程 TCP 主機。
public void Connect(IPAddress, int); 使用指定的 IP 地址和端口號將客戶端連接到 TCP 主機。
public void Connect(string, int); 將客戶端連接到指定主機上的指定端口。
需要指出的是,Connect方法的所有重載形式中的參數(shù)IPEndPoint網(wǎng)絡(luò)終
結(jié)點、IPAddress以及表現(xiàn)為string的Dns主機名和int指出的Port端口均指的是遠程服務(wù)器。
以下示例語句使用主機默認IP和Port端口號0與遠程主機建立連接:
TcpClient tcpClient = new TcpClient();//創(chuàng)建TcpClient對象實例
try{
tcpClient.Connect("www.contoso.com",11002);//建立連接
}
catch (Exception e ){
Console.WriteLine(e.ToString());
}
3、public TcpClient(string, int);初始化 TcpClient 類的新實例并連接到指定主機上的指定端口。與前兩個構(gòu)造函數(shù)不一樣,這個構(gòu)造函數(shù)將自動建立連接,你不再需要額外調(diào)用Connect方法,其中string類型的參數(shù)表示遠程主機的Dns名,如:www.tuha.net。
以下示例語句調(diào)用這一方法實現(xiàn)與指定主機名和端口號的主機相連:
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使用標準的Stream流處理技術(shù),使得它讀寫數(shù)據(jù)更加方便直觀,同時,.Net框架負責(zé)提供更豐富的結(jié)構(gòu)來處理流,貫穿于整個.Net框架中的流具有更廣泛的兼容性,構(gòu)建在更一般化的流操作上的通用方法使我們不再需要困惑于文件的實際內(nèi)容(HTML、XML 或其他任何內(nèi)容),應(yīng)用程序都將使用一致的方法(Stream.Write、Stream.Read) 發(fā)送和接收數(shù)據(jù)。另外,流在數(shù)據(jù)從 Internet 下載的過程中提供對數(shù)據(jù)的即時訪問,可以在部分數(shù)據(jù)到達時立即開始處理,而不需要等待應(yīng)用程序下載完整個數(shù)據(jù)集。.Net中通過NetworkStream類實現(xiàn)了這些處理技術(shù)。
NetworkStream 類包含在.Net框架的System.Net.Sockets 命名空間里,該類專門提供用于網(wǎng)絡(luò)訪問的基礎(chǔ)數(shù)據(jù)流。NetworkStream 實現(xiàn)通過網(wǎng)絡(luò)套接字發(fā)送和接收數(shù)據(jù)的標準.Net 框架流機制。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ù)讀取及寫入均針對這個通道來進行。
.Net框架中,NetworkStream流支持兩方面的操作:
1、 寫入流。寫入是從數(shù)據(jù)結(jié)構(gòu)到流的數(shù)據(jù)傳輸。
2、讀取流。讀取是從流到數(shù)據(jù)結(jié)構(gòu)(如字節(jié)數(shù)組)的數(shù)據(jù)傳輸。
與普通流Stream不同的是,網(wǎng)絡(luò)流沒有當前位置的統(tǒng)一概念,因此不支持查找和對數(shù)據(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 類的新實例,使用前你需要創(chuàng)建Socket對象實例,并通過Socket.Connect方法建立與遠程服務(wù)端的連接,而后才可以使用該方法得到網(wǎng)絡(luò)傳輸流。示例如下:
Socket s=new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);//創(chuàng)建客戶端Socket對象實例
try{
s.Connect("www.tuha.net",4088);//建立與遠程主機的連接
}
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 類的實例。使用前你需要先創(chuàng)TcpClient對象實例并建立與遠程主機的連接,示例如下:
TcpClient tcpClient = new TcpClient();//創(chuàng)建TcpClient對象實例
Try{
tcpClient.Connect("www.tuha.net",4088);//嘗試與遠程主機相連
}
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ò)流之后,你就可以使用標準流讀寫方法Write和Read來發(fā)送和接受數(shù)據(jù)了。
以上是.Net下使用TcpClient類實現(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)用程序。正是因為這樣的原因,像FTP 和 HTTP 這樣的應(yīng)用層協(xié)議都是在 TcpListener 類的基礎(chǔ)上建立的。
.Net中的TCPListener 用于監(jiān)視TCP 端口上的傳入請求,通過綁定本機IP地址和相應(yīng)端口(這兩者應(yīng)與客戶端的請求一致)創(chuàng)建TcpListener對象實例,并由Start方法啟動偵聽;當TcpListener偵聽到用戶端的連接后,視客戶端的不同請求方式,通過AcceptTcpClient 方法接受傳入的連接請求并創(chuàng)建 TcpClient 以處理請求,或者通過AcceptSocket 方法接受傳入的連接請求并創(chuàng)建 Socket 以處理請求。最后,你需要使用 Stop 關(guān)閉用于偵聽傳入連接的 Socket,你必須也關(guān)閉從 AcceptSocket 或 AcceptTcpClient 返回的任何實例。這個過程詳細解說如下:
首先,創(chuàng)建TcpListener對象實例,這通過TcpListener類的構(gòu)造方法來實現(xiàn):
public TcpListener(port);//指定本機端口
public TcpListener(IPEndPoint)//指定本機終結(jié)點
public TcpListener(IPAddress,port)//指定本機IP地址及端口
以上方法中的參數(shù)在前面多次提到,這里不再細述,唯一需要提醒的是,這些參數(shù)均針對服務(wù)端主機。下面的示例演示創(chuàng)建 TcpListener 類的實例:
IPHostEntry ipInfo=Dns.Resolve("127.0.0.1");//主機信息
IPAddressList[] ipList=ipInfo.IPAddressList;//IP數(shù)組
IPAddress ip=ipList[0];//IP
try{
TcpListener tcpListener = new TcpListener(ipAddress, 4088);//創(chuàng)建TcpListener對象實例以偵聽用戶端連接
}
catch ( Exception e){
MessageBox.Show("TcpListener錯誤:"+e.Message);
}
隨后,你需要調(diào)用Start方法啟動偵聽:
public void Start();
其次,當偵聽到有用戶端連接時,需要接受掛起的連接請求,這通過調(diào)用以下兩方法之一來完成連接:
public Socket AcceptSocket();
public TcpClient AcceptTcpClient();
前一個方法返回代表客戶端的Socket對象,隨后可以通過Socket 類的 Send 和 Receive 方法與遠程計算機通訊;后一個方法返回代表客戶端的TcpClient對象,隨后使用上面介紹的 TcpClient.GetStream 方法獲取 TcpClient 的基礎(chǔ)網(wǎng)絡(luò)流 NetworkStream,并使用流讀寫Read/Write方法與遠程計算機通訊。
最后,請記住關(guān)閉偵聽器:public void Stop();
同時關(guān)閉其他連接實例:public void Close();
下面的示例完整體現(xiàn)了上面的過程:
bool done = false;
TcpListener listener = new TcpListener(13);// 創(chuàng)建TcpListener對象實例(13號端口提供時間服務(wù))
listener.Start();//啟動偵聽
while (!done) {//進入無限循環(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)
}
綜合運用上面的知識,下面的實例實現(xiàn)了簡單的網(wǎng)絡(luò)通訊-雙機互連,針對客戶端和服務(wù)端分別編制了應(yīng)用程序??蛻舳藙?chuàng)建到服務(wù)端的連接,向遠程主機發(fā)送連接請求連接信號,并發(fā)送交談內(nèi)容;遠程主機端接收來自客戶的連接,向客戶端發(fā)回確認連接的信號,同時接收并顯示客戶端的交談內(nèi)容。在這個基礎(chǔ)上,發(fā)揮你的創(chuàng)造力,你完全可以開發(fā)出一個基于程序語言(C#)級的聊天室!
客戶端主要源代碼:
public void SendMeg()//發(fā)送信息
{
try
{
int port=Int32.Parse(textBox3.Text.ToString());//遠程主機端口
try
{
tcpClient=new TcpClient(textBox1.Text,port);//創(chuàng)建TcpClient對象實例 }
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; //連接標志
try
{
int port=Int32.Parse(textBox1.Text.ToString());//本地待偵聽端口
serverListener=new TcpListener(port);//創(chuàng)建TcpListener對象實例
serverListener.Start(); //啟動偵聽
}
catch(Exception ex)
{
MessageBox.Show("Can‘t Start Server"+ex.Message);
return;
}
isLinked=true;
while(true)//進入無限循環(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+="\r\n";
//received="";
received=sr.ReadLine();
}
listBox1.Items.Add(buffer);//顯示
//關(guān)閉
sr.Close();
netStream.Close();
tcpClient.Close();
}
}