wiki:Documentation/Tutorial/PingPong

Version 25 (modified by huebsch, 15 years ago) ( diff )

--

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.

01 #include <string>
02 #include "ariba/utility/startup/StartupWrapper.h"
03 #include "PingPong.h"
04
05 using std::string;
06 using ariba::utility::StartupWrapper;
07 using ariba::application::pingpong::PingPong;
08
09 int main( int argc, char** argv ) {
10
11	string config = "../etc/settings.cnf";
12	if (argc >= 2) config = argv[1];
13
14	StartupWrapper::initConfig( config );
15	StartupWrapper::initSystem();
16	
17	// this will do the main functionality and block
18	PingPong ping;
19	ping.setMode( !Configuration::instance().read<bool>("GENERAL_Initiator") );
20	StartupWrapper::startup(&ping, true);
21
22	// this will run blocking until <enter> is hit
23
24	StartupWrapper::shutdown();
25	return 0;
26 }

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 11-15). Now we are ready to start the ping-pong service, which we first create (line 18). Then we declare the role of the node (initiator or joiner), which is written down in the config file as a bool value. 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
03 namespace ariba {
04 namespace application {
05 namespace pingpong {
06
07 use_logging_cpp(PingPong);
08 ServiceID PingPong::PINGPONG_ID = ServiceID(111);
09
10 PingPong::PingPong() : pingid( 0 ) {
11 }
12 
13 PingPong::~PingPong(){
14 }
15
16 void PingPong::setMode(bool startingNode){
17	startping = startingNode;
18 }
19
20 void PingPong::startup(UnderlayAbstraction* _abstraction){
21	abstraction = _abstraction;
22
23	logging_info( "starting up PingPong service ... " );
24
25	SpoVNetID spovnetid (Identifier(5000));
26
27	NodeID nodeid = (Configuration::instance().exists("BASE_nodeid") ?
28			NodeID(Identifier(Configuration::instance().read<unsigned long>("BASE_nodeid"))) :
29			NodeID::UNSPECIFIED);
30
31	IPv4Locator* locallocator = (Configuration::instance().exists("BASE_localLocator") ?
32					&(IPv4Locator::fromString(Configuration::instance().read<string>("BASE_localLocator"))) :
33					NULL);
34
35	uint16_t localport = Configuration::instance().exists("BASE_port") ?
36				Configuration::instance().read<uint16_t>("BASE_port") :
37				ARIBA_DEFAULT_PORT;
38
39	if( !startping )
40		context = abstraction->createSpoVNet( spovnetid, nodeid, locallocator, localport );
41	else
42		context = abstraction->joinSpoVNet  ( spovnetid, nodeid, locallocator, localport );
43
44	overlay = &context->getOverlay();
45	overlay->bind( this, PingPong::PINGPONG_ID );
46
47	logging_info( "PingPong started up" );
48
49	
50
51 }

First we include the .h file, define the namespace, turn logging functionality on (line 07) 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. Via setMode (lines 16-18) one can indicate which node in the example should start the packet sending process. This method is called in main.cpp after getting the specific information from the config file.

The startup method (lines 20-51) is called from the StartupWrapper, jolting the operation of the ping pong service. With its call, the StartupWrapper passes an UnderlayAbstraction object to the service, being the main object for communication between the service and Ariba (lines 20/21). When starting up, the service chooses a servcie ID (line 25). Then, it creates a node ID for the SpoVNet node it represents in the instance, under usage of potential base information deposited in the config file (lines 27-29). Same applies to its locator (lines 31-33) and the port (lines 35-37). Then we get to the point where we decide the role of the specific ping pong instance (initiator versus joiner) (lines 39-42). Remember that we got this information in line 17. If the starting node is the initiator of the SpoVNet instance, it creates the SpoVNet, specifying its SpoVNetID, NodeID, Locator and Port (line 40). In contrast, other nodes join the instance, providing the same parameters (line 42). After starting up, a service needs to bind to the SpoVNet Base Overlay with its service ID (lines 44/45). Finally, we may give out all kinds of logging infos by calling the method logging_info (line 47).

60 void PingPong::shutdown(){
61	logging_info( "shutting down PingPong service ..." );
62
63	overlay->unbind( this, PingPong::PINGPONG_ID );
64	Timer::stop();
65
66	if( !startping ) abstraction->destroySpoVNet( context );
67	else             abstraction->leaveSpoVNet( context );
68
69	logging_info( "PingPong service shut down" );
70 }

To shut a service down after its usage, every service provides a method shutdown which is automatically called upon finishing (lines 60-70). In here, we unbind the service, stop any timers (mentioned later) and finally leave leave or destroy the SpoVNet instance, depending on the specific node's role.

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 }

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).

So far, the node is up and running, created or joined a SpoVNet instance. The initiaor started a timer as soon as another node had joined. Now we see what happens when the timer is triggered.

110 void PingPong::eventFunction(){
111
112	logging_info( "pinging our remote nodes" );
113
114	RemoteNodes::iterator i = remoteNodes.begin();
115	RemoteNodes::iterator iend = remoteNodes.end();
116
117	pingid++;
118
119	for( ; i != iend; i++ ){
120		logging_info( "     -> pinging " << i->first );
121
122		PingPongMessage pingmsg( pingid );
123		overlay->sendMessage( &pingmsg, i->second );
124	}
125 }

Everytime the timer 'fires', eventFunction is called on a node (lines 110-125). In this example, the initiator sends a message to every node that has joined up to this point in time. To accomplish this, it iterates through all established links (line 119), builts a message for every link and finally sends the message using the Base Overlay in Ariba (line 123). Sending messages is done by passing the message object to Ariba, together with the target link to use. . 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<PingPongMessage>();
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).

Note: See TracWiki for help on using the wiki.