IP

4.  IP 終結點

         現在介紹 .NET 網絡開發中最重要的一個概念—— IP 終結點, .NET 基類庫中使用 IPEndPoint 類型代表它。

         要想理解它,還得從 TCP/IP 說起。

         前面說過,所有連在網絡上的計算機都必須要有一個唯一的 IP 地址,它用於區分開網絡上的不同計算機,現在的問題是:一臺網絡計算機上可能跑着多個 網絡應用程序,它們可能會使用同一個網絡接口從網絡中接收(或發送)數據,因而共享同一個 IP 地址,在這種情況下,你怎麼將到達主機的數據包轉發給真正的“需求者”?

         爲了解決這個問題, TCP/IP 協議設計者引入了“端口( Port )”這個概念,規定每個提供網絡服務的應用程序都必須指定一個“端口”,不同的網絡應用程序不能使用相同的端口。

         請看以下 TCP 數據包的結構:

 

 

圖 4

 

         每個 TCP 數據包都包容着兩個端口信息(每個端口占 2 個字節,是一個 16 位的二進制數值,所以,最大端口值爲 2 的 16 次方減 1 ,爲 65535 )。 Source port 指明發送此數據包的網絡應用程序使用的端口, Destination port 指明接收方網絡應用程序使用的端口。

         這樣一來,接收方計算機依據 Destination port ,就可以把此數據包轉發給真正的網絡應用程序。

         在 Windows 平臺上,操作系統內核中的 TCP/IP 驅動( /Windows/System32/drivers 文件夾下的 tcpip.sys )負責完成處理 TCP 數據包的工作。

         端口的問題解決了,但我們在 TCP 數據包中沒有看到 IP 地址啊?沒有這個地址,數據包怎樣知道應該被送給哪臺計算機?

         方法是這樣的。

         由於 TCP 協議建構於 IP 協議之上, TCP 數據包由 IP 數據包承載,(圖 5 )。

 


 

 

 

 

圖 5

 

         圖 5 中清晰地展示出 IP 數據包中包容有發送此數據包的主機 IP 地址(即 Source IP Address )和接收此數據包的主機 IP 地址(即 Destination IP Address )信息。

         圖 5 中的“數據( Data )”部分,包容的就是 TCP 數據包。

         由此可知, IP 地址與端口唯一地標識了一個網絡中的網絡應用程序,我們將這個組合稱爲“ IP 終結點( IP EndPoint )”, IP EndPoint 是一個網絡服務的訪問點。

        

         提示:

         類似地, WCF 中也有一個服務終結點( ServiceEndpoint ),它代表一個 WCF 服務的訪問點。大家看到了軟件技術各領域之間的聯繫了嗎?

 

         在 .NET 中,使用 IPEndPoint 類表示一個 IP 終結點(圖 6 ),它派生自抽象基類 EndPoint ,注意它的 3 個屬性( Address/AddressFamily/Port )所表達的重要信息。

 


 


 

圖 6

 

         在 .NET 網絡應用程序中,套接字( Socket )對象必須綁定到一個 IPEndPoint 對象。

         爲了方便,我們編寫了一個靜態方法 GetRemoteMachineIPEndPoint ,並將其添加到前面介紹過的 AddressHelper 靜態類中:

 

        /// 以交互方式生成有效的遠程主機訪問終結點 , 適用於控制檯程序

        public static IPEndPoint GetRemoteMachineIPEndPoint()

        {

            IPEndPoint iep = null;

            try

            {

                Console.Write(" 請輸入遠程主機的 IP 地址: ");

                IPAddress address = IPAddress.Parse(Console.ReadLine());

                Console.Write(" 請輸入遠程主機打開的端口號: ");

                int port = Convert.ToInt32(Console.ReadLine());

                if (port > 65535 || port < 1024)

                    throw new Exception(" 端口號應該爲 [1024,65535] 範圍內的整數 ");

                iep = new IPEndPoint(address, port);

            }

            catch (ArgumentNullException)

            {

                Console.WriteLine(" 輸入的數據有誤! ");

            }

            catch (FormatException)

            {

                Console.WriteLine(" 輸入的數據有誤! ");

            }

            catch (Exception ex)

            {

                Console.WriteLine(ex.Message);

            }

            return iep;

        }

 

         後面的示例將大量使用此方法創建可供 Socket 綁定的 IP 終結點對象。

1.  我可以使用哪個端口?

         在實際開發中,經常需要爲某個網絡服務動態地指定一個端口,爲避免與一些重要的網絡服務程序衝突(比如 80 端口固定爲 Web 服務器所使用)通常要求這個端口應位於 [1024,65535] 這區間內。

         現在的問題是怎樣從中選取一個沒有被佔用的端口?要知道,某臺計算機上運行的網絡應用程序可能有多個,而且在不同的時間段內也不一樣。

         IPAddress.Any 這個特殊的地址可以派上用場了,當一個套接字對象綁定一個 IPAddress.Any 對象時, Windows 會自動爲其分配一個未被佔用的端口號,當套接字對象不再使用而可以被回收時,此端口將可以被複用。

         由此我們可以得到以下的思路:

         創建一個套接字對象,讓它綁定到 IPAddress.Any 對象,提取出系統分配的端口,然後再銷燬掉此套接字對象。

         Socket 對象實現了 IDisposable 接口,所以可以使用 using 關鍵字來自動回收其佔用的資源:

         以下是我們編寫的代碼片段:

 

public static int GetOneAvailablePortInLocalhost()

{

         IPEndPoint ep = new IPEndPoint(IPAddress.Any, 0);

         using (Socket tempSocket = new Socket(AddressFamily.InterNetwork,

                             SocketType.Stream, ProtocolType.Tcp))

         {

                   tempSocket.Bind(ep);

                   IPEndPoint ipep = tempSocket.LocalEndPoint as IPEndPoint;

                   return ipep.Port;

         }

}

 

         提示:

         一個對象實現 IDisposable 接口有許多講究, .NET 爲此設計了一個“ IDisposable 編程模式”,請參看《 .NET 4.0 面向對象編程漫談》的 5.5 節《與對象銷燬相關的話題》

 

         呵呵,現在,我們在應用程序中可以隨時調用 AddressHelper. GetOneAvailablePortInLocalhost() 方法獲取一個可用端口了,真給力!

         但別高興得太早!

         你考慮過兩個網絡應用程序(或同一應用程序的兩個線程)同時調用上述方法獲取可用端口的情形嗎?

         這時,無法保證此方法的兩次調用一定返回不同的端口。如果兩個 Socket 對象嘗試綁定到同一個 IPEndPoint ,將會引發一個 SocketException 異步。

         爲此,我們需要一個能跨越進程邊界的線程同步手段。

         命名的互斥同步對象 Mutex 可以解決這個問題,修正後的代碼如下:

 

// 獲取本機當前可用的端口號,此方法是線程安全的

public static int GetOneAvailablePortInLocalhost()

{

         Mutex mtx = new Mutex(false, "MyNetworkLibrary.AddressHelper.GetOneAvailablePort");

         try

         {

                   mtx.WaitOne();

                   IPEndPoint ep = new IPEndPoint(IPAddress.Any, 0);

                   using (Socket tempSocket = new Socket(

                            AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))

                   {

                            tempSocket.Bind(ep);

                            IPEndPoint ipep = tempSocket.LocalEndPoint as IPEndPoint;

                            return ipep.Port;

                   }

         }

         finally

         {

                   mtx.ReleaseMutex();

         }

}

 

 


提示:

         《 .NET 4.0 面向對象編程漫談 》的 17.3.2 節詳細介紹了 Mutex 對象的使用方法, 15.3.5 節介紹瞭如何活用命名 Mutex 對象實現應用程序的“單例模式”(即此應用程序不允許同時啓動兩個進程),以及如何利用它來現進程間的“通知機制”。

         建議讀者對照學習。

        

         好了,本文就寫到這裏吧。

         給讀者留個作業:

         1 依據本文介紹的內容使用 Visual Studio 創建類庫項目 MyNetworkLibrary ,將 GetLocalhostIPv4Addresses() 、 GetRemoteMachineIPEndPoint() 、 GetOneAvailablePortInLocalhost

() 三個方法封裝到 AddressHelper 靜態類中,後面文章的示例將調用它們。

 

         2 .NET 基類庫中提供了一個 Ping 組件用於檢測網絡連接性,請先通過 MSDN 自學它的使用方法。

 

本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/bitfan/archive/2010/12/06/6058367.aspx

發佈了23 篇原創文章 · 獲贊 0 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章