Site hosted by Angelfire.com: Build your free website today!

The NML Programmer's Guide (C++ Version)

LI>

Introduction

The Real-Time Control System (RCS) library is a C++ class library intended for multi-platform real-time distributed applications. It has been compiled and tested on several platforms including MS-DOS, MS Windows , and several UNIX Workstations. (See the "Platforms Tested Table".) This document describes the use of the Neutral Manufacturing Language (NML) components of the library.

The Communication Management System (CMS) provides access to a fixed-size buffer of general data to multiple reader or writer processes on the same processor, across a backplane, or over a network. Regardless of the communication method required, the interface to CMS is uniform. Methods are provided to encode all of the basic C data types in a machine independent or neutral format, and to return them to the native format. A CMS_HEADER is added to each buffer which provides information about whether the buffer has been written or read from last, whether the buffer is new to a particular process and the size of the last write to the buffer. CMS uses a configuration file so that users can change communications protocols or parameters without recompiling or relinking the applications.

The Neutral Manufacturing Language (NML) provides a higher level interface to CMS. It provides a mechanism for handling multiple types of messages in the same buffer as well as simplifying the interface for encoding and decoding buffers in neutral format and the configuration mechanism.

Most of the examples have corresponding text files which can be down-loaded and compiled. The examples are included both directly in this document for easy reading and as separate text-only files which are ready to be compiled. (WWW Users: You may have to use your browser's "Save" or "SaveAs" command to get the files.) Unfortunately, given the variety of systems and compilers that are available it is impossible for me to give detailed compiling instructions here. However the following form should work on most systems.(All typed on one line.)

[C++ Compiler] -I[Location of RCS Include Files] [Example C++ File(s)] [RCS Library for Platform] -o [Executable File]

Since a working knowledge of C++ will be very helpful for understanding or using the RCS library utilities you may want to review "A Quick C++ Introduction for RCS Library Users".

Terminology

The figure below illustrates the structure of a typical RCS application using NML. The application is distributed across three computers. Processes 1, 2, and 3 are able to write directly into the shared memory buffers they use because they are located in the same computer or backplane. It is for this reason that they are labeled "LOCAL". Processes 4,5 and 6 can only access the buffers through an NML Server and are therefore labeled "REMOTE". The description might need to be complicated in a system with buffers in more than one machine. Processes would then need to be described as local or remote with respect to a particular buffer.

NML Example System

NML servers must be run for each buffer that will be accessed by remote processes. They read and write to the buffer in the same way as local processes on the behalf of remote processes.

NML uses configuration files to store information about which processes communicate with which buffers and how. Most of the options available to NML programmers are chosen by specifying them in the configuration file. (The configuration files are ascii text files with a format described under "Writing a Configuration File".)

NML is message-based rather than stream-based. Each successful read operation retrieves the data sent in exactly one write operation. Unless queuing is enabled, each write operation moves one message into the buffer replacing any previous message.

More than one type of message can be sent to the same buffer so a unique type identifier is always contained in the message. After a read operation, the process must use this identifier to determine the type of message before using any of the data in the message. Each type of message implies a particular data structure. Most messages are user-defined.

Messages are called encoded if they have been translated into a machine-independent or neutral format such as the eXternal Data Representation (XDR). Buffers are called encoded if the messages in them are to be encoded which is established in the configuration file. NML servers can encode and decode messages on behalf of remote processes. An NML vocabulary defines the set of messages that may be used in an application and provides the necessary functions for encoding and decoding the messages.

Header File

All of the necessary header files will be included if rcs.hh is included.

Classes

The following classes provide the programming interface for CMS and NML:

NML
NMLmsg
NML_SERVER

CMS
CMS_HEADER
CMS_SERVER

CMS_USER

These classes are detailed in the following sections.

NML Application Structure

The next figure shows the structure of a single concurrent process module using NML (the memory buffer appears to be local to the application)

Application->NML->format->update->CMS

The applications routines initialize and use objects from class NML and NMLmsg which depend on some user-defined functions. The format function selects from a set of user defined update functions for each aggregate type the user will need to pass to the memory buffer. The update function for each aggregate type is built by updating each member individually using CMS routines for the basic C data types. These basic update routines write to and read from internal CMS buffers which are themselves read or written to memory buffers that are available to other concurrent processes using the CMS Communications Routines.

Designing an NML Application.

Because NML is configurable, programmers can choose between protocols with higher performance but which may be more restrictive or require more expensive hardware or those that are less restrictive or require less expensive more widely available hardware. By making a buffer local to a process you can improve the performance of that process. By moving processes you may be able to reduce the load on one CPU or increase the number of processes able to use the faster local protocol. Using servers to provide remote access to buffers frees local processes from being slowed down by the communications with remote processes.

Example: Robot Controller/Supervisor Design

A controller for a robot must poll a variety of inputs and perform some computations every "n" milliseconds and a remote supervisor should be able to check the status of the robot when needed.

The next figure shows one possible design for this application. Because the controller can write directly to the shared memory buffer, writing the status takes a minimum time for the controller. Using the NML server allows the supervisor to be located almost anywhere and on almost any host.

Controller/Supervisor System Diagram

Summary of Design Suggestions.

  1. Avoid overloading any CPU by assigning too many processes to it or building a single process which must do too much work.
  2. Place buffers so that they may be accessed locally by the most time-critical process(es).
  3. Use the "LOCAL" protocol whenever possible.
  4. Only use neutrally encoded buffers when necessary.(i.e. backplane communications between different types of processors)

Programming with NML

NML applications programmers need to create a message vocabulary and associated format function, write a configuration file, create an NML object, and use the read and write member functions.

Creating an NML Vocabulary (Format Functions, Update Functions, and Message Definitions)

The message vocabulary is a set of C++ classes, derived from NMLmsg, which can be thought of as data structures that are copied into the NML buffer during a write operation, and copied out during a read operation. Each class is associated with a unique identifier, a positive integer, that allows readers to identify which message is present in the buffer. Besides the particular data members of the class, each class also needs an update function which calls CMS methods to convert the data members to types CMS can handle. Currently, CMS provides support for the basic C language built-in types. However, avoid using enums or long doubles in NML messages. (See "Trouble Shooting - Insufficient Arguments Error")

To enable CMS to neutrally format the data in the buffer or to allow NML servers to encode and decode the data for remote processes, a format function is required. This format function is nothing more than a switch statement, associating NML identifiers with the update functions of particular NML message classes. The format function can be manually programmed as will be described below, or it can be automatically generated using the NML Code Generator.

Example: Message Definition.

Files needed for this example include: nml_ex1.hh, nml_ex1.cc

nml_ex1.hh
 
/* nml_ex1.hh */ 

#ifndef NML_EX1_HH 
#define NML_EX1_HH 
#include "rcs.hh"

/* Give the new structure a unique id number */ 
#define EXAMPLE_MSG_TYPE 101 

/* The id number must be unique within a CMS
buffer, i.e. the number must be different than the id of any other
type that might be written to a particular buffer. For simplicity it
is recommended that the id number also be unique within an
application. */

/* Define the new message structure */ 
struct EXAMPLE_MSG: public NMLmsg {

	/* The constructor needs to store the id number */
 	/* and the size of the new structure */
	/* by passing them as arguments to the base class constructor. */
 	EXAMPLE_MSG():NMLmsg(EXAMPLE_MSG_TYPE, sizeof(EXAMPLE_MSG)){};

	/* Each new type needs to overload the update function. */
	void update(CMS *cms);

	/* Data in this new message format. */
	float f;
	char c;
	int i; 
};

/* Declare the NML Format function. */ 
int ex_format(NMLTYPE type, void *buf, CMS *cms);

#endif  /* End of NML_EMC_HH */ 
nml_ex1.cc
 
/* nml_ex1.cc */ 
#include "rcs.hh" 
#include "nml_ex1.hh"

/* Add a new case to the format function. */ 
/* This is the format function that will be passed to the NML
constructor. "type" is the id stored by the constructors of classes
derived from NMLmsg in the type member. "buf" is a pointer
to a block of contiguous memory where data to be updated is stored. 
"cms" is a pointer to the cms object that is used to access
the globally accessible buffer. */ 
int ex_format(NMLTYPE type, void *buf, CMS *cms)
{
	switch(type)
	{
	/* . . .  */
	case EXAMPLE_MSG_TYPE: /* Add only this case. */
		((EXAMPLE_MSG *)buf)->update(cms);
		break;
	/* . . . */
	default:
		rcs_print("ex_format: No Matching NML type.\n");
		return(-1);
	}
	return(0); 
}

/* Create the update function */ 
void EXAMPLE_MSG::update(CMS *cms) 
{
	cms->update(f);
	cms->update(c);
	cms->update(i); 
} 

NOTE: All the NML updates are identical except that the body should call the CMS update function for each member in the structure. The update function has been overloaded to accept references to all of the basic C data types (ints, floats, etc.) Depending on the CMS mode the update functions will either store their argument in a neutrally encoded buffer or decode the buffer and store the output in the variables passed to the update functions. Just as with the format function, the update functions can be either manually coded or generated automatically with the NML Code Generator.

Creating an NML Object

NML has several constructors, but most users will use the following.

NML(NML_FORMAT_PTR f_ptr, char * buf, char *proc, char *file);

The parameters are:
f_ptr = <address of format function to use>;
buf = <name of the buffer to connect to as specified in configuration file>;
proc = <name under which to access the buffer>;
file = <name of the configuration file>;

Constructors can be called in the declaration of a variable or after new if the memory for the object is dynamically allocated.

Example: Constructors

Files needed for this example include: nml_ex2.cc, nml_ex1.hh, nml_ex1.cc, ex_cfg.nml

nml_ex2.cc
	 
/* nml_ex2.cc */
#include "rcs.hh" 
#include "nml_ex1.hh"

main() {
	/* NML( format function, buffer name, process name, configuration file ) */
	NML example_nml(ex_format, "ex_buf1","ex2_proc", "ex_cfg.nml");
	NML *example_nml_ptr;
	example_nml_ptr = new NML(ex_format, "ex_buf2","ex2_proc","ex_cfg.nml"); 
} 

Reading NML Data

If you examine many of the communications interfaces you'll find a function that looks like the UNIX read function.

/* UNIX general purpose read. */

int read(int fd, char *buf, int nbyte);

/* UNIX Read from message queue. */

int msgrcv(int msqid, struct msgbuf *, int msgsz, long msgtyp, int msgflg);

/* Read from a socket (often used for TCP/IP) */

int recv(int socket, char *buf, int len, int flags);

Notice that the first parameter is an identifier of the source to read, the second parameter is some type of pointer to a buffer, and the third parameter is a measure of the size of the buffer. Unfortunately this is a rather poor model for configurable message-based communication because the program receiving a message must be able to create a buffer large enough to hold the incoming message even though there is no way it can know the size of the new message. NML solves this problem by providing users access to a local buffer that was created based on the size specified in the configuration file and will contain a copy of the incoming message after a read operation.

These are the member functions used to perform read:

NMLTYPE NML::read();

If the read is successful the message currently in the global CMS buffer will be copied into a local buffer for this process. The address of this local buffer is available through the member function get_address. If the buffer is encoded the format function will be called to return the message to native format. The message should be some user defined type derived from NMLmsg.The member NML::error_type can be examined to see the cause of NML::read returning -1.(See "Handling Errors" ) If queuing is enabled on this buffer this read will remove the message from the queue so that other processes that are reading from this buffer will see the next message on the queue and potentially miss this one.

Returns:

0 if the buffer has not been written to since the last read or
-1 if an error occurred; or
the type id of the message received if the buffer contains new data.

NMLmsg *NML::get_address();

This function returns a pointer to the NML data stored during an NML::read() operation.

Example: Reading from an NML Channel.

Files needed for this example include: nml_ex3.cc, nml_ex1.hh, nml_ex1.cc, ex_cfg.nml

nml_ex3.cc
 
/* nml_ex3.cc */ 
#include "rcs.hh" 
#include "nml_ex1.hh"

#ifdef VXWORKS 
extern "C" void example_nml_read();

void example_nml_read() 
#else 
main() 
#endif 
{
	RCS_TIMER timer(0.1);
	NML example_nml(ex_format, "ex_buf1","ex3_proc", "ex_cfg.nml");
	EXAMPLE_MSG *example_msg_ptr;
	int quit = 0;

	while(!quit)
	{
		switch(example_nml.read())
		{
		case -1:
			rcs_print( "A communications error occurred.\n");
			quit = 1;
			break;

		case 0:
			/* The buffer contains the same message */
			/* you read last time. */
			break;

		case EXAMPLE_MSG_TYPE:
			example_msg_ptr = (EXAMPLE_MSG *)example_nml.get_address();
			rcs_print(" We have a new example message. \n");
			rcs_print(" The value of its members are:\n ");
			rcs_print(" f=%f, c=%c, i=%d\n ",
				example_msg_ptr->f,
				example_msg_ptr->c,
				example_msg_ptr->i);
			quit = 1;
			break;
		}
		timer.wait();
	} 
} 

This example also uses the RCS_TIMER class and the rcs_print function described in the guide for the RCS Library Lower Level Utilities. The symbolic constant VXWORKS should be defined only if you wish to compile the example for VxWorks. If you try to run this example, it will wait for something to be written into the buffer. To write something into the buffer, you can use the example in Writing NML Data

NMLTYPE NML::blocking_read(double timeout);

This performs the same function as the read() above except that if there is no new data the calling process will be put to sleep until either another process writes to the buffer or the timeout occurs. The timeout is given in seconds. to wait indefinitely provide a negative timeout. It is necessary to add "bsem=<key>" to the buffer line of the NML configuration file in order to be able to perform a blocking read.

Returns:

-1 if an error occurred; or
the type id of the message received if/when the buffer contains new data.

NMLTYPE NML::peek();

/* Read an NML message from a CMS buffer without changing the was_read flag */

Peek works exactly the same as read except that the flag that lets others know when the buffer is read is not changed and if queuing is enabled the message is not removed from the queue. This could be useful if you need to monitor a buffer without letting other processes using the buffer know. The member NML::error_type can be examined to see the cause of NML::peek returning -1.(See "Handling Errors")

Returns:
0 if the buffer has not been written to since the last read;
-1 if an error occurred; or
the type id of the message received if the buffer contains new data.

Writing NML Data

If you examine many of the communications interfaces you'll find a function that looks like the UNIX write function.

/* UNIX general purpose write. */

int write(int fd, char *buf, int nbyte);

/* UNIX Send to a message queue. */

int msgsnd(int msqid, struct msgbuf *, int msgsz, long msgtyp, int msgflg);

/* Output to a socket (often used for TCP/IP) */

int send(int socket, char *msg, int len, int flags);

Notice that the first parameter is an identifier of the destination to write, the second parameter is some type of pointer to a buffer, and the third parameter is a measure of the size of the buffer. However C++ allows us to considerably simplify this interface, by storing the size of a message when its constructed and by overloading the write function to accept either references or pointers to messages. All of the NML write functions are non-blocking.

These are the member functions used to perform writes:

/* Write an NML message into a CMS buffer */

int NML::write(NMLmsg &nml_msg); /* Reference version. */

int NML::write(NMLmsg *nml_msg); /* Pointer version. */

nml_msg should be a pointer or reference to an object of some user defined type derived from NMLmsg. If the buffer is configured to be in a neutral format the message will be encoded before it is written to the CMS buffer. The write functions overwrite the message currently in the buffer if queuing is not enabled. The member NML::error_type can be examined to see the cause of NML write returning -1.(See "Handling Errors" )

Returns:
0 if successful;
-1 otherwise.

Example: Writing to an NML Channel.

Files needed for this example include: nml_ex4.cc, nml_ex1.hh, nml_ex1.cc, ex_cfg.nml

nml_ex4.cc
 
/* nml_ex4.cc */ 
#include "rcs.hh" 
#include "nml_ex1.hh"

#ifdef VXWORKS 
extern "C" void example_nml_write()

void example_nml_write() 
#else main() 
#endif {
	NML example_nml(ex_format, "ex_buf1","ex4_proc", "ex_cfg.nml");
	EXAMPLE_MSG example_msg;
	example_msg.f = 123.456;
	example_msg.c = 'c';
	example_msg.i = 99;
	example_nml.write(example_msg); 
}

The symbolic constant VXWORKS should be defined only if you wish to compile the example for VxWorks. This example writes a message into a buffer. To read the message use the example in Reading NML Data

/* Write an NML message into a CMS buffer if it has been read. */

int NML::write_if_read(NMLmsg &nml_msg); /* Reference version. */

int NML::write_if_read(NMLmsg *nml_msg); /* Pointer version. */

These functions combine the operations of writing and checking if a buffer has been read. It checks to see if the buffer has been read. If it has then it writes the message into the buffer just as write would, but if not it returns -1. Since there is only one access to the buffer, there is no way for another process to write into the buffer between the check and the write. The member NML::error_type can be examined to see the cause of NML write_if_read returning -1.(See "Handling Errors")

Returns:
0 if successful;
-1 otherwise.

Checking If Data Has Been Read.

If queuing is not enabled then a flag is kept in every CMS buffer called was_read. Every time a write is performed on the buffer the flag is set to 0. Every time a read is performed on the buffer the flag is set to 1. The check_if_read function just returns the value of that flag. To avoid overwriting a buffer that has not been read yet, it is better to use the write_if_read function. The member NML::error_type can be examined to see the cause of NML::check_if_read returning -1.(See"Handling Errors" )

If queuing is enabled then the check_if_read function returns 1 only if all of the messages in the buffer have been read meaning that the queue is empty.

Here is the prototype:

int NML::check_if_read();

Returns:
0 The buffer contains a message that has never been read.
1 The buffer contains a message that has been read at least once.
-1 An error occurred that prevented NML from determining whether the buffer has been read.

Clearing a buffer.

You may want to clear a buffer to preempt previously sent messages still in the queue or to ensure that residual data in a buffer is not mistaken for NML messages.

int NML::clear();

Returns:
0 The buffer was successfully cleared.
-1 An error occurred.

Loading an NML Configuration file to memory.

In order to start up a process faster, it is possible to load the configuration file in to memory before creating several NML channels that use the same file. The file can later be unloaded to free the previously allocated memory.

Example: Loading a config file for faster startup.

 

. . .
	load_nml_config_file( "ex_cfg.nml" );
	NML example_nml1(ex_format, "ex_buf1","ex4_proc", "ex_cfg.nml");
	NML example_nml2(ex_format, "ex_buf2","ex4_proc", "ex_cfg.nml");
	NML example_nml3(ex_format, "ex_buf3","ex4_proc", "ex_cfg.nml");
	NML example_nml4(ex_format, "ex_buf4","ex4_proc", "ex_cfg.nml");
. . .

	unload_nml_config_file( "ex_cfg.nml" );
. . .

Converting an NMLmsg to a string.

It is occasionally helpful to be able to display the contents of any NMLmsg in a string. To accomplish this you will need an NML object which was initialized with a format function that handles your message type.

const char * NML::msg2str(NMLmsg *);

const char * NML::msg2str(NMLmsg &);

Returns:
This function returns a pointer to a string with each member of the NML message converted to a string and separated with commas if successful or NULL otherwise. The first two members will be the type and size of the message. The string may be cleared on the next call to read, write, check_if_read, peek, write_if_read, clear, or msg2str with that NML object, or when that NML object is deleted, so the string should be displayed or copied before any of these operations occur.

Using Phantom Buffers.

Occasionally users may wish to temporarily redirect messages intended for a CMS buffer. One way of doing this is setting up functions to simulate the behavior of the NML commands and set the pointers NML::phantom_clear, NML::phantom_check_if_read, NML::phantom_read, NML::phantom_peek, NML::phantom_write, and/or NML::phantom_write_if_read to those functions. If the buffer type or process type specified in the configuration file is "PHANTOM" then every NML call of that type will result in calling your phantom function. Otherwise, NML will access the buffer normally.(See the section "Writing a Configuration File")

Example: Using PHANTOM overrides.

Files needed for this example include: nml_ex5.cc, nml_ex1.hh, nml_ex1.cc, ex_cfg.nml

nml_ex5.cc
 
/* nml_ex5.cc */ 
#include "rcs.hh" 
#include "nml_ex1.hh"

/* This example uses the PHANTOM writes to send all messages to stdout.  */

int my_write(NMLmsg *msg) 
{
	printf("Message %d written.\n", msg->type);
}

main() 
{
	NML example_nml(ex_format, "ex_buf1","ex5_proc", "ex_cfg.nml");
	example_nml.phantom_write = my_write;
	EXAMPLE_MSG example_msg;
	/* . . .  */
	example_nml.write(example_msg); 
} 

Handling Errors

When the NML member functions cannot perform their task they try to provide developers with some information that may allow them to resolve the problem. This information is available in several forms.

The functions NML::read(), NML::write(), NML::peek(), and NML::write_if_read() return -1 if an error occurred. NML::get_address() returns NULL if an error occurs.

Messages are printed to character display devices or stored in a linked list with the rcs_print_error facility. (See the document "RCS Lower Level Utilities" for more information on the rcs_print_error facility. ) Often several messages are issued for the same error, because if an error occurs at a low level the low level function will print an error and return a value indicating an error to a higher level function which may then also print an error. This allows the user to see the detail available at the lower level and the context available at the higher level.

NML::error_type is a variable set by NML functions that fail. It may have one the following values:

int NML::valid();

NML::valid() returns 0 if the object was not properly constructed or if an error has occurred severe enough that it is unlikely that any of the other NML operations on this object will succeed or 1 if everything seems to be in order.

Example: Checking Error Status

Files needed for this example include: nml_ex6.cc, nml_ex1.hh, nml_ex1.cc, ex_cfg.nml

nml_ex6.cc
 
/* nml_ex6.cc */

#include "rcs.hh" 
#include "nml_ex1.hh"

/* This example prompts the user when NML times out to see if it
should try again. */

main() {
	NML example_nml(ex_format, "ex_buf1","ex6_proc", "ex_cfg.nml");
	EXAMPLE_MSG *example_msg_ptr;
	char input_array[10];

	TRY_AGAIN:

	switch(example_nml.read())
	{
	case -1:
		if(example_nml.error_type == NML_TIMED_OUT)
		{
			rcs_print("NML timed out\n");
			rcs_print("Do you want to try again? (y/n)");
			gets(input_array);
			if(input_array[0] == 'y')
				goto TRY_AGAIN;

		}
		break;

	case 0:
		/* The buffer contains the same message you read last time. */
		break;

	case EXAMPLE_MSG_TYPE:
		example_msg_ptr = (EXAMPLE_MSG *)example_nml.get_address();
		/* We have a new example message. */
		break;

	} 
} 

The Section called "Writing an NML Configuration File" has been moved to it's own page at
http://isd.cme.nist.gov/proj/rcs_lib/NMLcfg.html.

Spawning and Killing NML Servers

NML servers allow remote processes to access local buffers. The code for the servers has already been included in the RCS library but you must still start and stop them. There are several ways for you to control when servers are spawned and killed.

Using the run_nml_servers function.

void run_nml_servers();

Each time an NML object is created it is added to a global list. In operating systems like LynxOs and SunOs that use heavy-weight threads each process has its own list. In operating systems like VxWorks that use light-weight threads the list is shared by all processes currently running that use NML. The function run_nml_servers reads the lists, checks for the server configuration flag, and groups the buffers with the same RPC number. For each different RPC program number a server is spawned to handle requests for the group of buffers with that RPC number. If all of the RPC numbers were the same the current process would become the server for all the buffers on the list that have a non-zero server configuration flag. This function will not return.

Example: Two NML buffers with a single process server.

Files needed for this example include: nml_ex9.cc, nml_ex1.hh, nml_ex1.cc, ex_cfg.nml

nml_ex9.cc
#include "rcs.hh"
#include "nml_ex1.hh"

main()
{
	NML nml1(ex_format, "ex_buf1","ex9_svr","ex_cfg.nml");
	NML nml2(ex_format, "ex_buf2","ex9_svr","ex_cfg.nml");

	run_nml_servers(); /* This never returns. */
}

Using the nml_start and nml_cleanup functions.

void nml_start();

void nml_cleanup()

The nml_start function works like run_nml_servers except that it may spawn an additional process(es) so that it will return. The nml_cleanup function deletes all NML_SERVER objects and all NML objects so that all servers that were spawned will be stopped with SIGINT.

Example: Two NML buffers with servers and application code with single main.

Files needed for this example include: nml_ex10.cc, nml_ex1.hh, nml_ex1.cc, ex_cfg.nml

nml_ex10.cc
/* nml_ex10.cc */
#include "rcs.hh"
#include "nml_ex1.hh"

main()
{
	NML nml1(ex_format, "ex_buf1","ex10_svr","ex_cfg.nml");
	NML nml2(ex_format, "ex_buf2","ex10_svr","ex_cfg.nml");

	/* Spawn 2 servers and continue. */
	nml_start(); 

	/* . . . */
	/* Perform some processing. */
	/* . . . */


	/* Kill the 2 servers and close NML channels. */
	nml_cleanup();
}

Warning: Do NOT use any nml objects after calling nml_cleanup.

User Command Utilities.

There are several utilities that can be used directly by users without any additional programming.

Testing for the existence of NML buffers.

nmltest [config_file local_host]

The nmltest program reads the config_file and attempts to connect to every buffer in the file. If it succeeds the buffer exists. If it does not either the master for the buffer was not started, or the server was not started, or the config file contains an error. It attempts to use the local protocol for buffers on the local_host. If you do not specify the config_file or local host it will prompt you for them. It also reports the type and size of message if any in the buffer and some other information which may be useful.

Determining the performance of NML on a particular system.

nmlperf [config_file local_host master iterations increments detailed_output display_mode]

perfsvr [config_file local_host]

The performance of NML varies depending on the system you are running under and the type of protocol used by CMS. The nmlperf program connects to every buffer in config_file. It uses the local protocol if the buffer is on local_host. It initializes the buffer if master equals 1. It writes and reads the buffer with messages from a minimum size to the size of the buffer for iterations times with increments different sizes. If detailed_output equals 1 then it will display the time for every read and write. display_mode can be "B" for bytes-per-second or "M" for messages-per-second.

If you don't specify all the parameters then you will be prompted for them. The perfsvr program is run on a remote machine to allow testing of remote protocols.

Removing Unwanted Buffers

nmlclean [config_file local_host]

Occasionally severe errors cause programs to exit without deleting the NML buffers that they create. The nmlclean program attempts to free all of the operating system resources associated with buffers in the config_file on local_host and to kill the NML servers that are running.

Using NML Script Files.

NML script files allow programs that use NML to be tested with certain inputs without the need for running a separate program to create those inputs. To use script files the buffer type in the configuration file should be set to FILEMEM and the in=<script_file> should be added to the buffer line. Each time the program calls NML::read or NML::peek for that buffer the script file will be read line by line until a command is found

The following commands can be used:

MSG

When a line begins with "MSG>" the command causes the read to return a particular message. The rest of the line should contain the parameters of the message in the same order as the CMS update functions, including the type and size which are in every NMLmsg.

Example:

In the file nml_emc.hh the NML_INTERP_RUN message is defined as follows.

#define NML_INTERP_RUN_TYPE ((NMLTYPE) 410)
class NML_INTERP_RUN : public NMLmsg
{
 public:
  NML_INTERP_RUN() : NMLmsg(NML_INTERP_RUN_TYPE, sizeof(NML_INTERP_RUN)) {};
  void update(CMS *cms);

  char file[NML_INTERP_FILE_LEN];
};
To have an NML_INTERP_RUN message read from the script, with file set to "team.dms" use the following line.
MSG> 410, 0,team.dms,

The final comma is a good idea to make sure that no write space that might be on the end of the line is included in the string.

WAIT

When a line begins with "WAIT> the program will respond as if no new messages were received for some period of time.

COMMENTS

Comments can be inserted in the script file by beginning the line with the "#" character.

Trouble Shooting

Here is a list of some of the problems that NML users have encountered and some tips for resolving them.

Compile Time Errors.

While compiling one of the update functions for the NML vocabulary, the compiler complains that there are insufficient arguments for one of the CMS::update functions.

Insufficient Arguments Error

I received the error from g++.

types.cc:47: too few arguments for method `update`.

And in types.cc line 47 I have:

cms->update(x);

Solution: Check the type of the variable you are updating. It is probably either an enum or a pointer type, since neither of these is supported. If it is an enum, define it as an int. The problem with enumerated values is that although one could define a function that accepted enums of a particular type it is impossible to define a function that would accept a reference to any enum as a parameter. If the variable is a pointer type define it as whatever it was pointing to. If it is an array add the length of the array as a second parameter to CMS::update.

Run Time Errors

While starting an NML server, I get the following error.

server: Can`t register server.

Solution: Try it again. If possible the server will try to unregister the process currently registered on that number so it will be able to register. Check to see if another RPC server is already registered on the RPC program number that you are trying to use. (Use the command "rpcinfo -p" on a Sun, SGI or LynxOS machine.) If there is another server registered, then change the number in the configuration file.

When I am trying to initialize an NML channel, I get the following error:

shmget failed: No such file or directory.

Solution: The master was probably not started before this non-master. Start the master first.

When I am trying to initialize an NML channel, I get the following error.

shmget failed: Invalid argument.

Solution: This usually happens when the shared memory buffer exists but it's not the size that this process expects. Check that all of the processes are using the same configuration file and that the configuration file hasn't changed since the master process started. Also check the shared memory buffer with "ipcs" command to make sure no one else has created the buffer with a different size.

When I am trying to initialize an NML channel to a remote host, my program gets hung.

Solution: Check that the host specified for the buffer in the configuration file is correct, up and running and that the programs host can ping the remote host. If the host is not up and running or you can't ping it call your system administrator.

When I am trying to initialize an NML channel to a remote host, I get the following error.

RPCMEM::open: RPC: Unknown Host

Solution: The next line after this error should list the host the program expected to connect with. If it is not a spelling mistake, ask your system administrator to check and if necessary add the internet number of the remote host to the hosts database.

When I am trying to initialize an NML channel to a remote host, I get the following error.

RPCMEM::open: RPC: Program not registered.

Solution: The next line after this error should list the host the program expected to connect with and the RPC number that the NML server on that host should use. Make sure that the NML server has been started before any clients need to connect.


<

If you have questions or comments regarding this page or you would like to be notified of changes to the RCS library via email, please contact Will Shackleford at shackle@cme.nist.gov