|
1.
|
Overview of the CDEV Generic Server Engine
|
|
Purpose of This
Document
|
This document is designed to provide an overview and tutorial of how to implement
client/server applications by using the CDEV Generic Server Engine. Adherence to
the structure and syntax that is specified in this document will improve the likelihood
that the CDEV service/server developer's application will be compatible with other
similar applications using CDEV.
The class library was designed to be as efficient as possible and still maintain the
flexibility to allow CDEV client/server developers to use it with minimal modification. In
addition to describing the conceptual behavior of the server, this document will also
discuss the C++ classes and how inheritance and overloading may be used to build
the best server for your application.
|
|
Intended Audience
|
This document is intended for anyone who will be developing a CDEV server or will be
developing CDEV applications that will communicate with one another over a network.
This document will also be useful for software developers who wish to develop a non-
CDEV application that can communicate with an existing server that uses this class
library.
|
|
What is the CDEV
Generic Server
Engine
|
The CDEV Generic Server Engine is a collection of C++ classes that may be used to
quickly develop a client/server application. The communications component of the
library is based on the Adaptive Communications Environment (ACE), a freeware
product developed by Douglas Schmidt that is provided with the CDEV distribution.
CDEV servers use a global CDEV Name Server (provided with the source code
distribution) to register themselves. The client services can then use this Name Server
to locate servers by type, name or host. The Name Server insures that each server
name is unique within its type or domain. Servers that have not reregistered within a
specific time period (usually 60 seconds) are automatically removed from the Name
Server.
Clients and servers that are developed using this library will use the CDEV Linear
Internet Protocol to communicate. The documentation for this protocol is provided with
the CDEV distribution and its use ensures that the developer's server will be
accessible by all CDEV compliant applications.
|
|
Why Use the CDEV
Generic Server
Engine
|
The CDEV Generic Server Engine provides a robust and reliable mechanism for
quickly developing client/server applications. Because all of the network
communications intricacies are isolated by the C++ classes, the developer's server
can easily be modified and upgraded without significant modification to the network
internals. Additionally, by using CDEV, the client does not need to be 'network-aware',
the client C++ class library does all of the communications work.
The CDEV Generic Server Engine also provides a myriad of features that would
require a significant investment in time to develop for each new server. These features
are described in detail below.
|
|
Features
|
|
The developer is only required to create a subclass of the cdevServer C++ class
and overload a single method in order to generate the communications
component of his server.
|
|
The complete client communications portion of the application is accomplished by
inheriting a CDEV service class from the cdevClientService C++ class and writing
a boiler plate service loader.
|
|
The engine uses the CDEV Linear Internet Protocol (CLIP) to communicate. This
protocol uses cdevData objects (a self-describing data structure) to transfer data
allowing unique, application specific data structures to be transferred without
modifying the protocol.
|
|
The client and server side of the application use a global CDEV Name Server to
register and locate various servers by their type, name or host.
|
|
The socket utility classes use embedded buffering to optimize asynchronous
communications and increase communications speed.
|
|
The communications interface is completely abstracted from the client application.
Because the application has only a CDEV view of the world, the underlying
communications engine can be modified or upgraded without breaking the
program.
|
|
Clients automatically reconnect to server following a disconnect or
communications error.
|
|
Communications integrity is ensured by using TCP/IP and the Adaptive
Communications Environment (ACE) C++ library.
|
|
The server supports multiple concurrent client connections. Because the inbound
data is read incrementally and buffered, a slow client will not cause the server to
block while waiting for a transmission to be completed.
|
|
The server is completely event driven. It is activated whenever a client submits a
packet or packets, otherwise, it sleeps until it has inbound data to process.
|
|
An individual tag map is maintained for each connection. When data is received
the server will map the contents of the clients cdevData objects to the server's
representation prior to processing. The cdevData objects are remapped to the
client's representation prior to returning results.
|
|
The server has built-in mechanisms for storing, executing and removing client
specified monitors on server data objects. This monitoring capability easily allows
application developers to create event-driven client programs that respond to
changes in the server.
|
|
A timer-based CDEV 'polling' class is provided that allows the server to attach to
other CDEV servers or services to obtain information.
|
|
|
2.
|
Building the CDEV Generic Server Engine
|
|
Steps for
Compiling and
Testing the CDEV
Generic Server
Engine
|
|
1.
|
Install and compile the CDEV source code distribution. See the CDEV
distribution for specific instructions for compiling these libraries.
|
|
2.
|
Compile the Adaptive Communications Environment (ACE) Library. ACE is
located at the same level as the 'src' directory in the CDEV distribution tree. The
README file located in that directory will provide specific instructions on building
this library on your system. The ACE libraries should be automatically copied to
the CDEV library directory.
|
|
3.
|
Setup the Makefile for your platform. In the directory include/makeinclude there
are a collection of makefiles that are followed by the name of the platform for
which they were developed. Link the makefile associated with your platform to the
file Makefile.OS by typing the following command: "ln -s Makefile.XXXX
Makefile.OS".
If a makefile for your platform does not already exist, you may have to create one
in that directory.
|
|
4.
|
Compile the cdevGenericServer Library. The source code tree for this
distribution is located in the directory $CDEV/extensions/cdevGenericServer.
The makefile for this library requires the same environment variables that are
used by the main CDEV makefile.
|
CDEV
|
This is the root directory of the CDEV distribution.
|
|
CDEVVERSION
|
This is the version number of the CDEV class library.
|
|
CDEVSHOBJ
|
This is the directory for the CDEV shared objects.
|
|
CDEVLIB
|
This is the directory where the CDEV libraries reside.
|
|
CDEVINCLUDE
|
This is the directory where the CDEV include files reside.
|
To compile the libraries, go to the cdevGenericServer directory and type make. All
libraries and the associated test and example applications should be built.
|
|
5.
|
Run the test applications to ensure that the code is working correctly. These
applications are located in the test sub-directory of the cdevGenericServer tree.
The test applications require that a special DDL file is specified and that the
CDEV Name Server is operating. Perform the following steps to test the library.
|
5a.
|
Start the Name Server. The NameServer application is located in the bin
directory of the cdevGenericServer distribution tree. The NameServer should
produce no output and can be executed in the background by typing:
"NameServer &".
|
|
5b.
|
Specify the host name of the Name Server. Because all applications will
need to access the Name Server, the host where it is executing should be
specified in the CDEV_NAME_SERVER environment variable. This variable
must be specified in the environment of each shell that will need to access the
Name Server. If the Name Server is running on host foo.cebaf.gov, the Name
Server environment variable can be set by typing: "setenv
CDEV_NAME_SERVER foo.cebaf.gov".
|
|
5c.
|
Specify the CDEV Device Definition Language file for the test programs.
The DDL file for the test programs is named TestService.ddl and is located in
the test sub-directory of the cdevGenericServer distribution tree. In order for
this DDL file to be used as the default, it should be specified in the CDEVDDL
environment variable. This can be accomplished by moving to the test
directory and typing the following command: "setenv CDEVDDL $PWD/
TestService.ddl".
|
|
5d.
|
Specify the CDEVSHOBJ directory. The CDEVSHOBJ directory is the
directory that contains the versioned subdirectories for the service shared
objects. By default the makefile will place the file TestService.so in the
directory $CDEV/lib/PLATFORM-OSVERSION.XX/1.5/TestService.so, where
PLATFORM is the name of your platform and OSVERSION is the major
operating system. The following example shows the location of the
TestService.so on a Solaris 5.5 system and the correct setting for the
CDEVSHOBJ variable.
Location: $CDEV/lib/solaris-5.XX/1.5/TestService.so
CDEVSHOBJ: $CDEV/lib/solaris-5.XX
The CDEVSHOBJ variable may point to the directory that actually contains
the service shared objects, however, CDEV will always attempt to locate the
files in the version subdirectory first in order to support multiple CDEV
versions.
|
|
5e.
|
Start the Test Server. The environment is now correct to start the TestServer.
From the bin sub-directory type the command: "TestServer". The TestServer
should print a message indicating that it is ready to process user requests.
|
|
5f.
|
Specify the Client Tag Map. In order to test all capabilities of the server, the
client should use a tag map that is different from the one that is in use on the
server side of the connection. A special tag map has been provided that can
be used to test this feature. The tag map is located in the test sub-directory
and can be specified by moving to the test subdirectory and typing: "setenv
CDEVTAGMAP $PWD/cdevTagMap.txt".
|
|
5g.
|
Start the Test Client. In a new window, set the CDEV_NAME_SERVER and
the CDEVDDL environment variables as previously described. The test client
may then be started by typing the following command "TestProgram".
|
|
5h.
|
Examine Test Server and Test Client output. The server and the client
should periodically print a line indicating that the packets that they are
exchanging are correctly matched. If a mismatch occurs, both sides of the
connection will print out a verbose description of what differences were
detected.
|
|
5i.
|
Terminate the Test Server and the Test Client. The TestServer and
TestProgram applications are terminated using CTRL-C. When the
applications are terminated, they should display a disconnecting message
and exit gracefully.
|
|
|
|
3.
|
The Reflector Server - A Simple Client/Server System
|
|
Overview
|
The Reflector client/server system is a simple CDEV service that returns the
cdevData object unmodified to the caller. The Reflector server can be used as a
skeleton for any other server that the developer may wish to create. The source code
that is provided in the following sections is available in text form in the examples sub-
directory of the cdevGenericServer directory tree. A more complex example is
provided in section 11 of this document.
|
|
Reflector Server
Source Code
|
The server for the Reflector system is instituted as a single C++ file. The source code
for this application is listed below.
Figure 10:
ReflectorServer.cc - Source Code for the Reflector Server
|
|
#include <cdevServer.h>
// ******************************************************************
// * class ReflectorServer:
// * This is the server class for the reflector. It simply
// * receives messages from a client and immediately returns them.
// *
// * The constructor passes the domain, server, port and rate to the
// * underlying cdevServer class to be processed. The cdevServer
// * constructor will add this server to the Name Server and will
// * begin processing messages when the cdevServer::runServer()
// * method is executed.
// *
// * The processMessages method is the servers interface to the
// * world... Each time a complete message is received or the time
// * specified in rate expires, that method will be called.
// ******************************************************************
class ReflectorServer : public cdevServer
{
public:
ReflectorServer ( char *domain, char *server,
unsigned int port, double rate )
: cdevServer(domain, server, port, rate)
{
}
virtual void processMessages ( void )
{
cdevMessage * message;
while(dequeue(message)==0)
{
enqueue(message);
delete message;
}
}
};
void main()
{
ReflectorServer server("REFLECTOR", "TestServerX", 9120, 60);
cdevServer::runServer();
}
|
|
|
|
ReflectorServer
Header Files
|
In the source code for the Reflector server, the only header file that must be included
is the one for the cdevServer class. This header contains all of the definition
information that is required for the Adaptive Communications Environment (ACE) and
the CDEV Linear Internet Protocol.
|
|
The
ReflectorServer
Class
|
The ReflectorServer class inherits directly from the cdevServer class. Because
cdevServer defines all of the functionality necessary to establish a listening socket
and accept connections, the developers start-up is limited to initializing the
cdevServer class object with the domain name, server name, listening socket number
and the time-out rate.
The developer is required to create a processMessages method which will perform
whatever message processing that is required of the server. In this case, the
processMessages method will merely remove an entry from the queue and then re-
enqueue it for return to the client. Note that the enqueue method does not delete the
cdevMessage object, so it is the responsibility of the developer to delete the
cdevMessage object when it is no longer needed.
|
|
The main Function
|
The main function is responsible for starting the server when the application is started.
In order to perform this task, main must first create a new ReflectorServer object. The
ReflectorServer in this example has the Name Server domain "REFLECTOR" and the
server name "TestServerX". It will be listening for connections on socket 9120 and will
automatically process messages at least once every 60 seconds.
When the ReflectorServer was created it automatically registered itself with the ACE
Reactor that is embedded in the cdevServer class. In order to begin accepting
connections and processing messages the main function must call the static
runServer method of the cdevServer class. This method will continue servicing
requests until the static Finished flag of the cdevServer class is set to non-zero.
|
|
The
ReflectorService
Source Code
|
The ReflectorService is the CDEV interface to the ReflectorServer that is described
above. The source code for the ReflectorServer is implemented as a single source file
and its associated header file. The source code for the ReflectorService is as follows.
Figure 11:
ReflectorService.h - Header File for the Reflector Service
|
|
#include <cdevClientService.h>
// ******************************************************************
// * Function called to create initial instance of ReflectorService
// ******************************************************************
extern "C" cdevService *newReflectorService ( char *, cdevSystem *);
// ******************************************************************
// * class ReflectorService :
// * This class simply inherits from the cdevClientService and must
// * define only a constructor and destructor.
// ******************************************************************
class ReflectorService : public cdevClientService
{
public:
ReflectorService ( char * name, cdevSystem & system =
cdevSystem::defaultSystem());
protected:
virtual ~ReflectorService ( void ) {};
};
|
|
Figure 12:
ReflectorService.cc - Source Code for the Reflector Service
|
|
#include <ReflectorService.h>
// ******************************************************************
// * newReflectorService:
// * This function will be called by the cdevSystem object to create
// * an initial instance of the ReflectorService.
// ******************************************************************
extern "C" cdevService * newReflectorService
(char * name, cdevSystem * system)
{
return new ReflectorService(name, *system);
}
// ******************************************************************
// * ReflectorService::ReflectorService :
// * This is the constructor for the ReflectorService. It
// * initializes the underlying cdevClientService by specifying
// * that it is in the domain of REFLECTOR.
// ******************************************************************
ReflectorService::ReflectorService
( char * name, cdevSystem & system)
: cdevClientService("REFLECTOR", name, system)
{
system.reportError(CDEV_SEVERITY_INFO, "ReflectorService", NULL,
"Constructing a new ReflectorService");
}
|
|
|
|
The
ReflectorService.h
Header File
|
While the server is not required to have a specific header file, a CDEV service must
have a header file that may be used to create a loader for the archive version of the
library. This file is always named xxxxxService.h, where xxxxx is the name of the
service as it will be specified in the CDEV DDL file. This file must contain the complete
class definition for the service class.
|
|
The newReflector
Service Function
|
Each service in CDEV must have a function that the cdevSystem object can call to
create the initial instance of the object. In the case of the ReflectorService, this
method will create a new instance of the ReflectorService using the provided name
and cdevSystem object. This new object will then be returned as a pointer to a
cdevService object.
|
|
The
ReflectorService
Class
|
Because of the simplicity of the ReflectorService, all of the functionality of this class is
inherited from the cdevClientService class. The ReflectorService class is only
required to initialize its parent classes to be fully operational.
|
|
The CDEV DDL
File
|
After compiling this source code into a server application and a CDEV shared library
using the makefile that is provided in the examples sub-directory, the developer is
ready to generate a CDEV DDL file that will map certain device/message
combinations to the Reflector service. The following simple CDEV DDL file can be
used to map device "device1" and message "get attrib1" to the Reflector Service. Note
that by entering the server tag in the service data section, the default server name that
the message will be sent to may be specified. In this case all messages associated
with the "attrib1" attribute will be sent to "TestServerX".
Figure 13:
Reflector.ddl - A Simple CDEV DDL File
|
|
service Reflector
{
tags {server}
}
class Reflectors
{
verbs {get}
attributes
{
attrib1 Reflector {server=TestServerX};
}
}
Reflectors :
device1,
;
|
|
|
|
Testing the
Reflector Server
|
After compiling the server and the service components of the Reflector system, the
developer can test the functionality of the client/server application by performing the
following steps.
|
1.
|
Start the Name Server.
|
|
2.
|
Set the CDEV_NAME_SERVER environment variable in the shell where you will
execute the server and the client to indicate the host where you started the Name
Server. For instance: setenv CDEV_NAME_SERVER cebaf1.cebaf.gov.
|
|
3.
|
Set the CDEVSHOBJ environment variable in the shell where you will start the
client application to indicate the directory where the ReflectorService.so file is
stored.
|
|
4.
|
Set the CDEVDDL environment variable in the shell where you will start the client
application to indicate the absolute path to the CDEV DDL file where the Reflector
definitions have been created.
|
|
5.
|
Start the server.
|
|
6.
|
Start the cdevUtil application that is provided with the CDEV distribution and send
messages to the server by typing: device1 get attrib1. You may want to enter
additional output code in the processMessages method of the server to report
each time a message is received.
|
|
|
4.
|
Server Class Hierarchy
|
|
Server Classes
|
The server side of the CDEV Generic Server Engine library is composed of a series of
classes that are used in conjunction with the ACE Reactor class to provide event
driven socket management. The following diagram shows an object diagram of the
classes that are used on the server side of the connection.
Figure 1:
Object Hierarchy of Server Classes
|
|
FifoQueue Class
|
This class is a simple queue that is used to enqueue inbound messages that are
received from a socket. Because all messages will be processed by the same method,
there is only one FifoQueue object that is used by all client sockets. This object
resides in the cdevSessionManager class.
|
|
MultiQueue Class
|
A MultiQueue object is a special type of FifoQueue that allows the caller to create an
object that is to be placed into the queue and then place it into several queues at
once. If the message is removed from any queue, it will automatically be removed
from all other queues where it exists. This mechanism is used to provide the capability
of removing all of a specific client's outbound packets without stepping through all of
the packets in the associated socket's queue.
|
|
ClientSession
Class
|
The ClientSession class inherits its queue functionality from the MultiQueue class. It is
used to hold all outbound packets that are associated with a specific client identifier.
The ClientSession object also holds supplemental data that the cdevSessionManager
will need to manage the client. This class be subclassed by the developer in order to
associate more data with the client identifier. The following information is stored in the
ClientSession object:
|
|
Attributes of the
ClientSession
Class
|
|
localID
|
short localID;
This is the client identifier that will be used on the server to
uniquely identify the client.
|
|
clientID
|
int clientID;
This is the client identifier that was provided by the client
combined with the socket identifier.
|
|
socketID
|
int socketID;
This is the socket identifier (file descriptor) to which the client is
connected.
|
|
|
SocketSession
Class
|
The SocketSession class inherits its functionality from the MultiQueue class. It is used
to hold all packets that are destined for a specific socket. The SocketSession object is
also used to store supplemental data that the cdevSessionManager will need to
maintain the connection. This class be subclassed by the developer in order to
associate more data with the socket identifier. The following information is stored in
the SocketSession object:
|
|
Attributes of the
SocketSession
Class
|
|
socketID
|
int socketID;
This is the socket identifier (file descriptor) to which the remote
client is attached.
|
|
|
ClientAcceptor
Class
|
The ClientAcceptor class is used by the ACE Reactor to listen to the server socket and
accept each inbound client connection. When a connection is accepted, this class will
create a ClientHandler object that will manage the connection throughout its lifetime.
|
|
SocketReader
Class
|
The SocketReader class has the embedded mechanisms to read buffered packets
from a socket. The ClientHandler inherits the functionality of this class to read data
that is received on its associated socket.
|
|
SocketWriter
Class
|
The SocketWriter class has the embedded mechanisms to write buffered packets to a
socket. The class maintains a 56 kilobyte buffer that it uses to enqueue as many
outbound messages as possible before executing a network write. The ClientHandler
inherits the functionality of this class to write data to its associated socket.
|
|
ClientHandler
Class
|
A ClientHandler object is created each time a new connection is accepted by the
server. This class is used by the ACE Reactor to manage the input and output events
on the specific socket. When data is received from the socket by the handle_input
method, the ClientHandler object will enqueue the inbound packet in the FifoQueue
provided by the cdevSessionManager class. When the cdevSessionManager class
enqueues outbound packets, the handle_output method of the ClientHandler class will
write them to the socket using as many write operations as required to transmit all
data.
When the ClientHandler object is destroyed it will notify the cdevSessionManager
object which will remove its associated queues and will remove it from the ACE
Reactor.
|
|
cdevSessionMan
ager Class
|
The cdevSessionManager class maintains all of the queues, ClientSession and
SocketSession objects that are used to operate a server. This class also defines the
enqueue and dequeue methods that are used by the cdevServer to obtain inbound
packets and to submit outbound packets.
|
|
cdevServer Class
|
The cdevServer class inherits the queue management functionality that is provided by
the cdevSessionManager class and then implements the ClientAcceptor and
ClientHandler classes to accept and process connections. The cdevServer class also
introduces the concept of timed execution of the server function and automatic
registration of the server with the CDEV Name Server.
In order to construct a new CDEV Server application, the developer only needs to
inherit his server class from the cdevServer class and then define the
processMessages method. This method will be called whenever the server timer
expires or when data is ready to be processed in the inbound queue. Once called the
processMessages method should use the dequeue method to remove the inbound
cdevMessage object, process the message, and then use the enqueue method to
return the processed cdevMessage object to the client.
The encodePacket and decodePacket methods of this class are responsible for
coordinating the tables of context objects, performing tag mapping and converting
between the local client identifier and the foreign client identifier.
|
|
5.
|
Client Class Hierarchy
|
|
Client Classes
|
The client side of the CDEV Generic Server Engine consists of a collection of classes
that are used in conjunction with the CDEV service architecture and the ACE Reactor
to provide pollable event driven behavior. The following diagram shows the object
structure of the classes used by the client.
Figure 2:
Object Hierarchy of Client Classes
|
|
SocketReader
Class
|
The SocketReader class has the embedded mechanisms to read buffered packets
from a socket. The ServerHandler inherits the functionality of this class to read data
that is received on its associated socket.
|
|
SocketWriter
Class
|
The SocketWriter class has the embedded mechanisms to write buffered packets to a
socket. The class maintains a 56 kilobyte buffer that it uses to enqueue as many
outbound messages as possible before executing a network write. The ServerHandler
inherits the functionality of this class to write data to its associated socket.
|
|
ServerHandler
Class
|
A ServerHandler object is created for each server that the cdevServerInterface wishes
to communicate with. This object has an embedded FifoQueue object that is used to
store the outbound packets until the ServerHandler's data is flushed. If the
ServerHandler receives a sufficient number of packets or volume of data it will
automatically flush its buffer to the socket. The ServerHandler inherits much of its
communications functionality from the SocketReader and SocketWriter classes which
provide buffered communications methods.
The cdevClientRequestObject may obtain a pointer to the ServerHandler that is
associated with the server that it is communicating with. By referencing this pointer
when enqueueing messages to the server through the cdevService, the request
object can increase its performance significantly because it does not have to locate
the associated ServerHandler on each transmission.
A cdevClientRequestObject that is utilizing a ServerHandler will register itself in a list
of ServerHandlerCallback objects that are maintained in the ServerHandler object.
When the ServerHandler is destroyed it will notify each ServerHandlerCallback object
in the list to allow them to clear their pointers to it, avoiding the inadvertent use of an
invalid object.
|
|
ServerHandler
Callback Class
|
The ServerHandlerCallback class is a virtual base class that any class that uses a
ServerHandler object may use to detect when the object is destroyed. When an object
that is inherited from this class is registered with the ServerHandler, it will be called
prior to deleting the ServerHandler object. The inherited object should then set the
associated ServerHandler pointer to NULL to prevent inadvertent access to a deleted
object. The cdevClientRequestObject inherits from this class in order to support this
functionality.
|
|
ServerConnection
List Class
|
The ServerConnectionList is a table of all ServerHandler objects that are currently
connected to servers. The cdevServerInterface uses this table to locate a
ServerHandler by the name of its associated server. This table prevents multiple
connections from inadvertently being established to the same server.
|
|
cdevServerInter
face Class
|
This class has a ServerConnectionList that references all ServerHandlers that are
connected to servers for the specific service. This object provides the mechanisms
that are used to enqueue, dequeue, flush, poll and pend on the outbound connections.
The cdevServerInterface class contains an ACE Reactor that is used to respond to
input/output events that occur on the sockets within the ServerHandlers. Because this
Reactor is static within the cdevServerInterface class, it will handle events for all of the
server connections in all of the services that are inherited from it when it is called.
|
|
cdevClientRe
questObject Class
|
This class inherits the majority of its functionality from, the cdevRequestObject class
and is used to associate a device with a specific message. The identity of the server
that supports the specific device/message combination may be specified in several
ways: through the server tag in the context, through the CDEV DDL file, or by the
server specified by a prior call to set default. If a specific server has been named, the
cdevClientRequestObject will obtain a pointer to the associated ServerHandler object
and will use that as a refer |