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

相信很多人看到圖1應該不會陌生,這是一個利用TCP進行通信的經典模型圖。我想大家都應該把這張圖記在心中。在此我就不講述上圖中每個API的意思了,百度一下,你就知道。我想說的是,難道你不覺得這么編程很累嗎? 我們需要去調用每個API函數,然后每個判斷返回值是多少,如果你忘記了哪個API的參數形式還得去查MSDN,這種時間花費是巨大的,尤其當你做應用層的快速開發時。
圖2是利用UDP通信時的編程基本模型,這個模型較為簡單,但是應用極為廣泛,相比TCP而言,我本人覺得利用UDP通信是一門更為高深的技術,因為它是無連接的,換言之,它的效率與靈活度就更高些。

圖2. UDP編程基本模型
在此我補充一點,關於何時利用TCP通信、何時利用UDP通信的問題。他們的特性其實已經決定了他們的適用范圍。在進行大數據量、持續連接時,我們使用TCP,例如FTP協議;而在進行小規模數據、突發性高的通信時,我們使用UDP,例如聊天程序。但是,這並不是絕對的事情。例如流媒體通信,它是大數量、持續的通信,但是使用的是UDP協議,為什么呢?——因為我們不關心丟失的幀,人的肉眼是無法識別出少量的幀丟失的。那么使用UDP通信就可以大幅度提高效率,降低網絡負載。
C#之TCP編程
- 如何創建一個套接字?
我們先來看看利用Winsock2是如何建立一個套接字的:
首先,我們要加載套接字庫,然后再建立套接字。大致代碼如下:
- WORD wVersion=MAKEWORD(2,2);
- WSADATA wsaData;
- if(WSAStartup(wVersion,&wsaData))
- {
- WSACleanup();
- returnFALSE;
- }
- m_sock=WSASocket(AF_INET,SOCK_DGRAM,IPPROTO_UDP,NULL,0,0);
- if(m_sock==INVALID_SOCKET)
- {
- MessageBox("創建套接字失敗!");
- return FALSE;
- }
難道你不覺得利用Winsock2創建一個套接字很費勁嗎?如果你在Linux環境中變成倒是可以省掉加載套接字的部分,但是卻只能反復的調用API,這樣也是很費時的事情。那我們再看看看利用C#是如何幫你簡化工作的。這里我會介紹TCPClient類。
以上是從MSDN上截取的一段話,可見我們利用TCPClient還處理與TCP通信相關的操作。TCPClient有四個構造函數,每個構造函數的用法是有不同的。這里我補充一個知識,那就是端地址在C#中描述。我們知道,我們用一個IP地址和一個端口號就可以表示一個端地址。在C#中我們利用IPEndPoint類來表示一個端地址,本人經常利用如下的構造函數來創建一個IPEndPoint類。
- 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類。例如:
- TcpClient tcp_Client = new TcpClient(localEP);
- 如何監聽套接字?
到現在為此我們還沒討論如何監聽一個套接字。在傳統的socket編程中,我們創建一個套接字,然后把它綁定到一個端地址,而后調用Listen()來監聽套接字。而在C#中,我們利用TCPListener來幫我們完成這些工作。讓我們先來看看如何在C#監聽套接字。
- IPEndPointlocalEP = new IPEndPoint(IPAddress.Parse("127.0.0.1"),6666);
- TcpListenerListener = new TcpListener(localEP);
- Listener.Start(10);
我們首先創建需要綁定的端地址,而后創建監聽類,並利用其構造函數將其綁定到端地址,然后調用Start(int number)方法來真正實施監聽。這與我們傳統的socket編程不同。以前我們都是先創建一個socket,然后再創建一個sockaddr_in的結構體。我想你應該開始感受到了C#的優勢了,它幫我們省去了很多低級、繁瑣的工作,讓我們能夠真正專注於我們的軟件架構和設計思想。
- 如何接受客戶端連接?
接聽套接字后面自然就是接受TCP連接了。我們利用下面一句話來完成此工作:
- TcpClient remoteClient =Listener.AcceptTcpClient();
類似於accept函數來返回一個socket,利用TCPListener類的AcceptTcpClient方法我們可以得到一個與客戶端建立了連接的TCPClient類,而由TCPClient類來處理以后與客戶端的通信工作。我想你應該開始理解為什么會存在TCPClient和TCPListener兩個類了。這兩個類的存在有着更加明細的區分,讓監聽和后續的通信真正分開,讓程序員也更加容易理解和使用了。
這里我還得補充一點:監聽是一個非阻塞的操作(Listener.Start()),而接受連接是一個阻塞操作(Listener.AcceptTcpClient)。
說了這么多,還不如來個實例來的明確。接下來,我會通過一個簡單的控制台聊天程序來如何使用這些。先貼代碼吧!
服務器端:
- <span style="font-family:'Microsoft YaHei';font-size:18px;">using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Net;
- using System.Net.Sockets;
- namespace Demo
- {
- class Program
- {
- static void Main(string[]args)
- {
- byte[]SendBuf = Encoding.UTF8.GetBytes("Hello,Client!"); //發給客戶端的消息;
- IPEndPointlocalEP = new IPEndPoint(IPAddress.Parse("127.0.0.1"),6666); //本地端地址
- TcpListenerListener = new TcpListener(localEP); //建立監聽類,並綁定到指定的端地址
- Listener.Start(10); //開始監聽
- Console.WriteLine("Server is listening...");
- TcpClientremoteClient = Listener.AcceptTcpClient(); //等待連接(阻塞)
- Console.WriteLine("Client:{0} connected!",remoteClient.Client.RemoteEndPoint.ToString()) ; //打印客戶端連接信息;
- remoteClient.Client.Send(SendBuf); //發送歡迎信息;
- remoteClient.Close(); //關閉連接;
- }
- }
- }</span>
客戶端:
- <span style="font-family:'Microsoft YaHei';font-size:18px;">using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Net;
- using System.Net.Sockets;
- namespace Demo_Client
- {
- class Program
- {
- static void Main(string[] args)
- {
- byte[] RecvBuf=new byte[1024]; //申請接收緩存;
- int RecvBytes = 0; //接收字節數;
- string recvmsg=null; //接收消息;
- IPEndPoint remoteEP = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 6666); //遠程服務器端地址;
- TcpClient remoteServer = new TcpClient(); //創建TCPClient類來與服務器通信;
- remoteServer.Connect(remoteEP); //調用connect方法連接遠端服務器;
- Console.WriteLine("I'm using {0}.", remoteServer.Client.LocalEndPoint); //打印自己使用的端地址;
- RecvBytes=remoteServer.Client.Receive(RecvBuf); //接受服務器發送過來的消息;
- recvmsg=Encoding.UTF8.GetString(RecvBuf,0,RecvBytes); //將接受到的字節碼轉化為string類型;
- Console.WriteLine("Server says:{0}.", recvmsg); //打印歡迎信息;
- }
- }
- }</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。如需轉載本文,請注明出處!謝謝)