與Socket的第一次“約會”

來源:互聯網
上載者:User

.NET 4.0網路開發入門之旅--

與Socket的第一次“約會”

 

註:

   
這是一個針對
網路開發領域初學者
的系列文章,可作為《.NET 4.0 物件導向編程漫談



》一書的擴充閱讀,寫作過程中我假設讀者可以對照閱讀此書的相關章節,不再浪費筆墨重複介紹相關的內容。

   
對於其他類型的讀者,除非您已經有相應的.NET 技術背景與一定的開發經驗,否則,閱讀中可能會遇到困難。

   
我希望這系列文章能讓讀者領略到網路開發的魅力!

   
另外,這些文章均為本人原創,請讀者尊重作者的勞動,我允許大家出於知識共用的目的自由轉載這些文章及相關樣本,但未經本人許可,請不要用於商業盈利目的。

      本文如有錯誤,敬請回貼指正。

   
謝謝大家!

 

                                                         
金旭亮

=================================================

點擊以下連結閱讀本系列前面的文章:

 



開篇語——
無網不勝》


IP知多少》

《我在“網” 中央

 

=================================================

 

      在前面的文章中,我們已經介紹了使用.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美女”的“第一次約會”到此結束。您對她的第一印象如何?

 

點擊下載本文樣本


 

==============================================================================

    下一篇文章,將介紹Socket美女的“追求者”隊伍,以及如何開發“一問一答”的網路應用程式。


聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.