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

                                                PushNullException();

                                                m_UQueue.Push(QueryCountRtn);

                                                SendReturnData(sRequestID, m_UQueue);

                                }

                                                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);

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

                                                PushNullException();

                                                m_UQueue.Push(QueryGlobalFastCountRtn);

                                                SendReturnData(sRequestID, m_UQueue);

                                }

                                                break;

                                case TOneConst.idEchoCTOne:

                                {

                                                object objInput = null;

                                                object EchoRtn;

                                                m_UQueue.Pop(ref objInput);

                                                Echo(objInput, out EchoRtn);

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

                                                PushNullException();

                                                m_UQueue.Push(EchoRtn);

                                                SendReturnData(sRequestID, m_UQueue);

                                }

                                                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);

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

                                                PushNullException();

                                                SendReturnData(sRequestID, m_UQueue);

                                }

                                                break;

                                default:

                                                break;

                                }

                                return 0;

                }

 

                protected void PushNullException()

                {

                                if(TransferServerException)

                                {

                                                int nRtn = 0;

                                                m_UQueue.Push(nRtn); //required by SocketProAdapter client side!!!

                                }

                }

}

 

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. Once the method returns, we initialize the memory queue by setting its size to zero. At the last, save a returned object into the memory queue m_UQueue. At the very end, we send its return data to a client. For all of requests, it is the same.

 

Derive the class CTOneSvs and associate CTOnePeer with CTOneSvs – Afterwards, you derive a class CTOneSvs from CBaseService as shown in this sample. You must overwrite the pure virtual function GetPeerSocket to associate an instance of CSOnePeer simply by creating an instance of CTOnePeer. SocketPro uses an instance of CTOneSvs to manage a set of CTOnePeer instances from different clients. It is very simple to do so as shown in the below code.

           

//SocketPro will call this function to create a ClientPeer on the fly if needed

protected override CClientPeer GetPeerSocket(int hSocket)

{

                //Associate a peer object into this service

                //Note that you must not delete the object, but SocketProAdapter will take care it for you

                return new CTOnePeer();

}

 

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

                                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)

{

                m_CS.Lock();

                QueryGlobalCountRtn = (int)m_uGlobalCount;

                m_CS.Unlock();

}

 

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.