在 .NET Framework 2.0 版本中,System.Net.Sockets 命名空間提供了一個幾乎擁有 Windows WinSock Win32 API 的所有功能的 Socket 類。該功能所屬的類包含為Managed 程式碼開發人員設計的各種方法和屬性。在 Socket 上,有一組包括 Send 和 Receive 在內的同步方法,具備針對各種情況的參數重載。這些同步方法不僅便於使用,而且非常適合於使用通訊端的簡單網路任務。Socket 上還有一組基於非同步編程模型 (APM) 的非同步方法呼叫,APM 在 .NET Framework 中非常普遍(有關詳細資料,請參閱 msdn.microsoft.com/msdnmag/issues/07/03/ConcurrentAffairs)。這些非同步方法呼叫讓 Socket 類的非同步使用相對簡單,而且還提供了一種方法來處理許多通訊端,或處理在許多通訊端上進行的多個發送和接收操作。
2.0 版本的 Socket 類適合多種需要使用網路通訊端的用戶端應用程式,以及一些伺服器和服務類型的應用程式。遺憾的是,一些服務應用程式方案不適用於 2.0 版本的 Socket 類,卻和直接使用 Windows WinSock API 的母語相容。2.0 版本的 Socket 類的主要問題是它不僅在分配必要的基礎對象以便在大量通訊端上同時保持 I/O 操作時需要佔用過多的 CPU 迴圈,而且在執行單個通訊端 I/O 操作時也同樣如此。
憑藉 .NET Framework 3.5,通用語言執行平台 (CLR) 便可以更有效地同時管理大量的 Overlapped 對象。CLR 中的 Overlapped 對象可以有效封裝用於管理非同步 I/O 操作的本機 Windows OVERLAPPED 結構。每個進行中的 Socket 非同步 I/O 操作中都有一個 Overlapped 對象執行個體。現在可以擁有 6 萬個甚至更多的串連通訊端,並同時在每個通訊端上保持一個掛起的非同步接收 I/O 操作。
2.0 版本的 Socket 類使用 Windows I/O 完成連接埠來完成非同步 I/O 操作。這使應用程式可以輕易地擴充到大量的開啟的通訊端。.NET Framework 實現了 System.Threading.ThreadPool 類,該類提供可讀取完成連接埠並完成非同步 I/O 操作的完成線程。在開發即將發布的 3.5 版本的 .NET Framework 的過程中,我們將大量的精力放在了消除代碼路徑中的開銷上,包括讀取完成連接埠和調用應用程式的完成代理或在 IAsyncResult 對象中發出 I/O 完成事件對象訊號之間。
.NET Framework 中的 APM 也稱為 Begin/End 模式。這是因為會調用 Begin 方法來啟動非同步作業,然後返回一個 IAsyncResult 對象。可以選擇將一個代理作為參數提供給 Begin 方法,非同步作業完成時會調用該方法。或者,一個線程可以等待 IAsyncResult.AsyncWaitHandle。當回調被調用或發出等待訊號時,就會調用 End 方法來擷取非同步作業的結果。這種模式很靈活,使用相對簡單,在 .NET Framework 中非常常見。
但是,您必須注意,如果進行大量非同步通訊端操作,是要付出代價的。針對每次操作,都必須建立一個 IAsyncResult 對象,而且該對象不能被重複使用。由於大量使用對象分配和垃圾收集,這會影響效能。為瞭解決這個問題,新版本提供了另一個使用通訊端上執行非同步 I/O 的方法模式。這種新模式並不要求為每個通訊端操作分配操作內容物件。
我們沒有建立全新的模式,而只是採用現有模式並做了一個基本更改。現在,在 Socket 類中有了一些方法,它們使用基於事件的完成模型的變體。在 2.0 版本中,您可以使用下列代碼在某個通訊端上啟動非同步發送操作:
void OnSendCompletion(IAsyncResult ar) { }
IAsyncResult ar = socket.BeginSend(buffer, 0, buffer.Length, SocketFlags.None, OnSendCompletion, state);
在新版本中,您還可以實現:
void OnSendCompletion(object src, SocketAsyncEventArgs sae) { }
SocketAsyncEventArgs sae = new
SocketAsyncEventArgs();
sae.Completed += OnSendCompletion;
sae.SetBuffer(buffer, 0, buffer.Length);
socket.SendAsync(sae);
這裡有一些明顯的差別。封裝操作內容相關的是一個 SocketAsyncEventArgs 對象,而不是 IAsyncResult 對象。該應用程式建立並管理(甚至可以重複使用)SocketAsyncEventArgs 對象。通訊端操作的所有參數都由 SocketAsyncEventArgs 對象的屬性和方法指定。完成狀態也由 SocketAsyncEventArgs 對象的屬性提供。最後,需要使用事件處理常式回調完成方法。
所有這些更改都顯著改進了 .NET Framework 3.5 中 System.Net.Sockets 類的效能和延展性。現有應用程式可以自動實現其中的兩項改進。第三項改進,即新型 Socket 方法,只能通過修改應用程式而得到使用,但這些方法都能為基於通訊端的高要求應用程式提供更理想的延展性。
一個Tcp版的EchoServer的樣本如下:
class
EchoServer
{
Socket server;
public EchoServer(IPEndPoint localPoint)
{
server = new
Socket(localPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
server.Bind(localPoint);
server.Listen(5);
accept_async();
}
private
void accept_async()
{
var accept = new
SocketAsyncEventArgs();
accept.Completed += accept_Completed;
server.AcceptAsync(accept);
}
void accept_Completed(object sender, SocketAsyncEventArgs e)
{
accept_async();
var client = e.AcceptSocket;
e.Completed -= accept_Completed;
e.Completed += receive_Completed;
var buffer = new
byte[1024];
e.SetBuffer(buffer, 0, buffer.Length);
client.ReceiveAsync(e);
}
void receive_Completed(object sender, SocketAsyncEventArgs e)
{
var client = sender as
Socket;
if (e.BytesTransferred == 0)
{
Console.WriteLine("socket is closed");
client.Close();
}
else
{
client.Send(e.Buffer, e.BytesTransferred, SocketFlags.None);
client.ReceiveAsync(e);
}
}
}
參考文章:http://msdn.microsoft.com/zh-cn/magazine/cc163356.aspx