== '''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 does it''' Let's take a look at the code now. Writing a service is pretty simple when using ''Ariba'' because most difficulties and annoyances that could come up when struggling with writing network code are taken from the developer. We start with the ''main.cpp''. {{{ 1 #include 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 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 05-07) 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: {{{ 01 #include "PingPong.h" 02 #include "ariba/utility/configuration/Configuration.h" 03 04 using ariba::utility::Configuration; 05 using namespace ariba; 06 07 namespace ariba { 08 namespace application { 09 namespace pingpong { 10 11 // logging 12 use_logging_cpp( PingPong ); 13 14 // the service that the pingpong wants to use 15 ServiceID PingPong::PINGPONG_SERVICEID = ServiceID( 111 ); 16 17 // construction 18 PingPong::PingPong() : pingId( 0 ) { 19 Timer::setInterval( 5000 ); 20 } 21 22 // destruction 23 PingPong::~PingPong() { 24 } 25 26 // implementation of the startup interface 27 void PingPong::startup() { 28 29 logging_info( "starting up PingPong service ... " ); 30 31 // create ariba module 32 logging_debug( "creating ariba underlay module ... " ); 33 ariba = new AribaModule(); 34 35 // get the configuration object 36 Configuration& config = Configuration::instance(); 37 38 // generate spovnet name 39 Name spovnetName("pingpong"); 40 41 42 43 44 // get node name 45 Name nodeName = Name::UNSPECIFIED; 46 if (config.exists("node.name")) nodeName = config.read ("node.name"); 47 48 // configure ariba module 49 if (config.exists("ariba.ip.addr")) ariba->setProperty("ip.addr", 50 config.read("ariba.ip.addr")); 51 if (config.exists("ariba.tcp.port")) ariba->setProperty("tcp.port", 52 config.read("ariba.tcp.port")); 53 if (config.exists("ariba.udp.port")) ariba->setProperty("udp.port", 54 config.read("ariba.udp.port")); 55 if (config.exists("ariba.bootstrap.hints")) ariba->setProperty("bootstrap.hints", 56 config.read("ariba.bootstrap.hints")); 57 58 // start ariba module 59 ariba->start(); 60 61 // create node and join 62 node = new Node( *ariba, nodeName ); 63 64 // bind communication and node listener 65 node->bind( this ); /*NodeListener*/ 66 node->bind( this, PingPong::PINGPONG_SERVICEID); /*CommunicationListener*/ 67 68 // start node module 69 node->start(); 70 71 // initiate or join the spovnet 72 if (!isInitiator) node->join(spovnetName); 73 else node->initiate(spovnetName); 74 75 // ping pong started up... 76 logging_info( "pingpong starting up with" 77 << " [spovnetid " << node->getSpoVNetId().toString() << "]" 78 << " and [nodeid " << node->getNodeId().toString() << "]" ); 79 } }}} First we include the .h file, define the namespace, turn logging functionality on (line 12) 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 5 seconds), we come to the ''startup'' method. The ''startup'' method (lines 20 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 33). Then, we give our network instance a specific name (being ''spovnet'' in the example), before we collect some configurational parameters (lines 46-56). Those parameters are defined in the config file, they serve e.g. in giving IP adresses and port numbers to ariba instances. In line 56, 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 62 and the bound to the ariba object with its specific ServiceID. After this bnind 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 (line 76). {{{ 01 void PingPong::shutdown() { 02 03 logging_info( "pingpong service starting shutdown sequence ..." ); 04 05 // stop timer 06 Timer::stop(); 07 08 // leave spovnet 09 node->leave(); 10 11 // unbind communication and node listener 12 node->unbind( this ); /*NodeListener*/ 13 node->unbind( this, PingPong::PINGPONG_SERVICEID ); /*CommunicationListener*/ 14 15 // stop the ariba module 16 ariba->stop(); 17 18 // delete node and ariba module 19 delete node; 20 delete ariba; 21 22 // now we are completely shut down 23 logging_info( "pingpong service shut down" ); 24} }}} 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. {{{ 01 void PingPong::eventFunction() { 02 03 // we ping all nodes that are known in the overlay structure 04 // this can be all nodes (OneHop) overlay or just some neighbors 05 // in case of a Chord or Kademlia structure 06 07 // in this sample we use auto-links: we just send out our message 08 // to the node and the link is established automatically. for more 09 // control we would use the node->establishLink function to create 10 // a link and start using the link in the CommunicationListener::onLinkUp 11 // function that is implemented further down in PingPong::onLinkUp 12 13 logging_info( "pinging overlay neighbors with ping id " << ++pingId ); 14 15 PingPongMessage pingmsg( pingId ); 16 17 //----------------------------------------------------------------------- 18 // Option 1: get all neighboring nodes and send the message to each 19 //----------------------------------------------------------------------- 20 vector nodes = node->getNeighborNodes(); 21 BOOST_FOREACH( NodeID nid, nodes ){ 22 node->sendMessage( pingmsg, nid, PingPong::PINGPONG_SERVICEID ); 23 } 24 25 //----------------------------------------------------------------------- 26 // Option 2: send a "broadcast message" that actually does the same thing 27 // internally, gets all neighboring nodes and sends the message 28 //----------------------------------------------------------------------- 29 // node->sendBroadcastMessage( pingmsg, PingPong::PINGPONG_SERVICEID ); 30 } }}} 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 15) and iterates through all neighboring nodes (line 20-23). 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. {{{ 130 bool PingPong::receiveMessage(const Message* message, const LinkID& link, const NodeID& node){ 131 132 PingPongMessage* incoming = ((Message*)message)->decapsulate(); 133 134 logging_info( "received ping message on link " << link.toString() << 135 " from node with id " << (int)incoming->getid()); 136 137 } }}} 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 132). ''Ariba'' provides several callback functions that may used by services to catch all kinds of events that could be of interest. In this exampe we limit ourselves to the event cases of node joins and node leaves. When a node successfull joines to the SpoVNet instance, ''onNodeJoin'' is triggered nn the initiator's service side. He may then react to this event, exemplary shown in line 80-93, implementing ''onNodeJoin''. In this case, the initiator starts establishing a link to the joined node (line 85), essentially for all kinds of communications via ''Ariba''. We then store the link for further usage (line 88) and prepare a timer which intention is to trigger periodic events. In our case we initialize the timer to be triggered every 2 seconds (line 90), before starting it (line 91). {{{ 100 void PingPong::onNodeLeave( const NodeID& id, const SpoVNetID& spovnetid ){ 101 RemoteNodes::iterator i = remoteNodes.find( id ); 102 if( i != remoteNodes.end() ) remoteNodes.erase( i ); 103 } }}} Node leaves in our case only lead to deletion of links we had stored before, for we don't need them anymore (line 102). {{{ 80 void PingPong::onNodeJoin( const NodeID& nodeid, const SpoVNetID& spovnetid ){ 81 82 if( !startping ){ 83 84 logging_info( "establishing link to node " << nodeid.toString() ); 85 const LinkID link = overlay->establishLink( nodeid, PingPong::PINGPONG_ID ); 86 87 logging_info( "adding node to registered nodes in pingpong: " << nodeid.toString() ); 88 remoteNodes.insert( make_pair(nodeid,link) ); 89 90 Timer::setInterval( 2000 ); 91 Timer::start(); 92 } 93 } }}}