This section discusses how to create, access, and destroy a connection. To learn more about working with messages, see Chapter 1, Messages. The following example programs show the code used to send messages between two processes through a connection. The programs also show how to use all connection callback types. There are two parts to the example: a server process and a client process. These example programs do not use guaranteed message delivery (refer to Working With GMD for complete GMD examples).
The source code files for this example are located in these directories:
The online source files have additional #ifdefs
to provide C++ support. These #ifdefs
are not shown to simplify the example.
* connserv.c -- connections example server */
/*
This server process waits for a client to connect to it, creates some
callbacks, and then loops receiving and processing messages.
*/
#include <rtworks/ipc.h>/* =============================================================== */
/*..cb_process_numeric_data -- process callback for NUMERIC_DATA */
static void T_ENTRY cb_process_numeric_data( T_IPC_CONN conn, T_IPC_CONN_PROCESS_CB_DATA data, T_CB_ARG arg ) { T_STR name; T_REAL8 value; TutOut("Entering cb_process_numeric_data.\n");/* set current field to first field in message */
if (!TipcMsgSetCurrent(data->msg, 0)) { TutOut("Could not set current field of message: error <%s>.\n", TutErrStrGet()); return; }/* access and print fields */
while (TipcMsgNextStrReal8(data->msg, &name, &value)) { TutOut("%s = %s\n", name, TutRealToStr(value)); }/* make sure we reached the end of the message */
if (TutErrNumGet() != T_ERR_MSG_EOM) { TutOut("Did not reach end of message: error <%s>.\n", TutErrStrGet()); } }/* cb_process_numeric_data */
/* =============================================================== */
/*..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_read -- read callback */
static void T_ENTRY cb_read( T_IPC_CONN conn, T_IPC_CONN_READ_CB_DATA data, T_CB_ARG arg )/* really (T_IPC_MSG_FILE) */
{ TutOut("Entering cb_read.\n");/* print out the message to the message file */
if (!TipcMsgFileWrite((T_IPC_MSG_FILE)arg, data->msg)) { TutOut("Could not write to message file: error <%s>.\n", TutErrStrGet()); } }/* cb_read */
/* =============================================================== */
/*..cb_queue -- queue callback */
static void T_ENTRY cb_queue( T_IPC_CONN conn, T_IPC_CONN_QUEUE_CB_DATA data, T_CB_ARG arg ) { T_IPC_MT mt; T_STR name; TutOut("Entering cb_queue.\n");/* get 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; }/* print out the position of the message being inserted/deleted */
TutOut("A message of type %s is being %s at position %d.\n", name, data->insert_flag ? "inserted" : "deleted", data->position); }/* cb_queue */
/* =============================================================== */
/*..cb_error -- error callback */
static void T_ENTRY cb_error( T_IPC_CONN conn, T_IPC_CONN_ERROR_CB_DATA data, T_CB_ARG arg ) { TutOut("Entering cb_error.\n"); TutOut("The error number is %d.\n", data->err_num); }/* cb_error */
/* =============================================================== */
/*..main -- main program */
int main() { T_IPC_CONN server_conn;/* used to accept client */
T_IPC_CONN client_conn;/* connection to client */
T_IPC_MT mt;/* message type for creating callbacks */
T_IPC_MSG_FILE msg_file;/* message file for printing messages */
T_IPC_MSG msg;/* message received and processed */
TutOut("Creating server connection to accept clients on.\n"); server_conn = TipcConnCreateServer("tcp:_node:5252"); if (server_conn == NULL) { TutOut("Could not create server connection: error <%s>.\n", TutErrStrGet()); TutExit(T_EXIT_FAILURE); }/* accept one client */
TutOut("Waiting for client to connect.\n"); client_conn = TipcConnAccept(server_conn); if (client_conn == NULL) { TutOut("Could not accept client: error <%s>.\n", TutErrStrGet()); TutExit(T_EXIT_FAILURE); }/* destroy server conn: it’s not needed anymore */
TutOut("Destroying server connection.\n"); if (!TipcConnDestroy(server_conn)) { TutOut("Could not destroy server connection: error <%s>.\n", TutErrStrGet()); TutExit(T_EXIT_FAILURE); }/* create callbacks to be executed when certain operations occur */
TutOut("Create callbacks.\n");/* process callback */
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 (TipcConnProcessCbCreate(client_conn, mt, cb_process_numeric_data, NULL) == NULL) { TutOut("Could not create NUMERIC_DATA process cb: error <%s>.\n", TutErrStrGet()); TutExit(T_EXIT_FAILURE); }/* default callback */
if (TipcConnDefaultCbCreate(client_conn, cb_default, NULL) == NULL) { TutOut("Could not create default cb: error <%s>.\n", TutErrStrGet()); TutExit(T_EXIT_FAILURE); }/* create a message file to use in read callback */
msg_file = TipcMsgFileCreateFromFile(stdout, T_IPC_MSG_FILE_CREATE_WRITE); if (msg_file == NULL) { TutOut("Could not create message file from stdout: error <%s>.\n", TutErrStrGet()); TutExit(T_EXIT_FAILURE); }/* read callback */
if (TipcConnReadCbCreate(client_conn, NULL,/* global callback */
cb_read, msg_file) == NULL) { TutOut("Could not create read cb: error <%s>.\n", TutErrStrGet()); TutExit(T_EXIT_FAILURE); }/* queue callback */
if (TipcConnQueueCbCreate(client_conn, NULL,/* global callback */
cb_queue, NULL) == NULL) { TutOut("Could not create queue cb: error <%s>.\n", TutErrStrGet()); TutExit(T_EXIT_FAILURE); }/* error callback */
if (TipcConnErrorCbCreate(client_conn, cb_error, NULL) == NULL) { TutOut("Could not create error cb: error <%s>.\n", TutErrStrGet()); TutExit(T_EXIT_FAILURE); } TutOut("Read and process all messages.\n"); while ((msg = TipcConnMsgNext(client_conn, T_TIMEOUT_FOREVER)) != NULL) { if (!TipcConnMsgProcess(client_conn, msg)) { TutOut("Could not process message: error <%s>.\n", TutErrStrGet()); } if (!TipcMsgDestroy(msg)) { TutOut("Could not destroy message: error <%s>.\n", TutErrStrGet()); } }/* make sure we reached the end of the data */
if (TutErrNumGet() != T_ERR_EOF) { TutOut("Did not reach end of data: error <%s>.\n", TutErrStrGet()); } if (!TipcConnDestroy(client_conn)) { TutOut("Could not destroy client connection: error <%s>.\n", TutErrStrGet()); } if (!TipcMsgFileDestroy(msg_file)) { TutOut("Could not destroy message file: error <%s>.\n", TutErrStrGet()); } TutOut("Server process exiting successfully.\n"); return T_EXIT_SUCCESS;/* all done */
}/* main */
/* connclnt.c -- connections example client */
/*
The client process connects to the server process and sends two
messages to the server.
*/
#include <rtworks/ipc.h>/* =============================================================== */
/*..cb_write -- write callback */
static void T_ENTRY cb_write( T_IPC_CONN conn, T_IPC_CONN_WRITE_CB_DATA data, T_CB_ARG arg )/* really (T_IPC_MSG_FILE) */
{ TutOut("Entering cb_write.\n");/* print out the message to the message file */
if (!TipcMsgFileWrite((T_IPC_MSG_FILE)arg, data->msg)) { TutOut("Could not write to message file: error <%s>.\n", TutErrStrGet()); } }/* cb_write */
/* =============================================================== */
/*..main -- main program */
int main() { T_IPC_CONN conn;/* connection to server */
T_IPC_MT mt;/* message type for creating callbacks and messages */
T_IPC_MSG_FILE msg_file;/* message file for printing messages */
T_IPC_MSG msg;/* message to send */
TutOut("Creating connection to server process.\n"); conn = TipcConnCreateClient("tcp:_node:5252"); if (conn == NULL) { TutOut("Could not create connection to server: error <%s>.\n", TutErrStrGet()); TutExit(T_EXIT_FAILURE); }/* create callbacks to be executed when certain operations occur */
TutOut("Create callbacks.\n");/* create a message file to use in write callback */
msg_file = TipcMsgFileCreateFromFile(stdout, T_IPC_MSG_FILE_CREATE_WRITE); if (msg_file == NULL) { TutOut("Could not create message file from stdout: error <%s>.\n", TutErrStrGet()); TutExit(T_EXIT_FAILURE); }/* write callback */
if (TipcConnWriteCbCreate(conn, NULL,/* global callback */
cb_write, msg_file) == NULL) { TutOut("Could not create write cb: error <%s>.\n", TutErrStrGet()); TutExit(T_EXIT_FAILURE); } TutOut("Constructing and sending 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); } msg = TipcMsgCreate(mt); if (msg == NULL) { TutOut("Could not create NUMERIC_DATA message: error <%s>.\n", TutErrStrGet()); TutExit(T_EXIT_FAILURE); } if (!TipcMsgWrite(msg, 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 append to NUMERIC_DATA msg: error <%s>.\n", TutErrStrGet()); TutExit(T_EXIT_FAILURE); } if (!TipcConnMsgSend(conn, msg)) { TutOut("Could not send NUMERIC_DATA message: error <%s>.\n", TutErrStrGet()); } TutOut("Constructing and sending an INFO message.\n"); mt = TipcMtLookupByNum(T_MT_INFO); if (mt == NULL) { TutOut("Could not look up INFO message type: error <%s>.\n", TutErrStrGet()); TutExit(T_EXIT_FAILURE); }/* just reuse previous message */
if (!TipcMsgSetType(msg, mt)) { TutOut("Could not set message type: error <%s>.\n", TutErrStrGet()); } if (!TipcMsgSetNumFields(msg, 0)) { TutOut("Could not set message num fields: error <%s>.\n", TutErrStrGet()); } if (!TipcMsgAppendStr(msg, "Now is the time")) { TutOut("Could not append fields to INFO message: error <%s>.\n", TutErrStrGet()); TutExit(T_EXIT_FAILURE); } if (!TipcConnMsgSend(conn, msg)) { TutOut("Could not send INFO message: error <%s>.\n", TutErrStrGet()); } if (!TipcConnFlush(conn)) { TutOut("Could not flush buffered outgoing msgs: error <%s>.\n", TutErrStrGet()); } if (!TipcMsgDestroy(msg)) { TutOut("Could not destroy message: error <%s>.\n", TutErrStrGet()); } if (!TipcConnDestroy(conn)) { TutOut("Could not destroy connection: error <%s>.\n", TutErrStrGet()); } if (!TipcMsgFileDestroy(msg_file)) { TutOut("Could not destroy message file: error <%s>.\n", TutErrStrGet()); } TutOut("Client process exiting successfully.\n"); return T_EXIT_SUCCESS;/* all done */
}/* main */
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:
Compile and link the programs
$ cc connserv.c $ rtlink /exec=connserv.exe connserv.obj $ cc connclnt.c $ rtlink /exec=connclnt.exe connclnt.obj
On UNIX the rtlink
command by default uses the cc
command to compile and link. To use a C++ compiler or a C compiler with a name other than cc
, set the environment variable CC
to the name of the compiler, and rtlink
then uses this compiler. Use these commands to compile and link on UNIX with the GNU C++ compiler g++
:
To run the programs, start the server process first in one terminal emulator window and then the client process in another terminal emulator window.
Start the server program in the first window
Start the client program in the second window
The output from the server program is:
Creating server connection to accept clients on. Waiting for client to connect. Destroying server connection. Create callbacks. Read and process all messages. Entering cb_read. numeric_data _null { voltage 33.4534 switch_pos 0 } Entering cb_queue. A message of type numeric_data is being inserted at position 0. Entering cb_read. info _null "Now is the time" Entering cb_queue. A message of type info is being inserted at position 1. Entering cb_queue. A message of type numeric_data is being deleted at position 0. Entering cb_process_numeric_data. voltage = 33.4534 switch_pos = 0 Entering cb_queue. A message of type info is being deleted at position 0. Entering cb_default. Message type name is info. Entering cb_error. The error number is 10. Server process exiting successfully.
The output from the client program is:
Creating connection to server process. Create callbacks. Constructing and sending a NUMERIC_DATA message. Entering cb_write. numeric_data _null { voltage 33.4534 switch_pos 0 } Constructing and sending an INFO messages. Entering cb_write. info _null "Now is the time" Client process exiting successfully.
Code written in C or C++ that uses the SmartSockets Application Programming Interface (API) must include the header file <rtworks/ipc.h>
. This file is located in these directories:
The SmartSockets IPC API includes all the functions used for interprocess communication.
SmartSockets simplifies the creation of connections with logical connection names that are specified consistently for all protocols. A server process uses a logical connection name to create a server connection, and a client process uses the same logical connection name to create a client connection to the server process. Each logical connection name has the form:
which can be shortened to protocol
:node
, protocol
, or simply node
for normal connections. For the client process to find the server process, the logical connection name used by the client must exactly match the logical connection name used by the server (for example, the name tcp:moe:1234
does not match the name tcp:conan:1234
). The exception to this is when the server that the client process is trying to find has more than one IP address. For more information on this case, see Multiple IP Addresses.
This section describes the features of logical connection names in peer-to-peer connections. For a discussion of how RTserver and RTclient add more function to logical connection names, such as search lists and abbreviated names, see Logical Connection Names for RT Processes.
The protocol
part of the connection name refers to an IPC protocol type. The valid values for protocol
are local
and tcp
. RTclient and RTserver can also use the udp_broadcast
protocol to find an RTserver. See The Udp_Broadcast Protocol for more details on the udp_broadcast
protocol.
The node
part of the connection name refers to a computer node name. The special value _node
can be used for node
to indicate the name of the current node; this is useful for generalizing a connection name to work on any computer. See the TutSocketCreate* functions in the TIBCO SmartSockets Utilities reference for more details on the node name formats.
The address
part of the connection name refers to a protocol-specific IPC location, such as a TCP port number. The addresses for all protocols are:
Protocol Name
|
Description of Address
|
---|---|
local
|
file name in the directory specified by the function TutGetSocketDir
|
tcp
|
TCP port number or service name
|
udp_broadcast
|
UDP port number or service name
|
For the tcp
and udp_broadcast
protocols, the address can be either an integer port number or a service name accessible with the getservbyname
socket function (internally SmartSockets always uses "tcp"
for the proto
second argument to getservbyname
, even for udp_broadcast
logical connection names).
Generally, for the client process to find a server process, the logical connection name used by the client must exactly match the logical connection name used by the server. For example, the name tcp:moe:1234
does not match the name tcp:conan:1234
. However, this perfect match is not necessarily needed when the client is trying to find a server that has more than one IP address. A server or host with more than one IP address is called a multi-homed host. If the server uses _any
as its node in the logical connection name, the server listens on all IP addresses. Using _node
causes the server to listen only on the default IP address.
The standard client-server model for network applications is used when creating connections. The server starts first and waits to be contacted by the client. Once the server and client make contact, they become equal partners, and the model switches to a peer-to-peer model. Three steps (as shown in Figure 6, Figure 7, and Figure 8) are required when two processes create connections to each other.
The concepts of servers and clients only apply when the two processes are creating connections. Once the server has accepted the client, both processes have equivalent connections to each other. Both processes can send and receive messages using their connections, and there is no conceptual difference between the server and the client process. The model switches from client-server to peer-to-peer. A single process can have any number of server connections, client connections, and connections accepted from clients.
The server connection, which can use any one of the supported protocols, is created by calling the function TipcConnCreateServer with a logical connection name parameter used to identify the server connection. For example:
server_conn = TipcConnCreateServer("tcp:_node:5252"); if (server_conn == NULL) { TutOut("Could not create server connection: error <%s>.\n", TutErrStrGet()); TutExit(T_EXIT_FAILURE); }
The only purpose of a server connection is to accept client connections. Messages cannot be sent or received on a server connection. Two server connections on the same node using the same IPC protocol cannot use the same logical connection name address portion.
In a fashion similar to server connections, a client connection is created by calling the function TipcConnCreateClient with a logical connection name parameter that must match the name used with TipcConnCreateServer. For example:
conn = TipcConnCreateClient("tcp:_node:5252"); if (conn == NULL) { TutOut("Could not create connection to server: error <%s>.\n", TutErrStrGet()); TutExit(T_EXIT_FAILURE); }
The client connection must use the same IPC protocol as the server. The client connection must be created after the server connection, as the client needs something to contact.
Once the client has called TipcConnCreateClient to create its connection, the server process must accept the client by calling the function TipcConnAccept. For example:
client_conn = TipcConnAccept(server_conn); if (client_conn == NULL) { TutOut("Could not accept client: error <%s>.\n", TutErrStrGet()); TutExit(T_EXIT_FAILURE); }
When the server process accepts the client, a new connection is created and returned by TipcConnAccept. This new connection is the peer-to-peer link for the server process to communicate with the client process. The client process gets its peer-to-peer link directly from TipcConnCreateClient. After each call to TipcConnAccept, the original server connection is unchanged and can be used to accept more clients later.
A server process can accept any number of clients by calling TipcConnAccept once for each client. Each connection does use an operating system file descriptor, though, and most operating systems have a limit on how many open file descriptors a process can have. See File Descriptor Upper Limit for information on how to increase the open file descriptor limit.
While TipcConnCreateServer can execute and return immediately, both TipcConnCreateClient and TipcConnAccept do not return until both sides of the peer-to-peer link are established. During the transition from client-server to peer-to-peer, the client and server simultaneously exchange some initial handshaking information, such as the SmartSockets IPC protocol version (which is defined in <rtworks/ipc.h>
as T_IPC_PROTOCOL_VERSION), integer number format (C type T_INT_FORMAT), and real number format (C type T_REAL_FORMAT). One process cannot be both the server and client ends of the same connection due to this simultaneous handshaking. The protocol version exchange prevents incompatible clients and servers from contacting each other, and also allows newer protocol versions to communicate with older protocol versions. Future versions of SmartSockets will be protocol compatible with the current version, and new message and connection features will be added in a compatible way to allow old and new versions to interoperate. The integer and real number formats are used to convert fields in messages when messages are sent between heterogeneous nodes (see Sending Messages in a Heterogeneous Environment for information on the handling of messages in a heterogeneous environment).
If there are no clients waiting to be accepted, TipcConnAccept does not return until a client does connect (unless non-blocking operations are enabled with TipcConnSetBlockMode). The server process can use the function TipcConnCheck to check if a client has connected:
if (TipcConnCheck(server_conn, T_IPC_SOCKET_CHECK_READ)) { TutOut("A client is waiting to be accepted.\n"); }
When a process is finished with a connection, the connection should be destroyed to free up the memory it occupies and close the socket. This is done with the TipcConnDestroy function.
If a process no longer wants to accept clients on a server connection, that connection can be destroyed.
For example:
if (!TipcConnDestroy(server_conn)) { TutOut("Could not destroy server connection: error <%s>.\n", TutErrStrGet()); TutExit(T_EXIT_FAILURE); }
If a client process is done with a connection, the connection can be destroyed. For example:
if (!TipcConnDestroy(conn)) { TutOut("Could not destroy connection: error <%s>.\n", TutErrStrGet()); TutExit(T_EXIT_FAILURE); }
When a process or task exits, the operating system automatically closes any open sockets the process had. When a peer-to-peer socket of a connection is closed, the process at the other end eventually gets an error number of T_ERR_EOF when trying to read or write data on its connection.
A process with connections using the tcp
protocol may lose outgoing messages if the process terminates without calling TipcConnDestroy to destroy each connection. TCP/IP’s SO_LINGER
option, which preserves data, is ignored when closing a socket that has non-blocking I/O enabled. While data loss rarely occurs on UNIX and OpenVMS, it can happen frequently on Windows. TipcConnDestroy sets the block mode of the connection to FALSE
before closing the connection’s socket, which forces the operating system to deliver all flushed outgoing messages.
Once two processes have created connections to each other, each process can create connection callbacks to be executed when certain operations occur. Callbacks are conceptually equivalent to dynamically adding a line of code to a program. The SmartSockets Utilities reference contains more information on callbacks, which are manipulated using utilities.
Connection process callbacks are executed while processing a message with the function TipcConnMsgProcess. This callback type is the most frequently used. A process callback can be called for a specific type of message or created globally and called for all message types. For example, a process callback can be created for the NUMERIC_DATA message type. When any message of that type is processed by calling TipcConnMsgProcess, the process callback is called. If the process callback is created globally, it is called for all NUMERIC_DATA type messages as well as any other type of message.
TipcConnProcessCbCreate. For example:
TipcConnProcessCbLookup. For example:
Connection default callbacks are executed while processing a message with the function TipcConnMsgProcess if a process callback specific to the message type (that is, not a global process callback) has not been called. Default callbacks are useful for processing unexpected message types or for generic processing of most message types. For example, you have a client process to manage and display alarm messages. The only process callback you would want for that client process would be for messages of type "alarm." You could then write a default callback to process any other type of data received and display a warning.
TipcConnDefaultCbCreate. For example:
TipcConnDefaultCbLookup. For example:
Connection read callbacks are executed when an incoming message is read from a connection’s socket into the read buffer and first unpacked into a message. Read callbacks are most commonly used for writing incoming messages to message files.
TipcConnReadCbCreate. For example:
TipcConnReadCbLookup. For example:
Connection write callbacks are executed when an outgoing message is sent to a connection (that is, copied to a connection’s write buffer). Write callbacks are most commonly used for writing outgoing messages to message files.
TipcConnWriteCbCreate. For example:
TipcConnWriteCbLookup. For example:
Connection queue callbacks are executed when a message is inserted into or deleted from a connection’s message queue. Queue callbacks are useful for watching the messages that have been read in from a connection’s socket and inserted into the message queue, but not yet processed.
TipcConnQueueCbCreate. For example:
TipcConnQueueCbLookup. For example:
Connection error callbacks are executed when an unrecoverable error occurs. These errors include socket problems and network failures such as:
TipcConnErrorCbCreate. For example:
TipcConnErrorCbLookup. For example:
This creates a process callback, called whenever a NUMERIC_DATA message is processed:
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 (TipcConnProcessCbCreate(client_conn, mt, cb_process_numeric_data, NULL) == NULL) { TutOut("Could not create NUMERIC_DATA process cb: error <%s>.\n", TutErrStrGet()); TutExit(T_EXIT_FAILURE); }
This creates a default callback, called when a message is processed that has no process callbacks:
if (TipcConnDefaultCbCreate(client_conn, cb_default, NULL) == NULL) { TutOut("Could not create default cb: error <%s>.\n", TutErrStrGet()); TutExit(T_EXIT_FAILURE); }
This example creates a global read callback that is called whenever any message is read from a connection:
if (TipcConnReadCbCreate(client_conn,
NULL, /* global callback */
cb_read,
msg_file) == NULL) {
TutOut("Could not create read cb: error <%s>.\n",
TutErrStrGet());
TutExit(T_EXIT_FAILURE);
}
This example creates a global write callback that is called whenever any message is written to a connection:
if (TipcConnWriteCbCreate(conn,
NULL, /* global callback */
cb_write,
msg_file) == NULL) {
TutOut("Could not create write cb: error <%s>.\n",
TutErrStrGet());
TutExit(T_EXIT_FAILURE);
}
This example creates a global queue callback that is called whenever any message is inserted into or deleted from a connection’s message queue:
if (TipcConnQueueCbCreate(client_conn,
NULL, /* global callback */
cb_queue,
NULL) == NULL) {
TutOut("Could not create queue cb: error <%s>.\n",
TutErrStrGet());
TutExit(T_EXIT_FAILURE);
}
This example creates an error callback that is called whenever a non-recoverable error occurs on a connection:
if (TipcConnErrorCbCreate(client_conn, cb_error, NULL) == NULL) { TutOut("Could not create error cb: error <%s>.\n", TutErrStrGet()); TutExit(T_EXIT_FAILURE); }
Each connection callback has a specific function (such as cb_process_numeric_data
) and argument (such as NULL
). Several types of connection callbacks, such as process callbacks, also have a message type (such as NUMERIC_DATA) associated with them. The most commonly used connection callback type is the process callback. Process callback functions are used to perform the main processing of a message.
The following section describes a callback function in detail. This callback function, which is called when a message of type NUMERIC_DATA is processed with TipcConnMsgProcess, simply accesses and prints the fields of the message:
/*..cb_process_numeric_data -- process callback for NUMERIC_DATA */
static void T_ENTRY cb_process_numeric_data(
T_IPC_CONN conn,
T_IPC_CONN_PROCESS_CB_DATA data,
T_CB_ARG arg )
Notice that this callback is declared with the macro T_ENTRY. This is necessary for cross-platform portability. All prototypes and definitions of callbacks and thread functions must be declared with T_ENTRY.
All SmartSockets callback functions do not return a value and take three parameters:
The message being processed is available in the data
parameter. Every callback function should first set the current field of the message before accessing the message fields, as the current field could be set to any field when the callback function is called.
/* access and print fields */
while (TipcMsgNextStrReal8(data->msg, &name, &value)) { TutOut("%s = %s\n", name, TutRealToStr(value)); }/* make sure we reached the end of the message */
if (TutErrNumGet() != T_ERR_MSG_EOM) { TutOut("Did not reach end of message: error <%s>.\n", TutErrStrGet()); } }/* cb_process_numeric_data */
The above while loop accesses and prints all the name-value pairs from the NUMERIC_DATA message. The final error check for T_ERR_MSG_EOM ensures that the while loop reached the end of the message as expected (that is, some other error condition such as a message field type mismatch did not occur). Each application can add more or less error checking as desired.
The non-opaque callback type-specific data argument varies among callback types. All SmartSockets callback type-specific data argument types must have a T_CB callback value as their first field.
Table 4 shows the callback type-specific data argument types for all connection callback types and the fields in those argument types.
As described earlier, a connection has a priority queue of incoming messages. The message at the front of this queue can be retrieved with the function TipcConnMsgNext. This message is normally processed immediately with the function TipcConnMsgProcess and then destroyed with the function TipcMsgDestroy.
For example:
while ((msg = TipcConnMsgNext(client_conn, T_TIMEOUT_FOREVER))
!= NULL) {
if (!TipcConnMsgProcess(client_conn, msg)) {
TutOut("Could not process message: error <%s>.\n",
TutErrStrGet());
}
if (!TipcMsgDestroy(msg)) {
TutOut("Could not destroy message: error <%s>.\n",
TutErrStrGet());
}
}
/* make sure we reached the end of the data */
if (TutErrNumGet() != T_ERR_EOF) {
TutOut("Did not reach end of data: error <%s>.\n",
TutErrStrGet());
}
The above while loop receives and processes all messages from the connection, and the final error check for T_ERR_EOF ensures that the while loop reached the end of the data as expected. Each application can add more or less error checking as desired.
If there are no messages in the connection’s message queue, then TipcConnMsgNext reads data from the connection by calling the function TipcConnRead over and over until a full message has been read in, a certain period of time has elapsed, or an error occurs.
As TipcConnRead reads in each message packet, it converts the message packet into a message. The connection read callbacks are executed by TipcConnRead each time a full message is read into the connection’s read buffer. The connection queue callbacks are executed by TipcConnMsgInsert each time a message is inserted into a connection’s message queue, and by TipcConnMsgNext and TipcConnMsgSearch each time a message is removed from a connection’s message queue.
If necessary, TipcConnRead converts the integers, real numbers, and strings in the message header to the format used by the receiving process (see Sending Messages in a Heterogeneous Environment to learn more about sending messages between different types of computers). TipcConnRead also performs some checks for duplicate messages if the received message was sent with GMD. See Receiving Messages for details on the GMD-specific aspects of receiving messages.
The second parameter to TipcConnMsgNext specifies the maximum amount of time (in seconds) that it waits for an incoming message to arrive. The timeout 0.0
can be used to get the next message that is immediately available, the timeout T_TIMEOUT_FOREVER can be used to wait indefinitely for the next message, and any non-negative timeout can be used to wait for a certain period of time for the next message.
TipcConnMsgProcess executes the connection default callbacks only if there are no connection process callbacks for the type of the message being processed. The two levels of callbacks allow for greater flexibility: process callbacks can handle message type-specific needs and default callbacks can be used to handle generic needs. While most messages should be processed with TipcConnMsgProcess so that the connection process and default callbacks can be executed, there is no requirement that all messages be processed this way.
If a process does not need the flexibility and extensibility that connection callbacks offer, it can call TipcConnMsgNext to get a message and then access the fields of the message directly with the TipcMsgNextType
functions. All of the SmartSockets modules such as RTdaq, however, use callbacks to process messages, so that user-defined callbacks can also be added to extend the function.
The TipcConnMainLoop convenience function receives and processes messages by calling TipcConnMsgNext, TipcConnMsgProcess, and TipcMsgDestroy over and over. Using this function, the previous loop is rewritten as shown:
if (!TipcConnMainLoop(client_conn, T_TIMEOUT_FOREVER)) {
/* make sure we reached the end of the data */
if (TutErrNumGet() != T_ERR_EOF) {
TutOut("Did not reach end of data: error <%s>.\n",
TutErrStrGet());
}
}
In most situations, incoming messages should be accessed sequentially with TipcConnMsgNext. Sometimes, though, messages need to be accessed in non-priority order. Examples of this include remote procedure calls (see Remote Procedure Calls for more information) and checking for a message of a specific type. The function TipcConnMsgSearch is used to search for a specific message. For example, to search for a NUMERIC_DATA message, this search function could be used:
/* =============================================================== */
/*..search_numeric_data -- search for NUMERIC_DATA */
static T_BOOL T_ENTRY search_numeric_data(conn, msg, arg) T_IPC_CONN conn; T_IPC_MSG msg; T_PTR arg; { T_BOOL status; T_IPC_MT mt; T_INT4 num; if (!TipcMsgGetType(msg, &mt)) { TutOut("could not get msg type: error <%s>\n", TutErrStrGet()); (void)TipcMsgPrint(msg, TutOut); return FALSE; } if (!TipcMtGetNum(mt, &num)) { TutOut("could not get message type num: error <%s>\n", TutErrStrGet()); (void)TipcMsgPrint(msg, TutOut); return FALSE; }/* return TRUE if message type matches what we want */
return num == T_MT_NUMERIC_DATA; }/* search_numeric_data */
The above search function search_numeric_data
could then be used as follows:
msg = TipcConnMsgSearch(conn, T_TIMEOUT_FOREVER, search_numeric_data, NULL); if (msg == NULL) { TutOut("Could not find NUMERIC_DATA message: error <%s>.\n", TutErrStrGet()); }
TipcConnMsgSearch traverses a connection’s message queue and calls a user-defined search function once for each message. If the end of the message queue is reached, then TipcConnMsgSearch calls the function TipcConnRead to receive more messages into the message queue and then starts over again at the front of the queue. TipcConnMsgSearch returns when, the search function returns TRUE
, a certain period of time has elapsed, or an error occurs.
If the search function returns TRUE,
then TipcConnMsgSearch removes the desired message from the connection’s message queue and returns the message. The second parameter to TipcConnMsgSearch specifies a timeout just like the second parameter to TipcConnMsgNext.
The TipcConnMsgSearchType convenience function searches for a message with a specific type by calling TipcConnMsgSearch. Using this function, the code is rewritten as shown in the example:
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); } msg = TipcConnMsgSearchType(conn, T_TIMEOUT_FOREVER, mt); if (msg == NULL) { TutOut("Could not find NUMERIC_DATA message: error <%s>.\n", TutErrStrGet()); }
Note that if TipcConnMsgSearchType is used, no search function is needed.
When a message is received through a connection, it passes through several buffers before finally being accessed with TipcConnMsgNext or TipcConnMsgSearch. A buffer is an area of memory where data, such as messages, is stored while waiting to be accessed or transmitted. For incoming messages, these buffers are used:
Most of these buffers cannot be accessed directly, but inefficient use of these buffers can reduce the performance of connections. When messages are transferred from the connection’s socket’s incoming buffer to the connection’s read buffer (this is done by TipcConnRead), an (operating system) system call is performed. System call functions are necessary, but they are much more time-consuming than normal functions, and better performance can be achieved by batching data transfers into fewer system calls. A connection’s read buffer helps to batch incoming data transfers by allowing TipcConnRead to read as much data as possible for each system call.
Because sockets are byte streams, large messages may be received in pieces, which are buffered in the connection’s read buffer until the entire message is received. Once the full incoming message is received, it is transferred to the connection’s message queue and is available to be accessed and processed.
Once a message has been constructed with the TipcMsg* functions, it can be sent through the connection with the function TipcConnMsgSend. The code constructs and sends a NUMERIC_DATA message:
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); } msg = TipcMsgCreate(mt); if (msg == NULL) { TutOut("Could not create NUMERIC_DATA message: error <%s>.\n", TutErrStrGet()); TutExit(T_EXIT_FAILURE); } if (!TipcMsgWrite(msg, 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 append to NUMERIC_DATA msg: error <%s>.\n", TutErrStrGet()); TutExit(T_EXIT_FAILURE); } if (!TipcConnMsgSend(conn, msg)) { TutOut("Could not send NUMERIC_DATA message: error <%s>.\n", TutErrStrGet()); TutExit(T_EXIT_FAILURE); }
TipcConnMsgSend executes the connection write callbacks, converts the message into a message packet, and then appends the message packet to the end of the connection’s write buffer. If the message’s header string encode property is TRUE
, the message header string properties, such as destination, are stored as four-byte integers in the message packet, which compresses the message header so as to use less network bandwidth. See Header String Encode for more information about message header string encoding.
TipcConnMsgSend also saves a copy of the outgoing message in the connection GMD area if the message is sent with GMD. See Sending Messages for details on the GMD-specific aspects of sending messages. TipcConnMsgSend then calls TipcConnFlush to flush the buffered outgoing data to the connection’s socket if the connection’s auto flush size is not T_IPC_NO_AUTO_FLUSH, and the number of bytes buffered in the connection’s write buffer is larger than the value of the connection’s auto flush size.
See Buffering of Outgoing Messages for more information on how outgoing messages are buffered.
The TipcConnMsgWrite convenience function handles a variable number of arguments and allows you to create a message, append fields, send the message on a connection, and destroy the message. The function takes enumerated values that begin with T_IPC_FT_TYPE
, where TYPE
is replaced by a field type as shown in Table 1, such as T_IPC_FT_STR. A final parameter of NULL
is used to terminate the variable number of arguments. Using this function, the above NUMERIC_DATA message could be constructed and sent as follows:
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 (!TipcConnMsgWrite(conn, mt, 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 append to NUMERIC_DATA message: error <%s>.\n", TutErrStrGet()); TutExit(T_EXIT_FAILURE); }
![]() |
C/C++ functions that use a variable number of arguments often require fewer lines of code to use, but no type checking is done by the C/C++ compiler on the variable arguments.
|
As an added convenience, TipcConnMsgWrite also allows enumerated values that begin with T_IPC_PROP_NAME
, where NAME
is replaced by a message property name, such as T_IPC_PROP_DELIVERY_MODE. For example, a complex message can be constructed and sent with one call to TipcConnMsgWrite:
if (!TipcConnMsgWrite(conn, mt,
T_IPC_PROP_SENDER, "/_conan_5415",
T_IPC_PROP_DEST, "/system/thermal",
T_IPC_PROP_PRIORITY, 2,
T_IPC_PROP_DELIVERY_MODE, T_IPC_DELIVERY_ALL,
T_IPC_PROP_DELIVERY_TIMEOUT, 20.0,
T_IPC_PROP_LB_MODE, T_IPC_LB_WEIGHTED,
T_IPC_PROP_HEADER_STR_ENCODE, TRUE,
T_IPC_PROP_USER_PROP, 42,
T_IPC_FT_STR, "voltage",
T_IPC_FT_REAL8, 33.4534,
T_IPC_FT_STR, "switch_pos",
T_IPC_FT_REAL8, 0.0,
NULL)) {
/* error */
}
When a message is sent through a connection by calling TipcConnMsgSend, it passes through several buffers. A buffer is an area of memory where data, such as messages, is stored while waiting to be accessed or transmitted. For outgoing messages, these buffers are used:
These buffers cannot be accessed directly, but inefficient use of these buffers can reduce the performance of connections. When messages are transferred from the connection’s write buffer to the connection’s socket’s outgoing buffer (this is done by TipcConnFlush), an (operating system) system call is performed. System call functions are necessary, but they are much more time-consuming than normal functions, and better performance can be achieved by batching data transfers into fewer system calls. A connection’s write buffer helps to batch outgoing data transfers by allowing TipcConnFlush to write as much data as possible for each system call.
In most situations, the connection’s auto flush size property can be used to control when the function TipcConnFlush is called automatically. Sometimes, though, a program does need to use TipcConnFlush explicitly to force outgoing buffered messages to be flushed. If a program sends messages and then does not call any other TipcConn* functions for a period of time, the outgoing messages sit in the connection’s write buffer. If the program sends messages and then calls TipcConnMsgNext right away to get the next available message, the program does not need to call TipcConnFlush (as TipcConnMsgNext calls TipcConnRead, which automatically flushes the buffered outgoing data). Flushing the write buffer before reading allows responses to the outgoing messages to be available sooner.
SmartSockets allows messages to be sent between processes on different types of computers, such as Intel x86 and Sun SPARC. Converting a message from one kind of platform to another is handled transparently by the SmartSockets API. The rest of this section describes in detail how this is done.
In a heterogeneous environment, different computer nodes on a network often have incompatible integer, real number, and string formats. A common example is Sun SPARC and Intel x86. All of the platforms supported by SmartSockets use two’s complement integer arithmetic, but there are two common integer layouts:
Most non-Digital and non-Intel computers use big-endian format. Converting integers between big-endian and little-endian formats is simple and involves byte-swapping the entire integer. Only two-byte (C type T_INT2), four-byte integers (C type T_INT4), and eight-byte integers (C type T_INT8) have to be byte-swapped; one-byte (C type T_CHAR) integers and NULL
-terminated character strings do not need any conversion when moving data between platforms using the single byte, US ASCII character set. Additional character set considerations are discussed later in this section.
A similar situation exists for real numbers. These real number formats exist for the platforms supported by SmartSockets:
Most platforms supported by SmartSockets use IEEE floating-point format for real numbers. In addition to the above formats, real numbers are subject to the same endian-ness byte order that integers use. For example, IEEE numbers are big-endian on most platforms but are little-endian on a few platforms.
For characters and character strings, there are two possibilities:
When a message is read from a connection, the integers, real numbers, and strings within the message header are automatically converted from the formats of the sending process to the formats of the receiving process by the function TipcConnRead. The message data information is converted on an incremental per-field basis by the TipcMsgNext* functions. This conversion scheme is commonly known as receiver-makes-right data conversion. Because the data fields in a message are only converted when the data is accessed, RTserver routes publish-subscribe messages without performing any conversion on the data fields.
Message conversion always takes place in the receiving process and only happens when necessary (that is, no conversion happens when both processes have the same formats). As described in The Server Accepts the Client, when two processes create connections to each other, they first exchange with each other their integer format and real number format (the character format is deduced from the real number format). This format information is used by TipcConnRead to determine when conversion is needed.
TIBCO SmartSockets™ User’s Guide Software Release 6.8, July 2006 Copyright © TIBCO Software Inc. All rights reserved www.tibco.com |