Advanced RTclient Usage


This section covers advanced usage of RTclients, and many topics might cover issues that are not relevant to your applications. Advanced usage adds another layer of complication, so be sure you need the feature before designing your application around it. In other words, the simplest RTclient that does the job is the best for the application. It will be easier to debug and to maintain.

Advanced Example With Warm Connections and Server Callbacks

This example shows several advanced features of RTclient, including a warm connection to RTserver, server create callbacks, and server destroy callbacks. To learn more about working with the basic features of RTclient, see Working With RTclient.

The source code files for this example are located in these directories:

UNIX:
$RTHOME/examples/smrtsock/manual  
OpenVMS:
RTHOME:[EXAMPLES.SMRTSOCK.MANUAL] 
Windows:
%RTHOME%\examples\smrtsock\manual 

The online source files have additional #ifdefs to provide C++ support; these #ifdefs are not shown to simplify the example.

Advanced Example Source Code

Here is an example of the advanced RTclient code:

/* rtcladv.c -- RTclient advanced example */ 
 
/* 
This RTclient creates and destroys its connection to RTserver in various ways, and publishes 
messages to a subject it is subscribing to. 
*/ 
 
#include <rtworks/ipc.h> 
#include "rtclient.h" 
 
/* =============================================================== */ 
/*..cb_default -- default callback */ 
static void T_ENTRY cb_default( 
                      T_IPC_CONN conn, 
                      T_IPC_CONN_DEFAULT_CB_DATA data, 
                      T_CB_ARG arg) 
{ 
  T_IPC_MT mt; 
  T_STR name; 
 
  TutOut("Entering cb_default.\n"); 
 
  /* print out the name of the type of the message */ 
  if (!TipcMsgGetType(data->msg, &mt)) { 
    TutOut("Could not get message type from message: error 
<%s>.\n", 
           TutErrStrGet()); 
    return; 
  } 
  if (!TipcMtGetName(mt, &name)) { 
    TutOut("Could not get name from message type: error <%s>.\n", 
           TutErrStrGet()); 
      return; 
  } 
  TutOut("Message type name is <%s>\n", name); 
} /* cb_default */ 
 
/* =============================================================== */ 
/*..cb_server_create -- server create callback */ 
static void T_ENTRY cb_server_create( 
                      T_IPC_CONN conn, 
                      T_IPC_SRV_CREATE_CB_DATA data, 
                      T_CB_ARG arg) 
{ 
 
  /* Create other callbacks if we did not have a warm connection. */ 
  if (data->old_conn_status == T_IPC_SRV_CONN_WARM) { 
    TutOut("We already had a warm connection to RTserver, which "); 
    TutOut("preserves all callbacks.\n"); 
    return; /* nothing to do */ 
  } 
 
  /* A larger process would create many callbacks here. This */ 
  /* simple example only creates a connection default callback. */ 
  TutOut("Creating other callbacks.\n"); 
 
  /* default callback */ 
  if (TipcSrvDefaultCbCreate(cb_default, NULL) == NULL) { 
    TutOut("Could not create default callback: error <%s>.\n", 
           TutErrStrGet()); 
  } 
 
} /* cb_server_create */ 
 
/* =============================================================== */ 
/*..cb_server_destroy -- server destroy callback */ 
static void T_ENTRY cb_server_destroy( 
                      T_IPC_CONN conn, 
                      T_IPC_SRV_DESTROY_CB_DATA data, 
                      T_CB_ARG arg) 
{ 
  TutOut("Entering cb_server_destroy.\n"); 
  if (data->new_conn_status == T_IPC_SRV_CONN_WARM) { 
    TutOut("Leaving a warm connection to RTserver.\n"); 
  } 
  else { 
    TutOut("Leaving no connection to RTserver.\n"); 
  } 
 
 /* cb_server_destroy */ 
 
/* =============================================================== */ 
/*..main -- main program */ 
int main(argc, argv) 
int argc; 
char **argv; 
{ 
  T_OPTION option; 
  T_IPC_MT mt; 
 
  /* Set the option Project to partition ourself. */ 
  option = TutOptionLookup("project"); 
  if (option == NULL) { 
    TutOut("Could not look up option named project: error <%s>.\n", 
           TutErrStrGet()); 
    TutExit(T_EXIT_FAILURE); 
  } 
  if (!TutOptionSetEnum(option, EXAMPLE_PROJECT)) { 
    TutOut("Could not set option named project: error <%s>.\n", 
           TutErrStrGet()); 
    TutExit(T_EXIT_FAILURE); 
    } 
 
  /* Allow a command-line argument containing the name of a */ 
  /* SmartSockets startup command file. This file can be used */ 
  /* to set options like Server_Names. */ 
  if (argc == 2) { 
    if (!TutCommandParseFile(argv[1])) { 
      TutOut("Could not parse startup command file %s: error 
<%s>.\n", 
             argv[1], TutErrStrGet()); 
      TutExit(T_EXIT_FAILURE); 
    } 
  } 
  else if (argc != 1) { /* too many command-line arguments */ 
    TutOut("Usage: %s [ command_file_name ]\n", argv[0]); 
    TutExit(T_EXIT_FAILURE); 
  } 
 
  /* create callbacks to be executed when certain operations occur */ 
  TutOut("Create callbacks.\n"); 
 
  /* If an RTclient will be creating and destroying its connection */ 
  /* to RTserver over and over, it should use a server create */ 
  /* callback to create the rest of its callbacks. */ 
 
  /* server create callback */ 
  if (TipcSrvCreateCbCreate(cb_server_create, NULL) == NULL) { 
    TutOut("Could not create server create callback: error 
<%s>.\n", 
           TutErrStrGet()); 
    TutExit(T_EXIT_FAILURE); 
  } 
 
  /* server destroy callback */ 
  if (TipcSrvDestroyCbCreate(cb_server_destroy, NULL) == NULL) { 
    TutOut("Could not create server destroy callback: error 
<%s>.\n", 
           TutErrStrGet()); 
    TutExit(T_EXIT_FAILURE); 
  } 
 
  TutOut("Creating connection to RTserver.\n"); 
  if (!TipcSrvCreate(T_IPC_SRV_CONN_FULL)) { 
    TutOut("Could not create connection to RTserver: error 
<%s>.\n", 
           TutErrStrGet()); 
    TutExit(T_EXIT_FAILURE); 
  } 
 
  /* Destroy connection to RTserver to show how this can be done. */ 
  TutOut("\nDestroying connection to RTserver but leave it 
warm.\n"); 
  if (!TipcSrvDestroy(T_IPC_SRV_CONN_WARM)) { 
    TutOut("Could not destroy connection to RTserver: error 
<%s>.\n", 
           TutErrStrGet()); 
    TutExit(T_EXIT_FAILURE); 
  } 
 
  TutOut("Creating connection to RTserver from warm 
connection.\n"); 
  if (!TipcSrvCreate(T_IPC_SRV_CONN_FULL)) { 
    TutOut("Could not create connection to RTserver: error 
<%s>.\n", 
           TutErrStrGet()); 
    TutExit(T_EXIT_FAILURE); 
  } 
  TutOut("Completely destroying connection to RTserver.\n"); 
  if (!TipcSrvDestroy(T_IPC_SRV_CONN_NONE)) { 
    TutOut("Could not destroy connection to RTserver: error 
<%s>.\n", 
           TutErrStrGet()); 
    TutExit(T_EXIT_FAILURE); 
  } 
 
  /* At this point, the RTclient could proceed as if it had */ 
  /* never been connected to RTserver at all. For this example */ 
  /* we want to reconnect eventually, though. */ 
 
  /* Set the option Server_Auto_Connect to FALSE to show how */ 
  /* an RTclient can buffer outgoing messages even when not */ 
  /* connected to RTserver. */ 
  TutCommandParseStr("setopt server_auto_connect false"); 
 
  TutOut("\nCreating warm connection to RTserver.\n"); 
  if (!TipcSrvCreate(T_IPC_SRV_CONN_WARM)) { 
    TutOut("Could not create warm connection to RTserver.\n"); 
    TutOut("  error <%s>.\n", TutErrStrGet()); 
    TutExit(T_EXIT_FAILURE); 
  } 
 
  /* Now proceed with normal processing even though we only */ 
  /* have a warm connection. */ 
  TutOut("Start subscribing to standard subjects.\n"); 
  if (!TipcSrvStdSubjectSetSubscribe(TRUE, TRUE)) { 
    TutOut("Could not start subscribing to standard subjects.\n"); 
    TutOut("  error <%s>.\n", TutErrStrGet()); 
    TutExit(T_EXIT_FAILURE); 
  } 
 
  TutOut("Start subscribing to the %s subject.\n", 
EXAMPLE_SUBJECT); 
  if (!TipcSrvSubjectSetSubscribe(EXAMPLE_SUBJECT, TRUE)) { 
    TutOut("Could not start subscribing to %s subject: error 
<%s>.\n", 
           EXAMPLE_SUBJECT, TutErrStrGet()); 
    TutExit(T_EXIT_FAILURE); 
  } 
 
  TutOut("Publish a frame of data to the receiver’s subject.\n"); 
 
  TutOut("Publishing a TIME message.\n"); 
  mt = TipcMtLookupByNum(T_MT_TIME); 
  if (mt == NULL) { 
    TutOut("Could not look up TIME msg type: error <%s>.\n", 
           TutErrStrGet()); 
    TutExit(T_EXIT_FAILURE); 
  } 
  if (!TipcSrvMsgWrite(EXAMPLE_SUBJECT, mt, TRUE, 
                       T_IPC_FT_REAL8, 1.0, 
                       NULL)) { 
    TutOut("Could not publish TIME message: error <%s>.\n", 
           TutErrStrGet()); 
  } 
 
  TutOut("Publishing a NUMERIC_DATA message.\n"); 
  mt = TipcMtLookupByNum(T_MT_NUMERIC_DATA); 
  if (mt == NULL) { 
    TutOut("Could not look up NUMERIC_DATA msg type: error 
<%s>.\n", 
           TutErrStrGet()); 
    TutExit(T_EXIT_FAILURE); 
  } 
  if (!TipcSrvMsgWrite(EXAMPLE_SUBJECT, mt, TRUE, 
                       T_IPC_FT_STR, "voltage", 
                       T_IPC_FT_REAL8, 33.4534, 
                       T_IPC_FT_STR, "switch_pos", 
                       T_IPC_FT_REAL8, 0.0, 
                       NULL)) { 
    TutOut("Could not publish NUMERIC_DATA message: error <%s>.\n", 
           TutErrStrGet()); 
  } 
 
  TutOut("Publishing an END_OF_FRAME message.\n"); 
  mt = TipcMtLookupByNum(T_MT_END_OF_FRAME); 
  if (mt == NULL) { 
    TutOut("Could not look up END_OF_FRAME msg type: error 
<%s>.\n", 
           TutErrStrGet()); 
    TutExit(T_EXIT_FAILURE); 
  } 
if (!TipcSrvMsgWrite(EXAMPLE_SUBJECT, mt, TRUE, NULL)) { 
  TutOut("Could not publish END_OF_FRAME message: error <%s>.\n", 
         TutErrStrGet()); 
} 
 
TutOut("\nCreating connection to RTserver.\n"); 
if (!TipcSrvCreate(T_IPC_SRV_CONN_FULL)) { 
  TutOut("Could not create connection to RTserver: error <%s>.\n", 
         TutErrStrGet()); 
  TutExit(T_EXIT_FAILURE); 
} 
 
/* Each RTclient automatically creates a connection process */ 
/* callback for CONTROL messages. Use this to send the command */ 
/* "quit force" to the receiver’s command interface. */ 
TutOut("Publishing a CONTROL message to stop ourself.\n"); 
mt = TipcMtLookupByNum(T_MT_CONTROL); 
if (mt == NULL) { 
  TutOut("Could not look up CONTROL msg type: error <%s>.\n", 
         TutErrStrGet()); 
  TutExit(T_EXIT_FAILURE); 
} 
if (!TipcSrvMsgWrite(EXAMPLE_SUBJECT, mt, TRUE, 
                     T_IPC_FT_STR, "quit force", 
                     NULL)) { 
  TutOut("Could not publish CONTROL message: error <%s>.\n", 
         TutErrStrGet()); 
} 
 
/* Flush the buffered outgoing messages to RTserver. */ 
if (!TipcSrvFlush()) { 
  TutOut("Could not flush messages to RTserver: error <%s>.\n", 
         TutErrStrGet()); 
  } 
 
  /* If an error occurs, then TipcSrvMainLoop will restart RTserver */ 
  /* and return FALSE. We can safely continue. */ 
  for (;;) { 
    if (!TipcSrvMainLoop(T_TIMEOUT_FOREVER)) { 
      TutOut("TipcSrvMainLoop failed: error <%s>.\n", 
             TutErrStrGet()); 
    } 
  } 
 
  /* This line should not be reached. */ 
  TutOut("This line should not be reached!!!\n"); 
  return T_EXIT_FAILURE; 
} /* main */ 
 

Compiling, Linking, and Running

To compile, link, and run the example programs, first you must either copy the programs to your own directory or have write permission in these directories:

UNIX:
$RTHOME/examples/smrtsock/manual 
OpenVMS
RTHOME:[EXAMPLES.SMRTSOCK.MANUAL] 
Windows:
%RTHOME%\examples\smrtsock\manual 

Step 1

To compile and link the program, use:

UNIX:
$ rtlink -o rtcladv.x rtcladv.c 
OpenVMS:
$ cc rtcladv.c 
$ rtlink /exec=rtcladv.exe rtcladv.obj 
Windows:
$ nmake /f cladw32m.mak 

Step 2

Ensure RTserver is running

Step 3

To run the program, use:

UNIX:
$ rtcladv.x 
OpenVMS:
$ run rtcladv.exe 
Windows:
$ rtcladv.exe 

Here is an example of the output:

Create callbacks. 
Creating connection to RTserver. 
Connecting to project <example> on <_node> RTserver 
Using local protocol 
Message from RTserver: Connection established. 
Start subscribing to subject </_workstation_5415> 
Creating other callbacks. 
 
Destroying connection to RTserver but leave it warm. 
Entering cb_server_destroy. 
Leaving a warm connection to RTserver. 
Creating connection to RTserver from warm connection. 
Attempting to reconnect to RTserver 
Connecting to project <example> on <_node> RTserver 
Using local protocol 
Message from RTserver: Connection established. 
Start subscribing to subject </_workstation_5415> again 
We already had a warm connection to RTserver, which preserves all 
callbacks. 
Completely destroying connection to RTserver. 
Entering cb_server_destroy. 
Leaving no connection to RTserver. 
 
Creating warm connection to RTserver. 
Creating other callbacks. 
Start subscribing to standard subjects. 
Start subscribing to subject </_time> 
Start subscribing to subject </_workstation_5415> 
Start subscribing to subject </_all> 
Start subscribing to the rcv subject. 
Publish a frame of data to the receiver’s subject. 
Publishing a TIME message. 
Publishing a NUMERIC_DATA message. 
Publishing an END_OF_FRAME message. 
 
Creating connection to RTserver. 
Attempting to reconnect to RTserver. 
Connecting to project <example> on <_node> RTserver 
Using local protocol 
Message from RTserver: Connection established. 
Start subscribing to subject </_workstation_5415> again. 
Start subscribing to subject </_all> again. 
Start subscribing to subject </_workstation> again. 
Start subscribing to subject </_time> again 
Start subscribing to subject </_rcv> again. 
We already had a warm connection to RTserver, which preserves all 
callbacks. 
Publishing a CONTROL message to stop ourself. 
Entering cb_default. 
Message type name is <time> 
Entering cb_default. 
Message type name is <numeric_data> 
Entering cb_default. 
Message type name is <end_of_frame> 

Warm Connection to RTserver

A warm connection to RTserver is a subset of a full connection to RTserver. A warm connection keeps as much RTserver-related information as possible. The only difference between a warm connection and a full connection is that the warm connection does not have a valid socket (there is no communication link to RTserver with a warm connection). No messages can be flushed to RTserver on a warm connection and no messages can be read from the warm connection, but most TipcSrv* functions behave in a fashion similar to when a full connection exists. RTserver is not aware of the RTclient when the RTclient has a warm connection, unless RTclient has told RTserver to keep a warm RTclient record for this RTclient (see Warm RTclient in RTserver).

With a warm connection to RTserver, callbacks can be created, callbacks can be destroyed, and messages can be buffered. If RTclient has a warm connection and then creates a full connection (the connection conceptually changes from warm to full), the warm-buffered messages are flushed to the newly-created full connection.

If RTclient does not have a connection to RTserver, then TipcSrvCreate can be used to create a warm connection to RTserver. For example:

if (!TipcSrvCreate(T_IPC_SRV_CONN_WARM)) { 
  /* error */ 
} 

If RTclient has a full connection to RTserver, then TipcSrvDestroy can be used to leave a warm connection to RTserver. For example:

 
if (!TipcSrvDestroy(T_IPC_SRV_CONN_WARM)) { 
  /* error */ 
} 

Once RTclient destroys the full connection but leaves a warm connection, it can easily recreate a full connection to RTserver in the future and pick up where it left off. For more details on how TipcSrvDestroy works in this situation, see the man page for TipcSrvDestroy in the TIBCO SmartSockets Application Programming Interface reference.

Connection Status

There is some overlap between TipcSrvCreate and TipcSrvDestroy in the respect that both explicitly change the status of the connection to RTserver. The function TipcSrvGetConnStatus can be used to determine the status of the connection to RTserver. For example:

T_IPC_SRV_CONN_STATUS conn_status; 
 
if (!TipcSrvGetConnStatus(&conn_status)) { 
  /* error */ 
} 
switch (conn_status) { 
case T_IPC_SRV_CONN_NONE: 
  TutOut("RTclient has no connection to RTserver.\n"); 
  break; 
case T_IPC_SRV_CONN_WARM: 
  TutOut("RTclient has a warm connection to RTserver.\n"); 
  break; 
case T_IPC_SRV_CONN_FULL: 
  TutOut("RTclient has a full connection to RTserver.\n"); 
  break; 
default: 
  /* error */ 
} 

The main difference between TipcSrvCreate and TipcSrvDestroy is that TipcSrvCreate always moves the connection status closer to T_IPC_SRV_CONN_FULL, while TipcSrvDestroy moves the connection status the opposite direction towards T_IPC_SRV_CONN_NONE.

Continued Operation While RTserver is Down

As described in Automatically Reconnecting to RTserver, if an unrecoverable error occurs on the connection to RTserver, the full connection to RTserver is destroyed, but a warm connection is kept. If RTclient cannot successfully restart and reconnect to RTserver, it keeps the warm connection and continues operation. Depending on the settings of the Server_* options and the usage of the TipcSrv* functions, RTclient continues running and keeps trying to restart and reconnect to RTserver. When RTserver does come back up, then RTclient can create a full connection again.

RTclient-Specific Callbacks

In addition to all the callback types connections offer (see Callbacks for details on connection callback types), there are several additional callback types RTclient can use including: server create callbacks, server destroy callbacks, and subject callbacks. Table 11 shows the callback type-specific data argument types for all RTclient callback types and the fields in those argument types

Table 11 RTclient Callback Types
Callback Type
Type of Second Parameter to Callback Functions
Fields in This Type
 
server create
T_IPC_SRV_CREATE_CB_DATA
T_CB cb;
T_IPC_SRV_CONN_STATUS old_conn_status;
T_IPC_SRV_CONN_STATUS new_conn_status;
 
server destroy
T_IPC_SRV_DESTROY_CB_DATA
T_CB cb;
T_IPC_SRV_CONN_STATUS old_conn_status;
T_IPC_SRV_CONN_STATUS new_conn_status;
 
server names traverse
T_IPC_SRV_TRAVERSE_CB_DATA
T_CB cb;
T_STR server_name;
T_BOOL stop_traverse;
 
subject
T_IPC_SRV_SUBJECT_CB_DATA
T_CB cb;
T_IPC_MSG msg;
 

.

Server Create Callbacks

Server create callbacks are called when an RTclient creates either a full or warm connection to RTserver. Server create callbacks are useful for creating other callbacks, such as connection process callbacks on the connection to RTserver, that cannot be created until RTclient has a connection to RTserver. If an RTclient creates and destroys its connection to RTserver many times, it should use server create callbacks to create the other callbacks needed in the connection to RTserver. For example:

/* =============================================================== */ 
/*..cb_server_create -- server create callback */ 
static void T_ENTRY cb_server_create( 
                      T_IPC_CONN conn, 
                      T_IPC_SRV_CREATE_CB_DATA data, 
                      T_CB_ARG arg) 
{ 
  T_IPC_MT mt; /* for creating callback */ 
  /* Create other callbacks if we did not have a warm connection. */ 
  if (data->old_conn_status == T_IPC_SRV_CONN_WARM) { 
    TutOut("We already had a warm connection to RTserver, which "); 
    TutOut("preserves all callbacks.\n"); 
    return; /* nothing to do */ 
  } 
 
  /* A larger process would create many callbacks here. This */ 
  /* simple example only creates a connection default callback. */ 
  TutOut("Creating other callbacks.\n"); 
 
  /* default callback */ 
  if (TipcSrvDefaultCbCreate(cb_default, NULL) == NULL) { 
    TutOut("Could not create default callback: error <%s>.\n", 
           TutErrStrGet()); 
  } 
} /* cb_server_create */ 

Server Destroy Callbacks

Server destroy callbacks are called when an RTclient destroys its connection to RTserver, leaving either a warm connection or no connection. Server destroy callbacks are the opposite of server create callbacks.

Server destroy callbacks do overlap slightly with connection error callbacks, because the standard connection error callback function TipcCbSrvError (see Automatically Reconnecting to RTserver) calls TipcSrvDestroy, which causes the server destroy callbacks to be executed. Server destroy callbacks are useful for RTclients that need to know when the connection to RTserver no longer has a valid socket, such as RTclients that are also Motif or Windows GUI programs. See the man page for TipcSrvDestroyCbCreate in the TIBCO SmartSockets Application Programming Interface reference for an example of how server destroy callbacks help in this situation.

Server Names Traverse Callbacks

Server names traverse callbacks are called when an RTclient traverses the Server_Names list to connect to an RTserver. The callback is executed before a connection to each entry in the list is attempted. There are many uses for this callback, and the argument you pass in TipcSrvTraverseCbCreate can be anything.

The callback structure includes two fields, stop_traverse and server_name. When you want the process to stop traversing the Server_Names list and fail the TipcSrvCreate, set data->stop_traverse = T_TRUE. If you know the thread ID of the receive thread, you can check to see if this is the receive thread and set stop_traverse to TRUE if it is not. You can also pass a structure containing the thread ID and other information allowing you to do sophisticated backoff algorithms. By setting your server reconnect delay to a low value, you can then control how long to wait in this callback.

Here are some considerations to keep in mind when using the callback:

This is a server names traverse callback used to print a message for each server name in the Server_Names list. It stops traversing the Server_Names list if a successful connection is made or connection attempts fail for the first three servers in the list:

#include <rtworks/ipc.h> 
 
/* 
================================================================*/ 
/* ..my_server_traverse_cb - server name traversal callback */ 
void T_ENTRY my_server_traverse_cb 
( 
 T_IPC_CONN conn, 
 T_IPC_SRV_TRAVERSE_CB_DATA data, 
 T_CB_ARG arg 
) 
{ 
  T_INT4 *count = (T_INT4 *)arg; 
 
  TutOut("Inside my_server_traverse_cb: server_name = %s\n",  
        data->server_name); 
 
  /* stop trying at 3 servers */ 
  if(*count >= 3) { 
    TutWarning("Setting stop flag.\n"); 
    data->stop_traverse = T_TRUE; 
  } 
  *count += 1; 
} 
 
/* 
================================================================*/ 
/* ..main - main program */ 
int main(int argc, char **argv) { 
 
  T_INT4 trav_count = 0; 
 
  /*  create a callback to be called when traversing the server names list */ 
  if (TipcSrvTraverseCbCreate(my_server_traverse_cb,  
            (T_CB_ARG)&trav_count) == T_NULL) { 
    /* error */; 
  } 
 
  TipcSrvCreate(T_IPC_SRV_CONN_FULL); 
 
}  /* main */ 

Subject Callbacks

Earlier in this document, it was described how to define callbacks to process messages based on the type of the message. For RTserver connections, subject callbacks are available which are actually a superset of connection process callbacks, such as execute this callback when a message of type T arrives with destination S. Subject callbacks give you all the power of process callbacks with additional filtering capabilities. You should consider using subject callbacks rather than process callbacks when processing messages from RTserver.

Rather than processing a message based only on its type, subject callbacks allow you to process a message based on both its destination (subject) and its type. With a subject callback, you can specify a separate function for each subject (or group) of subjects you wish to operate on. When a message arrives at the receiver for the specified subject and is ready to be processed, the callback is executed. Subject callbacks operate similar to process callbacks. To create a subject callback, you make a call to TipcSrvSubjectCbCreate as shown:

T_CB TipcSrvSubjectCbCreate(subject, mt, func, arg ) 

where:

subject
is the destination you wish to specify the callback on and (NULL means any destination).
mt
is the message type the callback should be applied to (NULL means any message type).
func
is the callback function to be executed.
arg
is an optional argument to pass into func.

Here are some examples of creating subject callbacks:

mt = TipcMtLookupByNum(T_MT_INFO); 
 
/* Execute the function subj_cb for any message type which has a destination of "/stocks" */ 
TipcSrvSubjectCbCreate("/stocks", NULL, subj_cb, NULL); 
 
/* Execute the function subj_cb for any messages of type INFO, regardless of the destination */ 
TipcSrvSubjectCbCreate(NULL, mt, subj_cb, NULL); 
/* or */ 
TipcSrvSubjectCbCreate("/...", mt, subj_cb, NULL); 
 
/* Execute the function subj_cb for any messages of type INFO, which have a destination of 
"/stocks"*/ 
TipcSrvSubjectCbCreate("/stocks", mt, subj_cb, NULL); 
 
/* Execute the function subj_cb for messages of any type with any destination */ 
TipcSrvSubjectCbCreate(NULL, NULL, subj_cb, NULL); 
 
/* Execute the function default_subj_cb if there are no subject callbacks which match the message */ 
TipcSrvSubjectDefaultCbCreate(default_subj_cb, NULL); 

Wildcards can be used when specifying the subject argument when creating a subject callback. A NULL subject maps to the "/..." wildcard subject. When you register a wildcard subject the callback function will get executed for each incoming message whose destination matches the wildcard pattern. It is possible that a single message can invoke multiple callbacks if its destination matches multiple wildcards.

Connection process callbacks are separate from server subject callbacks in their priority sets. Do not mix connection process callbacks and server subject callbacks because the priorities between callback types are not honored.

Table 12 shows several examples of wildcarded subjects and message types and whether or not the subject callback gets executed.

Table 12 Subject Callback Execution
Subject
Message Type
When Callback Gets Executed
/stocks
NUMERIC_DATA
NUMERIC_DATA message is received with destination of /stocks.
/stocks
NULL
When any type of message is received with destination of /stocks.
/... or
NULL
STRING_DATA
STRING_DATA message is received, regardless of the destination. This is similar to the connection process callback behavior except that subject callbacks are always called after connection process callbacks.
/... or
NULL
NULL
When a message of any type with any destination is received (the default subject callback is never executed in this case).

Just as with process callbacks, you can define a default subject callback to be executed if no callback has been defined for a given subject. Default subject callbacks are only called when no other subject callback matches the incoming message criteria. Default subject callbacks are registered with the TipcSrvSubjectDefaultCbCreate function. If a subject callback is registered with a NULL subject and NULL message type, then default subject callbacks will never get executed.

As with other callback types, you can use the function TutCbSetPriority to set the priority of your subject callbacks.

Subject callbacks can be destroyed by looking up each callback and destroying it with the TutCbDestroy function. A convenience function, TipcSrvSubjectCbDestroyAll, has been created to facilitate destroying all subject callbacks including default subject callbacks.

See the man page for TipcSrvSubjectCbCreate and TipcSrvSubjectDefaultCbCreate in the TIBCO SmartSockets Application Programming Interface reference for an example of how subject callbacks work.

Example Using Subject Callbacks

This code shows the use of subject callbacks. Two different subject callbacks are defined in the program:

Both callback functions simply access the fields and print out the contents of the message:

/* rtsubjcb.c - print out contents of messages processed via  
subject callback */ 
/* $RTHOME/examples/smrtsock/manual/rtsubjcb.c */ 
 
#include <rtworks/ipc.h> 
#include "rtclient.h" 
 
/* ============================================================== */ 
/*..ProcessInfo - callback for processing INFO messages published  
                  to the EXAMPLE_SUBJECT subject */ 
static void T_ENTRY ProcessInfo( 
                      T_IPC_CONN conn, 
                      T_IPC_CONN_PROCESS_CB_DATA data, 
                      T_CB_ARG arg) 
{ 
  T_IPC_MSG msg = data->msg; 
  T_STR msg_text; 
   
  TutOut("Entering ProcessInfo callback.\n"); 
 
  if (!TipcMsgSetCurrent(msg, 0)) { 
    TutOut("Could not set current field of message: error <%s>.\n", 
            TutErrStrGet()); 
    return; 
  } 
 
  if (!TipcMsgNextStr(msg, &msg_text)) { 
    TutOut("Could not text from INFO message: error <%s>.\n", 
            TutErrStrGet()); 
    return; 
  } 
 
  TutOut("Text from message = %s\n", msg_text); 
} /* ProcessInfo */ 
/* =============================================================== */ 
/*..ProcessNumData - callback for processing NUMERIC_DATA messages  
                     published  to the EXAMPLE_SUBJECT subject */ 
static void T_ENTRY ProcessNumData( 
                      T_IPC_CONN conn, 
                      T_IPC_CONN_PROCESS_CB_DATA data, 
                      T_CB_ARG arg) 
{ 
  T_IPC_MSG msg = data->msg; 
  T_STR name; 
  T_REAL8 value; 
   
  TutOut("Entering ProcessNumData callback.\n"); 
  if (!TipcMsgSetCurrent(msg, 0)) { 
    TutOut("Could not set current field of message: error <%s>.\n", 
            TutErrStrGet()); 
    return; 
  } 
 
  /* Access and print fields */ 
  while (TipcMsgNextStrReal8(msg, &name, &value)) { 
    TutOut("Var Name = %s; Value = %s\n", name, 
TutRealToStr(value)); 
  } /* while */ 
 
  /* Make sure we reached end of message */ 
  if (TutErrNumGet() != T_ERR_MSG_EOM) { 
    TutOut("Did not reach end of message: error <%s>.\n", 
            TutErrStrGet()); 
  } 
} /* ProcessNumData */ 
 
/* =============================================================== */ 
int main(argc, argv) 
int argc; 
char **argv; 
{ 
  T_OPTION option; 
  T_IPC_MT mt; 
 
  /* Set the project name */ 
  option = TutOptionLookup("project"); 
  if (option == NULL) { 
    TutOut("Could not look up option named project: error <%s>.\n", 
            TutErrStrGet()); 
    TutExit(T_EXIT_FAILURE); 
  } 
  if (!TutOptionSetEnum(option, EXAMPLE_PROJECT)) { 
    TutOut("Could not set option named project: error <%s>.\n", 
            TutErrStrGet()); 
    TutExit(T_EXIT_FAILURE); 
  } 
 
  /* Connect to the RTserver */ 
  TutOut("Creating a connection to RTserver.\n"); 
  if (!TipcSrvCreate(T_IPC_SRV_CONN_FULL)) { 
    TutOut("Could not connect to RTserver!\n"); 
    TutExit(T_EXIT_FAILURE); 
  } /* if */ 
 
  /* Create subject callbacks to be executed when messages arrive 
     with a given destination and given type */ 
  TutOut("Creating subject callbacks.\n"); 
 
  /* Subject callback for INFO messages with destination  
     EXAMPLE_SUBJECT subject */ 
  mt = TipcMtLookupByNum(T_MT_INFO); 
  if (mt == NULL) { 
    TutOut("Could not lookup INFO message type: error <%s>.\n", 
            TutErrStrGet()); 
    TutExit(T_EXIT_FAILURE); 
  } 
  if (TipcSrvSubjectCbCreate(EXAMPLE_SUBJECT, mt,  
                             ProcessInfo, NULL) 
      == NULL) { 
    TutOut("Could not create ProcessInfo subject callback: ", 
            "error <%s>.\n", TutErrStrGet()); 
    TutExit(T_EXIT_FAILURE); 
  } 
  /* Subject callback for NUMERIC_DATA messages with destination  
     EXAMPLE_SUBJECT subject */ 
  mt = TipcMtLookupByNum(T_MT_NUMERIC_DATA); 
  if (mt == NULL) { 
    TutOut("Could not lookup NUMERIC_DATA message type: ", 
            "error ,<%s>.\n", TutErrStrGet()); 
    TutExit(T_EXIT_FAILURE); 
  } 
  if (TipcSrvSubjectCbCreate(EXAMPLE_SUBJECT, mt,  
                             ProcessNumData, NULL) 
      == NULL) { 
    TutOut("Could not create ProcessNumData subject callback: ", 
           "error <%s>.\n", TutErrStrGet()); 
    TutExit(T_EXIT_FAILURE); 
  } 
  /* Start subscribing to the EXAMPLE_SUBJECT subject */ 
  TutOut("Start subscribing to the %s subject.\n", 
EXAMPLE_SUBJECT); 
  if (!TipcSrvSubjectSetSubscribe(EXAMPLE_SUBJECT, TRUE)) { 
    TutOut("Could not subscribe to %s subject: error <%s>.\n", 
            TutErrStrGet()); 
    TutExit(T_EXIT_FAILURE); 
  } 
/* Read and process all incoming messages */ 
  if (!TipcSrvMainLoop(T_TIMEOUT_FOREVER)) { 
     TutOut("TipcSrvMainLoop failed: error <%s>.\n", 
            TutErrStrGet()); 
  } 
} /* main */ 

This code can be verified by first compiling, linking and running $RTHOME/examples/smrtsock/manual/rtsubjcb.c. Messages can be published to it using the example program found in $RTHOME/examples/smrtsock/manual/rtsndsub.c.

Remote Procedure Calls

The section Remote Procedure Calls describes how messages can be sent between processes to perform a remote procedure call. A remote procedure call (RPC) is a means for a process to execute a function in another process and wait for the result of the function call. This enables a request-reply communication model.

Normally when an RTclient publishes a message to a subject using TipcSrvMsgSend, the publishing RTclient continues and does not wait for the subscribing RTclients to receive the message and act on it. This normal mode of operation can be thought of as a one-to-many non-blocking RPC that may or may not return a result (depending on whether or not the receiving RTclients send back result messages).

The function TipcSrvMsgSendRpc performs a one-to-one blocking RPC that does return a result. One message is sent as the RPC call from the caller RTclient, and another message is sent back as the RPC result to the caller. The target RTclient must be prepared to receive the call message and send back the result message. TipcSrvMsgSendRpc uses a simple relationship between the call and result messages: the message type number of the result message must be one greater than the message type number of the call message.

See the reference information for TipcSrvMsgSendRpc in TIBCO SmartSockets Application Programming Interface for a code example of how to perform RPCs with other RTclients. TipcSrvMsgSendRpc cannot be used to achieve a one-to-many blocking RPC; this kind of RPC can be handled by sending the call message with TipcSrvMsgSend and then gathering all the necessary result messages with TipcSrvMsgSearchType.

Changing RTclient Options

Many options, such as Project, are only referenced when RTclient creates a connection to RTserver. One way to change these options dynamically is to destroy the connection to RTserver, keeping it warm, and then create a new RTserver connection using the warm information. Code like this can be used to change options like Project or Server_Names, the changes taking effect immediately:

/* destroy existing connection to RTserver, but keep it warm */ 
if (!TipcSrvDestroy(T_IPC_SRV_CONN_WARM)) { 
  /* error */ 
} 
 
/* change project */ 
TutCommandParseStr("setopt project new_project"); 
/* create a new connection to RTserver from warm info */ 
if (!TipcSrvCreate(T_IPC_SRV_CONN_FULL)) { 
  /* error */ 
} 

There are restrictions for changing certain RTclient options:

Connecting to Multiple RTservers

Most RTclient applications require only one connection to an RTserver. The TipcSrv API uses a single global connection to an RTserver. For those rare applications whose architecture requires an RTclient to connect to multiple RTservers, those RTclients can use special multiple connections instead of a global connection.

Multiple RTserver connections use the TipcSrvConn API which allows distinct connections to multiple RTservers. Each connection has its own set of subscriptions and callbacks. Setting different option values for individual connections requires the use of named options, defined using the setnopt command or TutOptionNamedLookup, and the TipcSrvConnCreateNamed function. For information on the TipcSrvConn API, and the associated TipcDispatcher and TipcEvent APIs, see the TIBCO SmartSockets Application Programming Interface.

Certain SmartSockets commands cannot be used with multiple connections outside of CONTROL messages, as there is no way for the command processor to know for which connection the command is intended. The commands created with TipcInitCommands (connect, disconnect, subscribe, and unsubscribe) are applied to the global connection if processed via a direct API call. If these commands are received as CONTROL messages, they will act on the connection on which they were received.

Because file-based GMD, by default, creates a sub-directory named after the RTclient’s unique subject, file conflicts can arise when different multiple RTserver connections sharing the same unique subject attempt to write to the same directory. To avoid this, specify a different GMD sub-directory for each connection using the Server_Gmd_Dir_Name option. For more information, see File-based GMD and Connections to Multiple RTservers.

A dispatcher is available to manage the incoming messages from multiple RTserver clouds. Using a dispatcher can increase the performance of your application. See Using a Dispatcher for more information.

Using multiple connections is recommended when an RTclient:
  • connects to two or more RTserver clouds, or
  • connects to two or more projects in the same RTserver cloud.

In most other cases, multiple connections are unnecessary. If used unwisely, they can cause serious performance issues across your SmartSockets application.


TIBCO SmartSockets™ User’s Guide
Software Release 6.8, July 2006
Copyright © TIBCO Software Inc. All rights reserved
www.tibco.com