Thursday, May 5, 2011

Tutorial Asynchronous Socket Programming using .NET (For Server)

The following example program creates a server that receives connection requests from clients. The server is built with an asynchronous socket, so execution of the server application is not suspended while it waits for a connection from a client. The application receives a string from the client, displays the string on the console, and then echoes the string back to the client. The string from the client must contain the string "" to signal the end of the message.


Code For VB



Imports System
Imports System.Net
Imports System.Net.Sockets
Imports System.Text
Imports System.Threading
Imports Microsoft.VisualBasic

' State object for reading client data asynchronously

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


Public Class AsynchronousSocketListener
' Thread signal.
Public Shared allDone As New ManualResetEvent(False)

' This server waits for a connection and then uses  asychronous operations to
' accept the connection, get data from the connected client, 
' echo that data back to the connected client.
' It then disconnects from the client and waits for another client. 
Public Shared Sub Main()
' Data buffer for incoming data.
Dim bytes() As Byte = New [Byte](1023) {}

' Establish the local endpoint for the socket.
Dim ipHostInfo As IPHostEntry = Dns.Resolve(Dns.GetHostName())
Dim ipAddress As IPAddress = ipHostInfo.AddressList(0)
Dim localEndPoint As New IPEndPoint(ipAddress, 11000)

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

' Bind the socket to the local endpoint and listen for incoming connections.
listener.Bind(localEndPoint)
listener.Listen(100)

While True
' Set the event to nonsignaled state.
allDone.Reset()

' Start an asynchronous socket to listen for connections.
Console.WriteLine("Waiting for a connection...")
listener.BeginAccept(New AsyncCallback(AddressOf AcceptCallback), listener)

' Wait until a connection is made and processed before continuing.
allDone.WaitOne()
End While
End Sub 'Main


Public Shared Sub AcceptCallback(ByVal ar As IAsyncResult)
' Get the socket that handles the client request.
Dim listener As Socket = CType(ar.AsyncState, Socket)
' End the operation.
Dim handler As Socket = listener.EndAccept(ar)

' Create the state object for the async receive.
Dim state As New StateObject
state.workSocket = handler
handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, New AsyncCallback(AddressOf ReadCallback), state)
End Sub 'AcceptCallback


Public Shared Sub ReadCallback(ByVal ar As IAsyncResult)
Dim content As String = String.Empty

' Retrieve the state object and the handler socket
' from the asynchronous state object.
Dim state As StateObject = CType(ar.AsyncState, StateObject)
Dim handler As Socket = state.workSocket

' Read data from the client socket. 
Dim bytesRead As Integer = handler.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))

' Check for end-of-file tag. If it is not there, read 
' more data.
content = state.sb.ToString()
If content.IndexOf("") > -1 Then
' All the data has been read from the 
' client. Display it on the console.
Console.WriteLine("Read {0} bytes from socket. " + vbLf + " Data : {1}", content.Length, content)
' Echo the data back to the client.
Send(handler, content)
Else
' Not all data received. Get more.
handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, New AsyncCallback(AddressOf ReadCallback), state)
End If
End If
End Sub 'ReadCallback

Private Shared Sub Send(ByVal handler 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.
handler.BeginSend(byteData, 0, byteData.Length, 0, New AsyncCallback(AddressOf SendCallback), handler)
End Sub 'Send


Private Shared Sub SendCallback(ByVal ar As IAsyncResult)
' Retrieve the socket from the state object.
Dim handler As Socket = CType(ar.AsyncState, Socket)

' Complete sending the data to the remote device.
Dim bytesSent As Integer = handler.EndSend(ar)
Console.WriteLine("Sent {0} bytes to client.", bytesSent)

handler.Shutdown(SocketShutdown.Both)
handler.Close()
' Signal the main thread to continue.
allDone.Set()
End Sub 'SendCallback
End Class 'AsynchronousSocketListener

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

// State object for reading client data asynchronously
public class StateObject {
// Client  socket.
public Socket workSocket = null;
// Size of receive buffer.
public const int BufferSize = 1024;
// Receive buffer.
public byte[] buffer = new byte[BufferSize];
// Received data string.
public StringBuilder sb = new StringBuilder();  
}

public class AsynchronousSocketListener {
// Thread signal.
public static ManualResetEvent allDone = new ManualResetEvent(false);

public AsynchronousSocketListener() {
}

public static void StartListening() {
// Data buffer for incoming data.
byte[] bytes = new Byte[1024];

// Establish the local endpoint for the socket.
// The DNS name of the computer
// running the listener is "host.contoso.com".
IPHostEntry ipHostInfo = Dns.Resolve(Dns.GetHostName());
IPAddress ipAddress = ipHostInfo.AddressList[0];
IPEndPoint localEndPoint = new IPEndPoint(ipAddress, 11000);

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

// Bind the socket to the local endpoint and listen for incoming connections.
try {
listener.Bind(localEndPoint);
listener.Listen(100);

while (true) {
// Set the event to nonsignaled state.
allDone.Reset();

// Start an asynchronous socket to listen for connections.
Console.WriteLine("Waiting for a connection...");
listener.BeginAccept( 
new AsyncCallback(AcceptCallback),
listener );

// Wait until a connection is made before continuing.
allDone.WaitOne();
}

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

Console.WriteLine("\nPress ENTER to continue...");
Console.Read();

}

public static void AcceptCallback(IAsyncResult ar) {
// Signal the main thread to continue.
allDone.Set();

// Get the socket that handles the client request.
Socket listener = (Socket) ar.AsyncState;
Socket handler = listener.EndAccept(ar);

// Create the state object.
StateObject state = new StateObject();
state.workSocket = handler;
handler.BeginReceive( state.buffer, 0, StateObject.BufferSize, 0,
new AsyncCallback(ReadCallback), state);
}

public static void ReadCallback(IAsyncResult ar) {
String content = String.Empty;

// Retrieve the state object and the handler socket
// from the asynchronous state object.
StateObject state = (StateObject) ar.AsyncState;
Socket handler = state.workSocket;

// Read data from the client socket. 
int bytesRead = handler.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));

// Check for end-of-file tag. If it is not there, read 
// more data.
content = state.sb.ToString();
if (content.IndexOf("") > -1) {
// All the data has been read from the 
// client. Display it on the console.
Console.WriteLine("Read {0} bytes from socket. \n Data : {1}",
content.Length, content );
// Echo the data back to the client.
Send(handler, content);
} else {
// Not all data received. Get more.
handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
new AsyncCallback(ReadCallback), state);
}
}
}

private static void Send(Socket handler, 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.
handler.BeginSend(byteData, 0, byteData.Length, 0,
new AsyncCallback(SendCallback), handler);
}

private static void SendCallback(IAsyncResult ar) {
try {
// Retrieve the socket from the state object.
Socket handler = (Socket) ar.AsyncState;

// Complete sending the data to the remote device.
int bytesSent = handler.EndSend(ar);
Console.WriteLine("Sent {0} bytes to client.", bytesSent);

handler.Shutdown(SocketShutdown.Both);
handler.Close();

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


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

Asynchronous server sockets use the .NET Framework asynchronous programming model to process network service requests. The Socket class follows the standard .NET Framework asynchronous naming pattern; for example, the synchronous Accept method corresponds to the asynchronous BeginAccept and EndAccept methods.

An asynchronous server socket requires a method to begin accepting connection requests from the network, a callback method to handle the connection requests and begin receiving data from the network, and a callback method to end receiving the data. All these methods are discussed further in this section.

In the following example, to begin accepting connection requests from the network, the method StartListening initializes the Socket and then uses the BeginAccept method to start accepting new connections. The accept callback method is called when a new connection request is received on the socket. It is responsible for getting the Socket instance that will handle the connection and handing that Socket off to the thread that will process the request. The accept callback method implements the AsyncCallback delegate; it returns a void and takes a single parameter of type IAsyncResult. The following example is the shell of an accept callback method.

Code For VB
Sub acceptCallback(ar As IAsyncResult)
' Add the callback code here.
End Sub 'acceptCallback

Code For C#
void acceptCallback( IAsyncResult ar) {
// Add the callback code here.
}

The BeginAccept method takes two parameters, an AsyncCallback delegate that points to the accept callback method and an object that is used to pass state information to the callback method. In the following example, the listening Socket is passed to the callback method through the state parameter. This example creates an AsyncCallback delegate and starts accepting connections from the network.

Code For VB
listener.BeginAccept( _
New AsyncCallback(SocketListener.acceptCallback),_
listener)

Code For C#
listener.BeginAccept(
new AsyncCallback(SocketListener.acceptCallback), 
listener);

Asynchronous sockets use threads from the system thread pool to process incoming connections. One thread is responsible for accepting connections, another thread is used to handle each incoming connection, and another thread is responsible for receiving data from the connection. These could be the same thread, depending on which thread is assigned by the thread pool. In the following example, the System.Threading..::.ManualResetEvent class suspends execution of the main thread and signals when execution can continue.

The following example shows an asynchronous method that creates an asynchronous TCP/IP socket on the local computer and begins accepting connections. It assumes that there is a global ManualResetEvent named allDone, that the method is a member of a class named SocketListener, and that a callback method named acceptCallback is defined.

Code For VB
Public Sub StartListening()
Dim ipHostInfo As IPHostEntry = Dns.Resolve(Dns.GetHostName())
Dim localEP = New IPEndPoint(ipHostInfo.AddressList(0), 11000)

Console.WriteLine("Local address and port : {0}", localEP.ToString())

Dim listener As New Socket(localEP.Address.AddressFamily, _
SocketType.Stream, ProtocolType.Tcp)

Try
listener.Bind(localEP)
listener.Listen(10)

While True
allDone.Reset()

Console.WriteLine("Waiting for a connection...")
listener.BeginAccept(New _
AsyncCallback(SocketListener.acceptCallback), _
listener)

allDone.WaitOne()
End While
Catch e As Exception
Console.WriteLine(e.ToString())
End Try
Console.WriteLine("Closing the listener...")
End Sub 'StartListening

Code For C#
public void StartListening() {
IPHostEntry ipHostInfo = 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);
listener.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...");
}

The accept callback method (acceptCallback in the preceding example) is responsible for signaling the main application thread to continue processing, establishing the connection with the client, and starting the asynchronous read of data from the client. The following example is the first part of an implementation of the acceptCallback method. This section of the method signals the main application thread to continue processing and establishes the connection to the client. It assumes a global ManualResetEvent named allDone.

Code For VB
Public Sub acceptCallback(ar As IAsyncResult)
allDone.Set()

Dim listener As Socket = CType(ar.AsyncState, Socket)
Dim handler As Socket = listener.EndAccept(ar)

' Additional code to read data goes here.
End Sub 'acceptCallback

Code For C#
public void acceptCallback(IAsyncResult ar) {
allDone.Set();

Socket listener = (Socket) ar.AsyncState;
Socket handler = listener.EndAccept(ar);

// Additional code to read data goes here.  
}

Reading data from a client socket requires a state object that passes values between asynchronous calls. The following example implements a state object for receiving a string from the remote client. It contains fields for the client socket, a data buffer for receiving data, and a StringBuilder for creating the data string sent by the client. 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
Public workSocket As Socket = Nothing
Public BufferSize As Integer = 1024
Public buffer(BufferSize) As Byte
Public sb As New StringBuilder()
End Class 'StateObject

Code For C#
public class StateObject {
public Socket workSocket = null;
public const int BufferSize = 1024;
public byte[] buffer = new byte[BufferSize];
public StringBuilder sb = new StringBuilder();
}

The section of the acceptCallback method that starts receiving the data from the client socket first initializes an instance of the StateObject class and then calls the BeginReceive method to start reading the data from the client socket asynchronously.

The following example shows the complete acceptCallback method. It assumes that there is a global ManualResetEvent named allDone, that the StateObject class is defined, and that the readCallback method is defined in a class named SocketListener.

Code For VB
Public Shared Sub acceptCallback(ar As IAsyncResult)
' Get the socket that handles the client request.
Dim listener As Socket = CType(ar.AsyncState, Socket)
Dim handler As Socket = listener.EndAccept(ar)

' Signal the main thread to continue.
allDone.Set()

' Create the state object.
Dim state As New StateObject()
state.workSocket = handler
handler.BeginReceive(state.buffer, 0, state.BufferSize, 0, _
AddressOf AsynchronousSocketListener.readCallback, state)
End Sub 'acceptCallback

Code For C#
public static void acceptCallback(IAsyncResult ar) {
// Get the socket that handles the client request.
Socket listener = (Socket) ar.AsyncState;
Socket handler = listener.EndAccept(ar);

// Signal the main thread to continue.
allDone.Set();

// Create the state object.
StateObject state = new StateObject();
state.workSocket = handler;
handler.BeginReceive( state.buffer, 0, StateObject.BufferSize, 0,
new AsyncCallback(AsynchronousSocketListener.readCallback), state);
}

The final method that needs to be implemented for the asynchronous socket server is the read callback method that returns the data sent by the client. Like the accept callback method, the read callback method is an AsyncCallback delegate. This method reads one or more bytes from the client socket into the data buffer and then calls the BeginReceive method again until the data sent by the client is complete. Once the entire message has been read from the client, the string is displayed on the console and the server socket handling the connection to the client is closed.

The following sample implements the readCallback method. It assumes that the StateObject class is defined.

Code For VB
Public Shared Sub readCallback(ar As IAsyncResult)
Dim state As StateObject = CType(ar.AsyncState, StateObject)
Dim handler As Socket = state.workSocket

' Read data from the client socket. 
Dim read As Integer = handler.EndReceive(ar)

' Data was read from the client socket.
If read > 0 Then
state.sb.Append(Encoding.ASCII.GetString(state.buffer, 0, read))
handler.BeginReceive(state.buffer, 0, state.BufferSize, 0, _
AddressOf readCallback, state)
Else
If state.sb.Length > 1 Then
' All the data has been read from the client;
' display it on the console.
Dim content As String = state.sb.ToString()
Console.WriteLine("Read {0} bytes from socket." + _
ControlChars.Cr + " Data : {1}", content.Length, content)
End If
End If
End Sub 'readCallback

Code For C#
public static void readCallback(IAsyncResult ar) {
StateObject state = (StateObject) ar.AsyncState;
Socket handler = state.WorkSocket;

// Read data from the client socket.
int read = handler.EndReceive(ar);

// Data was read from the client socket.
if (read > 0) {
state.sb.Append(Encoding.ASCII.GetString(state.buffer,0,read));
handler.BeginReceive(state.buffer,0,StateObject.BufferSize, 0,
new AsyncCallback(readCallback), state);
} else {
if (state.sb.Length > 1) {
// All the data has been read from the client;
// display it on the console.
string content = state.sb.ToString();
Console.WriteLine("Read {0} bytes from socket.\n Data : {1}",
content.Length, content);
}
handler.Close();
}
} 
 
Download project and code here

No comments:

Post a Comment