a4ab5e9a0bd1c7a2310cf4015627585abfb1acb8
[ric-plt/xapp-frame-cpp.git] / docs / user-guide.rst
1      
2  
3 .. This work is licensed under a Creative Commons Attribution 4.0 International License. 
4 .. SPDX-License-Identifier: CC-BY-4.0 
5 .. 
6 .. CAUTION: this document is generated from source in doc/src/* 
7 .. To make changes edit the source and recompile the document. 
8 .. Do NOT make changes directly to .rst or .md files. 
9  
10  
11 ============================================================================================ 
12 RIC xAPP C++ Framework 
13 ============================================================================================ 
14 -------------------------------------------------------------------------------------------- 
15 User's Guide 
16 -------------------------------------------------------------------------------------------- 
17  
18 Introduction 
19 ============================================================================================ 
20  
21 The C++ framework allows the programmer to create an xApp 
22 object instance, and to use that instance as the logic base. 
23 The xApp object provides a message level interface to the RIC 
24 Message Router (RMR), including the ability to register 
25 callback functions which the instance will drive as messages 
26 are received; much in the same way that an X-windows 
27 application is driven by the window manager for all activity. 
28 The xApp may also choose to use its own send/receive loop, 
29 and thus is not required to use the callback driver mechanism 
30 provided by the framework. 
31  
32 The Framework API 
33 ============================================================================================ 
34  
35 The C++ framework API consists of the creation of the xApp 
36 object, and invoking desired functions via the instance of 
37 the object. The following paragraphs cover the various steps 
38 involved to create an xApp instance, wait for a route table 
39 to arrive, send a message, and wait for messages to arrive. 
40  
41 Creating the xApp instance 
42 -------------------------------------------------------------------------------------------- 
43  
44 The creation of the xApp instance is as simple as invoking 
45 the object's constructor with two required parameters: 
46  
47  
48        
49       port 
50           
51          A C string (pointer to char) which defines the port that RMR will 
52          open to listen for connections. 
53           
54        
55       wait 
56           
57          A Boolean value which indicates whether or not the 
58          initialization process should wait for the arrival of a 
59          valid route table before completing. When true is 
60          supplied, the initialization will not complete until RMR 
61          has received a valid route table (or one is located via 
62          the RMR_SEED_RT environment variable). 
63        
64        
65       The following code sample illustrates the simplicity of 
66       creating the instance of the xApp object. 
67        
68        
69       :: 
70          
71             #include <memory>
72             #include <ricxfcpp/xapp.hpp>
73             int main( ) {
74                 std::unique_ptr<Xapp> xapp;
75                 char* listen_port = (char *) "4560";    //RMR listen port
76                 bool  wait4table = true;            // wait for a route table
77                 xapp = std::unique_ptr<Xapp>(
78                       new Xapp( listen_port, wait4table ) );
79             }
80        
81        
82       Figure 1: Creating an xAPP instance. 
83        
84       From a compilation perspective, the following is the simple 
85       compiler invocation string needed to compile and link the 
86       above program (assuming that the sample code exists in a file 
87       called man_ex1.cpp. 
88        
89        
90       :: 
91          
92            g++ man_ex1.cpp -o man_ex1 -lricxfcpp -lrmr_si -lpthread
93        
94        
95        
96       The above program, while complete and capable of being 
97       compiled, does nothing useful. When invoked, RMR will be 
98       initialized and will begin listening for a route table; 
99       blocking the return to the main program until one is 
100       received. When a valid route table arrives, initialization 
101       will complete and the program will exit as there is no code 
102       following the instruction to create the object. 
103        
104 Listening For Messages 
105 ============================================================================================ 
106        
107       The program in the previous example can be extended with just 
108       a few lines of code to enable it to receive and process 
109       messages. The application needs to register a callback 
110       function for each message type which it desires to process. 
111        
112       Once registered, each time a message is received the 
113       registered callback for the message type will be invoked by 
114       the framework. 
115        
116 Callback Signature 
117 -------------------------------------------------------------------------------------------- 
118        
119       As with most callback related systems, a callback must have a 
120       well known function signature which generally passes event 
121       related information and a "user" data pointer which was 
122       registered with the function. The following is the prototype 
123       which callback functions must be defined with: 
124        
125        
126       :: 
127          
128             void cb_name( Message& m, int mtype, int subid,
129                   int payload_len, Msg_component payload,
130                   void* usr_data );
131        
132        
133       Figure 2: Callback function signature 
134        
135       The parameters passed to the callback function are as 
136       follows: &multi_space 
137        
138        
139       m 
140           
141          A reference to the Message that was received. 
142           
143        
144       mtype 
145           
146          The message type (allows for disambiguation if the 
147          callback is registered for multiple message types). 
148           
149        
150       subid 
151           
152          The subscription ID from the message. 
153           
154        
155       payload len 
156           
157          The number of bytes which the sender has placed into the 
158          payload. 
159           
160        
161       payload 
162           
163          A direct reference (smart pointer) to the payload. (The 
164          smart pointer is wrapped in a special class in order to 
165          provide a custom destruction function without burdening 
166          the xApp developer with that knowledge.) 
167           
168        
169       user data 
170           
171          A pointer to user data. This is the pointer that was 
172          provided when the function was registered. 
173        
174        
175       To illustrate the use of a callback function, the previous 
176       code example has been extended to add the function, register 
177       it for message types 1000 and 1001, and to invoke the Run() 
178       function in the framework (explained in the next section). 
179        
180       :: 
181          
182             #include <memory>
183             #include <ricxfcpp/xapp.hpp>
184             long m1000_count = 0;    // message counters, one for each type
185             long m1001_count = 0;
186             // callback function that will increase the appropriate counter
187             void cbf( Message& mbuf, int mtype, int subid, int len,
188                         Msg_component payload,  void* data ) {
189                 long* counter;
190                 if( (counter = (long *) data) != NULL ) {
191                     (*counter)++;
192                 }
193             }
194             int main( ) {
195                 std::unique_ptr<Xapp> xapp;
196                 char* listen_port = (char *) "4560";
197                 bool  wait4table = false;
198                 xapp = std::unique_ptr<Xapp>(
199                       new Xapp( listen_port, wait4table ) );
200                 // register the same callback function for both msg types
201                 xapp->Add_msg_cb( 1000, cbf, (void *) &m1000_count );
202                 xapp->Add_msg_cb( 1001, cbf, (void *) &m1001_count );
203                 xapp->Run( 1 );        // start the callback driver
204             }
205        
206        
207       Figure 3: Callback function example. 
208        
209       As before, the program does nothing useful, but now it will 
210       execute and receive messages. For this example, the same 
211       function can be used to increment the appropriate counter 
212       simply by providing a pointer to the counter as the user data 
213       when the callback function is registered. In addition, a 
214       subtle change from the previous example has been to set the 
215       wait for table flag to false. 
216        
217       For an xApp that is a receive only application (never sends) 
218       it is not necessary to wait for RMR to receive a table from 
219       the Route Manager. 
220        
221 Registering A Default Callback 
222 -------------------------------------------------------------------------------------------- 
223        
224       The xApp may also register a default callback function such 
225       that the function will be invoked for any message that does 
226       not have a registered callback. If the xAPP does not register 
227       a default callback, any message which cannot be mapped to a 
228       known callback function is silently dropped. A default 
229       callback is registered by providing a *generic* message type 
230       of xapp->DEFAULT_CALLBACK on an Add_msg_cb call. 
231        
232 The Framework Callback Driver 
233 -------------------------------------------------------------------------------------------- 
234        
235       The Run() function within the Xapp object is invoked to start 
236       the callback driver, and the xApp should not expect the 
237       function to return under most circumstances. The only 
238       parameter that the Run() function expects is the number of 
239       threads to start. For each thread requested, the framework 
240       will start a listener thread which will allow received 
241       messages to be processed in parallel. If supplying a value 
242       greater than one, the xApp must ensure that the callback 
243       functions are thread safe as it is very likely that the same 
244       callback function will be invoked concurrently from multiple 
245       threads. 
246        
247 Sending Messages 
248 ============================================================================================ 
249        
250       It is very likely that most xApps will need to send messages 
251       and will not operate in "receive only" mode. Sending the 
252       message is a function of the message object itself and can 
253       take one of two forms: 
254        
255        
256        
257       $1 Replying to the sender of a received message 
258        
259       $1 Sending a message (routed based on the message type and subscription ID) 
260        
261        
262       When replying to the sender, the message type and 
263       subscription ID are not used to determine the destination of 
264       the message; RMR ensures that the message is sent back to the 
265       originating xApp. The xApp may still need to change the 
266       message type and/or the subscription ID in the message prior 
267       to using the reply function. 
268        
269       To provide for both situations, two reply functions are 
270       supported by the Message object as illustrated with the 
271       following prototypes. 
272        
273        
274       :: 
275          
276            bool Send_response(  int mtype, int subid, int response_len,
277                 std:shared_ptr<unsigned char> response );
278            bool Send_response(  int response_len, std::shared_ptr<unsigned char> response );
279        
280        
281       Figure 4: Reply function prototypes. 
282        
283       In the first prototype the xApp must supply the new message 
284       type and subscription ID values, where the second function 
285       uses the values which are currently set in the message. 
286       Further, the new payload contents, and length, are supplied 
287       to both functions; the framework ensures that the message is 
288       large enough to accommodate the payload, reallocating it if 
289       necessary, and copies the response into the message payload 
290       prior to sending. Should the xApp need to change either the 
291       message type, or the subscription ID, but not both, the 
292       NO_CHANGE constant can be used as illustrated below. 
293        
294        
295       :: 
296          
297             msg->Send_response( Message::NO_CHANGE, Message::NO_SUBID,
298                 pl_length, (unsigned char *) payload );
299        
300        
301       Figure 5: Send response prototype. 
302        
303       In addition to the two function prototypes for 
304       Send_response() there are two additional prototypes which 
305       allow the new payload to be supplied as a shared smart 
306       pointer. The other parameters to these functions are 
307       identical to those illustrated above, and thus are not 
308       presented here. 
309        
310       The Send_msg() set of functions supported by the Message 
311       object are identical to the Send_response() functions and are 
312       shown below. 
313        
314        
315       :: 
316          
317             bool Send_msg( int mtype, int subid, int payload_len,
318                 std::shared_ptr<unsigned char> payload );
319             bool Send_msg( int mtype, int subid, int payload_len,
320                 unsigned char* payload );
321             bool Send_msg( int payload_len,
322                 std::shared_ptr<unsigned char> payload );
323             bool Send_msg( int payload_len, unsigned char* payload );
324        
325        
326       Figure 6: Send function prototypes. 
327        
328       Each send function accepts the message, copies in the payload 
329       provided, sets the message type and subscription ID (if 
330       provided), and then causes the message to be sent. The only 
331       difference between the Send_msg() and Send_response() 
332       functions is that the destination of the message is selected 
333       based on the mapping of the message type and subscription ID 
334       using the current routing table known to RMR. 
335        
336 Direct Payload Manipulation 
337 -------------------------------------------------------------------------------------------- 
338        
339       For some applications, it might be more efficient to 
340       manipulate the payload portion of an Xapp Message in place, 
341       rather than creating it and relying on a buffer copy when the 
342       message is finally sent. To achieve this, the xApp must 
343       either use the smart pointer to the payload passed to the 
344       callback function, or retrieve one from the message using 
345       Get_payload() when working with a message outside of a 
346       callback function. Once the smart pointer is obtained, the 
347       pointer's get() function can be used to directly reference 
348       the payload (unsigned char) bytes. 
349        
350       When working directly with the payload, the xApp must take 
351       care not to write more than the actual payload size which can 
352       be extracted from the Message object using the 
353       Get_available_size() function. 
354        
355       When sending a message where the payload has been directly 
356       altered, and no extra buffer copy is needed, a NULL pointer 
357       should be passed to the Message send function. The following 
358       illustrates how the payload can be directly manipulated and 
359       returned to the sender (for simplicity, there is no error 
360       handling if the payload size of the received message isn't 
361       large enough for the response string, the response is just 
362       not sent). 
363        
364        
365       :: 
366          
367             Msg_component payload;  // smart reference
368             int pl_size;            // max size of payload
369             payload = msg->Get_payload();
370             pl_size = msg->Get_available_size();
371             if( snprintf( (char *) payload.get(), pl_size,
372                 "Msg Received\\n" ) < pl_size ) {
373               msg->Send_response( M_TYPE, SID, strlen( raw_pl ), NULL );
374             }
375        
376        
377       Figure 7: Send message without buffer copy. 
378        
379        
380 Sending Multiple Responses 
381 -------------------------------------------------------------------------------------------- 
382        
383       It is likely that the xApp will wish to send multiple 
384       responses back to the process that sent a message that 
385       triggered the callback. The callback function may invoke the 
386       Send_response() function multiple times before returning. 
387        
388       After each call, the Message retains the necessary 
389       information to allow for a subsequent invocation to send more 
390       data. It should be noted though, that after the first call to 
391       {Send_response() the original payload will be lost; if 
392       necessary, the xApp must make a copy of the payload before 
393       the first response call is made. 
394        
395 Message Allocation 
396 -------------------------------------------------------------------------------------------- 
397        
398       Not all xApps will be "responders," meaning that some xApps 
399       will need to send one or more messages before they can expect 
400       to receive any messages back. To accomplish this, the xApp 
401       must first allocate a message buffer, optionally initialising 
402       the payload, and then using the message's Send_msg() function 
403       to send a message out. The framework's Alloc_msg() function 
404       can be used to create a Message object with a desired payload 
405       size. 
406        
407 Framework Provided Callbacks 
408 ============================================================================================ 
409        
410       The framework itself may provide message handling via the 
411       driver such that the xApp might not need to implement some 
412       message processing functionality. Initially, the C++ 
413       framework will provide a default callback function to handle 
414       the RMR based health check messages. This callback function 
415       will assume that if the message was received, and the 
416       callback invoked, that all is well and will reply with an OK 
417       state. If the xApp should need to override this simplistic 
418       response, all it needs to do is to register its own callback 
419       function for the health check message type. 
420        
421 Example Programmes 
422 ============================================================================================ 
423        
424       The following sections contain several example programmes 
425       which are written on top of the C++ framework. 
426        
427 RMR Dump xAPP 
428 -------------------------------------------------------------------------------------------- 
429        
430       The RMR dump application is an example built on top of the 
431       C++ xApp framework to both illustrate the use of the 
432       framework, and to provide a useful diagnostic tool when 
433       testing and troubleshooting xApps. 
434        
435       The RMR dump xApp isn't a traditional xApp inasmuch as its 
436       goal is to listen for message types and to dump information 
437       about the messages received to the TTY much as tcpdump does 
438       for raw packet traffic. The full source code, and Makefile, 
439       are in the examples directory of the C++ framework repo. 
440        
441       When invoked, the RMR dump program is given one or more 
442       message types to listen for. A callback function is 
443       registered for each, and the framework Run() function is 
444       invoked to drive the process. For each recognised message, 
445       and depending on the verbosity level supplied at program 
446       start, information about the received message(s) is written 
447       to the TTY. If the forwarding option, -f, is given on the 
448       command line, and an appropriate route table is provided, 
449       each received message is forwarded without change. This 
450       allows for the insertion of the RMR dump program into a flow, 
451       however if the ultimate receiver of a message needs to reply 
452       to that message, the reply will not reach the original 
453       sender, so RMR dump is not a complete "middle box" 
454       application. 
455        
456       The following is the code for this xAPP. Several functions, 
457       which provide logic unrelated to the framework, have been 
458       omitted. The full code is in the framework repository. 
459        
460        
461        
462       :: 
463          
464         #include <stdio.h>
465         #include <unistd.h>
466         #include <atomic>
467         #include "ricxfcpp/xapp.hpp"
468         /*
469             Information that the callback needs outside
470             of what is given to it via parms on a call
471             by the framework.
472         */
473         typedef struct {
474             int        vlevel;             // verbosity level
475             bool    forward;            // if true, message is forwarded
476             int        stats_freq;         // header/stats after n messages
477             std::atomic<long>    pcount; // messages processed
478             std::atomic<long>    icount; // messages ignored
479             std::atomic<int>    hdr;    // number of messages before next header
480         } cb_info_t;
481         // ----------------------------------------------------------------------
482         /*
483             Dump bytes to tty.
484         */
485         void dump( unsigned const char* buf, int len ) {
486             int        i;
487             int        j;
488             char    cheater[17];
489             fprintf( stdout, "<RD> 0000 | " );
490             j = 0;
491             for( i = 0; i < len; i++ ) {
492                 cheater[j++] =  isprint( buf[i] ) ? buf[i] : '.';
493                 fprintf( stdout, "%02x ", buf[i] );
494                 if( j == 16 ) {
495                     cheater[j] = 0;
496                     fprintf( stdout, " | %s\\n<RD> %04x | ", cheater, i+1 );
497                     j = 0;
498                 }
499             }
500             if( j ) {
501                 i = 16 - (i % 16);
502                 for( ; i > 0; i-- ) {
503                     fprintf( stdout, "   " );
504                 }
505                 cheater[j] = 0;
506                 fprintf( stdout, " | %s\\n", cheater );
507             }
508         }
509         /*
510             generate stats when the hdr count reaches 0. Only one active
511             thread will ever see it be exactly 0, so this is thread safe.
512         */
513         void stats( cb_info_t& cbi ) {
514             int curv;                    // current stat trigger value
515             curv = cbi.hdr--;
516             if( curv == 0 ) {                    // stats when we reach 0
517                 fprintf( stdout, "ignored: %ld  processed: %ld\\n",
518                     cbi.icount.load(), cbi.pcount.load() );
519                 if( cbi.vlevel > 0 ) {
520                     fprintf( stdout, "\\n     %5s %5s %2s %5s\\n",
521                         "MTYPE", "SUBID", "ST", "PLLEN" );
522                 }
523                 cbi.hdr = cbi.stats_freq;        // reset must be last
524             }
525         }
526         void cb1( Message& mbuf, int mtype, int subid, int len,
527                         Msg_component payload,  void* data ) {
528             cb_info_t*    cbi;
529             long total_count;
530             if( (cbi = (cb_info_t *) data) == NULL ) {
531                 return;
532             }
533             cbi->pcount++;
534             stats( *cbi );            // gen stats & header if needed
535             if( cbi->vlevel > 0 ) {
536                 fprintf( stdout, "<RD> %-5d %-5d %02d %-5d \\n",
537                         mtype, subid, mbuf.Get_state(), len );
538                 if( cbi->vlevel > 1 ) {
539                     dump(  payload.get(), len > 64 ? 64 : len );
540                 }
541             }
542             if( cbi->forward ) {
543                 // forward with no change to len or payload
544                 mbuf.Send_msg( Message::NO_CHANGE, NULL );
545             }
546         }
547         /*
548             registered as the default callback; it counts the
549             messages that we aren't giving details about.
550         */
551         void cbd( Message& mbuf, int mtype, int subid, int len,
552                         Msg_component payload,  void* data ) {
553             cb_info_t*    cbi;
554             if( (cbi = (cb_info_t *) data) == NULL ) {
555                 return;
556             }
557             cbi->icount++;
558             stats( *cbi );
559             if( cbi->forward ) {
560                 // forward with no change to len or payload
561                 mbuf.Send_msg( Message::NO_CHANGE, NULL );
562             }
563         }
564         int main( int argc, char** argv ) {
565             std::unique_ptr<Xapp> x;
566             char*    port = (char *) "4560";
567             int ai = 1;                    // arg processing index
568             cb_info_t*    cbi;
569             int        ncb = 0;            // number of callbacks registered
570             int        mtype;
571             int        nthreads = 1;
572             cbi = (cb_info_t *) malloc( sizeof( *cbi ) );
573             cbi->pcount = 0;
574             cbi->icount = 0;
575             cbi->stats_freq = 10;
576             ai = 1;
577             // very simple flag parsing (no error/bounds checking)
578             while( ai < argc ) {
579                 if( argv[ai][0] != '-' )  {        // break on first non-flag
580                     break;
581                 }
582                 // very simple arg parsing; each must be separate -x -y not -xy.
583                 switch( argv[ai][1] ) {
584                     case 'f':                    // enable packet forwarding
585                         cbi->forward = true;
586                         break;
587                     case 'p':                     // define port
588                         port = argv[ai+1];
589                         ai++;
590                         break;
591                     case 's':                        // stats frequency
592                         cbi->stats_freq = atoi( argv[ai+1] );
593                         if( cbi->stats_freq < 5 ) {    // enforce sanity
594                             cbi->stats_freq = 5;
595                         }
596                         ai++;
597                         break;
598                     case 't':                        // thread count
599                         nthreads = atoi( argv[ai+1] );
600                         if( nthreads < 1 ) {
601                             nthreads = 1;
602                         }
603                         ai++;
604                         break;
605                     case 'v':            // simple verbose bump
606                         cbi->vlevel++;
607                         break;
608                     case 'V':            // explicit verbose level
609                         cbi->vlevel = atoi( argv[ai+1] );
610                         ai++;
611                         break;
612                     default:
613                         fprintf( stderr, "unrecognised option: %s\\n", argv[ai] );
614                         fprintf( stderr, "usage: %s [-f] [-p port] "
615                                         "[-s stats-freq]  [-t thread-count] "
616                                         "[-v | -V n] msg-type1 ... msg-typen\\n",
617                                         argv[0] );
618                         fprintf( stderr, "\\tstats frequency is based on # of messages received\\n" );
619                         fprintf( stderr, "\\tverbose levels (-V) 0 counts only, "
620                                         "1 message info 2 payload dump\\n" );
621                         exit( 1 );
622                 }
623                 ai++;
624             }
625             cbi->hdr = cbi->stats_freq;
626             fprintf( stderr, "<RD> listening on port: %s\\n", port );
627             // create xapp, wait for route table if forwarding
628             x = std::unique_ptr<Xapp>( new Xapp( port, cbi->forward ) );
629             // register callback for each type on the command line
630             while( ai < argc ) {
631                 mtype = atoi( argv[ai] );
632                 ai++;
633                 fprintf( stderr, "<RD> capturing messages for type %d\\n", mtype );
634                 x->Add_msg_cb( mtype, cb1, cbi );
635                 ncb++;
636             }
637             if( ncb < 1 ) {
638                 fprintf( stderr, "<RD> no message types specified on the command line\\n" );
639                 exit( 1 );
640             }
641             x->Add_msg_cb( x->DEFAULT_CALLBACK, cbd, cbi );        // register default cb
642             fprintf( stderr, "<RD> starting driver\\n" );
643             x->Run( nthreads );
644             // return from run() is not expected, but some compilers might
645             // compilain if there isn't a return value here.
646             return 0;
647         }
648        
649        
650       Figure 8: Simple callback application. 
651        
652 Callback Receiver 
653 -------------------------------------------------------------------------------------------- 
654        
655       This sample programme implements a simple message listener 
656       which registers three callback functions to process two 
657       specific message types and a default callback to handle 
658       unrecognised messages. 
659        
660       When a message of type 1 is received, it will send two 
661       response messages back to the sender. Two messages are sent 
662       in order to illustrate that it is possible to send multiple 
663       responses using the same received message. 
664        
665       The programme illustrates how multiple listening threads can 
666       be used, but the programme is **not** thread safe; to keep 
667       this example as simple as possible, the counters are not 
668       locked when incremented. 
669        
670        
671       :: 
672          
673         #include <stdio.h>
674         #include "ricxfcpp/message.hpp"
675         #include "ricxfcpp/msg_component.hpp"
676         #include "ricxfcpp/xapp.hpp"
677         // counts; not thread safe
678         long cb1_count = 0;
679         long cb2_count = 0;
680         long cbd_count = 0;
681         long cb1_lastts = 0;
682         long cb1_lastc = 0;
683         // respond with 2 messages for each type 1 received
684         void cb1( Message& mbuf, int mtype, int subid, int len,
685                     Msg_component payload,  void* data ) {
686             long now;
687             long total_count;
688             // illustrate that we can use the same buffer for 2 rts calls
689             mbuf.Send_response( 101, -1, 5, (unsigned char *) "OK1\\n" );
690             mbuf.Send_response( 101, -1, 5, (unsigned char *) "OK2\\n" );
691             cb1_count++;
692         }
693         // just count messages
694         void cb2( Message& mbuf, int mtype, int subid, int len, 
695                     Msg_component payload,  void* data ) {
696             cb2_count++;
697         }
698         // default to count all unrecognised messages
699         void cbd( Message& mbuf, int mtype, int subid, int len, 
700                     Msg_component payload,  void* data ) {
701             cbd_count++;
702         }
703         int main( int argc, char** argv ) {
704             Xapp* x;
705             char*    port = (char *) "4560";
706             int ai = 1;                            // arg processing index
707             int nthreads = 1;
708             // very simple flag processing (no bounds/error checking)
709             while( ai < argc ) {
710                 if( argv[ai][0] != '-' )  {
711                     break;
712                 }
713                 switch( argv[ai][1] ) {            // we only support -x so -xy must be -x -y
714                     case 'p':
715                         port = argv[ai+1];
716                         ai++;
717                         break;
718                     case 't':
719                         nthreads = atoi( argv[ai+1] );
720                         ai++;
721                         break;
722                 }
723                 ai++;
724             }
725             fprintf( stderr, "<XAPP> listening on port: %s\\n", port );
726             fprintf( stderr, "<XAPP> starting %d threads\\n", nthreads );
727             x = new Xapp( port, true );
728             x->Add_msg_cb( 1, cb1, NULL );                // register callbacks
729             x->Add_msg_cb( 2, cb2, NULL );
730             x->Add_msg_cb( x->DEFAULT_CALLBACK, cbd, NULL );
731             x->Run( nthreads );                // let framework drive
732             // control should not return
733         }
734        
735        
736       Figure 9: Simple callback application. 
737        
738        
739 Looping Sender 
740 -------------------------------------------------------------------------------------------- 
741        
742       This is another very simple application which demonstrates 
743       how an application can control its own listen loop while 
744       sending messages. As with the other examples, some error 
745       checking is skipped, and short cuts have been made in order 
746       to keep the example small and to the point. 
747        
748        
749       :: 
750          
751         #include <stdio.h>
752         #include <string.h>
753         #include <unistd.h>
754         #include <iostream>
755         #include <memory>
756         #include "ricxfcpp/xapp.hpp"
757         extern int main( int argc, char** argv ) {
758             std::unique_ptr<Xapp> xfw;
759             std::unique_ptr<Message> msg;
760             Msg_component payload;                // special type of unique pointer to the payload
761             int    sz;
762             int len;
763             int i;
764             int ai;
765             int response_to = 0;                // max timeout wating for a response
766             char*    port = (char *) "4555";
767             int    mtype = 0;
768             int rmtype;                            // received message type
769             int delay = 1000000;                // mu-sec delay; default 1s
770             // very simple flag processing (no bounds/error checking)
771             while( ai < argc ) {
772                 if( argv[ai][0] != '-' )  {
773                     break;
774                 }
775                 // we only support -x so -xy must be -x -y
776                 switch( argv[ai][1] ) {
777                     // delay between messages (mu-sec)
778                     case 'd':
779                         delay = atoi( argv[ai+1] );
780                         ai++;
781                         break;
782                     case 'p':
783                         port = argv[ai+1];
784                         ai++;
785                         break;
786                     // timeout in seconds; we need to convert to ms for rmr calls
787                     case 't':
788                         response_to = atoi( argv[ai+1] ) * 1000;
789                         ai++;
790                         break;
791                 }
792                 ai++;
793             }
794             fprintf( stderr, "<XAPP> response timeout set to: %d\\n", response_to );
795             fprintf( stderr, "<XAPP> listening on port: %s\\n", port );
796             // get an instance and wait for a route table to be loaded
797             xfw = std::unique_ptr<Xapp>( new Xapp( port, true ) );
798             msg = xfw->Alloc_msg( 2048 );
799             for( i = 0; i < 100; i++ ) {
800                 mtype++;
801                 if( mtype > 10 ) {
802                     mtype = 0;
803                 }
804                 // we'll reuse a received message; get max size
805                 sz = msg->Get_available_size();
806                 // direct access to payload; add something silly
807                 payload = msg->Get_payload();
808                 len = snprintf( (char *) payload.get(), sz, "This is message %d\\n", i );
809                 // payload updated in place, prevent copy by passing nil
810                 if ( ! msg->Send_msg( mtype, Message::NO_SUBID,  len, NULL )) {
811                     fprintf( stderr, "<SNDR> send failed: %d\\n", i );
812                 }
813                 // receive anything that might come back
814                 msg = xfw->Receive( response_to );
815                 if( msg != NULL ) {
816                     rmtype = msg->Get_mtype();
817                     payload = msg->Get_payload();
818                     fprintf( stderr, "got: mtype=%d payload=(%s)\\n",
819                         rmtype, (char *) payload.get() );
820                 } else {
821                     msg = xfw->Alloc_msg( 2048 );
822                 }
823                 if( delay > 0 ) {
824                     usleep( delay );
825                 }
826             }
827         }
828        
829        
830       Figure 10: Simple looping sender application. 
831