標籤:style blog http io ar color os 使用 sp
原文: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是如何建立一個通訊端的:
首先,我們要載入通訊端庫,然後再建立通訊端。大致代碼如下:
- 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)。
說了這麼多,還不如來個執行個體來的明確。接下來,我會通過一個簡單的控制台聊天程式來如何使用這些。先貼代碼吧!
伺服器端:
- 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(); //關閉串連;
- }
- }
- }
用戶端:
- 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); //列印歡迎資訊;
- }
- }
- }
在C#網路編程中,我們要用到兩個名空間,分別是System.Net和System.Net.Socket。可能有人會有這樣的疑惑,幹嘛要申請一個Byte數組。我們知道,在傳統socket編程中,我們都是用char*來發送或者接受訊息的,其實char*和Byte[]是同源的。他們都是一個Byte,而使用Byte[]能更易於人們理解和轉化為其他類型。我們知道網路間傳輸的位元組流,而Byte[]剛好符合了這個思想。如果對以上類的用法不理解或者不熟悉的話,建議查看MSDN,上面講解的很詳細。
現在看看運行效果:
圖3 運行效果(左為伺服器,右為用戶端)
好啦,到這裡我們C#網路編程初步之TCP基本上算告一段落了,我只講解了最為基礎的部分,僅做拋磚引玉的作用。每個類的使用千變萬化,希望你能找到最適合自己使用方法。現在你可以對比以前類似程式的代碼了,看看我前面有沒有說錯。而且,越到後來你會越來越體會到C#人性化的一面。
後期的博文中,我會更新C#網路編程初步之UDP.本人更喜歡利用UDP來進行通訊,至於為什麼我已經說過了。以後,我會逐步寫一些網路編程的進階內容,例如非同步通訊、多線程編程,並關注程式員經常遇到的一些棘手問題,比如TCP邊界的確定等等。有機會,我也會同大家討論網路編程中常用的軟體設計思想與架構。
C#網路編程初步之TCP