Ice defines the Slice language (Specification Language for Ice) for data interface definitions, similar to CORBA IDL and part of WSDL. A Slice interface definition is similar to a C++ class declaration, and can contain type, structure and function declarations etc. It is compiled into equivalent target language class declarations and definitions by an Ice tool, for example slice2cpp for C++. The application programmer then writes a derived class which implements virtual functions to realise desired server behaviour.
This section summarises Slice interface definitions used in the client-server tests, and the derived classes which implement server behaviour. Provision was made for both oneway (with no return value) and twoway (with a return value) function call semantics, to assess the relative costs. Interfaces for the publisher-subscriber tests were similar, except that only oneway calls were used, and 4 interface functions with identical behaviour were provided, to distinguish publishers at the subscriber.
This is a Slice definition for a variable length array of bytes (byte sequence) and functions which use it to pass data between client and server (or publisher and subscriber). Byte sequence is the simplest Slice type, with the lowest transmission cost because no marshaling, unmarshalling, or byte swapping is required - that has to be handled by the application if required. Unless the data is really byte-oriented (eg image format), it is better to use a float array or structure interface, leaving the dirty work to Ice.
module CODAC {
sequence<byte> ByteSeq;
interface Messages
{
void publishDataMessageOneWay(ByteSeq message);
int publishDataMessageTwoWay(ByteSeq message);
......
};
};
From this, slice2cpp generates Messages.h and Messages.cpp, containing the
declaration and definition of the Messages class among other things. In general
it's not necessary to look at the generated files.
With knowledge of the Ice mappings from Slice to C++, the application programmer writes a C++ header file for a MessagesI class derived from the Messages class. The 'I' suffix is just a convention standing for 'Interface'.
#include <Messages.h>
using namespace CODAC;
class MessagesI : public Messages
{
public:
virtual void publishDataMessageOneWay(const ByteSeq&,
const Ice::Current&);
virtual int publishDataMessageTwoWay(const ByteSeq&,
const Ice::Current&);
......
};
Then the application programmer implements the required server behaviour in the MessagesI class definition. Member function code is executed on the server when remote calls are made by a client, and can do anything with the message. In the tests we just validate messages with a known pattern to detect data loss or corruption.
#include <MessagesI.h>
#include <Ice/Ice.h>
using namespace std;
using namespace CODAC;
void
MessagesI::publishDataMessageOneWay(const ByteSeq& message,
const Ice::Current&)
{
// validate data
......
// no value returned to client
}
int
MessagesI::publishDataMessageTwoWay(const ByteSeq& message,
const Ice::Current&)
{
// validate data
......
return EXIT_SUCCESS; // returned to client
}
The Slice definition for a variable length array of floats (float sequence), and the associated MessagesI class files, are similar to those shown for byte array.
This is a Slice definition for a C-style structure and functions which use it to pass data between client and server (or publisher and subscriber). This interface might be used by a data acquisition system (the client) managing hundreds or thousands of signals. The structure stores signal samples over some time range, with all samples in the range for one signal followed by all samples for the next and so on. Data acquisition is assumed here to be synchronous for all signals, so only one set of time values is stored. This organisation will be convenient for servers which process the data, eg for archiving or display. Note that the definition does not prescribe sequence sizes, just structure. Sizes can change on a call-by-call basis to suit the circumstances of the client.
module CODAC {
sequence<float> FloatSeq;
struct SignalData {
string name;
FloatSeq samples;
};
sequence<SignalData> SignalSeq;
struct DataMessage {
string name;
int id;
int signalCount;
int sampleCount;
FloatSeq times;
SignalSeq signals;
};
interface Messages
{
void publishDataMessageOneWay(DataMessage message);
int publishDataMessageTwoWay(DataMessage message);
......
};
};
The derived interface class header is essentially the same as the byte array version.
#include <Messages.h>
using namespace CODAC;
class MessagesI : public Messages
{
public:
virtual void publishDataMessageOneWay(const DataMessage&,
const Ice::Current&);
virtual int publishDataMessageTwoWay(const DataMessage&,
const Ice::Current&);
......
};
As is the derived class implementation.
#include <Ice/Ice.h>
#include <MessagesI.h>
using namespace std;
void
MessagesI::publishDataMessageOneWay(const DataMessage& message,
const Ice::Current&)
{
// validate data
......
// no value returned to client
}
int
MessagesI::publishDataMessageTwoWay(const DataMessage& message,
const Ice::Current&)
{
// validate data
......
return EXIT_SUCCESS; // returned to client
}