非同期クライアント ソケットの使用
非同期クライアント ソケットでは、ネットワーク操作の完了までアプリケーションの実行が中断される、ということはありません。その代わりに、標準の .NET Framework 非同期プログラミング モデルを使用して、アプリケーションを元のスレッドで実行したまま、他のスレッド上でネットワーク接続が処理されます。非同期ソケットは、ネットワークを頻繁に使用するアプリケーションや、ネットワーク操作が完了するまでの間、処理を中断しておくことが不可能なアプリケーションに適しています。
Socket クラスは、非同期メソッドに対する .NET Framework の名前付け規則に従っています。たとえば、同期メソッドである Receive は、非同期の BeginReceive メソッドと EndReceive メソッドに対応しています。
非同期操作には、操作の結果を返すためのコールバック メソッドが必要です。アプリケーション側で結果を受け取る必要がない場合は、コールバック メソッドは不要です。ここで紹介するコード例では、ネットワーク デバイスへの接続を開始するメソッドと接続を完了するコールバック メソッド、データの送信を開始するメソッドと送信を完了するコールバック メソッド、データの受信を開始するメソッドとデータの受信を終了するコールバック メソッドを使用しています。
非同期ソケットは、システム スレッド プールから複数のスレッドを使用して、ネットワーク接続を処理します。あるスレッドでデータの送受信を開始し、その他のスレッドではネットワーク デバイスへの接続を完了したり、データを送受信したりします。次に示すいくつかの例では、System.Threading.ManualResetEvent クラスのインスタンスを使用してメイン スレッドの実行を中断し、実行を再開できるようになった時点でその旨を通知しています。
次の例では、非同期ソケットをネットワーク デバイスに接続するために、最初に Connect
メソッドで Socket を初期化してから、ネットワーク デバイスを表すリモートのエンドポイント、接続コールバック メソッド、および複数の非同期呼び出し間で状態情報を受け渡しするために使用される状態オブジェクトであるクライアント Socket を渡して、BeginConnect メソッドを呼び出しています。この例では、指定した Socket を、指定したエンドポイントに接続するために Connect
メソッドを実装しています。また、connectDone
というグローバルな ManualResetEvent が存在することを前提としています。
Public Shared Sub Connect(remoteEP As EndPoint, client As Socket)
client.BeginConnect(remoteEP, _
AddressOf ConnectCallback, client)
connectDone.WaitOne()
End Sub 'Connect
public static void Connect(EndPoint remoteEP, Socket client) {
client.BeginConnect(remoteEP,
new AsyncCallback(ConnectCallback), client );
connectDone.WaitOne();
}
接続コールバック メソッド ConnectCallback
は、AsyncCallback デリゲートを実装しています。このメソッドは、リモート デバイスが使用可能な場合にそのリモート デバイスに接続し、次に ManualResetEvent の connectDone
を設定することによって、接続が完了したことをアプリケーション スレッドに通知します。ConnectCallback
メソッドを実装するコード例を次に示します。
Private Shared Sub ConnectCallback(ar As IAsyncResult)
Try
' Retrieve the socket from the state object.
Dim client As Socket = CType(ar.AsyncState, Socket)
' 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 e As Exception
Console.WriteLine(e.ToString())
End Try
End Sub 'ConnectCallback
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
メソッドを実装するコード例を次に示します。
Private Shared Sub Send(client As Socket, data As [String])
' Convert the string data to byte data using ASCII encoding.
Dim byteData As Byte() = Encoding.ASCII.GetBytes(data)
' Begin sending the data to the remote device.
client.BeginSend(byteData, 0, byteData.Length, SocketFlags.None, _
AddressOf SendCallback, client)
End Sub 'Send
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 が存在することを前提としています。
Private Shared Sub SendCallback(ar As IAsyncResult)
Try
' Retrieve the socket from the state object.
Dim client As Socket = CType(ar.AsyncState, Socket)
' Complete sending the data to the remote device.
Dim bytesSent As Integer = client.EndSend(ar)
Console.WriteLine("Sent {0} bytes to server.", bytesSent)
' Signal that all bytes have been sent.
sendDone.Set()
Catch e As Exception
Console.WriteLine(e.ToString())
End Try
End Sub 'SendCallback
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 があります。これらのフィールドを状態オブジェクトに用意することにより、クライアント ソケットからデータを読み取るための複数の呼び出しの間で、これらの値を保持できます。
Public Class StateObject
' Client socket.
Public workSocket As Socket = Nothing
' Size of receive buffer.
Public BufferSize As Integer = 256
' Receive buffer.
Public buffer(256) As Byte
' Received data string.
Public sb As New StringBuilder()
End Class 'StateObject
public class StateObject {
// Client socket.
public Socket workSocket = null;
// Size of receive buffer.
public const int BufferSize = 256;
// Receive buffer.
public byte[] buffer = new byte[BufferSize];
// Received data string.
public StringBuilder sb = new StringBuilder();
}
次の例の Receive
メソッドは、状態オブジェクトを設定してから、BeginReceive メソッドを呼び出してクライアント ソケットからデータを非同期に読み取ります。Receive
メソッドを実装するコード例を次に示します。
Private Shared Sub Receive(client As Socket)
Try
' Create the state object.
Dim state As New StateObject()
state.workSocket = client
' Begin receiving the data from the remote device.
client.BeginReceive(state.buffer, 0, state.BufferSize, 0, _
AddressOf ReceiveCallback, state)
Catch e As Exception
Console.WriteLine(e.ToString())
End Try
End Sub 'Receive
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 メソッドを繰り返し呼び出します。クライアントからのデータをすべて読み取ったら、ManualResetEvent の sendDone
を設定することによって、ReceiveCallback
はデータ送信が完了したことをアプリケーション スレッドに通知します。
ReceiveCallback
メソッドを実装するコード例を次に示します。このコード例では、受信した文字列を保持する response
という名前のグローバル文字列と、receiveDone
というグローバルな ManualResetEvent が存在していることを前提としています。ネットワーク セッションを終了するには、サーバーでクライアント ソケットを正常にシャットダウンする必要があります。
Private Shared Sub ReceiveCallback(ar As IAsyncResult)
Try
' Retrieve the state object and the client socket
' from the asynchronous state object.
Dim state As StateObject = CType(ar.AsyncState, StateObject)
Dim client As Socket = state.workSocket
' Read data from the remote device.
Dim bytesRead As Integer = client.EndReceive(ar)
If bytesRead > 0 Then
' 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, state.BufferSize, 0, _
AddressOf ReceiveCallback, state)
Else
' All the data has arrived; put it in response.
If state.sb.Length > 1 Then
response = state.sb.ToString()
End If
' Signal that all bytes have been received.
receiveDone.Set()
End If
Catch e As Exception
Console.WriteLine(e.ToString())
End Try
End Sub 'ReceiveCallback
private static void ReceiveCallback( IAsyncResult ar ) {
try {
// Retrieve the state object and the client socket
// from the asynchronous 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());
}
}