void *_GalSS_init_server(GalIO_ServerStruct *server, int argc, char **argv)
Each MIT server accepts the following arguments:
static char *oas[] = {If both -port and -contact_hub are present, the server will start up as both client and listener.
"-port port", "run as a server, listening for client connections on this port", NULL,
"-assert", "exit unhappily if we fail to get the desired port",
"-color", "enable color printing in a cxterm",
#ifdef _REENTRANT
"-thread", "use threads instead of timed tasks (experimental)",
#endif /* _REENTRANT */
"-nottloop", "do not use the timed_tasks_loop mechanism",
"-ttloop", "obsolete -- maintained for compatibility only",
"-maxconns max", "maximum number of connections for ttloop", "1",
"-validate", "validate each message send and return against the message signature",
"-verbosity level", "set the verbosity of this server", NULL,
"-contact_hub \"host:port...\"", "run as client, contacting Hubs at the specified host and port pairs (overrides default port setting, but not -port)", NULL,
"-server_locations file", "a server locations file of lines server host:port [hub|server]", NULL,
"-session_id id", "if -contact_hub is used, lock this client to the specified Hub session", NULL,
NULL
};
See the oa library for details on how these flags are processed. These arguments will be removed from the arglist before it is passed to _GalSS_init_server. For more details on how to run this server, see the section on the server executables.
When the server is first run, the generic server library will initialize the server by calling _GalSS_init_server. If the function is not defined, the default in the library will be used. The argument of this function is a GalIO_ServerStruct *. This is the server structure which maintains information about the default listen port, the dispatch functions, the server name, the number of connections, etc. For more details about what functions can be used to manipulate the server object, see the server architecture documentation.
The _GalSS_init_server function in double_core.c sets the initial increment, extracted from the command line.
static char *oas[] = {
"-increment i", "set the initial value", "1",
NULL
};
void *_GalSS_init_server(GalIO_ServerStruct *s, int argc, char **argv)Returning a non-NULL value from _GalSS_init_server has the same effect as calling GalIO_SetServerData, although using this capability is strongly discouraged.
{
int i, increment = 1;if (!GalUtil_OACheckUsage(argc, argv, oas, &i))
exit(1);
GalUtil_OAExtract(argc, argv, oas, "-increment", GAL_OA_INT, &increment);
GalIO_SetServerData(s, (void *) increment, NULL);
return (void *) NULL;
}
void _GalSS_print_usage(int
argc,
char **argv)
This is one way
for the server to print out its usage information. The default way of defining
this function is
void _GalSS_print_usage(int argc, char **argv)The function GalUtil_OAPrintOptions is provided in connection with the oa library, and can be used when the appropriate argument array is defined. See the example at the beginning of this section.
{
GalUtil_OAPrintOptions(argc, argv, oas, NULL);
printf("\n");
}
It's important that every reply to a dispatch function include the administrative information associated with that call. The various ways of providing a dispatch function reply all guarantee that this information will be included (returning a frame from a dispatch function, as well as calling the functions GalSS_EnvDestroyToken, GalSS_EnvError, or GalSS_EnvReply, all of which we'll discuss in a moment). The call environment is also used to send new messages, using the functions GalSS_EnvWriteFrame and GalSS_EnvDispatchFrame.
Although the call environment is almost always the proper channel through which to communicate with the Hub, the actual communication is done via the connection object, which is stored in the call environment and is accessible via the function GalSS_EnvComm.
For more details about functions which manipulate the connection object, see the server architecture documentation. For more details on the use and manipulation of call environments, see the session management documentation.
multiply.c defines a dispatch function, multiply, which multiplies a number in the :int slot by a predefined factor and returns the number.
Gal_Frame multiply(Gal_Frame frame, void *server_data)If you need to cache information per connection across calls to dispatch functions, it is possible to set and get connection-specific data directly through the environment object.
{
int i = Gal_GetInt(frame, ":int");if ((INT_MAX / i) < Factor) {
/* If we're about to overflow... */
GalSS_EnvError((GalSS_Environment *) server_data,
"multiply would overflow MAXINT");
return (Gal_Frame) NULL;
} else {
Gal_SetProp(frame, ":int",
Gal_IntObject(Factor * i));
return frame;
}
}Memory management
Both the frame which is passed to the dispatch function and the frame which is returned (if different) are freed by the toplevel loop using Gal_FreeFrame. No dispatch function should free the incoming frame. See the frame documentation for detailed memory management comments about frames.
void *GalSS_EnvGetCommData(GalSS_Environment
*env)
Retrieves the connection-specific data
from the connection object stored in env. See also GalIO_GetCommData.
void GalSS_EnvSetCommData(GalSS_Environment
*env, void *data, void (*free_fn)(void *))
Sets the data specific to the connection
stored in env.. If free_fn is non-NULL, it will be called
on the
data when the data is reset or the connection is destroyed.
See also GalIO_SetCommData.
If you've cached information specific to
a given dispatch function, it is possible to access this information directly
through the environment object.
void *GalSS_EnvGetClientData(GalSS_Environment
*env, char *name)
Retrieves the information specific to
the dispatch function name from the connection object stored in
env.
See also GalIO_GetCommClientData.
You should always use the call environment to send these messages. It is possible to send them using the connection alone (accessible via the call environment using the function GalSS_EnvComm(); see the server structure documentation for details), but important session information will be lost. When you're inside a dispatch function, using the call environment is straightforward; if you're outside a call environment (if you've set up a brokering callback or a timed task), you must take special precautions to make sure the call environment isn't freed after the dispatch function exits. See the session management documentation for more details.
When a message is written to or read from
the Hub, the type of the message is also specified. The possible types
are:
GAL_MESSAGE_MSG_TYPE | A new message |
GAL_REPLY_MSG_TYPE | A normal reply to a message |
GAL_ERROR_MSG_TYPE | An error reply to a message |
GAL_DESTROY_MSG_TYPE | A destroy request for the specified token (also counts as a message reply) |
GAL_POSTPONE_MSG_TYPE | A reply from the server informing the Hub that the message return will be provided later (see the documentation on continuations) |
If the message type is GAL_ERROR_MSG_TYPE, the accompanying frame will currently have the following form (use GalIO_GetError to access the values):
{c system_errorAt this point, possible errors are:
:errno <num>
:err_description <string>
...}
GAL_APPLICATION_ERROR | Some server generated an error via GalSS_EnvError |
GAL_TRANSMISSION_ERROR | An error was encountered in sending a frame |
GAL_RECEPTION_ERROR | An error was encountered in reading a frame |
GAL_NO_OPNAME_ERROR | Some server did not implement a requested operation |
GAL_SERVER_DOWN_ERROR | The Hub could not contact a required server |
GAL_NO_FRAME_ERROR | A dispatch was requested on an empty frame |
int GalSS_EnvWriteFrame(GalSS_Environment
*env, Gal_Frame frame, int do_block)
This function writes
frame
to the Hub through the connection object stored in the env. If do_block
is set, the function guarantees that the write has happened before returning.
The message type written is always GAL_MESSAGE_MSG_TYPE.
double_core.c defines a dispatch function, twice, which takes the value of :int, doubles it, introduces a new message to the Hub with the doubled integer, and returns NULL. If it is about to overflow MAX_INT, or if the number it's supposed to double is 0, then it generates an error.
if (!program_name)
program_name = "main";
if (i == 0) {
/* We'll loop forever.
*/
GalSS_EnvError((GalSS_Environment
*) server_data,
"i is 0");
return (Gal_Frame) NULL;
} else if ((INT_MAX / i) < 2) {
/* If we're about to overflow...
*/
GalSS_EnvError((GalSS_Environment
*) server_data,
"double would overflow MAXINT");
return (Gal_Frame) NULL;
} else {
new_f = Gal_MakeFrame(program_name,
GAL_CLAUSE);
Gal_SetProp(new_f,
":int", Gal_IntObject(2 * i));
Gal_SetProp(new_f,
":program", Gal_StringObject(program_name));
GalSS_EnvWriteFrame((GalSS_Environment
*) server_data, new_f, 0);
Gal_FreeFrame(new_f);
return (Gal_Frame) NULL;
}
}
int GalIO_GetError(Gal_Frame
f,
char **err_desc)
Retrieves an error from a error frame
returned from GalSS_EnvDispatchFrame. If the frame is not a well-formed
error frame, this function will return -1 and set *err_desc to NULL
if err_desc is provided. If the frame is a well-formed error frame,
this function will return an error code,
and set *err_desc if err_desc is provided. If an error
description is present in the frame, the description will be a string,
otherwise NULL.
double_core.c defines a dispatch function, complex_twice, which doubles the result it gets from the multiply server:
Gal_Frame complex_twice(Gal_Frame frame, void *server_data)
{
Gal_Frame new_f = Gal_MakeFrame("multiply", GAL_CLAUSE);
Gal_Frame res_f;
GalIO_MsgType t;
int i;Gal_SetProp(new_f, ":int", Gal_IntObject(Gal_GetInt(frame, ":int")));
res_f = GalSS_EnvDispatchFrame((GalSS_Environment *) server_data, new_f, &t);
Gal_FreeFrame(new_f);if (!res_f) {
GalUtil_Warn("Didn't hear back from multiply");
return (Gal_Frame) NULL;
}switch (t) {
case GAL_REPLY_MSG_TYPE:
prog_name = Gal_GetString(frame, ":program");
if (!prog_name) prog_name = "main";
Gal_SetProp(new_f, ":program", Gal_StringObject(program_name));
return twice(res_f, server_data);
case GAL_ERROR_MSG_TYPE:
return (Gal_Frame) NULL;
default:
return (Gal_Frame) NULL;
}
}
Note that it is also possible to postpone the reply to a message beyond the scope of the dispatch function. The functions for doing this and the motivation for it are discussed extensively in the session management documentation.
int GalSS_EnvError(GalSS_Environment
*env, char *description)
Reports an error as the return value.
The message type of the message written is always GAL_ERROR_MSG_TYPE; it
is annotated with the administrative information of the frame passed in.
If you use this function, the return value from the dispatch function will
be ignored.
int GalSS_EnvDestroyToken(GalSS_Environment
*env)
Makes a destroy request. The token information
is taken from the frame passed in. The message type of the message written
is always GAL_DESTROY_MSG_TYPE. If you use this function, the return value
from the dispatch function will be ignored.
int GalSS_EnvReply(GalSS_Environment
*env, Gal_Frame f)
Provides the frame f as a normal
reply of type GAL_REPLY_MSG_TYPE. This function is provided mostly for
completeness, since it duplicates the functionality embodied in returning
a frame from the dispatch function. However, if for some reason you want
a dispatch function to keep running past its "natural" reply point, you
can use this function to satisfy the Hub's expectation of a reply.
Gal_Frame reinitialize(Gal_Frame frame, void *server_data);
The dispatch function reinitialize is special. It is called when the Hub first connects to the server. Any connection-specific initializations should be put in the reinitialize dispatch function. Like all other dispatch functions, the second argument is typed to void * for backward compatibility, but can reliably be cast to GalSS_Environment *.
As of version 3.0, reinitialize is no longer restricted in its use. However, because it is used to initialize a connection, it's worth observing that while is convenient to use the reinitialize message to "seed" the Hub by providing a new token via GalSS_EnvWriteFrame(), GalSS_EnvDispatchFrame should not be used, for three very good reasons:
frame key | source | type | default value |
<frame name> | (specified in code; cannot be changed) | string | reinitialize |
:server_type | the name of the server as the Hub knows it, if not already specified | string | <none> |
Variable | What it does | Obligatory? | Slot? |
MAKEFILE | Declares the name of the file, in order to generate the Makefile dependencies correctly | yes | yes |
ROOT_DIR | This is the root of the Communicator distribution | yes | yes |
CPPFLAGS | The usual C preprocessor flags. MIT provides a special preprocessor directive -DREPLACE_PRINTF which will cause all calls to printf() to be replaced with calls to GalUtil_Pinfo1(). | no | yes |
LDFLAGS | The usual C linker flags. | no | yes |
COMMON_LIBS | Defined here and
augmented in templates/Galaxy.make.
Third party libraries without variants. |
no | yes |
SPECIAL_LIBS,
SPECIAL_LIBS_D, SPECIAL_LIBS_P |
Libraries with variants (_debug, _profile) which are particular to the current executable. If any of the variants are non-standard, set SPECIAL_LIBS_D and/or SPECIAL_LIBS_P. These will be arguments to the link line. | no | yes |
LIBDEPS
LIBDEPS_D, LIBDEPS_P |
A list of libraries, which will be Makefile dependences (so real pathnames, not -l values). These should correspond to the special and common libraries. | no | yes for LIBDEPS |
LIBTARGET,
EXECTARGETS, SERVER, APPLET |
The primary target types. Exactly one of these must be set. The SERVER is the option for building a Communicator-compliant server which automaticaly generates the operations header file. | yes | yes |
LIBDIR | The directory to put a library in. Defaults to $(ROOT_DIR)/lib/. Note the trailing slash. The library will be put in the appropriate $(ARCHOS) subdirectory. We advise setting this so as not to write to the Communicator distribution. | no | no |
EXECDIR | The directory to put an executable or server in. Defaults to $(ROOT_DIR)/bin/. Note the trailing slash. The executable or server will be put in the appropriate $(ARCHOS) subdirectory. We advise setting this so as not to write to the Communicator distribution. | no | no |
THREAD_SAFE | Whether the server or library supports thread-safe compilation. If you uncomment this line, the compilation instruction "make thread" will compile a threaded version of the library or server. If you leave this line uncommented, this instruction will compile the normal version. See the thread notes. | no | yes |
SUBDIRS | A list of subdirectories where some of the sources may be. | no | yes |
SOURCES | The source files. | yes | yes |
GAL_SERVER_NAME(double)Here's a version of the double server declaration file using the extended signatures:
GAL_SERVER_PORT(2800)
GAL_SERVER_OP(twice)
GAL_SERVER_OP(complex_twice)
GAL_SERVER_OP(reinitialize)
GAL_SERVER_NAME(double)The extended version which specifies the signature works as follows:
GAL_SERVER_PORT(2800)
GAL_SERVER_OP_SIGNATURE(twice,
GAL_SERVER_OP_KEYS(":int" _ GAL_INT _ GAL_KEY_ALWAYS),
GAL_OTHER_KEYS_NEVER,
GAL_REPLY_NONE,
NULL,
GAL_OTHER_KEYS_NEVER)
GAL_SERVER_OP_SIGNATURE(complex_twice,
GAL_SERVER_OP_KEYS(":int" _ GAL_INT _ GAL_KEY_ALWAYS),
GAL_OTHER_KEYS_NEVER,
GAL_REPLY_NONE,
NULL,
GAL_OTHER_KEYS_NEVER)
GAL_SERVER_OP_SIGNATURE(reinitialize,
NULL,
GAL_OTHER_KEYS_NEVER,
GAL_REPLY_PROVIDED,
NULL,
GAL_OTHER_KEYS_NEVER)
argument | description | legal values |
dispatch_fn | the name of a dispatch function | |
in_keys | the keys which this dispatch function expects | Either NULL or a declaration GAL_SERVER_OP_KEYS(key _ type _ obligatory ... ), where underscores are used in place of commas because of the idiosyncracies of the C preprocessor. Here key is a string, type is a legal object type, and obligatory is either GAL_KEY_ALWAYS or GAL_KEY_SOMETIMES. This declaration may contain any number of key declarations (that is, its arguments must be a multiple of 3, all separated by underscores). The type GAL_FREE is used as a wildcard match. |
allow_other_in_keys | whether or not the list of in_keys provided is complete | either GAL_OTHER_KEYS_MAYBE or
GAL_OTHER_KEYS_NEVER |
reply_status | whether or not the dispatch function returns anything | one of GAL_REPLY_PROVIDED, GAL_REPLY_NONE, GAL_REPLY_UNKNOWN |
out_keys | the keys which this dispatch function returns | (see in_keys) |
allow_other_out_keys | whether or not the list of out_keys provided is complete | (see allow_other_in_keys) |
These signatures can be used to validate calls to the dispatch functions if you pass the -validate flag to the server, and will be used in the future to pass to the Hub and synchronize signatures.
Here is the header text from double.c:
#include "galaxy/galaxy_all.h"This technique for providing server information is compatible with non-MIT Makefiles (as well as being cross-platform-friendly). If not using the MIT Makefile templates to build your servers, be sure that you have -I. on your compile line to ensure that the compiler can find the declaration file.
#define SERVER_FUNCTIONS_INCLUDE "double_server.h"
#define USE_SERVER_DATA
#include "galaxy/server_functions.h"
Note: the value of SERVER_FUNCTIONS_INCLUDE is included multiple times in server_functions.h. Therefore, the usual header programming practice of ensuring the header is loaded only once will cause the declaration macros to fail. In other words, don't do this:
#ifndef __MY_SERVER_H__
#define __MY_SERVER_H__GAL_SERVER_NAME(my_server)
...
#endif
/* We don't need any signatures because the functions have already been defined. */void _GalSS_InitializeDefaults(GalIO_ServerStruct *scomm)void _GalSS_InitializeDefaults(GalIO_ServerStruct *s)
{
GalSS_InitializeServerDefaults(s, "double", 2800);
GalSS_AddDispatchFunction(s, "twice", twice, NULL,
GAL_OTHER_KEYS_MAYBE, GAL_REPLY_UNKNOWN,
NULL, GAL_OTHER_KEYS_MAYBE);
GalSS_AddDispatchFunction(s, "complex_twice", complex_twice, NULL,
GAL_OTHER_KEYS_MAYBE, GAL_REPLY_UNKNOWN,
NULL, GAL_OTHER_KEYS_MAYBE);
GalSS_AddDispatchFunction(s, "reinitialize", reinitialize, NULL,
GAL_OTHER_KEYS_MAYBE,
GAL_REPLY_UNKNOWN,
NULL, GAL_OTHER_KEYS_MAYBE);
}
The following typedefs are relevant to the functions below:
typedef Gal_Frame (*Gal_FrameFnPtr)(Gal_Frame frame);
typedef Gal_Frame (*Gal_FrameDataFnPtr)(Gal_Frame frame, void *data);
void GalSS_InitializeServerDefaults(GalIO_ServerStruct
*scomm, char *name, unsigned short port)
Stores the name
and
port
and function_map in the server scomm. The name corresponds
to a Hub service type.
void GalSS_AddDispatchFunction(GalIO_ServerStruct
*i, char *name, Gal_FrameDataFnPtr fn, Gal_DispatchFnSignatureKeyEntry
*in_key_array, int allow_other_in_keys, int reply_provided,
Gal_DispatchFnSignatureKeyEntry *out_key_array, int allow_other_out_keys)
Adds a fn
indexed by name to the server i. This function takes a frame
and a void * pointer (actually
a GalSS_Environment *) and
returns a frame. By convention, the index and the name of the function
are the same, and that's the way the header strategy implements things,
but this is not required (as long as the programmer is willing to assume
responsibility for negotiating the digression). The possible values of
allow_other_in_keys, reply_provided, and allow_other_out_keys are the same
as for the GAL_SERVER_OP_SIGNATURE() macro. The in_key_array and out_key_array
are created by the function Gal_CreateDispatchFnKeyArray().
Gal_DispatchFnSignatureKeyEntry *Gal_CreateDispatchFnKeyArray(int
ignore,
... )
This function takes an arbitrary number
of arguments. The first argument is ignored; it is present because the
ANSI C mechanism for variable arguments requires at least one listed argument.
The argument list must terminate with a NULL; otherwise, it's identical
to the arguments to the GAL_SERVER_OP_KEYS() macro. Here's an example:
Gal_CreateDispatchFnKeyArray(0, ":int", GAL_INT, GAL_KEY_ALWAYS, NULL);void Gal_FreeDispatchFnKeyArray(Gal_DispatchFnSignatureKeyEntry *entry)Memory management
The function GalSS_AddDispatchFunction copies the key array, so if you create an array to pass to GalSS_AddDispatchFunction, you must free it using Gal_FreeDispatchFnKeyArray.
SERVER: double-serverThis entry instructs the Hub to contact the server in question on port 2800 on the local machine, and informs the Hub that the server supports a dispatch function named twice. (It's also possible to have the server contact the Hub instead.) In addition, you might want to tell the Hub what to do with messages the server sends to it:
PORT: 2800
HOST: localhost
OPERATIONS: twice
RULE: :int --> twice
IN: :int
OUT: none!
#include "galaxy/galaxy_all.h"The value of GC_VERSION is a number in hexadecimal form, such that the lowest two hex digits are the "subminor" version, the next two hex digits are the minor version, and everything above that is the major version. So Galaxy Communicator version 4.2.3 (if we ever get there) will have a GC_VERSION of 0x40203.#if defined(GC_VERSION) && (GC_VERSION >= 0x30000)
char *version = "3 point 0";
#else
#if defined(GC_VERSION) && (GC_VERSION >= 0x20000)
char *version = "between 2 point 0 and 3 point 0";
#else
#ifndef GC_VERSION
char *version = "before 2 point 0";
#endif
#endif
#endif
Please send comments and suggestions to:
bugs-darpacomm@linus.mitre.org
Last updated January 7, 2002.
Copyright (c)
1998 - 2002
The MITRE
Corporation
ALL RIGHTS RESERVED