An Overlay-based
Virtual Network Substrate
SpoVNet

The Ping-Pong Example

To help getting into using Ariba, we provide a short example of how to use the architecture. For this, assume a simple service whose only intention is to exchange data packets between two participating nodes, just like playing ping pong. You will find the whole code within the package under sample/pingpong.

The source files divide into the main code (PingPong.h/.cpp) and the used message format (declared in PingPongMessage.h/.cpp). In addition, main.cpp acts as the entry point to the ping-pong example. We will first give a high-level introduction about what the example does and later dig deeper into the code.

What it does

As already mentioned, the example service simply exchanges packets between participating network nodes. This is accomplished by using the Ariba abstraction to create a communication context and hide underlay details. The participants form a SpoVNet instance in which the first one (as the initiator) creates the instance, while the second joins. As soon as some nodes have successfully joined the instance, they starts sending packets to each other periodically. This happens until a button is pressed.

How it works

Let's take a look at the code now. Writing a service is pretty simple when using Ariba because the developer gets relieved from most difficulties and annoyances that could come up when struggling with writing network code. We start with the main.cpp.

     1  #include <string>
     2  #include "ariba/utility/system/StartupWrapper.h"
     3  #include "PingPong.h"
     4  
     5  using std::string;
     6  using ariba::utility::StartupWrapper;
     7  using ariba::application::pingpong::PingPong;
     8  
     9  int main( int argc, char** argv ) {
    10  
    11          // get config file
    12          string config = "../etc/settings.cnf";
    13          if (argc >= 2) config = argv[1];
    14  
    15          StartupWrapper::initConfig( config );
    16          StartupWrapper::startSystem();
    17  
    18          // this will do the main functionality and block
    19          PingPong ping;
    20          StartupWrapper::startup(&ping);
    21  
    22          // --> we will run blocking until <enter> is hit
    23  
    24          StartupWrapper::shutdown(&ping);
    25          StartupWrapper::stopSystem();
    26  
    27          return 0;
    28  }

The main.cpp serves us as an entry point to the application. In the first lines we include class definitions we need here (e.g. strings because we want to handle some). Also, we include the StartupWrapper class that comes with Ariba. It provides some handy helpers for initialization. Finally, we need to include the PingPong.h, because this is the actual thing we want to execute. Then, we declare the used namespaces (lines 5-7) to be able to use the functionalities. Now we get to the main method, being our starting point. After determining the location of our config file, we initialize the system by passing the config file's location to the StartupWrapper and telling the same to start the architecture up (lines 15-16). Now we are ready to start the ping-pong service, which we first have to create (line 19). Finally, we start the service up by calling the specific method in the StartupWrapper. Now the service will run until we press the enter button.

Now we look into the PingPong.cpp, containing the actual ping pong code. We leave out .h files here because they only do definitions. Everyone intending to use Ariba should be familiar with the subject matter. The first parts look like this:

     1  #include "PingPong.h"
     2  #include "ariba/utility/configuration/Configuration.h"
     3  #include "ariba/utility/visual/DddVis.h"
     4  
     5  using ariba::utility::Configuration;
     6  using namespace ariba;
     7  
     8  namespace ariba {
     9  namespace application {
    10  namespace pingpong {
    11  
    12  // logging
    13  use_logging_cpp( PingPong );
    14  
    15  // the service that the pingpong wants to use
    16  ServiceID PingPong::PINGPONG_SERVICEID = ServiceID( 111 );
    17  
    18  // construction
    19  PingPong::PingPong() : pingId( 0 ) {
    20          Timer::setInterval( 1000 );
    21  }
    22  
    23  // destruction
    24  PingPong::~PingPong() {
    25  }
    26  
    27  // implementation of the startup interface
    28  void PingPong::startup() {
    29  
    30          logging_info( "starting up PingPong service ... " );
    31  
    32          // create ariba module
    33          logging_debug( "creating ariba underlay module ... " );
    34          ariba = new AribaModule();
    35  
    36          Name spovnetName("pingpong");
    37          Name nodeName = Name::UNSPECIFIED;
    38          this->name = string("<ping>");
    39  
    40          // get settings from configuration object
    41          if( Configuration::haveConfig() ){
    42                  Configuration& config = Configuration::instance();
    43  
    44                  // get node name
    45                  if (config.exists("node.name"))
    46                          nodeName = config.read<string> ("node.name");
    47  
    48                  // configure ariba module
    49                  if (config.exists("ariba.endpoints"))
    50                          ariba->setProperty("endpoints", config.read<string>("ariba.endpoints"));
    51                  if (config.exists("ariba.bootstrap.hints"))
    52                          ariba->setProperty("bootstrap.hints", config.read<string>("ariba.bootstrap.hints"));
    53                  if (config.exists("pingpong.name"))
    54                          name = config.read<string>("pingpong.name");
    55  
    56                  // get visualization
    57                  if( config.exists("ariba.visual3d.ip") && config.exists("ariba.visual3d.port")){
    58                          string ip = config.read<string>("ariba.visual3d.ip");
    59                          unsigned int port = config.read<unsigned int>("ariba.visual3d.port");
    60                          unsigned int color = config.exists("node.color") ?
    61                                          config.read<unsigned int>("node.color") : 0;
    62                          ariba::utility::DddVis::instance().configure(ip, port, color);
    63                  }
    64  
    65          } // if( Configuration::haveConfig() )
    66  
    67          // start ariba module
    68          ariba->start();
    69  
    70          // create node and join
    71          node = new Node( *ariba, nodeName );
    72  
    73          // bind communication and node listener
    74          node->bind( this );                              /*NodeListener*/
    75          node->bind( this, PingPong::PINGPONG_SERVICEID); /*CommunicationListener*/
    76  
    77          // start node module
    78          node->start();
    79  
    80          // when initiating, you can define the overlay type, default is Chord [CHORD_OVERLAY]
    81          SpoVNetProperties params;
    82          //params.setBaseOverlayType( SpoVNetProperties::ONE_HOP_OVERLAY ); // alternative: OneHop
    83  
    84          // initiate the spovnet
    85          logging_info("initiating spovnet");
    86          node->initiate(spovnetName, params);
    87  
    88          // join the spovnet
    89          logging_info("joining spovnet");
    90          node->join(spovnetName);
    91  
    92          // ping pong started up...
    93          logging_info( "pingpong starting up with"
    94                          << " [spovnetid " << node->getSpoVNetId().toString() << "]"
    95                          << " and [nodeid " << node->getNodeId().toString() << "]" );
    96  }

First we include the .h file, define the namespace, turn logging functionality on (line 13) and set the ID of the Service. Every service using Ariba is connected to a specific ID that may be chosen initially and arbitrarily. This ID serves Ariba to distinguish between several services that may use it concurrently.

After definitions for construction und destruction (the first also setting a timer to 1 second), we come to the startup method. The startup method (lines 28 ff.) is called from the StartupWrapper, jolting the operation of the ping pong service. Here, we first create an AribaModule? object, serving as our port to Ariba (line 34). Then, we give our network instance a specific name (being spovnet in the example), before we collect some configurational parameters (lines 41-65). Those parameters are defined in the config file, they serve e.g. in giving IP adresses and port numbers to ariba instances. In line 68, we start Ariba. Also, we need a Node object, reflecting our application running over Ariba (more than one could possibly be using one Ariba). This is created in line 71 and the bound to the ariba object with its specific ServiceID. After this bind operation, the Node is able to receive signals and messages from Ariba. After this, we start the node object and either initiate or join the network instance (depending on the role of the actual node, wich could be initiator or joiner). Finally, we may give out all kinds of logging infos by calling the method logging_info (e.g. line 93).

    99  void PingPong::shutdown() {
   100  
   101          logging_info( "pingpong service starting shutdown sequence ..." );
   102  
   103          // stop timer
   104          Timer::stop();
   105  
   106          // leave spovnet
   107          node->leave();
   108  
   109          // unbind communication and node listener
   110          node->unbind( this );                               /*NodeListener*/
   111          node->unbind( this, PingPong::PINGPONG_SERVICEID ); /*CommunicationListener*/
   112  
   113          // stop the ariba module
   114          ariba->stop();
   115  
   116          // delete node and ariba module
   117          delete node;
   118          delete ariba;
   119  
   120          // now we are completely shut down
   121          logging_info( "pingpong service shut down" );
   122  }

To shut a service down after its usage, every service provides a method shutdown which is automatically called upon finishing. In here, we stop the timer, leave the instance, unbind all bindings and finally stop the node and Ariba. Don't forget to delete yopur objects to prevent from memory leaks.

So far, the node is up and running, created or joined a SpoVNet instance. As very node starts a timer as soon as it has joined, we now inspect what happens when the timer is triggered.

   125  void PingPong::eventFunction() {
   126  
   127          // we ping all nodes that are known in the overlay structure
   128          // this can be all nodes (OneHop) overlay or just some neighbors
   129          // in case of a Chord or Kademlia structure
   130  
   131          // in this sample we use auto-links: we just send out our message
   132          // to the node and the link is established automatically. for more
   133          // control we would use the node->establishLink function to create
   134          // a link and start using the link in the CommunicationListener::onLinkUp
   135          // function that is implemented further down in PingPong::onLinkUp
   136  
   137          logging_info( "pinging overlay neighbors with ping id " << ++pingId );
   138          PingPongMessage pingmsg( pingId, name );
   139  
   140          //-----------------------------------------------------------------------
   141          // Option 1: get all neighboring nodes and send the message to each
   142          //-----------------------------------------------------------------------
   143          counter++;
   144          if (counter<0 || counter>4) {
   145                  counter = 0;
   146                  string s;
   147                  for (int i=0; i<names.size();i++) {
   148                          if (i!=0) s+= ", ";
   149                          s = s+names[i];
   150                  }
   151                  logging_info("----> I am " << name << " and I know " << s);
   152                  names.clear();
   153          }
   154  
   155          vector<NodeID> nodes = node->getNeighborNodes();
   156          BOOST_FOREACH( NodeID nid, nodes ){
   157                  logging_info( "sending ping message to " << nid.toString() );
   158                  node->sendMessage( pingmsg, nid, PingPong::PINGPONG_SERVICEID );
   159          }
   160  
   161          //-----------------------------------------------------------------------
   162          // Option 2: send a "broadcast message" that actually does the same thing
   163          //           internally, gets all neighboring nodes and sends the message
   164          //-----------------------------------------------------------------------
   165          // node->sendBroadcastMessage( pingmsg, PingPong::PINGPONG_SERVICEID );
   166  }

Everytime the timer 'fires', eventFunction is called on a node. In this example, every node sends a message to every node that is part of the network at this point in time. To accomplish this, it builds a message (line 138) and iterates through all neighboring nodes (line 156-159). To each of this nodes, it sends the message. Sending messages is simply done by passing the message object to Ariba, together with the target node ID. . We will now take a short look at how such a message is composed in the ping pong example.

    01 #include "PingPongMessage.h"
    02
    03 namespace ariba {
    04 namespace application {
    05 namespace pingpong {
    06
    07 vsznDefault(PingPongMessage);
    08
    09 PingPongMessage::PingPongMessage() : id(0) {
    10 }
    11
    12 PingPongMessage::PingPongMessage(uint8_t _id) : id(_id) {
    13 }
    14
    15 PingPongMessage::~PingPongMessage(){
    16 }
    17
    18 string PingPongMessage::info(){
    19  return "ping pong message id " + ariba::utility::Helper::ultos(id);
    20 }
    21
    22 uint8_t PingPongMessage::getid(){
    23  return id;
    24 }
    25
    26 }}} // namespace ariba, appplication, pingpong

Lines 01-26 show the whole PingPongMessage.cpp. One can see here that defining message types in Ariba is pretty simple. As the message in our case is empty and not of higher importance, we refer the reader to the documentation for details.

   187  void PingPong::onMessage(const DataMessage& msg, const NodeID& remote, const LinkID& lnk) {
   188          PingPongMessage* pingmsg = msg.getMessage()->convert<PingPongMessage> ();
   189          bool found=false;
   190          for (int i=0;i<names.size(); i++) if (names[i]==pingmsg->getName()) found=true;
   191          if (!found) names.push_back(pingmsg->getName());
   192          logging_info( "received ping message on link " << lnk.toString()
   193                          << " from node " << remote.toString()
   194                          << ": " << pingmsg->info() );
   195  }

Getting back to PingPong.cpp: After the initiator has send a message to a joiner, it will arrive and has to be handled. This is accomplished in receiveMessage (the name says it). Every received message has to be decapsulated by a service, casting the data back to the service's message format (line 188).

Last modified 6 years ago Last modified on Apr 18, 2011, 5:02:15 PM