Glue IIS with Business Logical Tier using Batching, Asynchrony, and Parallel Computation


1. Introduction

By this time, N-tier applications (distributed applications) have become the norm for building enterprise software. In essence, an N-tier application consists of a number software parts running within different machines. To achieve such a distribution, data communications among these machines are critical for all real enterprise software. I created a framework, named as SocketPro, to glue all of software parts across different machines with batching, asynchrony and parallel computation using non-blocking socket. Recently, I have read an article entitled as Asynchronous Pages in ASP.Net 2.0. This article is very interesting to me, and leads me to think how to integrate SocketPro with ASP.NET for developing high performance web application based on batching and asynchrony computation using a pool of pre-opened sockets and database objects.

2. Client and server setups

Client setup:

To play with this provided sample, you need to register two COM dlls, usocket.dll and udb.dll within SocketPro after you download it from www.udaparts.com. Additionally, you need to get dotNet framework version 2.0, and Visual studio 2005 installed.

Server setup:

See the link here. Make sure that RemoteConnector.exe runs properly. Afterwards, follow the guide and make sure that uodbsvr.dll is loaded into the application RemoteConnector.exe. This sample is running against with MS Access sample database Northwind. If you don't use the database, you can go to the provided source file and modify OLEDB connection string to whatever database you have. Note you don't have to install any dotNet components because the sample server application doesn't dependent on dotNet.

3. Brief introduction to socket pool

A socket pool, similar to a thread pool, contains a number of sockets running within a few threads. Each of threads supports a few sockets. Socket pool manages required threads and contained sockets internally. The socket pool is created for supporting thousands of web/client requests from different machines. When an application starts, it pre-creates a pool of sockets that are connected to a remote host. Afterwards, we can bind an opened socket with one or more objects. Those objects use their bound sockets to process all of their requests. See the following Figure 1.

Figure 1: A socket pool contains six sockets hosted within three threads.

Like a thread pool, we can use a limited number of sockets shared by different requests. When a client sends a request, we first lock a socket and its bound objects, and send all your requests in one batch to a remote host. When a remote host processes all of them and returns their results back, we can unlock the socket and its bound objects, and release all of then as one unit back into the pool. As you can see, we can avoid creating sockets and objects and building connections repeatedly so that an application performance and scalability are significantly improved using a small amount of pre-allocated resource.

4. Incredibly easy and simple to integrate SocketPro with ASP.Net 2.0 asynchronous pages

SocketPro is written from asynchrony computation that enables us to integrate various objects with asp.net asynchronous pages very easily. Let us see how simple code can be.

A. Start a pool of sockets and bind objects one time only.

protected void StartProcessing()
{
bool bInit = CDBHandler.IsInit;
if (!bInit)
{
//Start a pool of sockets running in asynchronous mode.
//Each of sockets is bound with one opened UDataSource, one opened USession, One opened UComamnd and one URowset.
//All of 6 sockets with three threads are connected to a remote SocketPro server forever.
bInit = CDBHandler.InitDBPool(
3,
//the number of threads
2, //the number of sockets per thread
"localhost", //a remote host address like 111.222.212.121, www.mydomain.com or MyServerName
17001, //a port number
"SocketPro", //a user id
"PassOne", // a password
"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=c:\\NWIND3.mdb"
//an OLEDB connection string to a backend database
);
}
if (bInit)
{
//register ASP.NET 2 methods for processing requests asynchronously.
AddOnPreRenderCompleteAsync(new BeginEventHandler(BeginAsyncOperation), new EndEventHandler(EndAsyncOperation));
}
}
[MTAThread]
protected void btnExecuteSQL_Click(object sender, EventArgs e)
{
StartProcessing();
}

When a request comes, it kicks the function StartProcessing. Inside the function, it starts a pool of sockets and bind the pool with a set of database objects. It happens only one time after an application starts. Afterwards, the pool runs forever and we will never create any new sockets or objects. Also, we will never build a socket connection again.

B. Send all of requests in one batch.

IAsyncResult BeginAsyncOperation(object sender, EventArgs e, AsyncCallback cb, object state)
{
//must lock a socket and its bound UDataSource, USession, UComamnd and URowset
//objects first from the socket pool.
m_DBHandler = CDBHandler.LockDBHandler(cb);
if (m_DBHandler != null)
{
//batch any number of requests together.
m_DBHandler.Socket.StartBatching();
m_DBHandler.ExecuteNonQuery(idDeleteStatement.Text);
m_DBHandler.ExecuteQuery(idQueryOne.Text, "Orders", 0, 0);
CParamInfo[] aParamInfo =
new CParamInfo[2];
aParamInfo[0] =
new CParamInfo();
aParamInfo[1] =
new CParamInfo();
aParamInfo[0].m_sDBType = (
short)UDBLib.tagSockDataType.sdVT_R8;
aParamInfo[1].m_sDBType = (
short)UDBLib.tagSockDataType.sdVT_R8;
m_DBHandler.OpenCommandWithParameters("Select * from Products Where UnitPrice between ? and ?", aParamInfo);
object[] aData = new object[2];
aData[0] =
double.Parse(txtPriceMin.Text);
aData[1] =
double.Parse(txtPriceMax.Text);
//open rowset with a set of parameters
m_DBHandler.OpenQueryFromParameters(aData, "Products", 0, 0);
//Of course, you can batch more requests here.
//true -- informs the remote socketpro server to return all of returned results in batches
m_DBHandler.Socket.CommitBatching(true);
//At this moment, all of requests are sent to a remote server for processing in one batch.
}
return m_DBHandler;
}

We starts locking a DB handler that represents a socket and a set of its bound database obejcts like data source, session, command and rowset. Once getting the handler, we batch all requests and send them to remote for processing without waiting for return results for deleting a number of records and creating a DataSet containing two tables. This step is truly very fast.

C. Bind tables with web DataGrid controls when all of requests are processed and returned.

Here is the code:

void EndAsyncOperation(IAsyncResult ar)
{
DataTable dt;
//At this moment, all of requests are processed and returned.
//binding data
dt = m_DBHandler.DataSet.Tables["Orders"];
gvQueryOne.DataSource = dt;
gvQueryOne.DataBind();
dt = m_DBHandler.DataSet.Tables["Products"];
gvQueryTwo.DataSource = dt;
gvQueryTwo.DataBind();
//After this function is called,
//CDBHandler will automatically unlock its socket and DB objects,
//and release those objects back into pool for reuse by a new page.
}

That is all you need to create an asynchronous web page. As said before, we need to unlock a socket and its bound database objects. However, I don't find the code to unlock a previously locked DB handler. Why? Actually, internally CDBHandler automatically releases those objects and socket back into pool for reuse when it finds that all of batched requests are processed and returned.

5. Understanding implementation of class CDBHandler and extending it as you need

CBHandler wraps one socket and four OLEDB database objects, data source, session, command and rowset. It implements the interface IAsyncResult. CDBHandler uses USocketPool COM object (usocket.dll) to manage a pool of sockets. See the below code:

m_SocketPool = new USocketPoolClass();
m_SocketPool.StartPool(bThreads, bSocketsPerThread);
m_SocketPool.ConnectAll(strHost, nPort, (
int)USOCKETLib.tagServiceID.sidOleDB, strUID, strPassword, sEncryptionMethod, bZip); 

Once a socket pool is started, we conect all of sockets onto a remote host with a initial remote service indicated by a server id as shown above. However, at this time there is no database connection establised between a romote server and a backend database. To bind CDBHandler to a backend database, we use remote database service component udb.dll at client side and component uodbsvr.dll at server side as discribed in the Section 2. First of all, we create four client database objects and set up events for each of the four database objects.

m_Command = new UCommandClass();
m_Rowset =
new URowsetClass();
m_DataSource =
new UDataSourceClass();
m_Session =
new USessionClass();
m_DataSource.OnRequestProcessed +=
new _IURequestEvent_OnRequestProcessedEventHandler(OnDataSourceRequestProcessed);
m_Session.OnRequestProcessed +=
new _IURequestEvent_OnRequestProcessedEventHandler(OnSessionRequestProcessed);
m_Command.OnRequestProcessed +=
new _IURequestEvent_OnRequestProcessedEventHandler(OnCommandRequestProcessed);
m_Rowset.OnRequestProcessed +=
new _IURequestEvent_OnRequestProcessedEventHandler(OnRowsetRequestProcessed); 

Once we get an interface to a socket, we can attach all of four database objects to the socket by the following code. Additionally, we can subscribe an event on the socket level to monitor all of requests processed.

private bool AttachSocket(USocketClass ClientSocket)
{
if (ClientSocket == null)
return false;
m_DataSource.AttachSocket(ClientSocket);
m_Rowset.AttachSocket(ClientSocket);
m_Session.AttachSocket(ClientSocket);
m_Command.AttachSocket(ClientSocket);
m_ClientSocket = ClientSocket;
m_ClientSocket.OnRequestProcessed +=
new _IUSocketEvent_OnRequestProcessedEventHandler(OnAllRequestProcessed);
return true;
}

Here is this way how to find a socket from a socket pool and bind it with the four database objects. Basically, we lock a socket first and get a socket interface. Afterwards, attach four database objects with the socket and connect to a backend database through the socket server. At last, we unlock it and the socket is ready for reuse in socket pool. Note that the code below also increase sending and receiving buffer sizes for both client and server sockets. This will increase data output speed if your network bandwidth is 100 mbps or over.

private static bool BindDBhandlerWithSocket(string strDBConnectionString, int nIOBufferSize)
{
int n;
for (n = 0; n < m_SocketPool.ConnectedSockets; n++)
{
USocketClass ClientSocket = (USocketClass)m_SocketPool.LockASocket(0,
null);
if (ClientSocket != null)
{
CDBHandler DBHandler =
new CDBHandler();
DBHandler.AttachSocket(ClientSocket);
if (DBHandler.ConnectDB(strDBConnectionString))
{
m_aDBHandler.Add(DBHandler);
if (nIOBufferSize > 5 * 1460)
{
ClientSocket.SetSockOpt((
int)USOCKETLib.tagSocketOption.soRcvBuf, nIOBufferSize, (int)USOCKETLib.tagSocketLevel.slSocket);
ClientSocket.SetSockOpt((
int)USOCKETLib.tagSocketOption.soSndBuf, nIOBufferSize, (int)USOCKETLib.tagSocketLevel.slSocket);
ClientSocket.StartBatching();
ClientSocket.SetSockOptAtSvr((
int)USOCKETLib.tagSocketOption.soRcvBuf, nIOBufferSize, (int)USOCKETLib.tagSocketLevel.slSocket);
ClientSocket.SetSockOptAtSvr((
int)USOCKETLib.tagSocketOption.soSndBuf, nIOBufferSize, (int)USOCKETLib.tagSocketLevel.slSocket);
ClientSocket.CommitBatching(
true);
}
}
else
{
m_SocketPool.UnlockASocket(ClientSocket);
}
}
}
for (n = 0; n < m_aDBHandler.Count; n++)
{
CDBHandler DBHandler = (CDBHandler)m_aDBHandler[n];
m_SocketPool.UnlockASocket(DBHandler.Socket);
}
return (m_aDBHandler.Count == m_SocketPool.ConnectedSockets);
}

As the sample shows, you can batch a lot of requests and send all of them onto a remote server. We need a way to find the time when all of requests are processed and returned so that we can tell another application that the process is ended. Actually, you can do this easily using the following code.

protected virtual void OnAllRequestProcessed(int hSocket, short sRequestID, int nLen, int nLenInBuffer, short sFlag)
{
if (sFlag != (short)USOCKETLib.tagReturnFlag.rfCompleted)
return;
//this callback is called with a socket pool thread.
if (m_ClientSocket.CountOfRequestsInQueue == 1)
{
//when the queue contains one (current) request only
//we know that all of requests are already processed.
if (m_cb != null)
{
//invoke an EndEventHandler
m_cb.Invoke(this);
UnlockDBHandler(
this);
m_cb =
null;
}
}
}

We use a pre-recorded callback (m_cb) to inform another application, for example, a web application that the process is ended. For simplifying code, the code also unlock a DB handler in the above. To lock a socket and its bound database objects, CDBHandler uses the following code to implement it simply.

public static CDBHandler LockDBHandler(AsyncCallback cb, int nTimeOut)
{
if (cb == null)
return null;
USocket ClientSocket =
null;
try
{
ClientSocket = m_SocketPool.LockASocket(nTimeOut,
null);
}
catch (COMException err)
{
string strErrorMessage = err.Message;
int hr = err.ErrorCode;
return null;
}
CDBHandler dbHandler = SeekDBHandler(ClientSocket.Socket);
if (dbHandler != null)
{
dbHandler.m_cb = cb;
dbHandler.m_Event.Reset();
dbHandler.m_aDataSourceErrorRecords.Clear();
dbHandler.m_aSessionErrorRecords.Clear();
dbHandler.m_aCommandErrorRecords.Clear();
dbHandler.m_aRowsetErrorRecords.Clear();
dbHandler.m_aTableNames.Clear();
dbHandler.m_DS.Tables.Clear();
dbHandler.m_DS =
new DataSet();
}
return dbHandler;
}

SocketPro is very powerful and has a lot of features. As a sample, the code also has three delegates to monitor dataset generation when we fetch one or multiple rowsets. For all of other codes, you can see the source file, debug it and see code execution. After understanding how the code works, you can add more and more features (For example, monitoring various socket events and data movement, canceling fetching records, notifying messages, online compressing, securing data movement , sending requests when fetching records, ...... etc) according to your requirements.

6. FAQs:

I listed a number of frequent asked questions about this article so that it further eliminates some your concerns.

Does the remote database service is free? Can I get source code for it?

You can use udb.dll and uodbsvr.dll through SocketPro for free. Beginning with SocketPro version 4.4.0.2, UDAParts makes remote database service compeletely free to any one for any purpose as long as you do not do anything against or harmful to UDAParts. In regards to source file, you can see the source file for udb.dll inside SocketPro package. However, you can't see the source file for uodbsvr.dll that is written from our OleDBPro library. Because our OleDBPro and its source code are not free to the public, we will not open its source code to the public but to our customers only.

How fast and scalable with remote database service? Do you have any comparison with dotNet remoting in performance and scalability?

We have compared our SocketPro with dotNet remoting as described in the article Develop high performance distributed applications with batching, asynchrony, and parallel computation -- performance comparison between SocketPro and dotNet remoting. You can see how fast and scalable SocketPro is. In short, dotNet remoting can never match SocketPro in performance and scalability under any cases no matter how much time and effort you put on dotNet remoting. SocketPro is carefully written directly from window socket APIs using ATL/COM that makes sure it delivers super performance and scalability for all of development languages with special features, batching, asynchrony and parallel computation with online compressing, although it has COM interop cost between native code and managed code.

Can I lock two or more DB handlers one time from your sample code so that I can process multiple requests from one page in parallel instead of in queue as shown in this sample?

Yes, you can lock two or more database handlers one time and divide all of requests onto different scoket and database objects for processing in parallel. This sample is written for processing all requests in batch and also in queue. To process all of requests in parallel, you need to modify the source code after understanding how to know all of requests over different sockets are processed and returned.

As you may know that MS SQL server provides notification service. Can I use the sample code to notify other clients when I change a table?

Yes, you can! SocketPro has a built-in service for every socket connection. After a set of requests are processed and returned in the function void EndAsyncOperation(IAsyncResult ar), you can call the below code to notify all of clients that joined group 1.

m_DBHandler.Socket.Speak("All guys, I just updated the table Orders where OrderID = 10250. Please update your GUI controls with latest data!", 1);

For details, see the article Notify your coworkers any messages anywhere.


Similar Articles