Introduction
For any software that handles client requests, at some point, there must be a persistent point where all data related to the clients are stored. Those storages can be any thing (database, mainframes, files etc.). What if this software needs to access those storages with concurrent requests, knowing that opening a connection is memory and CPU consuming? A Connection Pool is a container of open and reusable connections. It will help you save both memory and CPU time. Also, it will help you in managing connections.
Background
Since we are using the TCP protocol in this example, you should know about basic TCP implementation in C#. You should also be familiar with the System.Collection.Generic
namespace and the .NET framework.
Using the code
This article describes a custom implementation for a connection pooling mechanism; we have two main classes: ConnectionPool
and CustomSocket
.
CustomSocket
is a representation for a simple TCPClient
.
public class CustomSocket:TcpClient
{
private DateTime _TimeCreated;
public DateTime TimeCreated
{
get { return _TimeCreated; }
set { _TimeCreated = value; }
}
public CustomSocket(string host,int port)
: base(host,port)
{
_TimeCreated = DateTime.Now;
}
}
ConnectionPool
is the operation manager for the pooling mechanism, and it contains the following:
The InitializeConnectionPool
function initializes the connection pool, where hostIpAddress
is the destination TCP server, hostPortNumber
is the destination TCP server access port, while minConnection
represents the minimum connection to be created. Since we can create a default number of connections, it is important to control the creation of a new connection or we will get an overflow. maxConnections
represents the maximum number to be created; an exception will be thrown if the value is exceeded.
public static void InitializeConnectionPool(string hostIPAddress,
int hostPortNumber, int minConnections, int maxConnections)
{
POOL_MAX_SIZE = maxConnections;
POOL_MIN_SIZE = minConnections;
hostIP = hostIPAddress;
hostPort = hostPortNumber;
availableSockets = new Queue<CustomSocket>();
for(int i=0 ; i < minConnections ; i++)
{
CustomSocket cachedSocket = OpenSocket();
PutSocket(cachedSocket);
}
Initialized = true;
System.Diagnostics.Trace.WriteLine("Connection Pool is initialized" +
" with Max Number of " +
POOL_MAX_SIZE.ToString() + " And Min number of " +
availableSockets.Count.ToString());
}
The GetSocket
function returns a connection object with two flows. If there is an available connection in the pool, it will pop a connection; if not, it will create a new connection.
public static CustomSocket GetSocket()
{
if (ConnectionPool.availableSockets.Count > 0)
{
lock (availableSockets)
{
CustomSocket socket = null;
while (ConnectionPool.availableSockets.Count > 0)
{
socket = ConnectionPool.availableSockets.Dequeue();
if (socket.Connected)
{
System.Diagnostics.Trace.WriteLine("Socket Dequeued -> Pool size: " +
ConnectionPool.availableSockets.Count.ToString());
return socket;
}
else
{
socket.Close();
System.Threading.Interlocked.Decrement(ref SocketCounter);
System.Diagnostics.Trace.WriteLine("GetSocket -- Close -- Count: " +
SocketCounter.ToString());
}
}
}
return ConnectionPool.OpenSocket();
}
The PutSocket
function is responsible for handling the disposing of the connection; if the maximum limit is reached, then the connection will be disposed; else, it will be queued.
public static void PutSocket(CustomSocket socket)
{
lock (availableSockets)
{
TimeSpan socketLifeTime = DateTime.Now.Subtract(socket.TimeCreated);
if (ConnectionPool.availableSockets.Count <
ConnectionPool.POOL_MAX_SIZE && socketLifeTime.Minutes < 2)
{
if (socket != null)
{
if (socket.Connected)
{
ConnectionPool.availableSockets.Enqueue(socket);
System.Diagnostics.Trace.WriteLine(
"Socket Queued -> Pool size: " +
ConnectionPool.availableSockets.Count.ToString());
}
else
{
socket.Close();
}
}
}
else
{
socket.Close();
System.Diagnostics.Trace.WriteLine("PutSocket - Socket is forced " +
"to closed -> Pool size: " +
ConnectionPool.availableSockets.Count.ToString());
}
}
}
This method is called whenever an exception is thrown to inform the connection pool that a socket has been disposed:
public static void PopulateSocketError()
{
System.Threading.Interlocked.Decrement(ref SocketCounter);
System.Diagnostics.Trace.WriteLine("Populate Socket Error host " +
"Connections count: " + SocketCounter.ToString());
}
To use this component, do as follows:
ConnectionPool.InitializeConnectionPool(hostIPAddress, hostPortNumber,
minConnections, maxConnections);
try
{
CustomSocket = ConnectionPool.GetSocket();
ConnectionPool.PutSocket();
}
catch(Exception)
{
ConnectionPool.PopulateSocketError();
}
Notes
Every socket has an expiry time; if expired, the socket will be disposed.
History