Sunday, May 02, 2010

Async Socket Server Sample in C#

Networking Samples for .NET v4.0 contains some cool samples.

They also provide an example for an async socket server and client. very cool stuff!


Asynchronous Socket Server Sample
=================================

This sample demonstrates how to use the xxxAsync (xxx = receive, send, connect, etc) methods on the Sytem.Net.Sockets.Socket
class by implementing an echo server (ie. The server sends all the data read from a client back to the client).
The echo server implemented in this sample handles multiple clients simultaneously (up to a maximum specified as a command line argument)
and highlights some of the key elements of the event-based asynchronous socket methods.
The sample illustrates creating a pool of reusable data buffers and SocketAsyncEventArgs context objects as a method to
increase server performance.

The sample is intended for educational purposes and should not be used directly in production applications.



Sample Language Implementations
===============================
This sample is available in the following language implementations:
C#

Prerequisites
=============
This sample requires the .NET Framework v4.0



Building the Sample
===================
To build the sample using Visual Studio (preferred method):

1. Double-click the AsyncSocketServer.sln file to open the socket server sample in Visual Studio.

2. Using the menu, click Build > Build Solution.


To build the sample using the command prompt:

1. Open the Command Prompt window and navigate to the directory containing the socket server sample.

2. Type msbuild AsyncSocketServer.sln.


Running the Sample
==================
The socket server requires four command line parameters.


Usage:

AsyncSocketServer.exe <#connections>





# Connections: The maximum number of connections the server will accept simultaneously.

Receive Size in Bytes: The buffer size used by the server for each receive operation.

Address family: The address family of the socket the server will use to listen for incoming connections. Supported values are ‘ipv4’ and ‘ipv6’.

Local Port Number: The port to which the server will bind.



Example:

AsyncSocketServer.exe 500 1024 ipv4 8000




The client part:



using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;

//Implements a sample socket client to connect to the server implmented in the AsyncSocketServer project
//Usage: AsyncSocketClient.exe <destination IP address> <destination port number>

//Destination IP address: The IP Address of the server to connect to
//Destination Port Number: The port number to connect to

namespace AsyncSocketClient
{
class Program
{
static ManualResetEvent clientDone = new ManualResetEvent(false);

static void Main(string[] args)
{
IPAddress destinationAddr = null; // IP Address of server to connect to
int destinationPort = 0; // Port number of server
SocketAsyncEventArgs socketEventArg = new SocketAsyncEventArgs();


if (args.Length != 2)
{
Console.WriteLine("Usage: AsyncSocketClient.exe <destination IP address> <destination port number>");
}
try
{
destinationAddr = IPAddress.Parse(args[0]);
destinationPort = int.Parse(args[1]);
if (destinationPort <= 0){
throw new ArgumentException("Destination port number provided cannot be less than or equal to 0");
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
Console.WriteLine("Usage: AsyncSocketClient.exe <destination IP address> <destination port number>");
}

// Create a socket and connect to the server
Socket sock = new Socket(destinationAddr.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
socketEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(SocketEventArg_Completed);
socketEventArg.RemoteEndPoint = new IPEndPoint(destinationAddr, destinationPort);
socketEventArg.UserToken = sock;
sock.ConnectAsync(socketEventArg);
clientDone.WaitOne();
}

/// <summary>
/// A single callback is used for all socket operations. This method forwards execution on to the correct handler
/// based on the type of completed operation
/// </summary>
static void SocketEventArg_Completed(object sender, SocketAsyncEventArgs e)
{
switch (e.LastOperation)
{
case SocketAsyncOperation.Connect:
ProcessConnect(e);
break;
case SocketAsyncOperation.Receive:
ProcessReceive(e);
break;
case SocketAsyncOperation.Send:
ProcessSend(e);
break;
default:
throw new Exception("Invalid operation completed");
}
}

/// <summary>
/// Called when a ConnectAsync operation completes
/// </summary>
private static void ProcessConnect(SocketAsyncEventArgs e)
{
if (e.SocketError == SocketError.Success)
{
Console.WriteLine("Successfully connected to the server");

// Send 'Hello World' to the server
byte[] buffer = Encoding.UTF8.GetBytes("Hello World");
e.SetBuffer(buffer, 0, buffer.Length);
Socket sock = e.UserToken as Socket;
bool willRaiseEvent = sock.SendAsync(e);
if (!willRaiseEvent)
{
ProcessSend(e);
}
}
else
{
throw new SocketException((int)e.SocketError);
}
}

/// <summary>
/// Called when a ReceiveAsync operation completes
/// </summary>
private static void ProcessReceive(SocketAsyncEventArgs e)
{
if (e.SocketError == SocketError.Success)
{
Console.WriteLine("Received from server: {0}", Encoding.UTF8.GetString(e.Buffer, 0, e.BytesTransferred));

// Data has now been sent and received from the server. Disconnect from the server
Socket sock = e.UserToken as Socket;
sock.Shutdown(SocketShutdown.Send);
sock.Close();
clientDone.Set();
}
else
{
throw new SocketException((int)e.SocketError);
}
}


/// <summary>
/// Called when a SendAsync operation completes
/// </summary>
private static void ProcessSend(SocketAsyncEventArgs e)
{
if (e.SocketError == SocketError.Success)
{
Console.WriteLine("Sent 'Hello World' to the server");

//Read data sent from the server
Socket sock = e.UserToken as Socket;
bool willRaiseEvent = sock.ReceiveAsync(e);
if (!willRaiseEvent)
{
ProcessReceive(e);
}
}
else
{
throw new SocketException((int)e.SocketError);
}
}
}
}



The server classes:


using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;

namespace AsyncSocketSample
{

/// <summary>
/// This class is designed for use as the object to be assigned to the SocketAsyncEventArgs.UserToken property.
/// </summary>
class AsyncUserToken
{
Socket m_socket;

public AsyncUserToken() : this(null) { }

public AsyncUserToken(Socket socket)
{
m_socket = socket;
}

public Socket Socket
{
get { return m_socket; }
set { m_socket = value; }
}

}
}



using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;

namespace AsyncSocketSample
{
/// <summary>
/// This class creates a single large buffer which can be divided up and assigned to SocketAsyncEventArgs objects for use
/// with each socket I/O operation. This enables bufffers to be easily reused and gaurds against fragmenting heap memory.
///
/// The operations exposed on the BufferManager class are not thread safe.
/// </summary>
class BufferManager
{
int m_numBytes; // the total number of bytes controlled by the buffer pool
byte[] m_buffer; // the underlying byte array maintained by the Buffer Manager
Stack<int> m_freeIndexPool; //
int m_currentIndex;
int m_bufferSize;

public BufferManager(int totalBytes, int bufferSize)
{
m_numBytes = totalBytes;
m_currentIndex = 0;
m_bufferSize = bufferSize;
m_freeIndexPool = new Stack<int>();
}

/// <summary>
/// Allocates buffer space used by the buffer pool
/// </summary>
public void InitBuffer()
{
// create one big large buffer and divide that out to each SocketAsyncEventArg object
m_buffer = new byte[m_numBytes];
}

/// <summary>
/// Assigns a buffer from the buffer pool to the specified SocketAsyncEventArgs object
/// </summary>
/// <returns>true if the buffer was successfully set, else false</returns>
public bool SetBuffer(SocketAsyncEventArgs args)
{

if (m_freeIndexPool.Count > 0)
{
args.SetBuffer(m_buffer, m_freeIndexPool.Pop(), m_bufferSize);
}
else
{
if ((m_numBytes - m_bufferSize) < m_currentIndex)
{
return false;
}
args.SetBuffer(m_buffer, m_currentIndex, m_bufferSize);
m_currentIndex += m_bufferSize;
}
return true;
}

/// <summary>
/// Removes the buffer from a SocketAsyncEventArg object. This frees the buffer back to the
/// buffer pool
/// </summary>
public void FreeBuffer(SocketAsyncEventArgs args)
{
m_freeIndexPool.Push(args.Offset);
args.SetBuffer(null, 0, 0);
}


}
}



using System;
using System.Collections.Generic;
using System.Text;
using System.Net;


//This project implements an echo socket server.
//The socket server requires four command line parameters:
//Usage: AsyncSocketServer.exe <#connections> <Receive Size In Bytes> <address family: ipv4 | ipv6> <Local Port Number>

//# Connections: The maximum number of connections the server will accept simultaneously.
//Receive Size in Bytes: The buffer size used by the server for each receive operation.
//Address family: The address family of the socket the server will use to listen for incoming connections. Supported values are ‘ipv4’ and ‘ipv6’.
//Local Port Number: The port the server will bind to.

//Example: AsyncSocketServer.exe 500 1024 ipv4 8000



namespace AsyncSocketSample
{
class Program
{
static void Main(string[] args)
{
int numConnections;
int receiveSize;
IPEndPoint localEndPoint;
int port;

// parse command line parameters
//format: #connections, receive size per connection, address family, port num
if (args.Length < 4)
{
Console.WriteLine("Usage: AsyncSocketServer.exe <#connections> <receiveSizeInBytes> <address family: ipv4 | ipv6> <Local Port Number>");
return;
}

try
{
numConnections = int.Parse(args[0]);
receiveSize = int.Parse(args[1]);
string addressFamily = args[2].ToLower();
port = int.Parse(args[3]);


if (numConnections <= 0)
{
throw new ArgumentException("The number of connections specified must be greater than 0");
}
if (receiveSize <= 0)
{
throw new ArgumentException("The receive size specified must be greater than 0");
}
if (port <= 0)
{
throw new ArgumentException("The port specified must be greater than 0");
}

// This sample supports two address family types: ipv4 and ipv6
if (addressFamily.Equals("ipv4"))
{
localEndPoint = new IPEndPoint(IPAddress.Any, port);
}
else if (addressFamily.Equals("ipv6"))
{
localEndPoint = new IPEndPoint(IPAddress.IPv6Any, port);
}
else
{
throw new ArgumentException("Invalid address family specified");
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
Console.WriteLine("Usage: AsyncSocketServer.exe <#connections> <receiveSizeInBytes> <address family: ipv4 | ipv6> <Local Port Number>");
return;
}

Console.WriteLine("Press any key to start the server ...");
Console.ReadKey();

// Start the server listening for incoming connection requests
Server server = new Server(numConnections, receiveSize);
server.Init();
server.Start(localEndPoint);


}
}
}





using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Threading;


namespace AsyncSocketSample
{
/// <summary>
/// Implements the connection logic for the socket server. After accepting a connection, all data read
/// from the client is sent back to the client. The read and echo back to the client pattern is continued
/// until the client disconnects.
/// </summary>
class Server
{
private int m_numConnections; // the maximum number of connections the sample is designed to handle simultaneously
private int m_receiveBufferSize;// buffer size to use for each socket I/O operation
BufferManager m_bufferManager; // represents a large reusable set of buffers for all socket operations
const int opsToPreAlloc = 2; // read, write (don't alloc buffer space for accepts)
Socket listenSocket; // the socket used to listen for incoming connection requests
// pool of reusable SocketAsyncEventArgs objects for write, read and accept socket operations
SocketAsyncEventArgsPool m_readWritePool;
int m_totalBytesRead; // counter of the total # bytes received by the server
int m_numConnectedSockets; // the total number of clients connected to the server
Semaphore m_maxNumberAcceptedClients;

/// <summary>
/// Create an uninitialized server instance. To start the server listening for connection requests
/// call the Init method followed by Start method
/// </summary>
/// <param name="numConnections">the maximum number of connections the sample is designed to handle simultaneously</param>
/// <param name="receiveBufferSize">buffer size to use for each socket I/O operation</param>
public Server(int numConnections, int receiveBufferSize)
{
m_totalBytesRead = 0;
m_numConnectedSockets = 0;
m_numConnections = numConnections;
m_receiveBufferSize = receiveBufferSize;
// allocate buffers such that the maximum number of sockets can have one outstanding read and
//write posted to the socket simultaneously
m_bufferManager = new BufferManager(receiveBufferSize * numConnections * opsToPreAlloc,
receiveBufferSize);

m_readWritePool = new SocketAsyncEventArgsPool(numConnections);
m_maxNumberAcceptedClients = new Semaphore(numConnections, numConnections);
}

/// <summary>
/// Initializes the server by preallocating reusable buffers and context objects. These objects do not
/// need to be preallocated or reused, by is done this way to illustrate how the API can easily be used
/// to create reusable objects to increase server performance.
/// </summary>
public void Init()
{
// Allocates one large byte buffer which all I/O operations use a piece of. This gaurds
// against memory fragmentation
m_bufferManager.InitBuffer();

// preallocate pool of SocketAsyncEventArgs objects
SocketAsyncEventArgs readWriteEventArg;

for (int i = 0; i < m_numConnections; i++)
{
//Pre-allocate a set of reusable SocketAsyncEventArgs
readWriteEventArg = new SocketAsyncEventArgs();
readWriteEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(IO_Completed);
readWriteEventArg.UserToken = new AsyncUserToken();

// assign a byte buffer from the buffer pool to the SocketAsyncEventArg object
m_bufferManager.SetBuffer(readWriteEventArg);

// add SocketAsyncEventArg to the pool
m_readWritePool.Push(readWriteEventArg);
}

}

/// <summary>
/// Starts the server such that it is listening for incoming connection requests.
/// </summary>
/// <param name="localEndPoint">The endpoint which the server will listening for conenction requests on</param>
public void Start(IPEndPoint localEndPoint)
{
// create the socket which listens for incoming connections
listenSocket = new Socket(localEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
listenSocket.Bind(localEndPoint);
// start the server with a listen backlog of 100 connections
listenSocket.Listen(100);

// post accepts on the listening socket
StartAccept(null);

//Console.WriteLine("{0} connected sockets with one outstanding receive posted to each....press any key", m_outstandingReadCount);
Console.WriteLine("Press any key to terminate the server process....");
Console.ReadKey();
}


/// <summary>
/// Begins an operation to accept a connection request from the client
/// </summary>
/// <param name="acceptEventArg">The context object to use when issuing the accept operation on the
/// server's listening socket</param>
public void StartAccept(SocketAsyncEventArgs acceptEventArg)
{
if (acceptEventArg == null)
{
acceptEventArg = new SocketAsyncEventArgs();
acceptEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(AcceptEventArg_Completed);
}
else
{
// socket must be cleared since the context object is being reused
acceptEventArg.AcceptSocket = null;
}

m_maxNumberAcceptedClients.WaitOne();
bool willRaiseEvent = listenSocket.AcceptAsync(acceptEventArg);
if (!willRaiseEvent)
{
ProcessAccept(acceptEventArg);
}
}

/// <summary>
/// This method is the callback method associated with Socket.AcceptAsync operations and is invoked
/// when an accept operation is complete
/// </summary>
void AcceptEventArg_Completed(object sender, SocketAsyncEventArgs e)
{
ProcessAccept(e);
}

private void ProcessAccept(SocketAsyncEventArgs e)
{
Interlocked.Increment(ref m_numConnectedSockets);
Console.WriteLine("Client connection accepted. There are {0} clients connected to the server",
m_numConnectedSockets);

// Get the socket for the accepted client connection and put it into the
//ReadEventArg object user token
SocketAsyncEventArgs readEventArgs = m_readWritePool.Pop();
((AsyncUserToken)readEventArgs.UserToken).Socket = e.AcceptSocket;

// As soon as the client is connected, post a receive to the connection
bool willRaiseEvent = e.AcceptSocket.ReceiveAsync(readEventArgs);
if(!willRaiseEvent){
ProcessReceive(readEventArgs);
}

// Accept the next connection request
StartAccept(e);
}

/// <summary>
/// This method is called whenever a receive or send opreation is completed on a socket
/// </summary>
/// <param name="e">SocketAsyncEventArg associated with the completed receive operation</param>
void IO_Completed(object sender, SocketAsyncEventArgs e)
{
// determine which type of operation just completed and call the associated handler
switch (e.LastOperation)
{
case SocketAsyncOperation.Receive:
ProcessReceive(e);
break;
case SocketAsyncOperation.Send:
ProcessSend(e);
break;
default:
throw new ArgumentException("The last operation completed on the socket was not a receive or send");
}

}

/// <summary>
/// This method is invoked when an asycnhronous receive operation completes. If the
/// remote host closed the connection, then the socket is closed. If data was received then
/// the data is echoed back to the client.
/// </summary>
private void ProcessReceive(SocketAsyncEventArgs e)
{
// check if the remote host closed the connection
AsyncUserToken token = (AsyncUserToken)e.UserToken;
if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success)
{
//increment the count of the total bytes receive by the server
Interlocked.Add(ref m_totalBytesRead, e.BytesTransferred);
Console.WriteLine("The server has read a total of {0} bytes", m_totalBytesRead);

//echo the data received back to the client
bool willRaiseEvent = token.Socket.SendAsync(e);
if (!willRaiseEvent)
{
ProcessSend(e);
}

}
else
{
CloseClientSocket(e);
}
}

/// <summary>
/// This method is invoked when an asynchronous send operation completes. The method issues another receive
/// on the socket to read any additional data sent from the client
/// </summary>
/// <param name="e"></param>
private void ProcessSend(SocketAsyncEventArgs e)
{
if (e.SocketError == SocketError.Success)
{
// done echoing data back to the client
AsyncUserToken token = (AsyncUserToken)e.UserToken;
// read the next block of data send from the client
bool willRaiseEvent = token.Socket.ReceiveAsync(e);
if (!willRaiseEvent)
{
ProcessReceive(e);
}
}
else
{
CloseClientSocket(e);
}
}

private void CloseClientSocket(SocketAsyncEventArgs e)
{
AsyncUserToken token = e.UserToken as AsyncUserToken;

// close the socket associated with the client
try
{
token.Socket.Shutdown(SocketShutdown.Send);
}
// throws if client process has already closed
catch (Exception) { }
token.Socket.Close();

// decrement the counter keeping track of the total number of clients connected to the server
Interlocked.Decrement(ref m_numConnectedSockets);
m_maxNumberAcceptedClients.Release();
Console.WriteLine("A client has been disconnected from the server. There are {0} clients connected to the server", m_numConnectedSockets);

// Free the SocketAsyncEventArg so they can be reused by another client
m_readWritePool.Push(e);
}

}
}



using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;

namespace AsyncSocketSample
{
/// <summary>
/// Represents a collection of resusable SocketAsyncEventArgs objects.
/// </summary>
class SocketAsyncEventArgsPool
{
Stack<SocketAsyncEventArgs> m_pool;

/// <summary>
/// Initializes the object pool to the specified size
/// </summary>
/// <param name="capacity">The maximum number of SocketAsyncEventArgs objects the pool can hold</param>
public SocketAsyncEventArgsPool(int capacity)
{
m_pool = new Stack<SocketAsyncEventArgs>(capacity);
}

/// <summary>
/// Add a SocketAsyncEventArg instance to the pool
/// </summary>
/// <param name="item">The SocketAsyncEventArgs instance to add to the pool</param>
public void Push(SocketAsyncEventArgs item)
{
if (item == null) { throw new ArgumentNullException("Items added to a SocketAsyncEventArgsPool cannot be null"); }
lock (m_pool)
{
m_pool.Push(item);
}
}

/// <summary>
/// Removes a SocketAsyncEventArgs instance from the pool
/// </summary>
/// <returns>The object removed from the pool</returns>
public SocketAsyncEventArgs Pop()
{
lock (m_pool)
{
return m_pool.Pop();
}
}

/// <summary>
/// The number of SocketAsyncEventArgs instances in the pool
/// </summary>
public int Count
{
get { return m_pool.Count; }
}

}
}

Shared Cache - .Net Caching made easy

All information about Shared Cache is available here: http://www.sharedcache.com/. Its free and easy to use, we provide all sources at codeplex.

Facebook Badge