Synchronous Symphony C++ client tutorial

Goal

This tutorial guides you through the process of building, packaging, deploying, and running the hello grid sample client and service. It also walks you through the sample client application code.

You learn the minimum amount of code that you need to create a client.

At a glance

Before you begin, ensure you have installed and started Platform Symphony DE. You complete the following tasks:
  1. Build the sample client and service

  2. Package the sample service

  3. Add the application

  4. Run the sample client and service

  5. Walk through the code

Build the sample client and service

On Windows

You can build client application and service samples at the same time.

  1. In %SOAM_HOME%\5.0\samples\CPP\SampleApp, locate workspace file sampleCPP_vc6.dsw, or one of the following solution files: sampleCPP_vc71.sln, sampleCPP_vc80.sln, or sampleCPP_vc90.sln.
  2. Load the file into Visual Studio and build it.

On Linux

You can build client application and service samples at the same time.

  1. Change to the conf directory under the directory in which you installed Symphony DE.

    For example, if you installed Symphony DE in /opt/symphonyDE/DE50, go to /opt/symphonyDE/DE50/conf.

  2. Source the environment:
    • For csh, enter

      source cshrc.soam
    • For bash, enter

      . profile.soam
  3. Compile using the Makefile located in $SOAM_HOME/5.0/samples/CPP/SampleApp:

    make

Package the sample service

On Windows

To deploy the service, you first need to package it.

  1. Go to the directory in which the compiled samples are located.

    cd %SOAM_HOME%\5.0\samples\CPP\SampleApp\Output\

  2. Create the service package by compressing the service executable into a zip file.
    gzip SampleServiceCPP.exe

    You have now created your service package SampleServiceCPP.exe.gz.

On Linux

To deploy the service, you first need to package it.

  1. Change to the directory in which the compiled samples are located:

    cd $SOAM_HOME/5.0/samples/CPP/SampleApp/Output/

  2. Create the service package by archiving and compressing the service executable file:

    tar -cvf SampleServiceCPP.tar SampleServiceCPP

    gzip SampleServiceCPP.tar

    You have now created your service package SampleServiceCPP.tar.gz.

Add the application

When you add an application through the DE PMC, you must use the Add Application wizard. This wizard defines a consumer location to associate with your application, deploys your service package, and registers your application. After completing the steps with the wizard, your application should be ready to use.

  1. In the DE PMC, click Symphony Workload > Configure Applications.

    The Applications page displays.

  2. Select Global Actions > Add/Remove Applications.

    The Add/Remove Application page displays.

  3. Select Add an application, then click Continue.

    The Adding an Application page displays.

  4. Select Use existing profile and add application wizard. Click Browse and navigate to your application profile.
  5. Select your application profile xml file, then click Continue.

    For SampleApp, you can find your profile in the following location:

    • Windows—%SOAM_HOME%\5.0\samples\CPP\SampleApp\SampleApp.xml

    • Linux—$SOAM_HOME/5.0/samples/CPP/SampleApp/SampleApp.xml

    The Service Package location window displays.

  6. Browse to the service package you created in .gz or tar.gz format and select it. Click Continue.

    The Confirmation window displays.

  7. Review your selections, then click Confirm.

    The window displays indicating progress. Your application is ready to use.

  8. Click Close.

    The window closes and you are now back in the Platform Management Console. Your new application is displayed as enabled.

Run the sample client and service

On Windows

To run the service, you run the client application. The service a client application uses is specified in the application profile.

Run the client application:

%SOAM_HOME%\5.0\samples\CPP\SampleApp\Output\SyncClient.exe

You should see output on the command line as work is submitted to the system.

The client starts and the system starts the corresponding service. The client displays messages indicating that it is running.

On Linux

Run the client application:

$SOAM_HOME/5.0/samples/CPP/SampleApp/Output/SyncClient

You should see output on the command line as work is submitted to the system.

The client starts and the system starts the corresponding service. The client displays messages indicating that it is running.

Review and understand the samples

You review the sample client application code to learn how you can create a synchronous client application.

Locate the code samples


Operating System

Files

Location of Code Sample

Windows

Client

%SOAM_HOME%\5.0\samples\CPP\SampleApp\SyncClient

Message object

%SOAM_HOME%\5.0\samples\CPP\SampleApp\Common

Service code

%SOAM_HOME%\5.0\samples\CPP\SampleApp\Service

Application profile

The service required to compute the input data along with additional application parameters are defined in the application profile:

%SOAM_HOME%\5.0\samples\CPP\SampleApp\SampleApp.xml

Output directory

%SOAM_HOME%\5.0\samples\CPP\SampleApp\Output\

Linux

Client

$SOAM_HOME/5.0/samples/CPP/SampleApp/ SyncClient

Message object

$SOAM_HOME/5.0/samples/CPP/SampleApp/Common

Service code

$SOAM_HOME/5.0/samples/CPP/SampleApp/Service

Application profile

The service required to compute the input data along with additional application parameters are defined in the application profile:

$SOAM_HOME/5.0/samples/CPP/SampleApp/ SampleApp.xml

Output directory

$SOAM_HOME/5.0/samples/CPP/SampleApp/Output/


What the sample does

The client application sample opens a session and sends 10 input messages, and retrieves the results. The client application is a synchronous client that sends input and blocks the output until all the results are returned.

The service takes input data sent by client applications, returns the input data you have sent and replies "Hello Client!!"

Review the sample code

Input and output: declare the message object

Your client application needs to handle data that it sends as input, and output data that it receives from the service.

Tip:

Client applications and services share the same message class.

In MyMessage.h:

  • We declare the MyMessage class

  • We define serialization methods for input and output messages

  • We implement methods to handle the data

Note:

For this example, we have defined the same class for input and output messages. However, you can define separate classes for input and output messages.

#pragma once
#include "soam.h"
class MyMessage :
    public soam::Message
{
public:
    MyMessage();
    MyMessage(int i, bool isSync, char* str);
    virtual ~MyMessage(void);
    void onSerialize(
        /*[in]*/ soam::OutputStreamPtr &stream) throw (soam::SoamException); 
    void onDeserialize(
    /*[in]*/ soam::InputStreamPtr &stream) throw (soam::SoamException); 
// accessors
public:
    int getInt() const{return m_int;}
    void setInt(int _int) {m_int = _int;}
    const char* getString() {return m_string;}
    void setString(const char* str) {freeString(m_string); m_string = copyString(str);}
    bool getIsSync() const{return (m_isSync != 0);}
    void setIsSync(bool isSync) {m_isSync = isSync;}
private:
    char* copyString(const char* strSource);
    void freeString(char* strToFree);
private:
    int m_int;
    bool m_isSync;
    char* m_string;
};

Implement the MyMessage object

Once your message object is declared, implement handlers for serialization and deserialization.

In MyMessage.cpp, we implement methods to handle the data. For data types that are supported by Symphony DE, see the appropriate API reference.

Note:

If you already have an application with a message object that is serialized, you can pass a binary blob through the DefaultBinaryMessage class.

#include "stdafx.h"
#include <string.h>
#include "MyMessage.h"
#include "soam.h"
using namespace soam;
MyMessage::MyMessage()
{
    m_int = 0;
    m_string = copyString("");
}
MyMessage::MyMessage(int i, bool isSync, char* str)
{
    m_int = i;
    m_isSync = isSync;
    m_string = copyString(str);
}
MyMessage::~MyMessage(void)
{
    freeString(m_string);
}void MyMessage::onSerialize(OutputStreamPtr &stream) throw (SoamException)
{
    stream->write(m_int);
    stream->write(m_isSync);
    stream->write(m_string);
}void MyMessage::onDeserialize(InputStreamPtr &stream) throw (SoamException)
{
    stream->read(m_int);
    stream->read(m_isSync);
    freeString(m_string);
    stream->read(m_string);
}char* MyMessage::copyString(const char* strSource)
{
    SOAM_ASSERT(0 != strSource);
    size_t len = strlen(strSource);
    char* newString = new char[len+1];
    SOAM_ASSERT(0 != newString);
    strcpy(newString, strSource);
    return newString;
}
void MyMessage::freeString(char* strToFree)
{
    if (0 != strToFree)
    {
        delete []strToFree;
    }
}

Initialize the client

In SyncClient.cpp, when you initialize, you initialize the Symphony client infrastructure. You initialize once per client.

Important:

Initialization is required. Otherwise, API calls fail.

...
    try
    {
        // Initialize the API
        SoamFactory::initialize();
...

Connect to an application

To send data to be calculated in the form of input messages, you connect to an application.

You specify an application name, a user name, and password. The application name must match that defined in the application profile.

For Symphony DE, there is no security checking and login credentials are ignored—you can specify any user name and password. Security checking is done however, when your client application submits workload to the actual grid.

The default security callback encapsulates the callback for the user name and password.

Tip:

When you connect, a connection object is returned.

... 
  // Set up application specific information to be supplied to Symphony
     char appName[]="SampleAppCPP";
  // Set up application authentication information using the default security provider
     DefaultSecurityCallback securityCB("Guest", "Guest");
  // Connect to the specified application
     ConnectionPtr conPtr = SoamFactory::connect(appName, &securityCB);
  // Retrieve and print our connection ID
     cout << "connection ID=" << conPtr->getId() << endl; 
...

Create a session to group tasks

A session is a way of logically grouping tasks that are sent to a service for execution. The tasks are sent and received synchronously.

When creating a session, you need to specify the session attributes by using the SessionCreationAttributes object. In this sample, we create a SessionCreationAttributes object called attributes and set three parameters in the object.

The first parameter is the session name. This is optional. The session name can be any descriptive name you want to assign to your session. It is for information purposes, such as in the command-line interface.

The second parameter is the session type. The session type is optional. If you leave this parameter blank " " or do not set a session type, system default values are used for session attributes. If you specify a session type in the client application, you must also configure the session type in the application profile—the session type name in your application profile and session type you specify in the client must match. If you use an incorrect session type in the client and the specified session type cannot be found in the applicatin profile, an exception is thrown to the client.

The third parameter is the session flag, which we specify as ReceiveSync. You must specify it as shown. This indicates to Symphony that this is a synchronous session.

We pass the attributes object to the createSession() method, which returns a pointer to the session.

        // Set up session creation attributes
        SessionCreationAttributes attributes;
        attributes.setSessionName("mySession");
        attributes.setSessionType("ShortRunningTasks");
        attributes.setSessionFlags(Session::ReceiveSync);
        // Create a synchronous session
        SessionPtr sesPtr = conPtr->createSession(attributes);

Send input data to be processed

In this step, we create 10 input messages to be processed by the service. When a message is sent, a task input handle is returned. This task input handle contains the ID for the task that was created for this input message.
 int tasksToSend = 10;
        for (int taskCount = 0; taskCount < tasksToSend; taskCount++)
        {
            // Create a message
            char hello[]="Hello Grid !!";
            MyMessage inMsg(taskCount, true, hello);
            // Create task attributes
            TaskSubmissionAttributes attrTask;
            attrTask.setTaskInput(&inMsg);
                        // send it
            TaskInputHandlePtr input = sesPtr->sendTaskInput(attrTask);
            // Retrieve and print task ID
            cout << "task submitted with ID : " << input->getId() << endl;
        }
...

Retrieve output

Pass the number of tasks to the fetchTaskOutput() method to retrieve the output messages that were produced by the service. This method blocks until the output for all tasks is retrieved. The return value is an enumeration that contains the completed task results. Iterate through the task results and extract the messages using the populateTaskOutput() method. Display the task ID and the results from the output message.

        // Now get our results - will block here until all tasks retrieved
        EnumItemsPtr enumOutput = sesPtr->fetchTaskOutput(tasksToSend);
        // Inspect results
        TaskOutputHandlePtr output;
        while(enumOutput->getNext(output))
        {
            // Check for success of task
            if (true == output->isSuccessful())
            {
                // Get the message returned from the service
                MyMessage outMsg;
                output->populateTaskOutput(&outMsg);
                // Display content of reply
                cout << "Task Succeeded [" <<  output->getId() << "]" << endl;
                cout << outMsg.getResult() << endl;
            }
            else
            {
                // Get the exception associated with this task
                SoamExceptionPtr ex = output->getException();
                cout << "Task Failed : " << ex->what() << endl;
            }
        }

Catch exceptions

Any exceptions thrown take the form of SoamException. Catch all Symphony exceptions to know about exceptions that occurred in the client application, service, and middleware.

The sample code above catches exceptions of type SoamException.
catch(SoamException& exp)    
{       
// Report exception        
cout << "exception caught ... " << exp.what() << endl;    
}

Uninitialize

Always uninitialize the client API at the end of all API calls. If you do not call uninitialize, the client API is in an undefined state, resources used by the client are held indefinitely, and there is no guarantee your client will be stable.

Important:

Once you uninitialize, all objects become invalid. For example, you can no longer create a session or send an input message.

    // uninitialize the API
    // This is the only means to ensure proper shutdown 
    // of the interaction between the client and the system.
    SoamFactory::uninitialize();
...