C#網絡編程初步之TCP


http://blog.csdn.net/mymonkey110/article/details/6841347

 

  閱讀背景:本文針對有C#的初學者而寫的,主要講解如何利用C#進行網絡編程。如果你已經有一些網絡編程的經驗(只需要懂得網絡編程的基本常識即可),並且理解C#的基本語法,那么這篇文章可以很快地帶你進入C#網絡編程的世界。如果你的基礎不好,也不要緊,我相信這篇文章也會有你需要的內容。

 

網絡編程基礎復習:

 

 

      圖1. TCP編程基本模型

 

 

        相信很多人看到圖1應該不會陌生,這是一個利用TCP進行通信的經典模型圖。我想大家都應該把這張圖記在心中。在此我就不講述上圖中每個API的意思了,百度一下,你就知道。我想說的是,難道你不覺得這么編程很累嗎? 我們需要去調用每個API函數,然后每個判斷返回值是多少,如果你忘記了哪個API的參數形式還得去查MSDN,這種時間花費是巨大的,尤其當你做應用層的快速開發時。

 

 

        圖2是利用UDP通信時的編程基本模型,這個模型較為簡單,但是應用極為廣泛,相比TCP而言,我本人覺得利用UDP通信是一門更為高深的技術,因為它是無連接的,換言之,它的效率與靈活度就更高些。

 

圖2. UDP編程基本模型

 

        在此我補充一點,關於何時利用TCP通信、何時利用UDP通信的問題。他們的特性其實已經決定了他們的適用范圍。在進行大數據量、持續連接時,我們使用TCP,例如FTP協議;而在進行小規模數據、突發性高的通信時,我們使用UDP,例如聊天程序。但是,這並不是絕對的事情。例如流媒體通信,它是大數量、持續的通信,但是使用的是UDP協議,為什么呢?——因為我們不關心丟失的幀,人的肉眼是無法識別出少量的幀丟失的。那么使用UDP通信就可以大幅度提高效率,降低網絡負載。

 

 

C#之TCP編程

 

  • 如何創建一個套接字?

 

我們先來看看利用Winsock2是如何建立一個套接字的:

首先,我們要加載套接字庫,然后再建立套接字。大致代碼如下:

 

  1. WORD wVersion=MAKEWORD(2,2);  
  2. WSADATA wsaData;  
  3. if(WSAStartup(wVersion,&wsaData))  
  4. {  
  5. WSACleanup();  
  6. returnFALSE;  
  7. }  
  8.    
  9. m_sock=WSASocket(AF_INET,SOCK_DGRAM,IPPROTO_UDP,NULL,0,0);  
  10. if(m_sock==INVALID_SOCKET)  
  11. {  
  12.         MessageBox("創建套接字失敗!");  
  13.         return FALSE;  
  14. }  

 

        難道你不覺得利用Winsock2創建一個套接字很費勁嗎?如果你在Linux環境中變成倒是可以省掉加載套接字的部分,但是卻只能反復的調用API,這樣也是很費時的事情。那我們再看看看利用C#是如何幫你簡化工作的。這里我會介紹TCPClient類。

        以上是從MSDN上截取的一段話,可見我們利用TCPClient還處理與TCP通信相關的操作。TCPClient有四個構造函數,每個構造函數的用法是有不同的。這里我補充一個知識,那就是端地址在C#中描述。我們知道,我們用一個IP地址和一個端口號就可以表示一個端地址。在C#中我們利用IPEndPoint類來表示一個端地址,本人經常利用如下的構造函數來創建一個IPEndPoint類。

 

[csharp]  view plain copy print ?
  1. IPEndPoint localEP = new IPEndPoint(IPAddress.Parse("127.0.0.1"),6666);  

 

 

這樣來表示一個端地址是不是比創建一個struct sockaddr_in的結構體來的快呢?

 

 

 

  • 如何綁定一個端地址?

 

       我們已經創建了一個端地址,也構造了套接字(TCPClient類),那么如何將二者綁定起來呢?也許你已經發現了,在建立TCPClient的時候我們其實就可以綁定端地址了。如果你使用的TCPClient tcp_Client=new TCPClient()的構造函數來創建的TCPClient,那么系統會認為你沒有人為的制定端地址,而會自動幫你制定端地址,在創建客戶端的TCPClient時我們常常這樣做,因為我們不關心客戶端的端地址。如果是服務器監聽呢?在服務器監聽時我們會使用例外一個類,叫做TCPListener,接下來我會講到。我們可以利用TCPClient(IPEndPoint)來構造一個綁定到固定端地址的TCPClient類。例如:

 

[csharp]  view plain copy print ?
  1. TcpClient tcp_Client = new TcpClient(localEP);  

 

 

  • 如何監聽套接字?

 

       到現在為此我們還沒討論如何監聽一個套接字。在傳統的socket編程中,我們創建一個套接字,然后把它綁定到一個端地址,而后調用Listen()來監聽套接字。而在C#中,我們利用TCPListener來幫我們完成這些工作。讓我們先來看看如何在C#監聽套接字。

[csharp]  view plain copy print ?
  1. IPEndPointlocalEP = new IPEndPoint(IPAddress.Parse("127.0.0.1"),6666);  
  2. TcpListenerListener = new TcpListener(localEP);  
  3. Listener.Start(10);  

 

        我們首先創建需要綁定的端地址,而后創建監聽類,並利用其構造函數將其綁定到端地址,然后調用Start(int number)方法來真正實施監聽。這與我們傳統的socket編程不同。以前我們都是先創建一個socket,然后再創建一個sockaddr_in的結構體。我想你應該開始感受到了C#的優勢了,它幫我們省去了很多低級、繁瑣的工作,讓我們能夠真正專注於我們的軟件架構和設計思想。

 

  • 如何接受客戶端連接?

 

接聽套接字后面自然就是接受TCP連接了。我們利用下面一句話來完成此工作:

 

[csharp]  view plain copy print ?
  1. TcpClient remoteClient =Listener.AcceptTcpClient();  

 

        類似於accept函數來返回一個socket,利用TCPListener類的AcceptTcpClient方法我們可以得到一個與客戶端建立了連接的TCPClient類,而由TCPClient類來處理以后與客戶端的通信工作。我想你應該開始理解為什么會存在TCPClient和TCPListener兩個類了。這兩個類的存在有着更加明細的區分,讓監聽和后續的通信真正分開,讓程序員也更加容易理解和使用了。

 

這里我還得補充一點:監聽是一個非阻塞的操作(Listener.Start()),而接受連接是一個阻塞操作(Listener.AcceptTcpClient)。

 

 

說了這么多,還不如來個實例來的明確。接下來,我會通過一個簡單的控制台聊天程序來如何使用這些。先貼代碼吧!

服務器端:

 

[csharp]  view plain copy print ?
  1. <span style="font-family:'Microsoft YaHei';font-size:18px;">using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Text;  
  5. using System.Net;  
  6. using System.Net.Sockets;  
  7.    
  8. namespace Demo  
  9. {  
  10.     class Program  
  11.     {  
  12.         static void Main(string[]args)  
  13.         {  
  14.             byte[]SendBuf = Encoding.UTF8.GetBytes("Hello,Client!");    //發給客戶端的消息;  
  15.             IPEndPointlocalEP = new IPEndPoint(IPAddress.Parse("127.0.0.1"),6666);        //本地端地址  
  16.             TcpListenerListener = new TcpListener(localEP);            //建立監聽類,並綁定到指定的端地址  
  17.             Listener.Start(10);           //開始監聽                                                              
  18.             Console.WriteLine("Server is listening...");                             
  19.             TcpClientremoteClient = Listener.AcceptTcpClient();  //等待連接(阻塞)  
  20.             Console.WriteLine("Client:{0} connected!",remoteClient.Client.RemoteEndPoint.ToString()) ;     //打印客戶端連接信息;  
  21.             remoteClient.Client.Send(SendBuf);     //發送歡迎信息;  
  22.             remoteClient.Close();                  //關閉連接;  
  23.         }  
  24.     }  
  25. }</span>  


 

 

客戶端:

 

[csharp]  view plain copy print ?
  1.   

 

[csharp]  view plain copy print ?
  1. <span style="font-family:'Microsoft YaHei';font-size:18px;">using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Text;  
  5. using System.Net;  
  6. using System.Net.Sockets;  
  7.   
  8. namespace Demo_Client  
  9. {  
  10.     class Program  
  11.     {  
  12.         static void Main(string[] args)  
  13.         {  
  14.             byte[] RecvBuf=new byte[1024];                    //申請接收緩存;  
  15.             int RecvBytes = 0;                                            //接收字節數;  
  16.             string recvmsg=null;                                      //接收消息;  
  17.   
  18.             IPEndPoint remoteEP = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 6666);      //遠程服務器端地址;  
  19.             TcpClient remoteServer = new TcpClient();                   //創建TCPClient類來與服務器通信;  
  20.             remoteServer.Connect(remoteEP);                                  //調用connect方法連接遠端服務器;  
  21.             Console.WriteLine("I'm using {0}.", remoteServer.Client.LocalEndPoint);          //打印自己使用的端地址;  
  22.             RecvBytes=remoteServer.Client.Receive(RecvBuf);                           //接受服務器發送過來的消息;  
  23.             recvmsg=Encoding.UTF8.GetString(RecvBuf,0,RecvBytes);          //將接受到的字節碼轉化為string類型;  
  24.             Console.WriteLine("Server says:{0}.", recvmsg);              //打印歡迎信息;  
  25.         }  
  26.     }  
  27. }</span>  


 

 

      在C#網絡編程中,我們要用到兩個名空間,分別是System.Net和System.Net.Socket。可能有人會有這樣的疑惑,干嘛要申請一個Byte數組。我們知道,在傳統socket編程中,我們都是用char*來發送或者接受消息的,其實char*和Byte[]是同源的。他們都是一個Byte,而使用Byte[]能更易於人們理解和轉化為其他類型。我們知道網絡間傳輸的字節流,而Byte[]剛好符合了這個思想。如果對以上類的用法不理解或者不熟悉的話,建議查看MSDN,上面講解的很詳細。

 

現在看看運行效果:

 

圖3 運行效果(左為服務器,右為客戶端)

 

       好啦,到這里我們C#網絡編程初步之TCP基本上算告一段落了,我只講解了最為基礎的部分,僅做拋磚引玉的作用。每個類的使用千變萬化,希望你能找到最適合自己使用方法。現在你可以對比以前類似程序的代碼了,看看我前面有沒有說錯。而且,越到后來你會越來越體會到C#人性化的一面。

 

      后期的博文中,我會更新C#網絡編程初步之UDP.本人更喜歡利用UDP來進行通信,至於為什么我已經說過了。以后,我會逐步寫一些網絡編程的高級內容,例如異步通信、多線程編程,並關注程序員經常遇到的一些棘手問題,比如TCP邊界的確定等等。有機會,我也會同大家討論網絡編程中常用的軟件設計思想與架構。

(本文圖1、圖2來自互聯網,有部分信息來自MSDN。如需轉載本文,請注明出處!謝謝)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM