Programming Example: Create a Simple Service
To better understand the development of an RPC service, this section contains the VTScada code for a fully functional, yet simple service, which will maintain its synchronizable state correctly under all circumstances.
We will build this code up from scratch, starting with the simplest specification. The code contained within here is complete and fully functional. You can cut and paste the code into source files and use it.
The function of the service is to continually increment one numeric variable, on the server and broadcast the value of that variable to all clients. Each client that receives the new number verifies that it is one greater than the previous one. If it is not, the client increments an error counter.
If the service functions correctly, each client will receive the value of the counter as the synchronizable state, followed by incremental updates to the count, regardless of how many clients there are or at what point in the server’s life they are started.
The code for the main module of the service is quite straightforward. First, we need to register our service with RPC Manager and then enter a state where, if we are the server the counter is incremented and transmitted to all clients, via an RPC.
Here is the main module, in its entirety, along with an RPC subroutine that it calls:
{============================== SampleService ================================}
{ This service maintains a simple synchronizable state and properly supports }
{ all entry points required by RPC Manager for correct operation. }
{=============================================================================}
[
SvcName = "Sample" { Deliberately different from the module name.
Doesn't have to be different, but this allows
clear distinction in the code between the two};
RPCStatus { Current RPC connect status };
CurrentServer { Current server };
ServiceMode { Textual description of service mode };
{***** The synchronizable state of this service is represented by a simple,
continually incrementing numeric. The last value generated by this
service instance and the last value received by this service instance
are needed to ensure continuity of count. *****}
HeartbeatCountIn = 0 { Heartbeat counter };
HeartbeatCountOut = 1 { Heartbeat counter };
{***** Internal variables *****}
LastHeartbeatCount = 0 { Last Heartbeat count rxd };
SequenceErrors = 0 { Number of periodic event sequence errors };
ServerEnable { Server enable flag...prevents race condition};
{***** Regular service RPC modules which are service specific *****}
PeriodicRPC Module { Periodic event RPC call module };
{***** Required Synchronization modules, which RPC Manager will call *****}
GetServerChanges Module;
SetHeartbeat Module;
I;
]
Init [
If 1 Main;
[
{***** Register the sample service with RPC Manager. *****}
RPCStatus = \RPCManager\Register(SvcName, Invalid, Invalid,
Invalid, \RPC_SYNC_MODE);
]
]
Main [
{***** Maintain a couple of variables just to show the status of the service.
These can be displayed on a VTScada page *****}
ServiceMode = *RPCStatus == 2 { Server status == 2 }
? "Server"
: *RPCStatus == 1 { Client status == 1 }
? "Client"
: "Unknown" { Unknown yet == 0 };
CurrentServer = \RPCManager\GetServer(SvcName);
If Watch(1, *RPCStatus);
[
IfElse (*RPCStatus == 2, Execute(
{***** Just become server...set the output value to the last received
input value, plus 1 *****}
HeartbeatCountOut = HeartbeatCountIn + 1;
ServerEnable = 1;
);
{ else } Execute(
ServerEnable = 0;
));
]
{***** While I'm server, periodically increment the heart beat "out" counter
and update all clients *****}
If Timeout(PickValid(*RPCStatus == 2, 0) && PickValid(ServerEnable, 0), 1);
[
\RPCManager\Send(SvcName { Service sending the message },
\RemoteGUID { GUID defining app },
0 { Cut-off mode - NORMAL },
1 { Send to server flag - TRUE },
Invalid { Machine name or IP },
1 { Send to clients flag - TRUE },
0 { Execute local flag - FALSE },
1 { Recursive flag - TRUE },
"PeriodicRPC" { target RPC module to execute },
"SampleService" { context to execute in },
Invalid { object for update caching },
Invalid { InputSessionID },
HeartbeatCountOut++ { RPC module Parameters }
);
]
]
<
{================================ PeriodicRPC ================================}
{ Periodically RPC, called on the server and all clients, to update the }
{ heart-beat (synchronized state). }
{=============================================================================}
PeriodicRPC
(
Sequence { Info regarding call };
)
PeriodicRPC [
If 1;
[
CriticalSection(
IfElse ((PickValid(HeartbeatCountIn, 0) != 1) &&
PickValid(Sequence != HeartbeatCountIn + 1, 0),
IfThen (PickValid(*RPCStatus != 2, 1), { No sequence errors on server. }
SequenceErrors++; { It's already bumped the count. }
);
{ else } Execute(
HeartbeatCountIn = Sequence;
));
LastHeartbeatCount = Sequence;
);
Return(Invalid);
]
]
{ End of SampleService\PeriodicRPC }
>
Let us examine the code in a bit more detail:
The first task of the service code is to register itself as a service to RPC Manager:
RPCStatus = \RPCManager\Register(SvcName, Invalid, Invalid,
Invalid, \RPC_SYNC_MODE);
The Register() call is passed the name of the service. This must be unique within the application, but can be the same name as a service within another application. The fifth parameter to Register() is the only other one we are using at present and is set to a special constant value \RPC_SYNC_MODE which tells RPC Manager that our service has a synchronizable state. Omitting this parameter, or setting it to zero results in a service that has no synchronizable state. In this case, there is no need to provide any of the synchronization support modules that we will see presently.
Register() returns a reference to a value that RPC Manager maintains on behalf of the service. The value that RPCStatus addresses (*RPCStatus) will be set by RPC Manager to one of three values:
- 2 - If the service instance is the server.
- 1 - If the service instance is a client.
- 0 - If the state of the service instance has not yet been decided.
The remainder of the code in state Main uses *RPCStatus to determine what to do. ServiceMode and CurrentServer are set to values that can be put on a display page. They are not necessary for service operation.
When the service instance becomes the server and on the first evaluation of the service main module, the line:
If Watch(1, *RPCStatus);
evaluates to TRUE. This provides a point at which initialization can be performed when the instance that is server changes.
Finally, a timeout statement trips once per second on the server, to broadcast the counter contents to all connected clients and then increment the counter. The ServerEnable variable is simply there to prevent a race condition that would arise with two statements watching the same data value (*RPCStatus).
The RPC issued by the Send() call invokes module PeriodicRPC() on the server and all clients, carrying the count as the sole parameter to this RPC subroutine. When PeriodicRPC() is running in the server instance, it does not check the sequence number for errors.
Note that PeriodicRPC() performs its work in a CriticalSection, as RPC subroutines are run on the RPC Manager thread, whereas the remainder of the service code runs on the service thread.
This prevents erroneous results caused by one thread modifying a value shared by the service and the RPC subroutine. An alternative technique is to have the RPC subroutine launch a module that interacts with the service’s values. A module so launched, runs on the application thread. Selection of the most appropriate method is usually determined by the complexity of the operations to be performed in response to an RPC subroutine invocation.
Note also that the name for the service that is used for registration:
SvcName = "Sample" { Deliberately different from the module name
is different from the module name. The two names can be, and usually are, the same. However, they are two different entities, the service name being used to identify the service within the application and the module name used to identify a module within the scope of Code. The Send() call, in state Main, uses the module name to specify the context in which to find the RPC module, PeriodicRPC, but uses SvcName to identify the service within the application. The difference lies in the service name being used to determine which machines within the distributed system to send the RPC to, while the module name identifies the module within Code that contains the specified RPC module name.