The Cm package is an attempt to make simple and system independant the task-to-task communication problem. It covers the communication between tasks that operate on different operating systems, different architectures by hiding every detail of the use of TCPIP on which it is based.
These characteristics are quite important when one considers the domain of control-command in a real time environment, and in a more general meaning when one deals with distributed applications ans informations, the cooperative behaviour of applications permits to obtain a quite modular and structured architecture in the system design.
In order to maintain the global consistency of messages a framing format made of a prefix and a suffix is managed transparently for each CmMessage data. This protocol permits in particular to understand the global enveloppe of the CmMessage data, for detection of contiguous messages or for guaranteeing the completion of the data transfer for each CmMessage.
This mechanism makes the detection possible on message basis instead of on physical frame basis. Thus, a level of handlers is provided for CmMessages based on message types and specialized handlers are automatically triggered at each individual CmMessage reception.
A CmMessage aimed at being sent is not associated to an existing connection (and to the CmConnect object). Instead, the CmMessage will be built (accumulating data in it) and then sent to some destination, or possibly several destinations (each of those having a dedicated CmConnect object transparently managed).
A CmMessage object must first be created using the CmMessageNew function that prepares it to act as an extensible structure for typed values such as numeric values, texts, arrays etc...
Several specialized functions are provided by Cm in order to construct the CmMessage structure dynamically:
The last two functions install arrays of values within the CmMessage structure either by copying it directly into the CmMessage data or by rather installing a description for it, avoiding the physical duplication of the array data. In this case, the array data is actually accessed only when the CmMessage is transfered. Arrays elements may be of any one of the simple types managed as single items and are converted the same way as simple items.
In both cases, the array is available at reception side the same way.
The conversion mechanism (using the Cvt converter object) takes care of byte ordering and word alignment according to the receiving machine. Control informations are installed along the CmMessage structure for guaranteeing the data integrity and to control the semantic of the data items while retrieving data from a received CmMessage.
Each CmMessage object may be given a type through the CmMessageSetType function. The type is provided as a free character string, and is meant to be used at the reception side to trigger dedicated handlers.
The function CmMessageSend is then used to send the CmMessage object to an application (specified by its name). This function takes care of the global message formatting, creates (or reuse) a CmConnect object and send the CmMessage object without any acknowledge manipulation.
The CmMessageSend operation completes in two phases: it first posts the message and then waits for its tranfer until it is finished. The CmMessageWait function is used internally for the second phase.
It is possible to control manually the two phases by using the CmMessagePost function which does not wait for the entire transmisson of the message. The use of this function must be done quite carefully since, for instance the CmMessage object is set in a special state (Sending) while packets are sent. While it is in this state, the CmMessage object cannot be used (Cm takes care of the internal protections) and for instance, trying to Put any item in a CmMessage while it is in this state has no effect on its internal state.
The CmMessagePost function receives as an argument a termination handler (which has the same syntax as the handlers declared for receiving messages) that will be called when the last packet of the CmMessage has been successfully sent, or if the connection is lost.
Each CmMessage object is controled internally by a finite set of possible states that are checked upon at each CmMessage operation. Actions are then either performed (yielding possibly a state transition) or ignored if the corresponding transition is forbidden. The following diagram shows the set of possible states and transitions permitted to a CmMessage object:
Some explanation will help understanding this diagram:
The following example shows the steps of a CmMessage object building and how it is sent to applications with a type:
/* File example6.c */ #include <stdio.h> #include <CmMessage.h> void build_an_image (char** pixels, int* size) { static char image[100]; *pixels = image; *size = sizeof(image); } main() { CmMessage message; int images = 3; int image; char* pixels; int imageSize; if (!CmMessageOpenServer ("Toto")) { fprintf (stderr, "Declaration error.\n"); return (0); } message = CmMessageNew (); CmMessagePutText (message, "Hello you"); CmMessagePutText (message, "I send you"); CmMessagePutInt (message, images); CmMessagePutText (message, "images : "); for (image = 0; image < images; image++) { /* something to build an image... */ build_an_image (&pixels, &imageSize); CmMessagePutBytes (message, pixels, imageSize); } CmMessageSetType (message, "Image"); CmMessageSend (message, "Client1"); CmMessageSend (message, "Client2"); CmMessageSend (message, "Client3"); } |
CmMessage objects are received by type-dedicated handlers managed by the basic Cm engine.
The detection and assembly of individual message frames are performed internally and handlers are triggered when each complete message is available with an automatic detection of message types.
Handlers are installed using the CmMessageInstallHandler when they are associated with one particular type or using the CmMessageInstallDefaultHandler for handling unforeseen message type occurence.
The CmMessage data items are retrieved from it using the following functions:
One should notice that retrieving arrays is done the same way whether they have been produced as embedded or external arrays. In both situations, data are received within the message frame.
The CmMessageGetType permits in addition to get the actual type of the received message (this feature is likely to be usefull in a default handler).
It is also possible to enquire the CmMessage object about the type of the next available item while retrieving the data, by using the CmMessageGetItemType function. The result of this function may be:
The first value, CmMessageItemTail means that the message's tail is met and no further data item is available. It is then useless (though harmless) to keep on getting more data items.
On the other hand, trying to retrieve a data item with the wrong type yields an error message, and results in skipping the current item. Therefore, in case the type of each data item is not statically known in the handler's context, the use of CmMessageGetItemType is strongly encouraged.
An example of an application receiving messages from the one in the previous example. One uses here a dedicated handler that takes care selectively of the messages typed "Image":
/* File example7.c */ #include <stdio.h> #include <CmMessage.h< CmMessageStatus my_image_handler (CmMessage message, char* sender, char* serverName); void show_an_image (char* pixels, int size) { } main() { if (!CmMessageOpenServer ("Client1")) { fprintf (stderr, "Declaration error.\n"); return (0); } CmMessageInstallHandler (my_image_handler, "Image"); CmMessageWait (); } /* (to be continued) ... */ |
/* ... File example7.c (continued) */ CmMessageStatus my_image_handler (CmMessage message, char* sender, char* serverName) { char* text; int images; int image; char* pixels; int imageSize; text = CmMessageGetText (message); text = CmMessageGetText (message); images = CmMessageGetInt (message); text = CmMessageGetText (message); for (image = 0; image < images; image++) { pixels = CmMessageGetBytes (message, &imageSize); /* something to use this image... */ show_an_image (pixels, imageSize); } return (CmMessageok); } |
/* File example8.c */ #include <stdio.h> #include <CmMessage.h> CmMessageStatus my_image_handler (CmMessage message, char* sender, char* serverName); void show_an_image (char* pixels, int size) { } main() { if (!CmMessageOpenServer ("Client1")) { fprintf (stderr, "Declaration error.\n"); return (0); } CmMessageInstallHandler (my_image_handler, "Image"); for (;;) { CmMessageCheck (); /* Here come actions to be executed repeatedly with no explicit wait for messages. The CmMessageCheck will only detects them and activate the appropriate handlers. ... */ } } /* (to be continued) ... */ |
/* ... File example8.c (continued) */ CmMessageStatus my_image_handler (CmMessage message, char* sender, char* serverName) { char* text; int images; int image; char* pixels; int imageSize; text = CmMessageGetText (message); text = CmMessageGetText (message); images = CmMessageGetInt (message); text = CmMessageGetText (message); for (image = 0; image < images; image++) { pixels = CmMessageGetBytes (message, &imageSize); /* something to use this image... */ show_an_image (pixels, imageSize); } return (CmMessageOk); } |
Cm is mainly designed to be operated asynchronously, and in an event driven non blocking mode. This in particular means that protocols installed between applications should not be based on a blocking wait for a dedicated answer.
However logic of the applications often require that a specific answer is expected after having send a request. Cm provides an optional transaction based mechanism to implement such a logic.
A typical scenario showing how to use the Cm transaction could be:
An example of how to implement this scenario includes three code sections:
int id; CmMessage request; id = CmOpenTransaction ("my request", NULL); ... ( now building the request ) CmMessagePutInt (request, id); CmMessageSend (request, "server"); while (!CmIsTransactionTerminated (id)) { CmMessageWait (); } CmCloseTransaction (id); |
CmMessageStatus request_handler (CmMessage message, ...) { int id; CmMessage answer; ... ( getting the request contents ) id = CmMessageGetInt (message); ... ( working with the request ) ... ( and building the answer ) CmMessagePutInt (answer, id); CmMessageSend (answer, sender); ... return (CmMessageOk); } |
CmMessageStatus answer_handler (CmMessage message, ...) { int id; ... ( getting the answer contents ) id = CmMessageGetInt (message); CmTerminateTransaction (id); ... return (CmMessageOk); } |
One should notice the differences between this mechanism and the traditional use of a return (CmMessageBreak) used in the receiving handler:
Cm provides internal debugging facilities for transactions. Through sending a dedicated Cm messages, it is possible to
> cm send -to=my_app -type=CmMessageDebug text=Transactions |
> cm send -to=my_app -type=CmMessageDebug text=TerminateTransaction \ int= |
The following entry points are provided so as to manipulate transactions:
int CmOpenTransaction (const char* info, void* user_object) | This function opens a new transaction, allocating a free identifier for it. A free informational character string can be provided as well as a reference to a user object. This reference can be retrieved at any time using the CmGetTransactionObject function. |
void CmTerminateTransaction (int id) | This function terminates an opened transaction. The main effect of terminating a transaction is to stop the current waiting loop. |
void CmCloseTransaction (int id) | This function definitively closes an existing transaction. The identifier will be released (and can be reused for another transaction). |
int CmIsTransactionTerminated (int id) | This function tests whether the referenced transaction is terminated. |
void* CmGetTransactionObject (int id) | This function retrieves the reference to an object associated with the specified transaction. |
char* CmGetTransactionInfo (int id) | This function retrieves the informational character string associated with the specified transaction. |
CmTransactionStatus CmCloseTransaction (int id) | Definitively close a transaction, releasing its
identifier. No other operation are permitted on this
transaction.
Possible values for the returned CmTransactionStatus are:
|
CmTransactionStatus CmTerminateTransaction (int id) | Terminate an opened transaction. This operation generally results in stopping the current wait loop, marking the transaction as terminated(/i>. The transaction may then be either restarted or stopped. |
CmTransactionStatus CmRestartTransaction (int id) | Restart a transaction for a next occurence of the event associated with it. |
CmTransactionState CmGetTransactionState (int id) | Returns the current state of the referenced
transaction. Possible values are:
|
Two mechanisms permit a user to detect misfunctions while using Cm. Firstly, most (if not all!) Cm entry points return a value which can be either a requested object (such as CmMessageNew) or a status value (such as CmMessageSend or CmMessageWait). Whenever a failure in doing the required operation occurs, the return value would be NULL (for returned objects) or 0 (zero) (for status values). The user should therefore test upon these returned values in order to avoid continuing an illegal Cm sequence. However, internal states are maintained and checked upon for every Cm object in order to keep some safety level.
Secondly, internally detected errors that would not be directly translated into returned values often produce error messages that are sent by default onto stderr. A user defined printer operator, syntacticly similar to the standard vprintf function (use man vprintf in order to get the exact and complete syntax for it) may be declared to Cm with the CmMessageInstallPrinter function. Typical usages for such printer operators are for putting error messages into log-files or displaying them into graphical windows.
The NameServer is a special application (built with Cm) aimed at managing associations between logical names chosen by the Cm applications and their physical addresses (Internet host addresses and port numbers).
Each such address must be unique on the network, therefore, one of roles of the NameServer is to allocate one dedicated port number per application running on a given machine.
The mechanism used to ensure the correct allocation consists in the selection of a value within a specified range for the following entities:
One sees that the set of this four values is enough to specify a complete Cm domain or environment able to manage a coherent set of Cm servers, and opaque to any other Cm domain as far as there is no overlap on the specified address ranges. Within each such domain the server names should be unique, while a given name may exist in two different Cm domains.
The domains that correspond to an individual set of configuration parameters are described within one dedicated text file named $CMROOT/mgr/CmDomains. However, no mechanism or tool is provided yet to ensure that the definitions are consistent and yield no overlap. The greatest care is thus required by system managers while defining the address ranges in this database. Nevertheless, the NameServer applications that manage each individual domain are automatically configured from this database, giving some security level.
The format for this file is described as follows:
An application (corresponding to one executable object) may declare several servers. The behaviour is strictly equivalent then as if the servers would be handled each by one application. The port numbers are allocated individually as well. Clients for both servers should never notice that they are physically installed in the same space.
This document may be read at different levels, whether one merely wants to use Cm or if expertise is required in order to understand the detailed internal mechanisms.
So for a first approach the specification section, the section describing the CmMessage class and the one describing how to build a Cm application could be sufficient.
Then the CmConnect class description introduces some more internal mechanisms and permits to understand the detailed behaviour of the CmMessage objects.
The section on package installation and reconstruction is needed for the system manager who will install Cm, in particular for understanding how to configure the environment.
Lastly, a section is reserved for implementation details and other technical bits. Experienced programmers who want to work on additional layers on top on Cm (for instance) will require it.
Examples are provided at many places in the document. They all correspond to a real C file in the package distribution (the file name is mentionned with each example) and they may be compiled and linked to exercize Cm.
All possible remarks and suggestions either on the package (such as bugs, installation or behaviour problems) or on this document itself are welcome and will be addressed to the author:
Christian Arnault
email: arnault@lal.in2p3.fr
A set of requests may be sent to the NameServer to get informations on the connections it is managing or to ask it to perform some operations.
Each of those requests has to be sent under the form of a CmMessage sent to the NameServer, with dedicated types for each kind of request. When needed request parameters are installed as the CmMessage contents. An answer is always returned to the caller, in the form of CmMessages with corresponding types, so that an application will declare dedicated handlers being in charge of understanding the result of the requests.
On another hand, the prebuilt application cm (shipped in the distribution kit) exploits this functionality and allows to interactively interrogate the NameServer using a shell command as follows:
Unix> cm |
The various requests understood by the NameServer can be summarized in the following table. Each request is described with the message type used and the data items provided. Answers are described with the message type and the data items returned.
Request | Parameters | Answer |
NSGetAddress | {text name;} | NSAddress {text original-name; int port; text new-name;} |
NSGetPort | NSPort {text name; int port;} | |
NSGetNames | {text reg-expr;} | NSNames {text name; ...} |
NSStop | NSStopped | |
NSRestart | NSStopped | |
NSGetConnects | NSConnects {text name; text host; int port;} | |
NSGetPorts | NSPorts {int port; ...} | |
NSSetPeriod | {int period;} | NSPeriod {int new-value;} |
NSGetPeriod | NSGetPeriod {int value;} |
/* File example9.c */ #include <stdio.h> #include <CmMessage.h> CmMessageStatus handler (CmMessage message); main () { char* answer; char* destination; if (!CmMessageOpenServer ("Request")) { CmConnectExit (); return (1); } CmMessageInstallHandler ((CmMessageHandler) handler, "NSNames"); message = CmMessageNew (); CmMessageType (message, "NSGetNames"); CmMessagePutText ("Camera[0-9]*"); destination = CmConnectGetName (CmConnectGetNameServer ()); CmMessageSend (message, destination); CmMessageWait (); } CmMessageStatus handler (CmMessage message) { char* name; printf ("answer = "); while (CmMessageGetItemType (message) == CmMessageItemText) { name = CmMessageGetText (message); printf ("%s ", name); } printf ("\n"); return (CmMessageOk); } |
running this application will produce the following output:
answer = Camera12 Camera23 |
if the applications Camera12 and Camera23 were active when the request has been received by the NameServer.
The NSGetAddress and NSGetPort are meant for internal use only. A dedicated handler is declared within Cm and thus should never be overriden whithout unexpected results.
Each NameServer activation maintains within dedicated files the whole set of informations on connections it manages. This permits in particular to restart it after it stopped (either on explicit user's request or due to a crash!!) while rebuilding the complete knowledge of active connections.
The connections between applications are not killed by a NameServer interruption, only new connections are impossible (and of course requests to NameServer). Eventually, when the NameServer is restarted, reconnection by the applications is performed transparently.
Thie database is maintained in a directory named with the domain name and installed in a root directory specified in the domain configuration file.
Each connection is stored in a specific file named with connection's name.
The NameServer application must be activated with the entire set of values that defines one Cm domain. Domains available to a site where Cm is installed are all specified in a single textual file:
$CMROOT/mgr/CmDomains
which contains one individual line per domain with the following format:
The connection files are stored into the database directory and retrieved in subsequent re-activations of the NameServer.
The Cm distribution kit provides a tool (a Unix shell script located in $CMROOT/mgr/NameServer.start) that, in addition to performing some checks about the effectiveness of the current domain definitions (although these checks are also performed within the NameServer itself) would handle an automatic restart mechanism of the NameServer when it fails for strange reasons, guaranteeing a permanent life. This script is able to understand the various conditions that may occur for a termination of the NameServer, allowing to actually stop it when this is required by the manager, or when a non-recoverable error is detected (such as a bad environment definition or a failure to bind the TCPIP port).
It is important to understand that manually launching the NameServer executable (i.e. without using the dedicated script) is generally a non-safe operation since this may not be done within the proper directory, or on the appropriate machine. The result of doing this is therefore unspecified.
An example of a typical Unix session where the NameServer is first activated then stopped (by a request) and lastly restarted is shown as follows:
The special script could be installed in the system's frame for automatic daemon activation tools (rc.local or cron) but it also provides an automatic reactivation mechanism of the NameServer in case of unexpected crash.
The cm send utility available in the Cm package provides means of building and sending any kind of message to any application.
The message description is built using arguments given to the cm send command as follows:
-to <name>
-type <type>
-handler <type>...
When a single value is given such as in:
-int 10
then a scalar value is installed in the message (using CmMessagePutInt here) and when a list of values is specified, such as in:
-int 10,11,12,16
then an array of values is installed in the message (using CmMessagePutArray here).
The following example shows how to send a request for comment to an application and receive the corresponding answer:
> cm send -to DbServerv4r3 -type CmRequestComment -handler CmComment Message type CmComment received from DbServerv4r3 |
Linking Cm to XWindows, Motif or other such style of interactive environment requires to manage the event-loop coherently in the two worlds (since both are event-driven). Special modules are provided by the Cm distribution kit to handle this question properly either in a context of pure Xt/Motif manipulation or of the OnX package.
These modules provide each a setup function CmXtSetup or CmOnxSetup that will integrate the handler activation scheme of Cm and the callbacks mechanism through a common use of the general wait function CmMessageWait.
The integration of the two environments is shown in the following example:
int main (int argc, char** argv) { if (!CmMessageOpenServer ("Toto")) return (0); /* Declares the Cm handler */ CmMessageInstallDefaultHandler (MyHandler); /* Install Cm in the C interpretor */ CiPathNew ("CMSRC"); CiBindClass ("CmSwitch", (CiRoutine) CmSwitch); /* Declares the OnX callback */ CiDo ("void SendMessage (char* to, char* text);"); CiFunctionBind ("SendMessage", (CiRoutine) SendMessage); /* OnX is initialized */ OKitInitClass (argc, argv); /* Install the Onx dispatcher inside Cm */ { Display* display; Widget w; w = XtWidgetTop (); display = XtDisplay (w); CmOnxSetup (display, OXtDispatchEvent); } /* The CmMessageWait function is substituted to the usual OKitMainLoop function. */ CmMessageWait (); OKitClearClass (); } /* (to be continued) ... */ |
/* ... File example10.c (continued) */ CmMessageStatus MyHandler (CmMessage message, char* name, char* serverName) { char* text; text = CmMessageGetText (message); printf ("Received from %s : %s.\n", Name, text); return (CmMessageOk); } void SendMessage (char* to, char* text) { static CmMessage message = 0; if (!message) message = CmMessageNew (); CmMessageReset (message); CmMessagePutText (message, text); if (!CmMessageSend (message, to)) { printf ("Error sending message to %s\n", to); } } |
The Cm package is distributed with the following components:
The general management of Cm (both for its building up and its usage) is handled through the methods environment. This in particular controls the way environment variables and make macros are generated.
The system configuration for Cm is two-sided: on the one hand, some environment variables are used to access the package (sources, binaries or management tools) and on the other hand Cm domains are specified using a special environment variable. Let's first look at the configuration variables:
$CMROOT | The root directory where Cm is installed.
For instance, at LAL, the version v7r9 is installed
in
Unix> /lal/Cm/v7r9 |
$CMROOT/src | The directory where sources and header files are stored. |
$CMROOT/mgr | The directory where administration and reconstruction files (scripts and Makefile) are stored. |
$CMROOT/$CMCONFIG | The directory where package binaries are stored~: the
various libraries and executables (NameServer and
cm.exe). Usual values are:
|
Then the current Cm domain is specified using the CMDOMAIN environment variable. The set of possible domains is described in the file CmDomains (installed in the management directory of Cm) and contains one line per domain with each having the format (items are separated by spaces):
The Cm application code should first get access to both the Cm functions definitions and to the Cm types. This is done in C language by including the Cm header file:
#include <CmMessage.h> |
The first operation installed in the application's code is to initialize one Cm personnal connection, giving the selected server's name. The important decision that must be taken at this level is whether it will be clonable or not. Making a server clonable means that its name cannot be hard-coded in any of it's client, since its name will be suffixed by a sequence number by the NameServer. Only non clonable servers can be referred to explicitely by clients. Clonable ones may only be answered on requests.
The functions:
permit one of the two possible initialisation modes of Cm servers. They all receive the server's name as their argument, and can be tested upon normal completion.
During the application's life, several servers may be activated or disactivated. Disactivation of a server is done using the function:
An old style of server activation was used in the previous versions of Cm (up to version v5rx). The corresponding entry points have been marked as 'obsolete'. The developpers are thus strongly encouraged to switch to the new style. The entry points concerned with this remark are:
Developping an application using the methods environment automatically provides all required connections and references to include search paths or library usage related with Cm.
This will be obtained if one specifies in the requirements file of the developped package:
use Cm v7r9 application MyApp MyApp.c |
The proper Cm context has to be correctly setup prior any application can be started. This consists in the definition of some environment variables (usually by executing the shell script $CMROOT/mgr/setup.csh) and launching the NameServer application (only if thhis is required and by executing the shell script $CMROOT/mgr/NameServer.start). One way to ensure the proper installation is done one could be to execute the following sequence of commands:
This section and all subsequent ones in this version of the document always take into account the most recent changes in the package.
Cm is meant to manage the task-to-task communications (sending or receiving messages) running on heterogeneous machines (with different architectures or operating systems) without limitations on the number of active connections (apart those induced by the operating systems).
The set of tasks (or applications) that may participate this network define a Cm domain managed by one special application - the NameServer - in charge of the physical addressing scheme, allowing several independant such domains to coexist.
An application with which a connection is requested is referenced by a name, that must be unique within one Cm domain and that doesn't need to mention anyhow the machine on which it runs, nor the transport characteristics (such as TCPIP parameters).
The central manager application NameServer is in charge of every mechanism for name registration, port number allocation and physical addressing operations transparently for the user applications.
Sending and receiving messages are managed asynchronously (without acknowledge management). This in particular implies that a special data framing protocol is added to the internal basic protocol (TCPIP) used for Cm.
The basic TCPIP protocol ensures the effective message transmission but not the arrival time nor the data packets organization (since successive message may be concatenated or split into pieces). It is therefore required to add a software layer on top of it in order to ensure the asynchronous data architecture. Cm provides this feature by the CmMessage package.
On the reception side, a callback-based mechanism is installed, so that user declared functions are triggered on message detection. This mechanism is combined with blocking (with transaction handling) or non-blocking capabilities in order to provide a rich integration scheme for interactive or real-time environments.
In order to be properly rebuilt and operated on a given system, one should ensure the availability of an ANSI C compiler with the socket interface environment supporting TCPIP.
The Cm package is distributed as a compressed Unix tar file comprising the sources, the manual and the various administration or reconstruction files.
Once untarred, one should have a look at the file ./Cm/v7r9/mgr/requirements. It contains definitions for various environment variables, that may need to be adjusted. Once this is done, the following sequence of action should be performed:
Often, one has to understand on a given machine or environment whether Cm is configured, whether the NameServer is running or not, and which domains are available or used.
The following points can be easily checked upon in order to figure out what is the current setup of Cm:
The environment variables CMROOT and CMVERSION must be defined. When this is not true you should run the setup script (you need to explicitely know the exact location where Cm is installed). At LAL you should do:
csh> source /lal/Cm/v7r9/setup.csh |
The set of domains is described in the basic configuration file located in $CMROOT/mgr/CmDomains. the cat Unix command) one sees the list of available domains.
Selecting one of the domain is done by setting the environment variable CMDOMAIN the selected domain name.
Each domain is assigned one host address where the NameServer should run and a directory where the NameServer database is maintained. Only one NameServer may exist in the context of a particular domain. Therefore, before trying to run a new NameServer, one should check its existence. On the machine assigned to it, the Unix ps command can be used as follows:
home> ps -e | grep NameServer 8812 ttyp3 S + 0:00.01 grep NameServer 8762 ttyp4 S 0:00.10 csh -f NameServer.start 8808 ttyp4 S 0:00.05 NameServer.exe LAL v7r9 |
This shows that one NameServer is currently running in the context of the domain named LAL and under the version v7r9.
On the other hand, running this script must be done on the machine assigned to the selected domain.
The typical application that can be run in order to verify the correct behaviour of the NameServer is cm.
Typical use of it are:
home> cm names CmNameServer cm_1 |
This section presents the functions available on the CmConnect and CmMessage classes.
This class handles a structured protocol within data frames that have to be sent over the network through CmConnect objects. The mechanism they provide permits to operate the connection asynchronously since the data itself knows about their internal structure. In addition to the control operations, a general typing mechanism is added in order to install specialized behaviour on specific message receptions.
In most of the CmMessage's methods, the first argument is a reference to the CmMessage object acted upon in this method. For all these situations, the corresponding argument is not documented explicitely.
int CmMessageOpenServer (char* name)
int CmMessageOpenMultipleServer (char* genericName) |
These functions declare a server as a
participant to a Cm domain defined by its context
and selects a-priori an asynchronous operating mode for
incoming connections. One of these two functions should be
selected on the basis of whether the server should be unique
in the Cm domain or clonable.
This is the very first operation to do within a Cm application before any attempt to communicate with another server. For clonable servers, each clone is given a sequence number corresponding to the instance number for this server. Clone numbers are reused when clones disappear.
|
||||||||||||
CmMessage CmMessageNew (void) | Creates a Message object. It is meant to be filled up
with typed items using the CmMessagePutXxx
functions. Once filled up it can be sent to a server using
the CmMessageSend function.
A given Message object may be sent consecutively to several servers. A Message is not a-priori associated to any connection. Instead, either it is used for sending informations, and in this case connections (using Connect objects) will be created (or referenced) dynamically or if it used for receiving informations, the Message itself is brought along with the transfered data (and automatically instanciated locally in the application's space).
|
||||||||||||
void CmMessageDelete (CmMessage message) | Destroys the Message object.
Greatest care should be taken not to destroy Message objects received from another application~: the Cm engine is taking care of it automatically. Killing them would damage the system. |
||||||||||||
void CmMessageReset (CmMessage message) | This functions sets the Message back to the original state it had just after its creation, losing in particular all of its previously stored informations. | ||||||||||||
void CmMessageSetType (CmMessage message, char* type) | Defines the message type. The type can be redefined as
many times as wished, only the last value at sending time is
relevant.
Types are used for selecting at the reception side a specialized handler declared in the client application using the CmMessageInstallHandler function.
|
||||||||||||
int CmMessageSend (CmMessage message, char* name) | This function sends the specified Message object to a
server. The internal mechanism is such that one may consider
that one copy of the Message is actually sent over the
network, and will be received as it is by the server, thus
preserving the accumulated informations.
Once a Message has been sent, it is logically closed for further data accumulations. Thus any further CmMessagePutXxx operation will automatically re-open it, clearing the internal buffers, and accumulate again into a fresh area.
|
||||||||||||
CmMessagePutXxx | Several versions of this function are available, according to the type of the value that is to be accumulated within the Message: | ||||||||||||
void CmMessagePutChar (CmMessage message, char value)
void CmMessagePutShort (CmMessage message, short value) void CmMessagePutInt (CmMessage message, int value) void CmMessagePutLong (CmMessage message, long value) void CmMessagePutFloat (CmMessage message, float value) void CmMessagePutDouble (CmMessage message, double value) void CmMessagePutText (CmMessage message, char* value) void CmMessagePutBytes (CmMessage message, char* value, int length) void CmMessagePutArray (CmMessage message, CmMessageArrayType type, int elements, void* address) void CmMessagePutExtArray (CmMessage message, CmMessageArrayType type, int elements, void* address) |
These functions accumulate typed values within the
internal data buffers of the specified Message object. The
values are internally formatted in a machine-independant way
for transparent transfer.
Arrays can be installed either with direct copy into the Message's frame or by referencing it. In the latter case, data will be used only when the message is sent to some application, putting the array at that time only onto the network. The Message internal buffers are not extended either in this case.
|
||||||||||||
char* CmMessageGetType (CmMessage message) | This function reads the type of the specified
message. It is typically used within a reception handler and
is often usefull when the handler is a default
handler.
|
||||||||||||
CmMessageGetXxx | Several versions of this function are available, according to the type of the value that has to be extracted from the message. | ||||||||||||
char CmMessageGetChar (CmMessage message)
short CmMessageGetShort (CmMessage message) int CmMessageGetInt (CmMessage message) long CmMessageGetLong (CmMessage message) float CmMessageGetFloat (CmMessage message) double CmMessageGetDouble (CmMessage message) char* CmMessageGetText (CmMessage message) char* CmMessageGetBytes (CmMessage message, int* returnedLength) void* CmMessageGetArray (CmMessage message, CmMessageArrayType* type, int* elements) |
These functions sequentially decode values from the
internal message buffer. Values are not actually copied into
the user's space, except for simple numeric ones. Arrays and
strings are merely returned as a reference to the actual
value within the message buffer. Their life time is
therefore limited to the duration of the reception handler
from which these functions are called.
For functions that accept additionnal information (such as CmMessageGetBytes or CmMessageGetArray) a reference to the variable that is meant to receive the value is given in the argument list. If the NULL poiter value is provided, then this argument is ignored.
|
||||||||||||
CmMessageItemType CmMessageGetItemType (CmMessage message) | This function accesses the item type of the current item
within the data buffer of the specified message.
This information may be used for instance to check whether no more information is available from this message.
|
||||||||||||
int CmMessageInstallPrinter (CmConnectPrinter printer) | This function installs a printer operator | ||||||||||||
void CmMessageInstallDefaultHandler (CmMessageHandler handler) | This function installs a reception handler that will be
activated (by the Cm engine) for any message which type is
not handled.
The handler should likely be able to cope with any unforeseen message type (and its data format too !). The use of the CmMessageGetType is usually recommanded in such a handler. Handlers should be prototyped as follows~: CmMessageStatus handler (CmMessage message, char* sender, char* serverName); |
||||||||||||
void CmMessageInstallHandler (CmMessageHandler handler, char* type) | This function installs a reception handler meant to be
activated (by the Cm engine) when the message type matches
the character string specified here.
The handler specified here is assumed to know about the data format within messages of that particular type. Handlers should be prototyped as follows~: CmMessageStatus handler (CmMessage message, char* sender, char* serverName); |
||||||||||||
CmMessageStatus any-message-handler (CmMessage message,
char* sender, char* serverName) |
This is how a user defined reception handler should be
defined for a proper activation by the Cm engine.
The handler must return either CmMessageOk or CmMessageBreak when the innermost wait loop must be broken.
|
||||||||||||
void CmMessageUninstallHandler (char* type) | This function uninstalls a reception handler previously declared for the specified message type. | ||||||||||||
CmConnectCondition CmMessageCheck (void) | This function checks in non-blocking mode whether any of
the existing connection has a pending message. If any, the
associated handler (when defined) will be executed.
On another hand, this function checks the internal service messages such as connection requests, connection losses and previously inactivated connection destructions (deleting the associated Connect objects). The critical role played by this function for physical connection management makes it strongly required whenever a long loop is to be performed in the application. In such a case, the developper should ensure that CmMessageCheck is regularly called within such a loop, specially if messages are sent within the loop (see Fig.NN)
|
/* File example12.c */ /* Application looping on operations while being listening to incoming messages. */ #include <stdio.h> #include <CmMessage.h> main () { CmMessageOpenServer ("Toto"); for (;;) { CmMessageCheck (); /* Any message received prior to the check function will trigger the activation of the corresponding handler (if any). */ /* Actual processing loop ... */ } } |
/* File example11.c */ #include <stdio.h> #include <CmMessage.h> CmMessageStatus my_handler (CmMessage message, char* sender, char* serverName); main() { CmConnectCondition condition; if (!CmMessageOpenServer ("Client1")) { fprintf (stderr, "Declaration error.\n"); return (0); } CmMessageInstallHandler (my_handler, "Image"); condition = CmMessageWaitWithTimeout (1.0); switch (condition) { case CmConnectTimeoutDetection : /* The image did not come */ break; case CmConnectBreakDetection : /* The image is arrived */ break; case CmConnectNoHandler : break; case CmConnectErrorCondition : break; } } /* (to be continued) ... */ |
/* ... File example11.c (continued) */ CmMessageStatus my_handler (CmMessage message, char* sender, char* serverName) { char* text; text = CmMessageGetText (message); return (CmMessageBreak); } |
CmConnectCondition CmMessageServerCheck (char* namePattern) | This function checks in non-blocking mode whether any of
the existing servers whose name match the pattern has a
pending message. If any, the associated handler (when
defined) will be executed.
Otherwise, the behaviour is similar to the CmMessageCheck function.
|
/* File example13.c */ /* Application looping on operations while being listening to incoming messages. */ #include <stdio.h> #include <CmMessage.h> main () { CmMessageOpenServer ("A"); CmMessageOpenServer ("B"); CmMessageOpenServer ("C"); for (;;) { CmMessageServerCheck ("[BC]"); /* Any message received prior to the check function will trigger the activation of the corresponding handler (if any) if they had been sent to a server whose name contain B or C. */ /* Actual processing loop ... */ } } |
CmConnectCondition CmMessageWait (void) | This function is similar to the CmMessageCheck
function except that it waits continuously instead of just
checking for the messages. Handlers are activated and
internal service activities are performed. However this
function may complete when a message is detected on a
connection that has no declared handler.
|
||||||||
CmConnectCondition CmMessageWaitWithTimeout (double seconds) | This function is similar to the CmMessageWait
function except that it waits up to a given number of
seconds. The timeout is given as a floating point value of
seconds.
|
||||||||
CmConnectCondition CmMessageServerWait (char* namePattern) | This function is similar to the CmMessageServerCheck function except that it waits
continuously instead of just checking for the messages for
the selected servers. Handlers are activated and internal
service activities are performed. However this function may
complete when a message is detected on a connection that has
no declared handler. The messages sent to servers whose name
does not match the pattern are left pending and stacked
until a non-selective wait function is used.
|
||||||||
CmConnectCondition CmMessageServerWaitWithTimeout
(char* namePattern, double seconds) |
This function is similar to the CmMessageServerWait function except that it waits up to a
given number of seconds. The timeout is given as a floating
point value of seconds.
|
The CmConnect class implements the basic connection mechanisms, hiding the internal transport layer (based itself on TCPIP and the C socket interface).
Generally, the CmMessage interface is enough to access the Cm features. However, some general requests about the knowledge base of Cm are performed using the direct CmConnect interface.
In most of the CmConnect's methods, the first argument is a reference to the CmConnect object acted upon in this method. For all these situations, the corresponding argument is not documented explicitely.
CmConnect CmConnectNew (char* name) | This function establishes a new connection to
another server (found in the same Cm domain). If the
connection already exists, it is merely reused.
This function is useful when one wants to establish at a desired time the connection to a given server, avoiding for instance any undesirable time penalty in a real-time application when the first message would be sent or received to/from it.
|
||||||
CmConnect CmConnectGetReference (char* name) | This function looks for a Connect object supporting
an active connection to the specified server.
|
||||||
char* CmConnectGetName (CmConnect connect) | Yields the server's Cm name currently connected through
the specified Connect object. This is often usefull within
a handler since a reference to the Connect object that
produced data is provided as the first argument to any
handler.
|
||||||
char* CmConnectGetHost (CmConnect connect) | Provide the host name of the specified connection. Use
it in conjunction with the CmConnectGetReference
in a Message handler.
|
||||||
char* CmConnectGetOwner (CmConnect connect) | Provide the owner name of the specified connection. Use
it in conjunction with the CmConnectGetReference
in a Message handler.
|
||||||
CmConnect CmConnectGetServer (CmConnect connect) | Returns the Connect object representing the server which actually handles this connection. | ||||||
void CmConnectCleanup (void) | This function closes all currently active connections,
deletes all objects managed by Cm and cleans up the
dynamically allocated memory used by Cm.
This is clearly a function that has to be called only at the very end of a Cm application. It may also show various information messages about possibly uncorrectly cleaned up items. These messages are only informational but might be sent to the Cm's author for debugging purposes. |
||||||
CmConnect CmConnectSelectServer (char* name) | This function permits to select one of the servers
managed within the current application to become the
current server. This notion has no real internal
meaning except that it can be inquired using the
CmConnectWhoAmI function.
|
||||||
CmConnect CmConnectWhoAmI (void) | This function permits to access the personnal connection
of the current application. This particular connection is
used to handle the connection requests from other
applications. The Connect object may then be used for
management purposes (such as retrieving the actual Cm name
of the application using the CmConnectGetName
function).
|
||||||
CmConnect CmConnectGetNameServer (void) | This function permits to access the personnal connection established with the NameServer. This Connect object may then be used for communicating with it (together with the CmConnectGetName function). |
Parameter | Usage |
return | A reference to the Connect object used for the NameServer. |
These versions fix several internal problems found in the database file management, and introduce the transaction management mechanisms.
The package has also been cleaned up using the Insure++ tool.
The interactive Cm exerciser (the cm send utility) is also introduced.
The configuration management is now entirely and explicitely managed within the context of the methods environment.
This version introduces quite major changes, especially in the internal protocol management, since all synchronous aspects of the internal management are now removed. The complete protocol, be it between applications and the NameServer or between applications is now asynchronous and fully based on CmMessages objects. In particular, sending CmMessages is now asynchronous, and although the CmMessageSend function still provides the same functionality as before, its internal behaviour relies now on a loop over CmMessageWait allowing the reception of messages while the transmisson of individual packets is processed.
A new function CmMessagePost is provided in order to start the transmission of a (maybe long) message without blocking the calling application. Termination is acknowledged by specifying as an argument to this function a termination handler.
One should note that the NameServer is now a full CmMessage oriented application and that the various service messages exchanged between applications or between applications and the NameServer are all based on CmMessage objects. The result of this new mechanism is that even the connection requests are non-blocking and can be managed entirely while a real-time activity is performed.
This should not imply major changes in the user programming interface for the vast majority of normal Cm users since only the internal acknowledge control mechanisms have been removed.
The only major change in the user programming interface concerns the syntax of message handlers which should now return a value (that can be either CmMessageOk or CmMessageBreak) that indicates whether the current wait loop must be broken or not. The previous mechanism was using the CmMessageBreak function which is now removed from the Cm interface. The main impact of this change is that handlers may now have wait loops themselves and that the break operations act upon the most inner wait loop.
Besides the fact that this version was a major change, a few clean-up actions have been performed on the implementation, that deal with:
CmConnectStartupAsServer and CmConnectStartupAsMultipleServer are now really obsolete, and will produce a informational message and return zero when called. The recently introduced functions CmMessageOpenServer and CmMessageOpenMultipleServer are now the unique way to initialize a Cm server.
This new version adds the capability of waiting selectively for messages sent to one of the servers declared within one Cm application. This is achieved only within the CmMessage environment by providing the new wait functions to the CmMessage package:
The first two functions duplicate the two wait functions that were already available in the Connect package (with identical argument syntaxes) and the two others perform a selective wait (without or with a timeout specified). For these two selective wait functions, a pattern for selecting one or several server names is provided as the first argument.
The same duplication has been offered (for the sake of symetry!) for the non-blocking check functions with:
The following example shows how to wait for messages sent to servers which names start with an 'A' then wait for any message:
main() { ... CmMessageServerWait ("^A"); CmMessageWait (); } |
A change in the parameter syntax for the user defined message handlers reflects now the fact that a particular message has been sent to a particular server. Therefore, a third argument receives the server name to which this message was addressed (The first argument is still the reference of the message and the second argument is still the name of the sender).
Connections are now selectively handled by the different servers that a given application declares. Therefore, several connections may have the same name as long as they are managed by different servers. On the other hand, one may now retrieve the server that handles a particular connection, using the CmConnectGetServer function. The server that handles the servers themselves is always the NameServer.
Few fixes have been done in this release in the mechanisms involved when deleting a message object, in order to effectively release the memory exploited by this object. The consequence of this is that the CmMessageCleanup operation should end up now with a completely released memory.
This new version introduces some major changes in the protocol between applications and the NameServer that will increase the security level or even bring new functionnalities.
New features for management issues are supported here that introduce in particular the notion of domain in order to give a more centralized vision of Cm environments.
A summary of the new features or changes is:
Some defficiencies or bugs have also been fixed up:
Cm has been designed using a conceptual methodology using object oriented principles. The two main concepts manipulated by Cm are the connection that handles the path between two tasks and the message that manages structured information that can be worked on and transported along the connections. Implementation relies on the classes corresponding to these two concepts for organizing the functions presented to the users and data manipulated by Cm.
Cm exploits specifically the features and properties of the TCPIP protocol and for the implementation, the socket interface of the C language, and one of the main goals of Cm is to hide both of them to the user, showing only their major properties.
Cm is written with ANSI C and a set of principles based on object orientation. in order to achieve a good quality level for maintainance, portability on wide range of environments and easy evolutions (Cm has been ported to different operating systems such as DEC-ULTRIX, DEC-OSF1, HP-UX, LynxOS, OS9, SunOS, Solaris and DEC-VMS).
This release deals mainly with bug fixing but improves slightly the reporting facilities for the NameServer:
Several important features have been introduced in this Cm version:
This is a reminder of the changes between the version v3 and the version v4.
Cm is built around two main concepts the connection and the message. Two classes correspond to these concepts: the CmConnect class and the CmMessage class.
CmConnect manages the physical connections between applications by hiding the internal TCP-IP based mechanisms. It supports the packet-oriented data transfers and relies on the data-framing protocol provided by CmMessages in order to achieve fully asynchronous communications.
CmMessage gives the structuration to data needed for operating Cm in asynchronous mode. Many levels of security will guarantee internal integrity of message data as well as separation between messages themselves.
CmMessage objects generally hide the use of the CmConnect class and the user interface of CmMessage is sufficient to handle most of the operations.
The Cm package provides both a library meant to be linked to user applications and a set of predefined utility applications:
CmConnect handles the basic communication mechanisms. It is based on the use of the TCPIP protocol, and exploits the C socket interface. It manages (creates, opens, closes, destroys) the sockets and the primary level of unformatted data exchange.
The first role of CmConnect is to hide the C socket interface and the complexity of the TCPIP protocol, providing a simplified and secured environment.
Then it's able to handle the various management communications with the NameServer when connections are established. Therefore, a physical connection is closely related to the existence of one CmConnect instance, thus the construction method for such an object: CmConnectNew is used for initializing this connection by using the logical application name (received as its argument). Each CmConnect object is actually created only when the connection is possible (if the application is alive). Similarly, a connection loss (due to the application or the network) will yield a desactivation of the corresponding object and eventually its destruction.
Therefore properties of CmConnect objects may be summarized as follows:
Before any operation on CmConnect objects (and therefore on CmMessage objects) an application must declare one or several servers to the system by a name for each server. Each server may be declared either as a unique instance for this name with the CmMessageOpenServer function, or as a clonable server with the CmMessageOpenMultipleServer function. The actual name is a text string that must be unique in one Cm domain. Therefore, when used for a clonable server, the name is considered as a generic name and is suffixed by the NameServer by the sequence number of each particular instance.
The NameServer is requested to handle the name checking in both situations and to perform the port number allocation by the two open functions. One private connection is then created for each server if all required conditions are met.
An example of such a declaration that would like to be called Toto could be:
/* File example1.c */ #include <stdio.h> #include <CmMessage.h> main() { if (!CmMessageOpenServer ("Toto")) { fprintf (stderr, "Declaration error.\n"); return (0); } } |
Each CmConnect maintains templates describing the byte ordering context characteristic of its originating machine. This context is visible through a converter object (instance of the Cvt class) which is able to convert to local representation any kind of received data. Each connection knows the converter required by the particular association of the two machines (possibly both the same) that are involved in a point-to-point communication.
At the connection time (thus only once in the connection life) this conversion information is exchanged between the two applications and from then on will be used for every message transfer (in both directions).
The current converter object of a given CmConnect may be accessed using the CmConnectGetCvt function, and conversions use the CvtGetShort, CvtGetInt, CvtGetFloat, CvtGetDouble functions. One should note that these translations are automatically performed by the CmMessage objects.
This class exploits the connections in full asynchronous mode while keeping the CmConnect objects manipulations tranparent to the users. Although the direct access to the internally managed CmConnect objects is always possible it is never required to get such access for a normal user. Two specialized startup functions CmMessageOpenServer and CmMessageOpenMultipleServer must be used to initialize Cm. Once the appropriate startup is performed CmConnect objects are transparently managed by CmMessage objects both when sending a CmMessage (the CmConnect is created internally) and when receiving it (the CmMessage is created at connection request internally).
The CmMessage objects handle an extensible-array based mechanism to construct the message data, permitting to accumulate typed informations while the CmMessage object keeps the knowledge of the type structure provided by the user. Each CmMessage object built so is then transported across the connection, propagating its internal structure.
The internal data formatting takes care of the different machine architectures at each side of the connection by an automatic translation (using the Cvt converter object of the relevant CmConnect object).
Then CmMessage objects can be given a type (specified as a character string) that will be used at reception level to trigger dedicated activities or handlers (these handlers are declared by the user using the CmMessageInstallHandler function).