Tutorial One

 

Contents:

 

Preface

Goals

Write client and server codes with uidparser.exe

Server side development

Experiments with SocketPro server

Responsibilities of main thread with SocketPro server

Client development

Summary

FAQs

 

Preface

           

            SocketPro is written with batching, asynchrony and parallel computations using raw non-blocking socket, which is very different from common distribution framework like Java RMI, DCOM, dotNet remoting and web service. Many features are specific and can’t be found in other frameworks. Therefore, we have created a series of tutorial samples to assist your development. As you study these samples, it is strongly recommended that you need to understand every one of features and code comments while experimenting them. All of tutorial samples are written with C++, C# and VB.Net languages. Each of samples contains two small applications, a client application and a server application for each of these development languages.
                       

1.                 Goals

                       

Understand a uid (universal interface definition) file, and use SocketPro tool uidparser.exe to quickly create skeleton code for both client and server through the file.

 

This very first sample is designed for quickly creating a service to process the following five requests:

A.        Echo an object data (VARIANT in C++) between client and server applications.

B.         Query the number of its requests processed since a client is connected to a server.
C.        Query the number of all of processed requests (both fast and slow ones) from all of clients since a SocketPro server is started.
D.        Query the number of all of fast requests processed from all of clients since a SocketPro server is started. The number doesn’t include the number of slow requests processed.
E.         Sleep for a specified time in ms, which simulates a slow processing request.

A client window application is created to demonstrate how to build a socket connection, how to send one or a set of requests in batch, and how to process returned results. Besides, it shows how to switch between two computation models, asynchrony and synchrony, and how to control window events.

A console server application is created to process the above five requests with help of SocketProAdapter, an open source module for simplifying SocketPro server development. The server application shows how to start a listening socket for receiving requests to process, how to create a service and how to process requests. All of these are very simple with SocketPro development at server side. The most important objective is to explain thread management within SocketPro, and shows you what variables are required to be synchronized with window thread locking objects and what variables should not be synchronized. Besides, the sample gives you clues how to give or deny a socket connection at server side.

 

2.         Write client and server codes with uidparser.exe

                       

SocketPro now comes with a tool to write skeleton client and server code from a given universal interface definition file having a file extension uid. It is very similar to other interface definition files like COM and CORBA. Take this sample, its uid file contains the below code.

 

[              

                ServiceID = 20 //service id can't be less than 0

]                                              

CTOne

{

                int QueryCount(); //Returns the count of calls from one socket connection

               

                /*Returns the count of all requests from all of socket connections */

                int QueryGlobalCount();

               

                /*Returns the count of fast requests from all of socket connections */

                int QueryGlobalFastCount();

 

                /* Sleep for a given time nTime. It is a slow request. */

                $void Sleep(in int nTime);

 

                object Echo(in object objInput); //echo an arbituary data

}

 

In regards to correct use of uidparser.exe, please see detailed comments inside the file TOne.uid. There is no need to re-describe various simple rules in this tutorial again. However, you must be clear if a request takes a long time to process. Take this sample as an example, the request Sleep requires a long time to process, and all of other four requests will require very little time to be completed. You can put a char ‘$’ in front of a function to indicate the request is a slow one.


Once having such a file, you can use the tool to quickly write basic client and server codes by executing the tool from MS DOS command. See the below Figure for creating skeleton code for C#, C++ and VB.NET by use of uidparser.exe.

 

           

 

            Figure 1. Create SocketPro client and server skeleton codes from a given uid file with uidparser.exe.

 

For this sample, uidparser creates three files with proper file extensions for each of three development languages. The first file named as TOne_i.cs for C# contains all of constants definitions as shown in the following.

 

public class TOneConst

{

                //defines for service CTOne

                public const int sidCTOne = ((int)USOCKETLib.tagOtherDefine.odUserServiceIDMin + 20);

 

                public const short idQueryCountCTOne = ((short)USOCKETLib.tagOtherDefine.odUserRequestIDMin + 0);

                public const short idQueryGlobalCountCTOne = (idQueryCountCTOne + 1);

                public const short idQueryGlobalFastCountCTOne = (idQueryGlobalCountCTOne + 1);

                public const short idSleepCTOne = (idQueryGlobalFastCountCTOne + 1);

                public const short idEchoCTOne = (idSleepCTOne + 1);

}

 

This file will be referenced by both client and server applications. The constant sidCTOne is a service id for this sample service. All of others are ids for five requests. SocketPro supports multiple services within one listening socket. Each of services must be identified by one unique integer number, named as service id. A service id should be between 0x10000000 (USOCKETLib.tagOtherDefine.odUserServiceIDMin) and 0xFFFFFFFF. UDAParts reserves service ids from 0x00000000 through 0x10000000 - 1. This sample has a service id (USOCKETLib.tagOtherDefine.odUserServiceIDMin + 20). For each of requests with the scope of one specific service, it requires one unique short integer number, named as request id. The request id must be between 0x2000 (USOCKETLib.tagOtherDefine.odUserRequestIDMin) and 0xFFFF. Similarly, UDAParts reserves request id from 0x0000 through 0x2000. For example, this sample service has five request ids defined (0x2001 ~ 0x2005). Each of ids represents one of the above requests.
                       

3.         Server side development

Reference SocketProAdapter for easy development -- At the very beginning, you need to reference SocketProAdapter dll if your development environment is one of dotNet languages (C# and VB.NET), or add the files sprowrap.cpp and sprowrap.h into your C++ project for a server application. Also, use namespaces SocketProAdapter and SocketProAdapter.ServerSide.

Derive CTOnePeer from CClientPeer -- Next, derive a class from the abstract class CClientPeer inside the namespace SocketProAdapter.ServerSide as shown in the file TOneImpl.cs. You must implement two pure virtual functions, OnFastRequestArrive and OnSlowRequestArrive. Each of its instances represents a socket connection, corresponding to a client. As function names indicate, you should process all of fast requests inside the first function that is called inside a main thread, and slow requests inside the second function that is called within a worker thread. The main thread is running with listening socket. At this point, you simply ignore it, and we’ll discuss more about the main thread later in this section. As shown in the sample code, only the request Sleep is processed inside the second request, and all of other four requests are processed inside the first function. See the below code.

 

public class CTOnePeer : CClientPeer

{

                protected override void OnSwitchFrom(int nServiceID)

                {

                                //initialize the object here

                }

 

                protected override void OnReleaseResource(bool bClosing, int nInfo)

                {

                                if(bClosing)

                                {

                                                //closing the socket with error code = nInfo

                                }

                                else

                                {

                                                //switch to a new service with the service id = nInfo

                                }

                                //release all of your resources here as early as possible

                }

 

                protected void QueryCount(out int QueryCountRtn)

                {

                                QueryCountRtn = 0;

 

                                // TODO: Add your code here

                }

 

                protected void QueryGlobalCount(out int QueryGlobalCountRtn)

                {

                                QueryGlobalCountRtn = 0;

 

                                // TODO: Add your code here

                }

 

                protected void QueryGlobalFastCount(out int QueryGlobalFastCountRtn)

                {

                                QueryGlobalFastCountRtn = 0;

 

                                // TODO: Add your code here

                }

 

                protected void Sleep(int nTime)

                {

 

                                // TODO: Add your code here

                }

 

                protected void Echo(object objInput, out object EchoRtn)

                {

                                EchoRtn = null;

 

                                // TODO: Add your code here

                }

 

                protected override void OnFastRequestArrive(short sRequestID, int nLen)

                {

                                switch(sRequestID)

                                {

                                case TOneConst.idQueryCountCTOne:

                                {

                                                int QueryCountRtn;

                                                QueryCount(out QueryCountRtn);

                                                m_UQueue.SetSize(0); //initialize memory chunk size to 0

                                                SendResult(sRequestID, QueryCountRtn);

                                }

                                                break;

                                case TOneConst.idQueryGlobalCountCTOne:

                                {

                                                int QueryGlobalCountRtn;

                                                QueryGlobalCount(out QueryGlobalCountRtn);

                                                m_UQueue.SetSize(0); //initialize memory chunk size to 0

                                                PushNullException();

                                                m_UQueue.Push(QueryGlobalCountRtn);

                                                SendReturnData(sRequestID, m_UQueue);

                                }

                                                break;

                                case TOneConst.idQueryGlobalFastCountCTOne:

                                {

                                                int QueryGlobalFastCountRtn;

                                                QueryGlobalFastCount(out QueryGlobalFastCountRtn);

                                                SendResult(sRequestID, QueryGlobalCountRtn);

                                }

                                                break;

                                case TOneConst.idEchoCTOne:

                                {

                                                object objInput = null;

                                                object EchoRtn;

                                                m_UQueue.Pop(ref objInput);

                                                Echo(objInput, out EchoRtn);

                                                SendResult(sRequestID, QueryGlobalFastCountRtn);

                                }

                                                break;

                                default:

                                                break;

                                }

                }

 

                protected override int OnSlowRequestArrive(short sRequestID, int nLen)

                {

                                switch(sRequestID)

                                {

                                case TOneConst.idSleepCTOne:

                                {

                                                int nTime = 0;

                                                m_UQueue.Pop(ref nTime);

                                                Sleep(nTime);

                                                SendResult(sRequestID, EchoRtn);

                                }

                                                break;

                                default:

                                                break;

                                }

                                return 0;

                }

}

 

SocketPro is dependent on a memory queue object to easily pack and unpack various data before exchanging data between a client and a server. Take the request Echo as an example, we first load an object from m_UQueue by calling m_UQueue.Pop. Afterwards, call the method Echo.

Register a service and deal with slow requests SocketPro requires registering all of services supported. Also, SocketPro server requires all of services having a unique identification number like sidCTOne. In order to simplify SocketPro server side development, SocketPro automatically creates and manages threads for you at run time. Here is the sample code to register a service and a slow request.

 

private void AddService()

{

                bool bSuc;

 

                //No COM -- taNone; STA COM -- taApartment; and Free COM -- taFree

                bSuc = m_CTOne.AddMe(TOneConst.sidCTOne, 0, tagThreadApartment.taNone);

                //If bSuc is false, very possibly you have two services with the same service id!

 

                bSuc = m_CTOne.AddSlowRequest(TOneConst.idSleepCTOne);

}

 

Inside the function, it calls CBaseService::AddMe with a service id, events required and a COM thread apartment model. For this sample, they are set to sidCTOne, 0, and taNone, respectively. The taNone indicates a worker thread will not be initialized into any COM thread apartment because this sample doesn’t create any COM object. The second parameter indicates what events a client will be interested in. No matter what data are set, you always get common events OnSwitchTo, OnClose, OnIsPermitted, OnFastRequestArrive, OnSlowRequestArrive and OnCleanPool. SocketPro can expose many events to your code. The above events are common ones, and all of others are rarely used. Therefore, if you use these rarely used events, just set these events and overwrite functions of either CClientPeer or CBaseService. The second call inside the function is to tell SocketPro all of slow requests for a specific service. The request Sleep is the slow one for this sample. When SocketPro server receives a request, it will analysis the request and route it into the function OnFastRequestArrive for a fast request and OnSlowRequestArrive for a slow request. When calling the function CBaseService::AddSlowRequest, it trains SocketPro server. SocketPro server uses the information to route a request to different threads and also create a thread automatically on the fly if necessary. SocketPro simplifies your code development at server side a lot. It manages all of threads for you automatically. Usually, you will never create a thread from your code even though you are indeed able to create your own threads. SocketPro manages three pools of threads, taNone, taApartment and taFree. The last two pools are for threads entering COM single-threaded and free-threaded apartments, respectively. The threads inside the pools taNone and taFree are sharable among different clients. However, a thread inside the pool taApartment is not sharable or reusable, and always dedicated to one socket connection. After the function CClientPeer::OnReleaseResource is called, the associated taApartment thread is killed. Threads in the pools taNone and taFree are always returned into their pools for reuse after completing a request. However, these threads will be killed if they are idle for a period of time and have nothing to work. You can control the time by setting CSocketProServer::MaxThreadIdleTimeBeforeSuicide.
                       

Start listening socket -- At this point, we almost complete the server application. Now let’s start a listening socket. Look at the sample code and see the code inside function CMySocketProServer::Run.

 

public void Run()

{

                do

                {

                                //try amIntegrated and amMixed

                                Config.AuthenticationMethod = tagAuthenticationMethod.amOwn;

 

                                //add service(s) into SocketPro server

                                AddService();

 

                                //start listening socket

                                if(!StartSocketProServer(20901))

                                {

                                                //Can't start SocketPro server!

                                                //Check if the port 20901 is available, please.

                                                //Error code = CClientPeer.LastSocketError.

                                                break;

                                }

                                //SocketPro successfully started at the port 20901!

 

                                AskForEvents((int)tagEvent.eOnAccept + (int)tagEvent.eOnClose + (int)tagEvent.eOnIsPermitted); //three common global events

 

                                //start message pump. SocketPro requires it and runs at 100% non-blocking fashion

                                //if a message already exist, don't call the following function

                                StartMessagePump();

 

                                //stop listening socket, and shutdown all of socket connections

                                StopSocketProServer();

                }while(false);

}

 

At the very beginning, set the CMySocketProServer property AuthenticationMethod to amOwn. Then, add one or more service into SocketPro. Afterwards, start a SocketPro with a given port number and set global events by calling the function AskForEvents for subscribing a few events like OnAccept, OnClose and OnIsPermitted. At last, start a window message pump by calling the function CSocketProServer::StartMessagePump. At the very end, call the function CSocketProServer::StopSocketProServer to stop listening socket when we don’t need it any more. In addition to these basic functions, you can call many others to tune SocketPro server. We’ll discus other features in other tutorial samples.

Permissions to client – We need to control socket connections from different clients for the sake of security. You can deny or give permission to a client from client credentials (user id and password). To achieve such a goal, you can implement it within the below function. If the function returns true, you give permission to a client. Otherwise, we deny the socket connection and SocketPro will automatically close it for you. We’ll discuss this topic further in Tutorial two.

 

protected override bool OnIsPermitted(int hSocket, int nSvsID)

{

                //give permission to all

                return true;

}

 

At very end, run a SocketPro server with following code.

 

//MTA preferred at server side usually

[System.MTAThread]

static void Main(string[] args)

{

CMySocketProServer MySocketProServer = new CMySocketProServer();

MySocketProServer.Run();

}

 

By this time, you can run the sample SocketPro server although it does not do any thing useful. It is multiple-threaded to supports thousands or more clients.

 

You may think that it requires a lot time to write the above code. However, it is very simple with help of uidparser.exe, and all of these skeleton codes are automatically generated from the file TOne.uid in less than one second.

 

Complete server side code -- Now, open attached sample source code file TOneImpl.cs. We complete server side coding inside functions with TODO comments as shown in the below.

 

protected void QueryCount(out int QueryCountRtn)

{

                QueryCountRtn = (int)m_uCount;

}

 

protected void QueryGlobalCount(out int QueryGlobalCountRtn)

{

                lock(m_cs)

                {

                                QueryGlobalCountRtn = (int)m_uGlobalCount;

                }

}

 

protected void QueryGlobalFastCount(out int QueryGlobalFastCountRtn)

{

                QueryGlobalFastCountRtn = (int)m_uGlobalFastCount;

}

 

protected void Sleep(int nTime)

{

                if (TransferServerException && nTime < 200)

                                throw new CSocketProServerException(12345, "Sleeping time is too short!");

                System.Threading.Thread.Sleep(nTime);

}

 

protected void Echo(object objInput, out object EchoRtn)

{

                EchoRtn = objInput;

}


Understand one statement for synchronizing sharable data
-- This sample service has three state data, m_GlobalCount, m_uGlobalFastCount and m_uCount. The first two variables are static (global), and record the number of all of processed requests (both fast and slow ones) and the number of fast requests from all of clients since a SocketPro server is started, respectively. The m_uCount records the number of the number of its requests processed since a client is connected to a server. It is not a static or global variable. Look at the sample code, and you will find that the static member m_uGlobalFastCount is always accessed from the main thread, and never from another thread, even through it tracks all of fast requests from different clients. It requires no data synchronization at all. Therefore, we should not synchronize it with a thread lock object. The variable m_uCount is indeed accessed from different threads, but it also requires no synchronization at all because it accessed from one client only. A client can send multiple requests in batch, but all of requests always processed one by one like a queue within one socket connection. In reality, there is no way for the variable m_uCount to be accessed by two threads at the same time. However, the variable m_GlobalCount is accessed from different threads and different clients. It could be accessed from multiple threads at a time. Therefore, it will require you a careful consideration. As shown in the sample code, the variable m_GlobalCount must be properly synchronized (locked). Please keep the following statement in mind, which will help you figure out if a variable should be synchronized. Pay close attention to the words bolded.

A variable should be synchronized only when it is accessed from different threads and also accessed from different socket connections.

SocketPro server development is extremely simple. As long as you understand the above statement, you will not meet other problems at all.

Within this sample, the derived class CTOnePeer also overwrites the base class functions OnSwithFrom and OnReleaseResource. At the very beginning after a socket connection is established, a client is connected with a startup service with a service id (sidStartup) equal to 256, which only supports one built-in request, SwitchTo. After a client sends the request for a service with a destination service id, the virtual function OnSwithFrom is called. When a client sends the request SwitchTo for another service, or the client socket connection is going to be shut down, the virtual function OnReleaseResource is called. Usually, you should release all of unwanted resources back to operation system as soon as possible for the sake of better performance.

Look at the class CTOneSvs constructor, you will find the protected member m_bUsePool is set to true. As the name implies, it is related to object pool for reusing an object in the pool without creating a new one for the better performance. When the function OnReleaseResource is called, an instance of CTOnePeer is not destroyed immediately but put into an object pool of its service if its service m_bUsePool is set to true. Later, when a new instance of CTOnePeer is required, it is just reused from the pool instead of creating a new one. This considerably benefits server performance and scalability for supporting a large number of clients if your CClientPeer derived objects contains large collections of objects, COM objects and large memory chunks but your clients often call the request SwitchTo or disconnect/reconnect socket connections. Typically, you may need to set these objects onto an initial state inside the function either OnReleaseResource or OnSwithFrom. A pooled object could be destroyed after a specific time. See the SocketProServer::CleanPoolInterval.


4.         Experiments with the sample SocketPro server

Let’s do two simple experiments against the same server by using telnet, a tool for detecting a socket server running a port. Please make sure that MS loopback adapter is installed.

Inside the directory socketpro4\tutorial\......\Release, double click the application SOne.exe. A console application will be started and a listening socket should be successfully started at the port 20001. Now go to Start->Run->cmd and click the button OK, and cmd.exe should be started. After typing telnet localhost 20901, immediately SOne.exe will output a few lines of words after a connection is established. If you don’t type any thing, the application cmd.exe will say connection to host lost in about 60 seconds. By default, SocketPro will automatically turn off the connection in 60 seconds with error code 0x8F100003 (uecClientRejected) after a socket connection is built, if a client does not make the call SwitchTo. You can change the setting by changing CSocketProServer::SwitchTime. Re-connect to SOne.exe, type any eight chars and SOne will close the socket connection with error code 0x8F000000 (uecUnexpected). SocketPro monitors all of data from a client and will abort a socket connection if data is strange or unexpected.


5.         Responsibilities of main thread with SocketPro server
                       

Main thread inside a SocketPro server is the thread running with listening socket. The main thread has a lot of responsibilities. Major ones are listed in the following.

a.         Monitoring various network events and communication events between worker threads and the main thread.
b.         Manage various pools such as thread pools, global and private memory pools, pools of CClientPeer-derived objects, and pool holding incoming requests to be processed.
c.         Route various slow requests onto different worker threads.
d.         Process all of fast requests from all of clients.
e.         Authenticate a client.
f.          Decompress incoming data if it is originally compressed (zipped).
g.         Decrypt incoming data if it is originally encrypted.
h.         Create or kill threads on the fly if necessary.
i.          Encrypt or compress returned data of all of fast requests.
j.          Manage plug-in libraries written from C/C++.

You are not required to understand how the main thread to manage these with details. However, you should make sure that no slow requests should be processed in the main thread. Worker threads are used to process all of slow requests, and to encrypt or compress their returned results.

 

6.         Client development


SocketPro has a standard COM library (usocket.dll) that must be used for all of client applications development and must be registered first. No matter what a development language you use, you should know how to reference the library, how to instantiate a USocket COM object, and how to advise for various events from the COM object USocket. Assuming you have all of these basic skills, we are going to study coding SocketPro client applications. Note that you can use any one language that supports MS COM technology although these tutorial samples cover C++, C# and VB.net only.

Step 1: Create an instance USocket and advise for various events. The most important events are OnSocketConnected, OnSocketClosed and OnRequestProcessed.
Step 2: Call ISocketBase::Connect for establishing a socket connection. After calling IUSocket::Connect, the event OnSocketConnected will be always called with an error code. If no error happens, you can call the method IUSocket::SwitchTo with a specified service id and enable various window controls.
Step 3: Send one or more requests in batch. No matter what a request is called and its return result is available, the event OnRequestProcessed is called. Inside the function, you need to retrieve its return data in bytes usually with an instance of CUQueue. Finally, use CUQueue.Pop to pop out each of data.
Step 4: A USocket COM object always monitors various socket events. Once a socket is disconnected, the event OnSocketClosed will automatically be called. Usually, you need to disable window controls and clean some other resources once the event OnSocketClosed is called. You can use ISocketBase::Disconnect to abort a socket connection or ISocketBase::Shutdown to gracefully close a socket connection. Note that if a socket server may also close a socket connection, the event is also called.

In order to reduce the coding complexity at client side, SocketPro provides wrappers inside SocketProAdapter. To use these helpful wrappers, simply use the name spaces SocketProAdapter and SocketProAdapter.ClientSide. Usually, you need to create one class that is derived from the abstract class CRequestAsynHandlerBase for sending requests and processing returned results.

Overwrite a set of virtual functions for tracking socket events -- To track socket events, the most important three virtual functions are
OnSocketConnected, OnRequestProcessed and OnSocketClosed in C++. With .NET development languages, you use delegates to get these events. Typically, you call IUSocket::SwitchTo for a service when the event OnSocketConnected happens with no error (nError or lError is zero). Also, you can enable critical window controls. Once OnSocketClosed is called, as usual you can disable some window controls so that a user can see that an Internet connection is closed immediately. The parameter nError (or lError) tells what cause the socket connection closed. If it is zero, the socket is closed normally. The event OnRequestProcessed is very important. As its name indicates, it happens when a result is returned. When a returned result comes from a remote server, it may cause the event a few times with different values for the parameter sFlag. For details, see the enumerator tagReturnFlag. By the default, you may see the flags rfDataComing and rfCompleted. You can use the property ReturnEvents of IUSocket to control the event. However, you always can see the flag rfCompleted because you must query a buffer of a returned result and process it when the event OnRequestProcessed happens with the sFlag equal to rfCompleted. CClientSocket processes the event and cause the pure virtual function CRequestAsynHandlerBase::OnResultReturned called when its sFlag is rfCompleted. In addition, it may cause the virtual function CRequestAsynHandlerBase::OnBaseRequestProcessed called if a base/builtin request is processed. Therefore, normally you will not overwrite CClientSocket::OnRequestProcessed.

Your asynchronous request handler -- When you derive a class from the abstract class CRequestAsynHandlerBase for sending requests and processing returned results, you have to overwrite the two pure virtual functions GetSvsID and OnResultReturned. As tutorial samples show, the first function just returns a service id only. Inside the second function, you must pop out each of returned data from parameter UQueue, a memory queue. Take this sample as a model, we pop out m_EchoRtn for request idEcho inside the function CTOne::OnResultReturned.

 

Client code generated from uidparser.exe You may think that the above coding is complex, but it is very simple with somewhat tedious. However, with help of the tool uidparser.exe, you can easily create a fully functional client code in no time as shown in the below.

 

public class CTOne : CRequestAsynHandlerBase

{

                public override int GetSvsID()

                {

                                return TOneConst.sidCTOne;

                }

 

                protected int m_QueryCountRtn;

                protected void QueryCountAsyn()

                {

                                SendRequest(TOneConst.idQueryCountCTOne,);

                }

 

                protected int m_QueryGlobalCountRtn;

                protected void QueryGlobalCountAsyn()

                {

                                SendRequest(TOneConst.idQueryGlobalCountCTOne);

                }

 

                protected int m_QueryGlobalFastCountRtn;

                protected void QueryGlobalFastCountAsyn()

                {

                                SendRequest(TOneConst.idQueryGlobalFastCountCTOne);

                }

 

                protected void SleepAsyn(int nTime)

                {

                                SendRequest(TOneConst.idSleepCTOne, nTime);

                }

 

                protected object m_EchoRtn;

                protected void EchoAsyn(object objInput)

                {

                                SendRequest(TOneConst.idEchoCTOne, objInput);

                }

 

                //When a result comes from a remote SocketPro server, the below virtual function will be called.

                //We always process returning results inside the function.

                protected override void OnResultReturned(short sRequestID, CUQueue UQueue)

                {

                                if(SocketProServerException.HResult != 0) return; //exception transfered from SocketPro server

                                switch(sRequestID)

                                {

                                case TOneConst.idQueryCountCTOne:

                                                UQueue.Pop(ref m_QueryCountRtn);

                                                break;

                                case TOneConst.idQueryGlobalCountCTOne:

                                                UQueue.Pop(ref m_QueryGlobalCountRtn);

                                                break;

                                case TOneConst.idQueryGlobalFastCountCTOne:

                                                UQueue.Pop(ref m_QueryGlobalFastCountRtn);

                                                break;

                                case TOneConst.idSleepCTOne:

                                                break;

                                case TOneConst.idEchoCTOne:

                                                UQueue.Pop(ref m_EchoRtn);

                                                break;

                                default:

                                                break;

                                }

                }

                public int QueryCount()

                {

                                QueryCountAsyn();

                                GetAttachedClientSocket().WaitAll();

                                return m_QueryCountRtn;

                }

 

                public int QueryGlobalCount()

                {

                                QueryGlobalCountAsyn();

                                GetAttachedClientSocket().WaitAll();

                                return m_QueryGlobalCountRtn;

                }

 

                public int QueryGlobalFastCount()

                {

                                QueryGlobalFastCountAsyn();

                                GetAttachedClientSocket().WaitAll();

                                return m_QueryGlobalFastCountRtn;

                }

 

                public void Sleep(int nTime)

                {

                                SleepAsyn(nTime);

                                GetAttachedClientSocket().WaitAll();

                }

 

                public object Echo(object objInput)

                {

                                EchoAsyn(objInput);

                                GetAttachedClientSocket().WaitAll();

                                return m_EchoRtn;

                }

}

 

Without any modification, the generated client code is fully functional without any modification for this sample.

 

Asynchrony and synchrony computations -- SocketPro is very different from common remoting frameworks like dotNet remoting, DCOM, Corba, Java RMI and web service. It is designed with batching, asynchrony and parallel computation in mind. Let us analysis the C# code of request Echo below.
                       

protected object m_EchoRtn;

protected void EchoAsyn(object objInput)

{

                SendRequest(TOneConst.idEchoCTOne, objInput);

}

 

public object Echo(object objInput)

{

                EchoAsyn(objInput);

                GetAttachedClientSocket().WaitAll();

                return m_EchoRtn;

}


Actually, SocketPro returns without waiting its returned result, right after sending a request to a remote SocketPro server. When calling the public function Echo, you actually calls the protected function EchoAsyn first, then IUSocket::WaitAll (GetAttachedClientSocket().WaitAll()) to wait until its returned result is processed inside the function CTOne::OnResultReturned, and at last return m_EchoRtn. Inside the function EchoAsyn, you call a proper version of methods SendRequest by generics in .NET or template in C++ to send the request Echo onto a remote server and returns immediately. The other important point is that we call IUSocket::WaitAll to wait for a returned result without disabling a window graphic user interface and GUI continues to function as usual. If your request is expected to take a long time to be returned, this key feature switch asynchronous computation into synchronous computation so that you can continue to program as usual, which eases your development because asynchronous computation usually makes coding more complicate and much less natural to read and understand. Besides, the key feature will avoid creating a worker thread for processing such a request that requires a long time to process at the server side like the request idSleep. Your code will be much more maintainable and you will never meet the problems like wired crash, data synchronization and deadlock. With help of SocketPro, you are not required to have multithreads programming experience at all with client side development. It is pointed out that SocketPro doesn’t have a concept like proxy and stub as common remoting frameworks do, we need to pack all of data by ourselves. With help of generics or template, packing data is really simple. Further, all of requests are coded in the exactly same way without any exception. As long as you understand one, you know all as shown in all of tutorial and other samples, because SocketPro client side development has very good consistence coding logical. In case you want to disable GUIs, you can set the property IUSocket::Frozen (CClientSocket::DisableUI).


Batch a set of requests -- In addition to the above special feature, let us see another important feature with SocketPro, batching requests, as shown in the below C# code.
                       

public void GetAllCounts(out int nCount, out int nGlobalCount, out int nGlobalFastCount)

{

                GetAttachedClientSocket().BeginBatching();

                QueryCountAsyn();

                QueryGlobalFastCountAsyn();

                QueryGlobalCountAsyn();

                GetAttachedClientSocket().Commit(true); //true -- ask server to send three results back in one batch

                GetAttachedClientSocket().WaitAll();

                nCount = m_QueryCountRtn;

                nGlobalCount = m_QueryGlobalCountRtn;

                nGlobalFastCount = m_QueryGlobalFastCountRtn;

}

 

The above top six lines of code are not usually seen with other common remoting frameworks. The code starts with beginning batching, packs all of three requests together, sends all of them in one packet, and tells a remote SocketPro server to return three results back in one packet. It reduces network trips from three to one. At the last, we call IUSocket::WaitAll and return all of expected results back to a caller. This feature is very critical to net application performance. This key feature ensures all of SocketPro application runs at the super speed with all of types of networks. Once you develop LAN (local area network) application, it will also runs over telephone modem network with decent speed. If you use other common remoting frameworks, you may find that your application runs fine with LAN, but so miserably slow with WAN (wide area network) that you may have to re-write your application just for supporting WAN only.

 

The above feature can definitely reduce complexity of designing distributed application. Traditionally, we have to carefully design distributed component interfaces in advance so that network trips should be reduced as much as possible. It causes all of remote request methods having a large number of parameters. However, SocketPro does not require that design. Usually, it requires an interface containing a small set of simple but fundamental asynchronous requests with a few parameters. Once you need more remote requests, you may just add them by simply batching them without recompiling server side code at all. This is great for maintaining and extending your distributed applications.

 

Online compressing -- In addition to the above feature, you can use SocketPro builtin feature online compressing to zip all of requests or returned results by enabling the property IUSocket::ZipIsOn at client side or sending a request TurnOnZipAtSvr for server online compressing as shown in the following code.

 

private void chkZip_CheckedChanged(object sender, System.EventArgs e)

{

                m_ClientSocket.GetUSocket().ZipIsOn = chkZip.Checked;

                m_ClientSocket.GetUSocket().TurnOnZipAtSvr(chkZip.Checked);

}

 

Online compressing is highly beneficial to your applications running across networks. In addition to the above two ways, SocketPro comes with other ways to further improve the performance. We’ll demonstrate these methods in the third and fourth tutorials.

 

Attach service handlers onto an instance of CClientSocket – Your service handler should be attached with an instance of CClientSocket for connecting it with a socket. Additionally, we need set up two delegates here to monitor socket connecting and closing events. Note that an instance of CClientSocket can maintain multiple service handlers with different service ids. For this sample, it is simply attached with one asynchronous service handler as shown in the below.

 

private void frmSampleOne_Load(object sender, System.EventArgs e)

{

                m_MySvsHandler = new CTOne();

m_ClientSocket = new CClientSocket();

m_ClientSocket.m_OnSocketClosed += new DOnSocketClosed(OnClosed);

                m_ClientSocket.m_OnSocketConnected += new DOnSocketConnected(OnConnected);

                m_MySvsHandler.Attach(m_ClientSocket);

}

                       

7.       Summary

         

            Here is a list of summaries you have just learnt:

 

a.                   SocketPro comes with a tool to create client and server skeleton code from a simple interface. This is a helpful tool for beginners to quickly get used to SocketPro programming style with use of a set of help classes inside SocketProAdapter namespace and its sub-namespaces.

b.                  On server side, SocketPro has one main thread and three pools of threads, and it is able to create and kill pooled threads on the fly at a proper time. SocketPro is able to automatically route fast and slow requests to different threads for processing.

c.                   On server side, SocketPro is able to manage various states. Contrary to many common remoting frameworks, many global and static variables are not required to be protected by operation system locking objects. This feature simplifies developing complex server and middle tier applications.

d.                  On client side, SocketPro does everything asynchronously by default with use of non-blocking socket. Additionally, SocketPro supports synchronous computation.

e.                   On both client and server sides, SocketPro is able to batch a set of requests and returning results for better network communication efficiency. This is one of the most important features within SocketPro. There are no other frameworks having such a fundamental feature. With help of this feature, SocketPro is able to deliver decent performance over slow and expensive networks like Dial-up and DSL/Cable modems. This feature is also a key factor to ensure SocketPro performance and scalability with LAN. With help of this feature, you can develop a high performance fat client application easily.

f.                    On client side, SocketPro does not freeze graphical user interfaces for sending all types of requests without use of any thread. However, SocketPro has a way to freeze graphical user interfaces if required.

g.                   SocketPro provides a way (WaitAll and Wait) to convert asynchronous requests into synchronous ones on the fly. Specially, synchronous requests within SocketPro doe not freeze graphical user interfaces either by default. No other frameworks are able to provide this feature. Converting asynchronous requests into synchronous ones makes coding simpler in logical. If you use asynchronous requests only for development, the coding logical could be very complex and tough to maintain.

h.                   SocketPro has a built-in support to online zip and unzip on both client and server side. This is also a helpful feature to improve network through output.

i.                     SocketPro monitors all of network events on both client and server sides.

j.                    This tutorial code for client applications can be just re-compiled for running applications on Pocket PC. SocketPro supports developments on PocketPC with C++ and Compact .NET version 2. 

k.                  Client and server are not tightly coupled with SocketPro. For example, it means that you can write a 100% native SocketPro server application that is perfectly able to communicate with a 100% managed dotNet application. You can’t do this with other common frameworks like dotNet remoting.

 

8.       FAQs

           

            a.         I can use a worker thread to send a slow request and wait for a result so that I can keep my window user interface from being frozen. What is the difference between my way and SocketPro?

            There are three key differences between the two. Let me first compare the two from the view of development simplicity and code maintainability. If you use a worker thread, you have to create and destroy a worker thread properly. Since you need to exchange data between user and worker threads, you have to synchronize various data and window controls, which may result in dead locks and strange crashes. Contrarily, you will never meet these problems because there is no need to create a thread at all with SocketPro. SocketPro is written from 100% non-blocking socket.

            Multithreads make code much fragile and difficult to read, debug and understand. With SocketPro, you will never have this problem because its coding logical is consistently through all of service handlers with no exception. If you understand one, you understand all. It is this reason that SocketPro makes your code more reusable and understandable to other programmers.

            With SocketPro, you can batch multiple requests and batch returned results effortlessly, but you can’t do batching through a worker thread with other remoting frameworks. Since SocketPro can batch multiple requests, it greatly helps distributed application performance and scalability.

 

            b.         How many requests can I batch one time?

            Theoretically, you are able to batch as many requests as you want as long as you computer has enough memory resource. However, practically you shouldn’t batch too large number of requests because batching too many requests will cost too much memory and actually decrease performance. For the best result, you may test for an optimal value.

 

            c.         Can I modify the code of opened classes inside the namespace of SocketProAdapter, SocketProAdapter.ClientSide and SocketProAdapter.ServerSide?

            Yes, you can! We prefer modifying it for you if you point out a problem so that others can benefit from your bug report.

 

            d.         Does SocketPro provide bidirectional communication? I know standard dotNet remoting has unidirectional TCP channel, which gives me big trouble with firewalls, NATs and other network devices if I implement a callback event. Does SocketPro have these problems too?

            SocketPro is written from raw TCP/IP socket that provides bidirectional communication. Unlike dotNet remoting, SocketPro never requires opening a listen socket at a client side. It has no problem at all with firewalls, NATs and other network devices. In regards to callback events, SocketPro already has a builtin chat service for this purpose for every socket connection. You don’t have to implement it by yourself and just reuse it. For details, see the coming tutorial 2.

 

            e.         In comparison with MS dotNet remoting and DCOM, what performance benefit can I get through SocketPro?

            We have compared our SocketPro with both dotNet remoting and DCOM. Typically, SocketPro is 10 times faster than dotNet remoting, and four times faster than DCOM. In some cases, SocketPro can be 100 or more times faster than dotNet remoting. Under all of cases and hardware conditions, SocketPro is ALWAYS faster than dotNet remoting and DCOM. A general window XP/2K machine with one P4 processor can easily support thousands of client connections.

 

            f.          dotNet remoting requires a developer understands lease time, singleton, singleCall,  and different client and server activation models. Does SocketPro have similar concepts that I have to care for?

            No! SocketPro does not have these concepts at all. Therefore, you will never meet these problems. SocketPro automatically tracks all kinds of network events at both client and server sides. Even for abnormal socket connection termination, SocketPro can transparently handle it for you without requiring you a single line of code.

 

            g.         Can I use old VB6 code to directly talk MS dotNet and C++ code?

            Yes! The set of tutorials doesn’t cover VB6, but you can see many samples are written with VB6 for client side development. You may use ATL COM object UQueue to convert VB6 data types into bytes or bytes into VB6 data types. For details, see the sample udemoVB inside the directory of ..\ samples\client\vb\udemoVB.

 

            h.         Can I use window socket APIs within SocketPro?

            Generally speaking, you shouldn’t use window socket APIs within SocketPro for both client and server developments. Under a particular case, you can use a window socket function ONLY IF you can’t find the function within SocketPro.

 

            i.          Can I use SocketPro for development on the pocket pc? Do I need to write a separate copy of code to process my requests and their returned results?

            You can use SocketPro for development on the pocket pc. The pocket pc OS must be MS CE 2003 or later. You don’t have to write a separate copy of code for processing my requests and their returned results at all. You just reuse code for normal window platforms because usocket.dlls for both CE and non-CE platforms have exactly the same interfaces. However, you can’t use SocketPro for server side development on Win CE.

 

            j.          What window platforms does SocketPro support? It supports old window platforms?

            SocketPro fully supports all of MS Win32 platforms including Win9x, ME, NT4.0, Win2k, XP and Win2k3 for both client and server developments. Also, SocktPro supports MS Win CE 2k3 or later for client side development.

 

            k.         I saw the methods IUSocket::StartBatching, CommitBatching and AbortBatching. They are interesting to me. Do I still need to design remoting call methods with a lot of input parameters to reduce network round trips in SocketPro as in other common remoting frameworks?

            These methods are unique with SocketPro. Other remoting frameworks don’t have these methods. As shown in this tutorial, coming tutorials and other samples, use of these methods can reduce network round trips if you have multiple requests. Therefore, you can design remoting calls with much fewer parameters. Doing so will not decrease the performance with SocketPro but make your design more beautiful and more understandable. Contrary to other common remoting frameworks, SocketPro favors remoting methods with a few parameters just because SocketPro has such key methods, which other frameworks don’t have.

 

            l.          I see all of asynchronous remoting methods don’t have any output parameters and just have input parameters only. Is this one of SocketPro programming styles?

            Yes. This is indeed one of SocketPro programming styles.

 

            m.        As you know, modern object oriented programming very often uses try and catch exception handlers. After looking at your code, I do not see try and catch very much. Why?

            SocketPro is written from asynchrony computation, and not from synchrony computation like other common remoting frameworks. Try and catch are not friendly with asynchrony computation. That is the main reason. However, you can transfer server exception onto client by setting the second input parameter of the method CClientSocket::SwitchTo to true as shown in the below.

 

//switch for the service identified by sidSOneSvs

m_ClientSocket.SwitchTo(m_MySvsHandler, true);

 

            Later, we can catch exception from server using the code below.

 

private void btnSleep_Click(object sender, System.EventArgs e)

{

                int nSleep = Int32.Parse(txtSleep.Text);

                btnSleep.Enabled = false;

                try

{

                m_MySvsHandler.Sleep(nSleep);

}

catch (CSocketProServerException err)

{

                MessageBox.Show(err.Message);

                }

if (m_ClientSocket.IsConnected())

                btnSleep.Enabled = true;

}

 

If you really like try and catch, you can create more detailed exceptions by yourself within a synchronous method like the below.

           

public object EchoData(object obj)

{

                EchoAsyn(obj);

                bool bWait = GetAttachedClientSocket().WaitAll();

                if(!GetAttachedClientSocket().IsConnected())

{

                                throw new YourException(GetAttachedClientSocket().GetErrorCode(),GetAttachedClientSocket().GetErrorMsg());

                }

               

                if(!bWait)

{

                throw new YourException(nTimeoutCode, “Timeout”);

}

                return m_EchoRtn;

}

 

            n.         I have read many books about thread pools. It seems to me that SocketPro is somewhat different from others. What advantages does SocketPro thread pool implementation have in comparison to other common thread pool implementations?

            SocketPro thread pool implementation has two major advantages over others. First, many global/static sharable variables are not required to be synchronized. Take this sample as an example. We don’t synchronize the above sharable variable m_uGlobalFastCount. Contrarily, we have to synchronize the variable with other thread pool implementations. This feature reduces many global/static sharable variables to be synchronized so that we have much less chance to get dead locks and smaller thread contention problems. After playing with the coming tutorial three, we’ll even see a way to get rid of synchronizing the variable m_GlobalCount. Therefore, SocketPro thread pool implementation helps us develop multithreaded server applications. Keep in mind that data synchronization is the toughest job in multithreaded application development. The more sharable variables, the more difficult data synchronization. Second, SocketPro thread pool implementation reduces thread context switches because we can directly process fast requests within main thread without dispatching those requests onto different worker threads. In many cases, thread context switches are much more expensive than directly processing fast requests with main thread. This feature helps server performance and scalability. With other thread pool implementation, all of requests will cause thread context switches no matter they are fast or slow.