TUT:Simple Async Application

From Net-SNMP Wiki
Revision as of 17:05, 6 February 2007 by Wes (Talk | contribs)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Here we discuss how to write an asynchronous application. It's only purpose is to retrieve the value of a set of variables from a set of remote hosts.

Here are the files discussed in this example so you can download them:

 XXX
 File
 Description
 Makefile  A simple Makefile to build the application
   asyncapp.c  The C source code

This discussion assumes that you alread have read the snmpdemoapp tutorial section.

The program assumes that you have a set of hosts that you want to interrogate for a set of variables. For simplicitys sake each query just asks for one variable.

The program defines two structures, struct host which holds the host specific data, hostname and community to be used, and struct oid which hold to object to be queried for.

 /*
  * a list of hosts to query
  */
 struct host {
   char *name;
   char *community;
 } hosts[] = {
   { "test1",            "public" },
   { "test2",            "public" },
   { "test3",            "public" },
   { "test4",            "public" },
   { NULL }
 };
 
 /*
  * a list of variables to query for
  */
 struct oid {
   char *Name;
   oid Oid[MAX_OID_LEN];
   int OidLen;
 } oids[] = {
   { "system.sysDescr.0" },
   { "interfaces.ifNumber.1" },
   { "interfaces.ifNumber.0" },
   { NULL }
 };

As a brush up, we then define the function synchronous that scans the hosts using synchronous calls:

 void synchronous (void)
 {
   struct host *hp;
   for (hp = hosts; hp->name; hp++) {
     struct snmp_session ss, *sp;
     struct oid *op;
 
     snmp_sess_init(&ss);                        /* initialize session */
     ss.version = SNMP_VERSION_2c;
     ss.peername = hp->name;
     ss.community = hp->community;
     ss.community_len = strlen(ss.community);
     snmp_synch_setup(&ss);
     if (!(sp = snmp_open(&ss))) {
       snmp_perror("snmp_open");
       continue;
     }
     for (op = oids; op->Name; op++) {
       struct snmp_pdu *req, *resp;
       int status;
       req = snmp_pdu_create(SNMP_MSG_GET);
       snmp_add_null_var(req, op->Oid, op->OidLen);
       status = snmp_synch_response(sp, req, &resp);
       if (!print_result(status, sp, resp)) break;
       snmp_free_pdu(resp);
     }
     snmp_close(sp);
   }
 }

This should contain no surprises, as you already mastered the snmpdemoapp chapter.

Now onto the new material. We define a struct session to hold our per host state:

 struct session {
   struct snmp_session *sess;          /* SNMP session data */ 
   struct oid *current_oid;            /* How far in our poll are we */
 } sessions[sizeof(hosts)/sizeof(hosts[0])];
 
 int active_hosts;                     /* hosts that we have not completed */

We then need a callback function, to be called whenever a response is received:

 int asynch_response(int operation, struct snmp_session *sp, int reqid,
                     struct snmp_pdu *pdu, void *magic)
 {
   struct session *host = (struct session *)magic;
   struct snmp_pdu *req;
 

whenever a response is received, we will print it out, and send the next request out:

   if (operation == RECEIVED_MESSAGE) {
     if (print_result(STAT_SUCCESS, host->sess, pdu)) {
       host->current_oid++;                      /* send next GET (if any) */
       if (host->current_oid->Name) {
 

there is still more to do ... build the next request

         req = snmp_pdu_create(SNMP_MSG_GET);
         snmp_add_null_var(req, host->current_oid->Oid, host->current_oid->OidLen);
         if (snmp_send(host->sess, req))
 

we are still in business

           return 1;
         else {
 

something went wrong. Print out error message, and fall through to decrement active_hosts

           snmp_perror("snmp_send");
           snmp_free_pdu(req);
         }
       }
     }
   }
   else
     print_result(STAT_TIMEOUT, host->sess, pdu);
   /* something went wrong (or end of variables)
    * this host not active any more
    */
   active_hosts--;
   return 1;
 }

Now the code to fire this up, and keep it running until we are done

 void asynchronous(void)
 {
   struct session *hs;
   struct host *hp;

First we loop through all the hosts, opening a session for each and sending out the first request:

   /* startup all hosts */
   for (hs = sessions, hp = hosts; hp->name; hs++, hp++) {
     struct snmp_pdu *req;
     struct snmp_session sess;
     snmp_sess_init(&sess);                    /* initialize session */
     sess.version = SNMP_VERSION_2c;
     sess.peername = hp->name;
     sess.community = hp->community;
     sess.community_len = strlen(sess.community);

he next two lines is where we deviate from the good old sync setup

     sess.callback = asynch_response;            /* default callback */
     sess.callback_magic = hs;
     if (!(hs->sess = snmp_open(&sess))) {
       snmp_perror("snmp_open");
       continue;
     }
     hs->current_oid = oids;
     req = snmp_pdu_create(SNMP_MSG_GET);        /* send the first GET */
     snmp_add_null_var(req, hs->current_oid->Oid, hs->current_oid->OidLen);
     if (snmp_send(hs->sess, req))
       active_hosts++;
     else {
       snmp_perror("snmp_send");
       snmp_free_pdu(req);
     }
   }

That was the startup code. Now we just wait for things to settle down again. All the real work is handled inside the callback function.

   /* loop while any active hosts */
   while (active_hosts) {
     int fds = 0, block = 1;
     fd_set fdset;
     struct timeval timeout;

snmp_select_info is the function that gets us all we need to be able to call select

     FD_ZERO(&fdset);
     snmp_select_info(&fds, &fdset, &timeout, &block);
     fds = select(fds, &fdset, NULL, NULL, block ? NULL : &timeout);

snmp_read will read all sockets with pending data, and process them

     if (fds) snmp_read(&fdset);
     else snmp_timeout();
   }

Now active_hosts == 0, and all data have been returned. Close all sessions and finish up.

   /* cleanup */
   for (hp = hosts, hs = sessions; hp->name; hs++, hp++) {
     if (hs->sess) snmp_close(hs->sess);
   }
 }

The main program just demonstrates the two ways to do the job:

 int main (int argc, char **argv)
 {
   initialize();
 
   printf("---------- synchronous -----------\n");
   synchronous();
 
   printf("---------- asynchronous -----------\n");
   asynchronous();
 
   return 0;
 }

Thats it. Lets now see it in action:

 % ./asyncapp
 ---------- synchronous -----------
 23:08:49.429595 test1: Timeout
 23:08:49.609789 test2: system.sysDescr.0 = SunOS test2 5.6 Generic_105181-16 sun4u
 23:08:49.759717 test2: interfaces.ifNumber.1 = No Such Instance currently exists
 23:08:49.899715 test2: interfaces.ifNumber.0 = No Such Object available on this agent
 23:08:50.059725 test3: system.sysDescr.0 = Linux test3 2.2.5-22 #1 Wed Jun 2 09:17:03 EDT 1999 i686
 23:08:50.199715 test3: interfaces.ifNumber.1 = No Such Object available on this agent
 23:08:50.339712 test3: interfaces.ifNumber.0 = No Such Object available on this agent
 23:08:56.429595 test4: Timeout
 ---------- asynchronous -----------
 23:08:50.519702 test2: system.sysDescr.0 = SunOS test2 5.6 Generic_105181-16 sun4u
 23:08:50.569680 test3: system.sysDescr.0 = Linux test3 2.2.5-22 #1 Wed Jun 2 09:17:03 EDT 1999 i686
 23:08:50.669682 test2: interfaces.ifNumber.1 = No Such Instance currently exists
 23:08:50.699669 test3: interfaces.ifNumber.1 = No Such Object available on this agent
 23:08:50.809714 test2: interfaces.ifNumber.0 = No Such Object available on this agent
 23:08:50.879675 test3: interfaces.ifNumber.0 = No Such Object available on this agent
 23:08:56.429590 test1: Timeout
 23:08:56.430095 test4: Timeout
 %