Tutorial One
Contents:
Write client and server codes with uidparser.exe
Experiments with SocketPro server
Responsibilities of main thread with SocketPro server
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.
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
{
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.
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.