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