Thursday, April 21, 2011

Tutorial Asynchronous Socket Programming using .NET (For Client)

The following example program creates a client that connects to a server. The client is built with an asynchronous socket, so execution of the client application is not suspended while the server returns a response. The application sends a string to the server and then displays the string returned by the server on the console.




Code For VB
Imports System
Imports System.Net
Imports System.Net.Sockets
Imports System.Threading
Imports System.Text


' State object for receiving data from remote device.

Public Class StateObject
' Client socket.
Public workSocket As Socket = Nothing
' Size of receive buffer.
Public Const BufferSize As Integer = 256
' Receive buffer.
Public buffer(BufferSize) As Byte
' Received data string.
Public sb As New StringBuilder
End Class 'StateObject


Public Class AsynchronousClient
' The port number for the remote device.
Private Const port As Integer = 11000

' ManualResetEvent instances signal completion.
Private Shared connectDone As New ManualResetEvent(False)
Private Shared sendDone As New ManualResetEvent(False)
Private Shared receiveDone As New ManualResetEvent(False)

' The response from the remote device.
Private Shared response As String = String.Empty


Public Shared Sub Main()
' Establish the remote endpoint for the socket.
' For this example use local machine.
Dim ipHostInfo As IPHostEntry = Dns.Resolve(Dns.GetHostName())
Dim ipAddress As IPAddress = ipHostInfo.AddressList(0)
Dim remoteEP As New IPEndPoint(ipAddress, port)

' Create a TCP/IP socket.
Dim client As New Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)

' Connect to the remote endpoint.
client.BeginConnect(remoteEP, New AsyncCallback(AddressOf ConnectCallback), client)

' Wait for connect.
connectDone.WaitOne()

' Send test data to the remote device.
Send(client, "This is a test")
sendDone.WaitOne()

' Receive the response from the remote device.
Receive(client)
receiveDone.WaitOne()

' Write the response to the console.
Console.WriteLine("Response received : {0}", response)

' Release the socket.
client.Shutdown(SocketShutdown.Both)
client.Close()
End Sub 'Main


Private Shared Sub ConnectCallback(ByVal ar As IAsyncResult)
' 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()
End Sub 'ConnectCallback


Private Shared Sub Receive(ByVal client As Socket)

' 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, StateObject.BufferSize, 0, New AsyncCallback(AddressOf ReceiveCallback), state)
End Sub 'Receive


Private Shared Sub ReceiveCallback(ByVal ar As IAsyncResult)

' 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, StateObject.BufferSize, 0, New AsyncCallback(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
End Sub 'ReceiveCallback


Private Shared Sub Send(ByVal client As Socket, ByVal 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, 0, New AsyncCallback(AddressOf SendCallback), client)
End Sub 'Send


Private Shared Sub SendCallback(ByVal ar As IAsyncResult)
' 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()
End Sub 'SendCallback
End Class 'AsynchronousClient

Code For C#
using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Text;

// State object for receiving data from remote device.
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();
}

public class AsynchronousClient {
// The port number for the remote device.
private const int port = 11000;

// ManualResetEvent instances signal completion.
private static ManualResetEvent connectDone = 
new ManualResetEvent(false);
private static ManualResetEvent sendDone = 
new ManualResetEvent(false);
private static ManualResetEvent receiveDone = 
new ManualResetEvent(false);

// The response from the remote device.
private static String response = String.Empty;

private static void StartClient() {
// Connect to a remote device.
try {
// Establish the remote endpoint for the socket.
// The name of the 
// remote device is "host.contoso.com".
IPHostEntry ipHostInfo = Dns.Resolve(Dns.GetHostName());
IPAddress ipAddress = ipHostInfo.AddressList[0];
IPEndPoint remoteEP = new IPEndPoint(ipAddress, port);

// Create a TCP/IP socket.
Socket client = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);

// Connect to the remote endpoint.
client.BeginConnect( remoteEP, 
new AsyncCallback(ConnectCallback), client);
connectDone.WaitOne();

// Send test data to the remote device.
Send(client,"This is a test");
sendDone.WaitOne();

// Receive the response from the remote device.
Receive(client);
receiveDone.WaitOne();

// Write the response to the console.
Console.WriteLine("Response received : {0}", response);

// Release the socket.
client.Shutdown(SocketShutdown.Both);
client.Close();

} catch (Exception e) {
Console.WriteLine(e.ToString());
}
}

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());
}
}

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());
}
}

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());
}
}

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, 0,
new AsyncCallback(SendCallback), client);
}

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());
}
}

public static int Main(String[] args) {
StartClient();
return 0;
}
}

An asynchronous client socket does not suspend the application while waiting for network operations to complete. Instead, it uses the standard .NET Framework asynchronous programming model to process the network connection on one thread while the application continues to run on the original thread. Asynchronous sockets are appropriate for applications that make heavy use of the network or that cannot wait for network operations to complete before continuing.

The Socket class follows the .NET Framework naming pattern for asynchronous methods; for example, the synchronous Receive method corresponds to the asynchronous BeginReceive and EndReceive methods.

Asynchronous operations require a callback method to return the result of the operation. If your application does not need to know the result, then no callback method is required. The example code in this section demonstrates using a method to start connecting to a network device and a callback method to complete the connection, a method to start sending data and a callback method to complete the send, and a method to start receiving data and a callback method to end receiving data.

Asynchronous sockets use multiple threads from the system thread pool to process network connections. One thread is responsible for initiating the sending or receiving of data; other threads complete the connection to the network device and send or receive the data. In the following examples, instances of the System.Threading.ManualResetEvent class are used to suspend execution of the main thread and signal when execution can continue.

In the following example, to connect an asynchronous socket to a network device, the Connect method initializes a Socket and then calls the BeginConnect method, passing a remote endpoint that represents the network device, the connect callback method, and a state object (the client Socket), which is used to pass state information between asynchronous calls. The example implements the Connect method to connect the specified Socket to the specified endpoint. It assumes a global ManualResetEvent named connectDone.

Code For VB
Public Shared Sub Connect(remoteEP As EndPoint, client As Socket)
client.BeginConnect(remoteEP, _
AddressOf ConnectCallback, client)

connectDone.WaitOne()
End Sub 'Connect

Code For C#
public static void Connect(EndPoint remoteEP, Socket client) {
client.BeginConnect(remoteEP, 
new AsyncCallback(ConnectCallback), client );

connectDone.WaitOne();

The connect callback method ConnectCallback implements the AsyncCallback delegate. It connects to the remote device when the remote device is available and then signals the application thread that the connection is complete by setting the ManualResetEvent connectDone. The following code implements the ConnectCallback method.

Code For VB
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

Code For 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());
}
}

The example method Send encodes the specified string data in ASCII format and sends it asynchronously to the network device represented by the specified socket. The following example implements the Send method.

Code For VB
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

Code For 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);
}


The send callback method SendCallback implements the AsyncCallback delegate. It sends the data when the network device is ready to receive. The following example shows the implementation of the SendCallback method. It assumes a global ManualResetEvent named sendDone.

Code For VB
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

Code For 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());
}
}

Reading data from a client socket requires a state object that passes values between asynchronous calls. The following class is an example state object for receiving data from a client socket. It contains a field for the client socket, a buffer for the received data, and a StringBuilder to hold the incoming data string. Placing these fields in the state object allows their values to be preserved across multiple calls to read data from the client socket.

Code For VB
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

Code For C#
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();
}

The example Receive method sets up the state object and then calls the BeginReceive method to read the data from the client socket asynchronously. The following example implements the Receive method.

Code For VB
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

Code For 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());
}
}

The receive callback method ReceiveCallback implements the AsyncCallback delegate. It receives the data from the network device and builds a message string. It reads one or more bytes of data from the network into the data buffer and then calls the BeginReceive method again until the data sent by the client is complete. Once all the data is read from the client, ReceiveCallback signals the application thread that the data is complete by setting the ManualResetEvent sendDone.

The following example code implements the ReceiveCallback method. It assumes a global string named response that holds the received string and a global ManualResetEvent named receiveDone. The server must shut down the client socket gracefully to end the network session.

Code For VB
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

Code For C#
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());
}
}

Download project and code here

No comments:

Post a Comment