訪問
使用非同步用戶端通訊端
非同步用戶端通訊端在等待網路操作完成時不掛起應用程式。相反,它使用標準 .NET 架構非同步編程模型在一個線程上處理網路連接,而應用程式繼續在原始線程上運行。非同步通訊端適用於大量使用網路或不能等待網路操作完成才能繼續的應用程式。
Socket 類遵循非同步方法呼叫的 .NET 架構命名模式;例如,同步 Receive 方法對應非同步 BeginReceive 和 EndReceive 方法。
非同步作業要求回調方法返回操作結果。如果應用程式不需要知道結果,則不需要任何回調方法。本節中的程式碼範例闡釋如何使用某個方法開始與網路裝置的串連並使用回調方法結束串連,如何使用某個方法開始發送資料並使用回調方法完成發送,以及如何使用某個方法開始接收資料並使用回調方法結束接收資料。
非同步通訊端使用多個系統線程池中的線程處理網路連接。一個線程負責初始化資料的發送或接收;其他線程完成與網路裝置的串連並發送或接收資料。在下列樣本中,System.Threading.ManualResetEvent 類的執行個體用於掛起主線程的執行並在執行可以繼續時發出訊號。
在下面的樣本中,為了將非同步通訊端串連到網路裝置,Connect 方法初始化
Socket 執行個體,然後調用 BeginConnect 方法,傳遞表示網路裝置的遠程終結點、串連回調方法以及狀態物件(即用戶端
Socket 執行個體,用於在非同步呼叫之間傳遞狀態資訊)。該樣本實現 Connect 方法以將指定的
Socket 執行個體串連到指定的終結點。它假定存在一個名為 connectDone 的全域
ManualResetEvent。
[C#]
public static void Connect(EndPoint remoteEP, Socket client) {
client.BeginConnect(remoteEP,
new AsyncCallback(ConnectCallback), client );
connectDone.WaitOne();
}
串連回調方法 ConnectCallback 實現 AsyncCallback 委託。它在遠程裝置可用時串連到遠程裝置,然後通過設定
ManualResetEvent connectDone 嚮應用程式線程發出串連完成的訊號。下面的代碼實現 ConnectCallback 方法。
[C#]
private static void ConnectCallback(IAsyncResult ar) {
try {
// Retrieve the socket from the state object.
Socket client = (Socket) ar.AsyncState;
// Complete the connection.
client.EndConnect(ar);
Console.WriteLine("Socket connected to {0}",
client.RemoteEndPoint.ToString());
// Signal that the connection has been made.
connectDone.Set();
} catch (Exception e) {
Console.WriteLine(e.ToString());
}
}
Send 樣本方法以 ASCII 格式對指定的字串資料進行編碼,並將其非同步發送到指定的通訊端所表示的網路裝置。下面的樣本實現 Send 方法。
[C#]
private static void Send(Socket client, String data) {
// Convert the string data to byte data using ASCII encoding.
byte[] byteData = Encoding.ASCII.GetBytes(data);
// Begin sending the data to the remote device.
client.BeginSend(byteData, 0, byteData.Length, SocketFlags.None,
new AsyncCallback(SendCallback), client);
}
發送回調方法 SendCallback 實現 AsyncCallback 委託。它在網路裝置準備接收時發送資料。下面的樣本顯示 SendCallback 方法的實現。它假定存在一個名為 sendDone 的全域
ManualResetEvent 執行個體。
[C#]
private static void SendCallback(IAsyncResult ar) {
try {
// Retrieve the socket from the state object.
Socket client = (Socket) ar.AsyncState;
// Complete sending the data to the remote device.
int bytesSent = client.EndSend(ar);
Console.WriteLine("Sent {0} bytes to server.", bytesSent);
// Signal that all bytes have been sent.
sendDone.Set();
} catch (Exception e) {
Console.WriteLine(e.ToString());
}
}
從用戶端通訊端讀取資料需要一個在非同步呼叫之間傳遞值的狀態物件。下面的類是用於從用戶端通訊端接收資料的樣本狀態物件。它包含以下各項的欄位:用戶端通訊端,用於接收資料的緩衝區,以及用於儲存傳入資料字串的 StringBuilder。將這些欄位放在該狀態物件中,使這些欄位的值在多個調用之間得以保留,以便從用戶端通訊端讀取資料。
[C#]
public class StateObject {
public Socket workSocket = null; // Client socket.
public const int BufferSize = 256; // Size of receive buffer.
public byte[] buffer = new byte[BufferSize]; // Receive buffer.
public StringBuilder sb = new StringBuilder();// Received data string.
}
Receive 方法樣本設定狀態物件,然後調用
BeginReceive 方法從用戶端通訊端非同步讀取資料。下面的樣本實現 Receive 方法。
[C#]
private static void Receive(Socket client) {
try {
// Create the state object.
StateObject state = new StateObject();
state.workSocket = client;
// Begin receiving the data from the remote device.
client.BeginReceive( state.buffer, 0, StateObject.BufferSize, 0,
new AsyncCallback(ReceiveCallback), state);
} catch (Exception e) {
Console.WriteLine(e.ToString());
}
}
接收回調方法 ReceiveCallback 實現
AsyncCallback 委託。它接收來自網路裝置的資料並產生訊息字串。它將來自網路的一個或多個資料位元組讀入資料緩衝區,然後再次調用
BeginReceive 方法,直到用戶端發送的資料完成為止。從用戶端讀取所有資料後,ReceiveCallback 通過設定
ManualResetEvent sendDone 嚮應用程式線程發出資料完成的訊號。
下面的範例程式碼實現 ReceiveCallback 方法。它假定存在一個名為 response 的全域字串(該字串儲存接收的字串)和一個名為 receiveDone 的全域
ManualResetEvent 執行個體。伺服器必須正常關閉用戶端通訊端以結束網路會話。
[C#]
private static void ReceiveCallback( IAsyncResult ar ) {
try {
// Retrieve the state object and the client socket
// from the async state object.
StateObject state = (StateObject) ar.AsyncState;
Socket client = state.workSocket;
// Read data from the remote device.
int bytesRead = client.EndReceive(ar);
if (bytesRead > 0) {
// There might be more data, so store the data received so far.
state.sb.Append(Encoding.ASCII.GetString(state.buffer,0,bytesRead));
// Get the rest of the data.
client.BeginReceive(state.buffer,0,StateObject.BufferSize,0,
new AsyncCallback(ReceiveCallback), state);
} else {
// All the data has arrived; put it in response.
if (state.sb.Length > 1) {
response = state.sb.ToString();
}
// Signal that all bytes have been received.
receiveDone.Set();
}
} catch (Exception e) {
Console.WriteLine(e.ToString());
}
}
用通訊端進行偵聽
接聽程式或伺服器通訊端在網路上開啟一個連接埠,然後等待用戶端串連到該連接埠。儘管存在其他網路地址族和協議,但本樣本說明如何為 TCP/IP 網路建立遠程服務。
TCP/IP 服務的唯一地址是這樣定義的:將主機的 IP 位址與服務的連接埠號碼組合,以便為服務建立終結點。Dns 類提供的方法返回有關本網裝置支援的網路地址的資訊。當本網裝置有多個網路地址時,或者當本地系統支援多個網路裝置時,
Dns 類返回有關所有網路地址的資訊,應用程式必須為服務選擇正確的地址。Internet 分配號碼機構 (Internet Assigned Numbers Authority, IANA) 定義公用服務的連接埠號碼(有關更多資訊,請訪問 http://www.iana.org/assignments/port-numbers)。其他服務可以具有 1,024 到 65,535 範圍內的註冊連接埠號碼。
下面的程式碼範例通過將
Dns 為主機返回的第一個 IP 位址與從註冊連接埠號碼範圍內選擇的連接埠號碼組合,為伺服器建立 IPEndPoint。
[C#]
IPHostEntry ipHostInfo = Dns.Resolve(Dns.GetHostName());
IPAddress ipAddress = ipHostInfo.AddressList[0];
IPEndPoint localEndPoint = new IPEndPoint(ipAddress, 11000);
確定本地終結點後,必須使用 Bind 方法將 Socket 與該終結點關聯,並使用 Listen 方法設定
Socket 在該終結點上偵聽。如果特定的地址和連接埠組合已經被使用,則
Bind 將引發異常。下面的樣本闡釋如何將
Socket 與
IPEndPoint 關聯。
[C#]
listener.Bind(localEndPoint);
listener.Listen(100);
Listen 方法帶單個參數,該參數指定在向串連用戶端返回伺服器忙錯誤之前允許的
Socket 掛起串連數。在本樣本中,在向第 101 個用戶端返回伺服器忙響應之前,最多可以在串連隊列中放置 100 個用戶端。
使用同步伺服器通訊端
同步伺服器通訊端掛起應用程式的執行,直到通訊端上接收到串連請求。同步伺服器通訊端不適用於在操作中大量使用網路的應用程式,但它們可能適用於簡單的網路應用程式。
使用 Bind 和 Listen 方法設定 Socket 以在終結點上偵聽之後,
Socket 就可以隨時使用 Accept 方法接受傳入的串連請求了。應用程式被掛起,直到調用
Accept 方法時接收到串連請求。
接收到串連請求時,
Accept 返回一個與串連用戶端關聯的新
Socket 執行個體。下面的樣本讀取用戶端資料,在控制台上顯示該資料,然後將該資料回顯到用戶端。
Socket 不指定任何訊息協議,因此字串“<EOF>”標記訊息資料的結尾。它假定名為 listener 的
Socket 執行個體已初始化並綁定到終結點。
[C#]
Console.WriteLine("Waiting for a connection...");
Socket handler = listener.Accept();
String data = null;
while (true) {
bytes = new byte[1024];
int bytesRec = handler.Receive(bytes);
data += Encoding.ASCII.GetString(bytes,0,bytesRec);
if (data.IndexOf("<EOF>") > -1) {
break;
}
}
Console.WriteLine( "Text received : {0}", data);
byte[] msg = Encoding.ASCII.GetBytes(data);
handler.Send(msg);
handler.Shutdown(SocketShutdown.Both);
handler.Close();
使用非同步伺服器通訊端
非同步伺服器通訊端使用 .NET 架構非同步編程模型處理網路服務請求。Socket 類遵循標準 .NET 非同步命名模式;例如,同步 Accept 方法對應非同步 BeginAccept 和 EndAccept 方法。
非同步伺服器通訊端需要一個開始接受網路連接請求的方法,一個處理串連請求並開始接收網路資料的回調方法以及一個結束接收資料的回調方法。本節將進一步討論所有這些方法。
在下面的樣本中,為開始接受網路連接請求,方法 StartListening 初始化
Socket,然後使用
BeginAccept 方法開始接受新串連。當通訊端上接收到新串連請求時調用接受回調方法。它負責擷取將處理串連的
Socket 執行個體,並將
Socket 提交給將處理請求的線程。接受回調方法實現 AsyncCallback 委託;它返回 void,並帶一個 IAsyncResult 類型的參數。下面的樣本是接受回調方法的外殼程式。
[C#]
void acceptCallback( IAsyncResult ar) {
//Add the callback code here.
}
BeginAccept 方法帶兩個參數:指向接受回調方法的
AsyncCallback 委託和一個用於將狀態資訊傳遞給回調方法的對象。在下面的樣本中,偵聽
Socket 通過
狀態參數傳遞給回調方法。本樣本建立一個
AsyncCallback 委託並開始接受網路連接。
[C#]
listener.BeginAccept(
new AsyncCallback(SocketListener.acceptCallback),
listener);
非同步通訊端使用系統線程池中的線程處理傳入的串連。一個線程負責接受串連,另一線程用於處理每個傳入的串連,還有一個線程負責接收串連資料。這些線程可以是同一個線程,具體取決於線程池所分配的線程。在下面的樣本中,System.Threading.ManualResetEvent 類掛起主線程的執行並在執行可以繼續時發出訊號。
下面的樣本顯示在本機電腦上建立非同步 TCP/IP 通訊端並開始接受串連的非同步方法呼叫。它假定以下內容:存在一個名為 allDone 的全域
ManualResetEvent 執行個體,該方法是名為 SocketListener 類的成員,以及定義了一個名為 acceptCallback 的回調方法。
[C#]
public void StartListening() {
IPHostEntry ipHostInfo = new Dns.Resolve(Dns.GetHostName());
IPEndPoint localEP = new IPEndPoint(ipHostInfo.AddressList[0],11000);
Console.WriteLine("Local address and port : {0}",localEP.ToString());
Socket listener = new Socket( localEP.Address.AddressFamily,
SocketType.Stream, ProtocolType.Tcp );
try {
listener.Bind(localEP);
s.Listen(10);
while (true) {
allDone.Reset();
Console.WriteLine("Waiting for a connection...");
listener.BeginAccept(
new AsyncCallback(SocketListener.acceptCallback),
listener );
allDone.WaitOne();
}
} catch (Exception e) {
Console.WriteLine(e.ToString());
}
Console.WriteLine( "Closing the listener...");
}
接受回調方法(即前例中的 acceptCallback)負責向主應用程式發出訊號,讓它繼續執行處理、建立與用戶端的串連並開始非同步讀取用戶端資料。下面的樣本是 acceptCallback 方法實現的第一部分。該方法的此節向主應用程式線程發出訊號,讓它繼續處理並建立與用戶端的串連。它假定存在一個名為 allDone 的全域
ManualResetEvent 執行個體。
[C#]
public void acceptCallback(IAsyncResult ac) {
allDone.Set();
Socket listener = (Socket) ar.AsyncState;
Socket handler = listener.EndAccept(ar);
// Additional code to read data goes here.
}
從用戶端通訊端讀取資料需要一個在非同步呼叫之間傳遞值的狀態物件。下面的樣本實現一個用於從遠程用戶端接收字串的狀態物件。它包含以下各項的欄位:用戶端通訊端,用於接收資料的資料緩衝區,以及用於建立用戶端發送的資料字串的 StringBuilder。將這些欄位放在該狀態物件中,使這些欄位的值在多個調用之間得以保留,以便從用戶端通訊端讀取資料。
[C#]
public class StateObject {
public Socket workSocket = null;
public const int BufferSize = 1024;
public byte[] buffer = new byte[BufferSize];
public StringBuilder sb = new StringBuilder();
}