在前面的文章中,我們已經介紹了使用.NET平臺開發網絡應用程序諸如IP地址、網絡接口之類的背景知識,本文將介紹.NET網絡應用程序的主角--Socket。如果把Socket比喻爲一位“美女”,那麼有關她的“愛情故事”實在太多,而本系列後繼的文章,就圍繞着這位“美女”所展開。
1 Socket美女的“家庭背景”
Socket,中文譯爲“套接字”,最早在UNIX中引入並得到廣泛應用,後來微軟在設計Windows時引入了UNIX中的這個概念和相應的設計理念,並針對Windows的特性略作調整,形成了Windows平臺上的Socket,簡稱爲“WinSock”,併爲開發者提供了一整套的API,稱爲“Windows WinSock Win32 API ”。
WinSock經歷了兩個版本,Windows Sockets 2是目前用得最多的版本(參看 http://en.wikipedia.org/wiki/Winsock ),微軟似乎從來沒有宣佈要開發WinSock 3,也許“永遠也不會有”了。
圖 1所示爲.NET平臺下網絡應用程序的層次架構:
圖 1
WinSock在底層使用一個運行於操作系統核心的系統驅動(Windows Sockets Knernel-mode Driver)tcpip.SYS,由它們負責管理網絡連接和緩衝管理。
還有另一個驅動Afd.sys(Ancillary Function Driver for WinSock)則用於支持基於 window socket的應用程序,比如ftp、telnet等,被稱爲“ Windows NT 套接字驅動程序 ”。
早期的Windows開發者,需要使用C/C++去調用WinSock,比如MFC就提供了一個“CSocket”類封裝底層的Socket。
.NET也提供了一組類來封裝WinSock Win32 API,這些類集中於System.Net這一命名空間中,其中的核心類型就是Socket。
Socket類是對WinSock API一個很淺的封裝,擁有不少方法直接對應於WinSock中的C/C++函數,比如Poll、Select、IOControl等。
Socket有一個Handle屬性,它引用位於操作系統核心的Socket核心對象。
提示:
有關係統核心對象(Kernel Object)的通俗解釋,請參看《.NET 4.0面向對象編程漫談 》中的15.1.2節《操作系統的進程管理》
Socket提供了衆多的屬性,還提供了SetSocketOption方法來設置各種選項,對.NET網絡應用程序的數據通訊進行“微調”。
Socket的功能出奇地強大,在.NET平臺上,它支持以下四種典型的編程模式:
(1) 居於阻塞模式的Socket編程(單線程或多線程的),每個線程處理一個客戶端連接
(2)“非阻塞”模式的Socket編程,這是早期UNIX爲提升網絡應用程序性能而採用的編程模式,出於兼容和方便移植原有程序的目的而保留,建議新開發的.NET網絡程序不要再使用。
(3) 使用IAsyncResult的異步編程模式:Socket類提供有一堆的“BeginXXX/EndXXX”方法實現異步Socket編程,使用線程池中的線程完成工作,性能較好。
(4) 使用EAP的異步編程模式:Socket類提供了“另一堆”以“Async”結尾的方法,在底層使用Windows操作系統的Completion Port(完成端口)和Overlapped I/O mechanism(重疊輸入/輸出機制),據說可以提供“最高”的性能。
在後面的文章中,將逐步地展開介紹這些編程模式。
提示:
強烈建議讀者仔細閱讀《.NET 4.0面向對象編程漫談 》中的第10章《異步編程模式》,以提前掌握.NET異步編程的基礎知識與基本技能,否則,後面的文章可以不用看了。
瞭解了Socket這位“美女”的“家庭背景”之後,在與她進行第一次“約會”之前,我們不妨弄清楚一個問題:
現在我們還有必要掌握Sokcet編程技術嗎?
2 Socket是否已人老珠黃?
基於Socket開發網絡應用程序已經有很多年的歷史了,現在的新技術層出不窮,在.NET平臺之上,WCF大有“一統江湖”的勢頭,Socket是否真的“人老珠黃”?
請看圖 2所示的多層“松花蛋”:
圖 2
圖 2說明,WCF與WinSocket等底層技術之間實際上是一種“包含”關係,每一層都在下一層所提供服務的基礎上,又擴充了新的功能,越外層的應用程序,可以使用的功能往往越多,開發效率往往也會更高。
WCF在WinSocket的基礎之上擴充了大量的功能,使用它可以很高效地開發網絡應用程序,尤其非常適合於開發基於SOA的分佈式軟件系統,但這並不是說它可以完全把Socket打入冷宮。在不少場合,拋棄WCF那龐大的框架,直接使用Socket更合適:
(1)需要實現自己的通訊協議的場合(比如你要架設一個網絡遊戲服務器)
(2)你開發的系統需要實現“一問一答”的“交互式”運行模式
(3)你希望能全面控制你的網絡應用程序的“每個方面”,不想花時間去理解WCF那個複雜無比的內部架構
(4)你的網絡應用程序應用背景非常單一與明確,比如就解決一個問題:定期將分佈於多臺計算機上的數據文件上傳“彙總”到一臺中心服務器上。
(5)……
如果需要基於各種標準協議(比如WS-*等)開發SOA的分佈式軟件系統,再使用Socket就不合適了,那會大大地增加開發的工作量和難度,WCF更適合於解決這個問題。
在實際開發中,我們還可以混用WCF和Socket。比如我們可以基於WCF開發P2P的應用程序,使用NetPeerTcpBinding在P2P節點間“廣播消息”,然後,在兩個P2P節點之間直接使用Socket“私下”裏傳送一個“祕密”文件。
是可謂“運用之妙,存乎一心 ”。
好了,下面就介紹使用Socket開發的最基礎知識吧。
3 第一個Socket應用程序
一般我們都將網絡應用中用於提供“服務”的一方稱爲“服務端應用程序(Server)”,另一方訪問這些服務的稱爲“客戶端應用程序(Client)”。Server端和Client端的Socket用法是不一樣的。
3.1 服務端應用程序
開發網絡程序的第一步,是創建Socket對象,以下是示例代碼:
Socket newsock = new Socket(
AddressFamily.InterNetwork, //使用IPv4
SocketType.Stream, //使用可靠的雙向數據流,不保存信息邊界
ProtocolType.Tcp //使用TCP協議
);
緊接着,需要將Socket對象“綁定(Bind) ”到一個“終結點(IPEndPoint的實例)”。
IPEndPoint ipep = new IPEndPoint(IP地址,打開端口); //綁定
newsock.Bind(ipep);
提示:
前面的《IP知多少 》一文中介紹過IPEndPoint。WCF中也定義了“終結點 ”,它代表一個WCF服務的訪問點。
“綁定(Bind) ”這個術語非常值得關注,簡單地說,“綁定”就是將原先可能不相關的兩個事物“關聯”起來,打個可能不太恰當的比喻,“綁定”就是相愛的兩個人最終決定結婚,並領了結婚證。
“綁定”的身影在.NET平臺中頻頻出現,比如“數據綁定(DataBind)”,就是使用控件將數據源中的數據展示在應用程序的界面上,並且將用戶對數據的修改和查詢等傳給數據源。
在Socket應用程序中,“綁定”的作用是讓某個Socket對象關聯上特定的網絡接口(Network Interface)。一臺網絡主機可能安裝有多個網絡接口,“綁定”之後,Socket對象將可以在指定那個網絡接口(Network Interface )上監聽。如果不需要指定特定的網絡接口,也不在意使用的端口,那麼,可以創建一個使用IPAddress.Any,端口爲0的IPEndPoint,Socket綁定這一IPEndPoint之後,操作系統會決定最終使用哪個網絡接口,並且在“[1024,5000]”之間的選擇一個未用端口分配給此Socket。
注意:
WCF中也有“綁定”,但WCF中的“綁定”的含義要豐富得多,它其實是一組特殊的對象,它的主要功能是創建用於實現WCF應用程序間相互通訊的“信道棧”,WCF基類庫中提供了一堆的“綁定”,特定的綁定使用特定的通訊協議和技術,比如NetTcpBinding採用TCP協議,NetMsmqBinding則使用了微軟消息隊列。
Socket對象綁定網絡接口之後,就可以監聽並等待客戶端連接了:
newsock.Listen(10); //開始監聽
Socket client = newsock.Accept(); //等待客戶端連接
所謂“監聽 (Listen) ”,其實是告訴操作系統:“我關心本機某個網絡接口上的數據包,當有數據包到達,並且端口號和我所規定的一致,請通知我”。
Socket.Listen方法的參數有着特殊的含義。此處暫時按下,留待後文分解。
Socket.Accept方法等待客戶端發來的連接請求數據包,默認情況下,這一方法是“同步”方法,線程將在此處阻塞等待,直到有客戶發來連接請求。
當客戶端發來連接請求時,Accept方法返回一個Socket對象,這個對象代表雙方已建立了一條數據通訊的鏈路,可以相互傳送數據了。這時,原先的Socket將得到“解放”,可以繼續監聽。
注意:
負責監聽的Socket不負責發送與接收數據,而Accept方法返回的Socket可以用於接收和發送數據,但不能用於接收新的連接,同時,其RemoteEndPoint方法可以獲取遠程客戶端的IP地址和使用的端口
以下代碼調用剛得到的Socket對象的Receive方法接收客戶端發來的數據:
byte[] data = new byte[1024];
int r ecv = client.Receive(data);
Socket.Receive方法也是一個“阻塞”的同步方法,它將收到的數據保存到一個字節數組中,這個字節數組通常稱爲“數據緩衝區”。
提示:
數據緩衝區在Socket編程中非常重要,讀者會發現,在開發中你時時刻刻都得關注它,一不小心,它就給你搗亂。
Receive方法的返回值代表接收的數據字節數。以下代碼使用這一返回值瞭解客戶端到底發來了什麼消息:
Console.WriteLine(Encoding.UTF8 .GetString(data, 0, recv ));
上面這句代碼中有幾點需要特別注意:
(1)一定要使用recv來“定界”客戶端傳來的數據。
(2)我們假設客戶端發送過來的消息是一個字符串,這裏使用UTF8進行解碼。很明顯,這要求客戶端與服務端必須事先達成一致,使用同樣的編碼和解碼方式。這種需要在事先進行協商的“東西”,就是“通訊協議 ”。不同的網絡應用會使用不同的通訊協議,比如互聯網普遍使用HTTP,這是一個業界標準,而我們也可以定義自己的通訊協議,比如QQ就有自己的通訊協議。
提示:
我在《 漫談.NET開發中的字符串編碼 》一文中介紹了字符串編碼的基礎知識。
數據接收完畢,服務端就可以斷開客戶的連接:
client.Shutdown(SocketShutdown.Both); //通知OS,不再接收與發送數據
client.Close(); //關閉Socket
完成數據傳送任務之後,注意應該及時地關閉Socket。這通常分爲兩步:
(1)調用Shutdown方法通知TCP/IP協議棧發送所有未發送的數據,或停止接收數據
(2)調用Close方法關閉套接字。
Socket本身對應着一個核心對象,它有一個句柄(Handle)供操作系統內核進行管理。因此,它不再有用時必須及時地被關閉,否則,有可能會造成嚴重的問題。
提示:
操作系統能管理的句柄數是有限的,而網絡應用服務端程序通常會運行很長的時間,如果不及時地關閉不用的Socket,將導致它所佔用的句柄不能及時回收,有可能導致服務器Down掉。
Socket本身實現了IDisposable接口,所以也可以使用using關鍵字實現“自動釋放”:
using (newsock)
{
……
} //自動關閉newsock
3.2 客戶端應用程序
客戶端應用程序與服務端大同小異:
首先創建好一個Socket對象,然後再調用其Connect方法創建到服務端的連接,如果之前Socket沒有使用Bind方法指定一個端口,Connect方法會自動選擇一個未用的端口:
Socket server = new Socket( AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp );
server.Connect(服務端的IP終結點);
如果Connect方法沒有拋出異常,則表示成功連接服務器,現在,就可以使用Socket對象的Send方法發送數據,數據同樣保存於一個數據緩衝區(其實就是一個byte[])中:
server.Send(Encoding.UTF8 .GetBytes(要發送的消息));
注意這裏選擇的字符串編碼方式必須要與服務端一致,否則,將導致服務端無法正確地解碼出字符串。
數據發送完畢,關閉套接字就行了。
3.3 處理網絡應用程序中的異常
Socket對象的Connect、Send、Receive等方法都有可能出錯,這時,.NET基類庫將拋出一個SocketException,它實際上封裝的是底層WinSock出錯信息。
每一個SocketException對象都有一個對應的錯誤號,其含義是由底層的WinSock定的。比如錯誤號爲10048的SocketException其含義是:地址已被使用。發生這一異常的原因通常是你嘗試把兩個Socket對象綁定到同一個IPEndPoint。
以下是Socket網絡應用程序中的典型代碼框架:
Socket remote=new Socket(……);
try
{
//……
remote.Connect(iep); //iep爲遠程主機的終結點
//……
remote.Send(……);
//……
}
catch (SocketException e)
{
Console.WriteLine("無法連接遠程主機 {0} ,原因:{1},
NativeErrorCode:{2},SocketErrorCode:{3}", iep.Address,
e.Message, e.NativeErrorCode, e.SocketErrorCode);
}
finally
{
server.Close();
}
示例項目IntroduceSocket展示了本文所介紹的知識(圖 3)。
圖 3
到此,我們與“Socket美女”的“第一次約會”到此結束。您對她的第一印象如何?
本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/bitfan/archive/2010/12/24/6097011.aspx