Putting It Together: A Phonebook Application

This chapter illustrates the use of Flick for implementing a simple network "phonebook." In our examples, a single server process will maintain one or more phonebooks, with each phonebook containing a set of name-and-number entries. Client processes will be able to connect to the server in order to add, remove, or locate phonebook entries. The complete phonebook application will be implemented in three different ways: first as a CORBA C program, then as a CORBA C++ program, and finally as an ONC RPC (C) program. By reading through these examples, you will learn how to use Flick to develop your own network applications.

5.1 The CORBA Phonebook in C

The source code for the CORBA phonebook application is contained in the test/examples/phone/corba directory of the Flick source tree.

5.1.1 Interface Definition

The CORBA IDL specification of our phonebook interface is straightforward:
    module data {
        typedef string<200> name;
        typedef string<20>  phone;
 
        struct entry { name n; phone p; };
 
        exception duplicate { phone p; };
        exception notfound  {};
 
        interface phonebook {
            void  add(in entry e)   raises (duplicate);
            void  remove(in name n) raises (notfound);
            phone find(in name n)   raises (notfound);
        };
    };

Assuming that this interface is contained in a file phone.idl, the following commands will compile the phonebook specification into client stubs and a server skeleton:1
    flick-fe-newcorba phone.idl
 
    flick-c-pfe-corba -c -o phone-client.prc phone.aoi
    flick-c-pbe-iiop phone-client.prc
    # Final outputs: `phone-client.c' and `phone-client.h'.
 
    flick-c-pfe-corba -s -o phone-server.prc phone.aoi
    flick-c-pbe-iiop phone-server.prc
    # Final outputs: `phone-server.c' and `phone-server.h'.

5.1.2 Server Functions

For the server, Flick will create a main function (found in phone-server.c). To complete the server you must:

Our simple server will use a linked list to represent its collection of phone book objects. Each node in that list will be a `struct impl_node_t' and will represent a single phonebook:
    typedef struct impl_node_t *impl_node;
 
    struct impl_node_t {
        CORBA_ReferenceData id;     /* The search key for this node. */
        data_phonebook obj;         /* CORBA Object ref for this.    */
 
        data_entry **pb;            /* The array of ptrs to entries. */
        int pb_elems;               /* # of entries in `pb'.         */
        int pb_size;                /* The size of the `pb' array.   */
 
        impl_node next;             /* Ptr to the next list element. */
    };
 
    /* `pb_impl_list' is the list of phonebook implementations. */
    impl_node pb_impl_list = 0;

The CORBA_ReferenceData slot of each node will allow us to find the data for a phonebook, given its CORBA object reference.2 The other slots should be obvious, although you may be wondering where data_entry is defined. It is defined in the file phone-server.h, which is created for you by Flick. The data_entry structure type is automatically generated from the the entry structure declaration in the source IDL file.

The next step is to write register_objects, which will be called from the Flick-generated main function so that the server can instantiate its objects and register them with the Flick runtime (the ORB). The code for this function is shown below and is contained in the phone-workfuncs.c file in Flick's CORBA phonebook source directory. This register_objects function searches the command line for options of the form "-I name " and creates one object for each such option. In the code below, note that data_phonebook_server is the name of the server skeleton function that was generated for you by Flick. That is the function that dispatches requests to objects that support the phonebook interface defined in our IDL.
    void register_objects(CORBA_ORB o, CORBA_BOA b, int argc, char **argv,
                          CORBA_Environment *ev)
    {
        int i;
 
        orb = o; /* Globals: save ORB and BOA handles for later use. */
        boa = b;
 
        pb_impl_list = 0;
 
        for (i = 1; i < argc; i++) {
            if (!strcmp(argv[i], "-I") && (i + 1 < argc)) {
                CORBA_ReferenceData id;
                data_phonebook obj;
                impl_node list = pb_impl_list;
 
                /* Register object with name `argv[i + 1]'. */
                id._length = strlen(argv[i + 1]);
                id._maximum = id._length;
                id._buffer = (CORBA_char *)
                              CORBA_alloc(id._maximum * sizeof(CORBA_char));
                if (!id._buffer) {
                    signal_no_memory(ev);
                    return;
                }
                memcpy(id._buffer, argv[i + 1], id._length);
 
                obj = CORBA_BOA_create(boa, &id,
                                       "data::phonebook",
                                       &data_phonebook_server,
                                       ev);
                if (ev->_major != CORBA_NO_EXCEPTION)
                    return;
 
                /* Add a corresponding `impl_node' to our list. */
                pb_impl_list = (impl_node)
                               malloc(sizeof(*pb_impl_list));
                if (!pb_impl_list) {
                    signal_no_memory(ev);
                    return;
                }
 
                pb_impl_list->id = id;
                pb_impl_list->obj = obj;
                pb_impl_list->pb = 0;
                pb_impl_list->pb_elems = 0;
                pb_impl_list->pb_size = 0;
                pb_impl_list->next = list;
            }
        }
 
        /* Signal failure if no objects were registered. */
        if (!pb_impl_list)
            signal_initialize(ev);
    }

We are now ready to write the server "work functions" -- the functions that implement the operations defined in our phonebook interface. Abbreviated code for the data_phonebook_add function (implementing the add operation defined in our IDL) is shown below. Complete source code for all the work functions is contained in the phone-workfuncs.c file in the example source code directory.
    void data_phonebook_add(data_phonebook obj, data_entry *arg,
                            CORBA_Environment *ev)
    {
        /* Find the object implementation --- see `find_impl' below. */
        impl_node impl = find_impl(obj, ev);
        int i;
 
        /* If we failed to find the implementation, return. */
        if (ev->_major != CORBA_NO_EXCEPTION)
            return;
 
        /* See if this entry is already in the phonebook. */
        for (i = 0; i < impl->pb_size; ++i) {
            if (impl->pb[i] && !strcmp(impl->pb[i]->n, arg->n)) {
                /* Found a duplicate!  Raise a `data_duplicate' exception. */
                data_duplicate *d =
                    (data_duplicate *) CORBA_alloc(sizeof(data_duplicate));
 
                if (!d) {
                    signal_no_memory(ev); return;
                }
                d->p = (CORBA_char *) CORBA_alloc(strlen(impl->pb[i]->p) + 1);
                if (!d->p) {
                    CORBA_free(d); signal_no_memory(ev); return;
                }
                strcpy(d->p, impl->pb[i]->p);
                CORBA_BOA_set_exception(boa, ev, CORBA_USER_EXCEPTION,
                                        ex_data_duplicate, d);
                return;
            }
        }
 
        /* Find an empty entry in `impl'; grow the phonebook if necessary. */
        i = find_empty_entry(impl, ev);
        if (ev->_major != CORBA_NO_EXCEPTION)
            return;
 
        /*
         * Allocate memory for the new entry.  Note that we have to copy the
         * `arg' data because CORBA says we can't keep pointers into `in' data
         * after this function has returned.
         */
        impl->pb[i] = (data_entry *) malloc(sizeof(data_entry));
        if (!impl->pb[i]) {
            signal_no_memory(ev); return;
        }
 
        impl->pb[i]->n = (char *) malloc(sizeof(char) * (strlen(arg->n) + 1));
        impl->pb[i]->p = (char *) malloc(sizeof(char) * (strlen(arg->p) + 1));
        if (!(impl->pb[i]->n) || !(impl->pb[i]->p)) {
            /* Free what we have allocated and signal an exception. */
            ... ; return;
        }
 
        /* Copy the `arg' information into our phonebook. */
        strcpy(impl->pb[i]->n, arg->n);
        strcpy(impl->pb[i]->p, arg->p);
 
        /* Increment the number of entries in our phonebook. */
        impl->pb_elems++;
 
        /* Success! */
        return;
    }

The above function calls find_impl to find the node that represents a phonebook, given its CORBA object reference. That helper function is shown below.
    impl_node find_impl(data_phonebook obj, CORBA_Environment *ev)
    {
        impl_node this_impl = pb_impl_list;
        CORBA_ReferenceData *obj_id = CORBA_BOA_get_id(boa, obj, ev);
 
        if (ev->_major != CORBA_NO_EXCEPTION)
            return 0;
 
        while (this_impl) {
            if ((this_impl->id._length == obj_id->_length)
                && !memcmp(this_impl->id._buffer,
                           obj_id->_buffer,
                           obj_id->_length))
                break;
            this_impl = this_impl->next;
        }
        if (!this_impl) {
            /* This should never be reached. */
            signal_object_not_exist(ev);
        }
 
        CORBA_free(obj_id->_buffer);
        CORBA_free(obj_id);
 
        return this_impl;
    }

5.1.3 Client Program

For the phonebook client, Flick creates stubs that will send requests to the server process and receive the server's replies. You must write the client's main function. Our client will be interactive, and its main function is shown below. Note that our client uses CORBA_ORB_string_to_object to establish its connection to an object that resides in the server. Flick's IIOP runtime will cause the server to print the names of its objects as they are registered. The argument to our client must be either the IOR-style or the URL-style name of an object in the server; refer to Section 4.2.3 for more information about object names.
    int main(int argc, char **argv)
    {
        CORBA_ORB orb = 0;
        CORBA_Environment ev;
        data_phonebook obj;
        int sel, done;
 
        if (argc != 2) {
            fprintf(stderr, "Usage: %s <phone obj reference>\n", argv[0]);
            exit(1);
        }
 
        orb = CORBA_ORB_init(&argc, argv, 0, &ev);
        if (ev._major != CORBA_NO_EXCEPTION) {
            printf("Can't initialize the ORB.\n");
            exit(1);
        }
 
        obj = CORBA_ORB_string_to_object(orb, argv[1], &ev);
        if (ev._major != CORBA_NO_EXCEPTION) {
            printf("Can't convert `%s' into an object reference.\n",
                   argv[1]);
            exit(1);
        }
 
        done = 0;
        while (!done) {
            read_integer(("\n(1) Add an entry (2) Remove an entry "
                          "(3) Find a phone number (4) Exit: "),
                         &sel);
            switch(sel) {
            case 1:  add_entry(obj); break;
            case 2:  remove_entry(obj); break;
            case 3:  find_entry(obj); break;
            case 4:  done = 1; break;
            default: printf("Please enter 1, 2, 3, or 4.\n");
            }
        }
        return 0;
    }

The client's add_entry function invokes data_phonebook_add, which is the Flick-generated stub for the add operation. It handles exceptions by calling the handle_exception function; the source for handle_exception can be found in the phonebook.c file in our example's source directory.
    void add_entry(data_phonebook obj)
    {
        data_entry e;
        char name[NAME_SIZE], phone[PHONE_SIZE];
        CORBA_Environment ev;
 
        e.n = name;
        e.p = phone;
 
        read_string("Enter the name: ", e.n, NAME_SIZE);
        read_string("Enter the phone number: ", e.p, PHONE_SIZE);
 
        data_phonebook_add(obj, &e, &ev);
        if (ev._major != CORBA_NO_EXCEPTION)
            handle_exception(&ev);
        else
            printf("`%s' has been added.\n", name);
    }

5.1.4 Compiling the Application

The test/examples/phone/corba directory contains a simple Makefile for compiling the phonebook server and client programs. You will need to edit the Makefile slightly in order to suit your build environment. Once that is done, and you have built Flick and the IIOP runtime, you should be able to type make to build the CORBA phonebook. Two programs will be created: phoneserver, the server, and phonebook, the client.

5.1.5 Using the Phonebook

To run the application you must first start the phonebook server. The server expects to receive at least two arguments on the command line:

-OAport portnumber
The port on which to run the server.
-I name
The names of the object instances that the server will create. This option may be repeated in order to create several object instances. Each object must have a unique name.

See Section 4.2.1 for a list of additional, optional arguments. Once the phoneserver program is running, you can invoke the phonebook program, giving it the IOR or URL-style name of a server object.
    1 marker:~> phoneserver -OAport 1253 -I OfficeList -I DeptList
    Warning: no `-ORBipaddr' specified; using `marker.cs.utah.edu'.
    Object `OfficeList' is ready.
      URL:  iiop:1.0//marker.cs.utah.edu:1253/data::phonebook/OfficeList
      IOR:  IOR:0100000010000000646174613a3a70686f6e65626f6f6b0001000...
    Object `DeptList' is ready.
      URL:  iiop:1.0//marker.cs.utah.edu:1253/data::phonebook/DeptList
      IOR:  IOR:0100000010000000646174613a3a70686f6e65626f6f6b0001000...
 
    # Run a client on the same machine or on a different machine.
 
    1 fast:~> phonebook iiop:1.0//marker.cs.utah.edu:1253/...
 
    (1) Add an entry (2) Remove an entry (3) Find a phone number (4) Exit:
    ...

5.2 The CORBA Phonebook in C++

In this section we will show you how to make a CORBA C++ version of the phonebook application. The source code for this example can be found in the test/examples/phone/tao-corbaxx directory of the Flick distribution.

Note that you must acquire and install TAO version 1.0, the real-time ORB from Washington University in St. Louis, before you will be able to compile the CORBA C++ phonebook or any other Flick-generated CORBA C++ code. See Section 2.2 for more information.

5.2.1 Interface Definition

The CORBA IDL specification of our phonebook, contained in the phone.idl file, is the same as the IDL shown previously in Section 5.1.1. Because CORBA IDL is independent of the target programming language, we can use the same IDL file to generate both C and C++ versions of our phonebook application. We produce our C++ stubs with the following commands:3
    flick-fe-newcorba phone.idl
 
    flick-c-pfe-corbaxx -c -o phoneC.prc phone.aoi
    flick-c-pbe-iiopxx -f phoneS.h phoneC.prc
    # Final outputs: `phoneC.cc' and `phoneC.h'.
 
    flick-c-pfe-corbaxx -s -o phoneS.prc phone.aoi
    flick-c-pbe-iiopxx -F phoneC.h phoneS.prc
    # Final outputs: `phoneS.cc' and `phoneS.h'.

The reasons for the -f and -F command line options are described in Section 3.4.2.

5.2.2 Server Implementation

Flick generates a server skeleton from the phonebook IDL. To complete the server, we need to write an actual implementation of the phonebook object ourselves. Writing an implementation class is just like writing a regular C++ class, but with some additional idioms imposed by CORBA. Below is the definition of our phonebook implementation class, called phone_i:
    class phone_i : public POA_data::phonebook
    {
    public:
        phone_i();
        ~phone_i();
 
        /* The functions that implement the `phone.idl' interface. */
        virtual void add(const data::entry &e, CORBA::Environment &);
        virtual void remove(const char *n, CORBA::Environment &);
        virtual data::phone find(const char *n, CORBA::Environment &);
 
    private:
        /* We implement the phonebook with a simple linked list. */
        struct impl_node_t {
            data::entry         entry;
            struct impl_node_t *next;
        };
        typedef struct impl_node_t *impl_node_ptr;
 
        /* The head of the linked list. */
        impl_node_ptr head;
 
        /*
         * `find_node' locates and returns an `impl_node_ptr' to an existing
         * node that contains the given name, or a null pointer if no node
         * contains the name.
         */
        impl_node_ptr find_node(const char *name);
    };

The declaration of this class is made up of a constructor and destructor, some virtual functions corresponding to the interface operations, and some private members used in the implementation.

Notice that the phone_i class inherits from a special class called POA_data::phonebook. This POA class, also called a skeleton class, is created for you by Flick and is the abstract base class for all implementations of the data::phonebook interface. The skeleton class takes care of marshaling and unmarshaling data for the methods that implement the phonebook interface (add, remove, and find). Flick also generates special "tie" and "collocated" classes for each interface in the IDL; to learn more about these, consult the CORBA specification and the TAO documentation.

Now that the phone_i class is declared, we need to write the methods that implement the operations listed in our IDL. Code for the phone_i::add method is shown below. Complete source code for all of the phone_i methods is contained in the phone_i.cc file in the example source code directory.
    void phone_i::add(const data::entry &e, CORBA::Environment &ACE_TRY_ENV)
    {
        impl_node_ptr node_ptr;
 
        /* Check for a duplicate name. */
        node_ptr = find_node(e.n);
        if (node_ptr) {
            /*
             * We already have this number.  Use the `ACE_THROW' macro to
             * throw an exception.
             */
            ACE_THROW(data::duplicate(node_ptr->entry.p));
        } else {
            /*
             * Make a new node and add it in.  Note that the structure
             * assignment below does a *deep* copy: the structure type is
             * defined in IDL, and the generated assignment operator does a
             * deep copy as required by the CORBA C++ language mapping.
             */
            node_ptr = new impl_node_t;
            node_ptr->entry = e; /* Deep copy. */
            node_ptr->next = head;
            head = node_ptr;
        }
    }

The implementation of our phonebook operations is rather ordinary C++ code, plus some special idioms for handling exceptions. CORBA suggests that an IDL-to-C++ compiler should produce code that handles CORBA-defined exceptions as regular C++ exceptions, whenever the target C++ compiler has working exception support. Flick does not yet implement this code style because many C++ compilers still have poor support for exceptions. Instead, Flick supports the CORBA standard's alternate mapping for exceptions, which works with all C++ compilers. In this mapping, CORBA exceptions are communicated through an extra CORBA::Environment parameter to the generated stubs, as in the standard CORBA C language mapping. TAO provides some exception-related macros (e.g., ACE_THROW) that hide the details of exception handling, thus allowing our code to be largely independent of the specific exception mapping implemented by Flick. Refer to the TAO documentation for more information about these facilities.

Once the phone_i implementation is complete, the only remaining task for our server is to implement the main function. Our main function initializes the ORB and then creates a set of phonebook objects as described by the server's command line. (The command line is parsed by the function parse_args, not shown.) When everything is set up, we simply set the system in motion and let the server process requests from clients.
    int main(int argc, char **argv)
    {
        TAO_ORB_Manager orb_manager;
        CORBA::Environment ACE_TRY_ENV;
 
        /* Use `try' because there might be an exception. */
        ACE_TRY {
            phone_impl *pi;
 
            /* Initialize the POA. */
            orb_manager.init_child_poa(argc, argv, "child_poa", ACE_TRY_ENV);
            ACE_TRY_CHECK;
 
            /* Parse the command line; creates `the_impls' list. */
            if (parse_args(argc, argv) != 0)
                return 1;
 
            /*
             * Walk through the list of implementations and attach each
             * one to the server.
             */
            for (pi = the_impls; pi; pi = pi->next) {
                /*
                 * Add the head of the `pi' list to the POA and get the
                 * IOR for the object.
                 */
                CORBA::String_var ior
                    = (orb_manager.
                       activate_under_child_poa(pi->name,
                                                &pi->servant,
                                                ACE_TRY_ENV));
                ACE_TRY_CHECK;
 
                /* Give the IOR to the user. */
                ACE_DEBUG((LM_DEBUG, "Object `%s' is ready.\n", pi->name));
                ACE_DEBUG((LM_DEBUG, "  IOR:  %s\n", ior.in()));
            }
            /*
             * Make sure there is at least one object and then start the
             * server.
             */
            if (the_impls)
                orb_manager.run(ACE_TRY_ENV);
            else
                ACE_ERROR_RETURN((LM_ERROR,
                                  "No implementations were specified.\n"),
                                 1);
            ACE_TRY_CHECK;
        }
        /* Catch any system exceptions. */
        ACE_CATCH (CORBA::SystemException, ex) {
            ex._tao_print_exception("System Exception");
            return 1;
        }
        ACE_ENDTRY;
 
        return 0;
    }

5.2.3 Client Program

With the server side implemented, we can now create a client to allow for interactions with the phonebook. Flick creates the stubs that will send requests to the server process and receive the server's replies. A programmer, however, must write the client program's main function to initialize the ORB and establish the client's connection to a phonebook, i.e., obtain a reference to a phonebook object that resides in the server. The code for our sample client main function is shown below.

Our client establishes a connection by taking an IOR from the command line. (The arguments "-k IOR " are parsed by the function parse_args, not shown.) Once the client has established the phonebook connection, we can treat the phonebook reference as a normal C++ object, which makes it easy to write the rest of the client.

The client is interactive so there is a small event loop that prompts the user for requests. When the user decides to exit, the main function terminates, and the connection to the server is closed. (When we leave the main function, the `_var'-style object references are automatically destroyed, with the result being that our connection to the server is closed.)
    int main(int argc, char **argv)
    {
        CORBA::ORB_var orb;
        CORBA::Object_var object;
        data::phonebook_var pb;
        CORBA::Environment ACE_TRY_ENV;
        const char *op_name;
        int sel, done;
 
        /* Parse the command line; initializes `ior'. */
        if (parse_args(argc, argv) != 0)
            return 1;
 
        ACE_TRY {
            /* Initialize the ORB. */
            op_name = "CORBA::ORB_init";
            orb = CORBA::ORB_init(argc, argv, 0, ACE_TRY_ENV);
            ACE_TRY_CHECK;
 
            /* Get the object reference with the IOR. */
            op_name = "CORBA::ORB::string_to_object";
            object = orb->string_to_object(ior, ACE_TRY_ENV);
            ACE_TRY_CHECK;
 
            /* Narrow the object to a `data::phonebook'. */
            op_name = "data::phonebook::_narrow";
            pb = data::phonebook::_narrow(object.in(), ACE_TRY_ENV);
            ACE_TRY_CHECK;
        }
        ACE_CATCH (CORBA::Exception, ex) {
            ex._tao_print_exception(op_name);
            return 1;
        }
        ACE_ENDTRY;
 
        done = 0;
        while (!done) {
            read_integer(("\n(1) Add an entry (2) Remove an entry "
                          "(3) Find a phone number (4) Exit: "),
                         &sel);
            switch (sel) {
            case 1:  add_entry(pb); break;
            case 2:  remove_entry(pb); break;
            case 3:  find_entry(pb); break;
            case 4:  done = 1; break;
            default: printf("Please enter 1, 2, 3, or 4.\n");
            }
        }
        return 0;
    }

The client's add_entry function invokes the data::phonebook::add method, which is the Flick-generated stub for the add operation. Because the stub can signal an exception, the add_entry function is careful to catch exceptions as shown below. (As previously described in Section 5.2.2, we use the ACE_* macros provided by TAO to make our code more portable across different C++ compilers and different ways of handling CORBA exceptions.)
    void add_entry(data::phonebook_var obj)
    {
        data::entry e;
        char name[NAME_SIZE], phone[PHONE_SIZE];
        CORBA::Environment ACE_TRY_ENV;
 
        read_string("Enter the name: ", name, NAME_SIZE);
        read_string("Enter the phone number: ", phone, PHONE_SIZE);
        /*
         * Duplicate the strings; they will be freed when `e' is destroyed.
         */
        e.n = CORBA::string_dup(name);
        e.p = CORBA::string_dup(phone);
 
        ACE_TRY {
            obj->add(e, ACE_TRY_ENV);
            /* Check for exceptions. */
            ACE_TRY_CHECK;
            printf("`%s' has been added.\n", name);
            break;
        }
        ACE_CATCH(data::duplicate, ex) {
            /*
             * Catch a `data::duplicate' exception.  It contains the
             * phone number that is already in the database.
             */
            printf("A user exception was raised: ");
            printf("duplicate, phone = `%s'.\n", (const char *) ex.p);
        }
        ACE_CATCH(CORBA::Exception, ex) {
            /* Catch all other exceptions. */
            ex._tao_print_exception("data::phonebook::add");
        }
        ACE_ENDTRY;
    }

5.2.4 Compiling the Application

The test/examples/phone/tao-corbaxx directory contains a Makefile for compiling the phonebook server and client programs. You will need to edit the Makefile slightly in order to suit your build environment. Once that is done, and you have built both Flick and TAO, you should be able to type make to build the CORBA phonebook. Two programs will be created: phoneserver, the server, and phonebook, the client.

5.2.5 Using the Phonebook

To run the application you must first start the phonebook server. The server expects to receive at least one argument on the command line:

-I name
The names of the object instances that the server will create. This option may be repeated in order to create several object instances. Each object must have a unique name.

Once the phoneserver program is running, you can invoke the phonebook program, giving it the IOR of a server object.
    1 marker:~> phoneserver -I OfficeList -I DeptList
    Object `DeptList' is ready.
      IOR:  IOR:010000001700000049444c3a646174612f70686f6e65626f6f6b3...
    Object `OfficeList' is ready.
      IOR:  IOR:010000001700000049444c3a646174612f70686f6e65626f6f6b3...
 
    # Run a client on the same machine or on a different machine.
 
    1 fast:~> phonebook -k IOR:010000001700000049444c3a646174612f7068...
 
    (1) Add an entry (2) Remove an entry (3) Find a phone number (4) Exit:
    ...

5.3 The ONC RPC Phonebook

In the following sections we will reimplement our phonebook application using Flick's ONC RPC tools rather than Flick's CORBA tools. The source code for the ONC RPC phonebook is contained in the test/examples/phone/oncrpc directory of the Flick distribution.

5.3.1 Interface Definition

Again, the ONC RPC IDL specification of the phonebook interface is straightforward:
    typedef string name<200>;
    typedef string phone<20>;
 
    struct entry { name n; phone p; };
 
    program netphone {
        version firstphone {
            int     add(entry)   = 1;
            int     remove(name) = 2;
            phone   find(name)   = 3;
        } = 1;
    } = 58239;

If you compare this interface to the one we previously defined using the CORBA IDL (see Section 5.1.1), you will notice some minor differences. Because ONC RPC does not allow us to define explicit exceptions, the return types of the add and remove operations have changed in order to indicate success or failure. Rather than change the return type of the find operation, however, our application will simply use an empty phone number string to indicate search failures.

Assuming that the above interface is contained in a file phone.x, the following commands will compile the phonebook specification into client stubs and a server skeleton:4
    flick-fe-sun phone.x
 
    flick-c-pfe-sun -c -o phone-client.prc phone.aoi
    flick-c-pbe-sun phone-client.prc
    # Final outputs: `phone-client.c' and `phone-client.h'.
 
    flick-c-pfe-sun -s -o phone-server.prc phone.aoi
    flick-c-pbe-sun phone-server.prc
    # Final outputs: `phone-server.c' and `phone-server.h'.

5.3.2 Server Functions

For the server, Flick will create a main function (found in phone-server.c). In order to complete the server, you must:

In comparison with the CORBA version of our phonebook server, the ONC RPC server will be simpler because it will only manage one phonebook, not several. (If we wanted our ONC RPC server to manage multiple books, we would need to change our IDL file so that we could pass a phonebook name to each operation.) So, our ONC RPC server simply needs to maintain a single array of phonebook entries:
    entry **pb = 0;      /* The array of pointers to entries. */
    int pb_elems = 0;    /* # of entries in `pb'.             */
    int pb_size = 0;     /* The size of the `pb' array.       */

The entry type definition is created by Flick from the entry definition in the original IDL file. Now we are ready to write the server work functions to implement the operations defined in our phonebook interface. The C prototypes of those functions are shown below:

int *add_1(entry *arg, struct svc_req *obj); Add an entry to the phonebook. Return pointer to zero to indicate success, or pointer to non-zero to indicate an error.
int *remove_1(name *arg, struct svc_req *obj); Remove the entry containing the given name from the phonebook. Return pointer to zero to indicate success, or pointer to non-zero to indicate an error.
phone *find_1(name *arg, struct svc_req *obj); Find the phone number corresponding to the given name in the phonebook. Return an empty string to indicate failure.

The name of each function is constructed from the name of the corresponding operation defined in IDL, followed by an underscore and the version number of the interface (in this case, 1). The second argument to each function is a "service request" structure that would generally contain data about the current request and the requesting client. Flick's ONC/TCP runtime does not currently initialize this structure! Flick-generated stubs include this parameter simply to be prototype-compatible with rpcgen-generated code.

Complete code for all of these functions is contained in the phone-workfuncs.c file in Flick's ONC RPC phonebook source directory. Abbreviated code for add_1 is shown below. Note that as expected of ONC RPC work functions, the return value is a pointer to a statically allocated result.
    int *add_1(entry *arg, struct svc_req *obj)
    {
        static int result;
        int i;
 
        /* Return result: zero for success, or non-zero for error. */
        result = 1;
 
        /* See if this entry is already in the phonebook. */
        for (i = 0; i < pb_size; ++i) {
            if (pb[i] && !strcmp(pb[i]->n, arg->n)) {
                /* We found a duplicate!  Return an error code. */
                return &result;
            }
        }
 
        /* Find an empty entry in `pb'; grow the phonebook if necessary. */
        i = find_empty_entry();
        if (i == NULL_PB_INDEX)
            return &result;
 
        /*
         * Allocate memory for the new entry.  Note that we have to copy the
         * `arg' data because ONC RPC says we can't keep pointers into `in'
         * data after this function has returned.
         */
        pb[i] = (entry *) malloc(sizeof(entry));
        if (!pb[i])
            return &result;
 
        pb[i]->n = (char *) malloc(sizeof(char) * (strlen(arg->n) + 1));
        pb[i]->p = (char *) malloc(sizeof(char) * (strlen(arg->p) + 1));
        if (!(pb[i]->n) || !(pb[i]->p)) {
            /* Free what we have allocated and signal an exception. */
            ...
        }
 
        /* Copy the `arg' information into our phonebook. */
        strcpy(pb[i]->n, arg->n);
        strcpy(pb[i]->p, arg->p);
 
        /* Increment the number of entries in our phonebook. */
        pb_elems++;
 
        /* Success! */
        result = 0;
        return &result;
    }

5.3.3 Client Program

You must provide the main function for the client, which will allow the user to invoke the Flick-generated stubs to communicate with the ONC RPC phonebook server. The host name of the server is provided on the command line; the values of netphone and firstphone are defined in the file phone-client.h, which is created by Flick. The FLICK_SERVER_LOCATION structure is described in Section 4.3.2.
    int main(int argc, char **argv)
    {
        CLIENT client_struct, *c;
        FLICK_SERVER_LOCATION s;
        int sel, done;
 
        c = &client_struct;
 
        if (argc != 2) {
            fprintf(stderr, "Usage: %s <host>\n", argv[0]);
            exit(1);
        }
 
        s.server_name = argv[1];
        s.prog_num = netphone;
        s.vers_num = firstphone;
        create_client(c, s);
 
        done = 0;
        while (!done) {
            read_integer(("\n(1) Add an entry (2) Remove an entry "
                          "(3) Find a phone number (4) Exit: "),
                         &sel);
            switch(sel) {
            case 1:  add_entry(c); break;
            case 2:  remove_entry(c); break;
            case 3:  find_entry(c); break;
            case 4:  done = 1; break;
            default: printf("Please enter 1, 2, 3, or 4.\n");
            }
        }
        return 0;
    }

The client's add_entry function invokes add_1, which is the Flick-generated stub for the add operation. Exceptions are detected by examining the return value of the stub function. A null pointer indicates an RPC failure; otherwise, res points to the return code for the operation.
    void add_entry(CLIENT *c)
    {
        entry e;
        char name_array[NAME_SIZE], phone_array[PHONE_SIZE];
        int *result;
 
        e.n = name_array;
        e.p = phone_array;
 
        read_string("Enter the name: ", e.n, NAME_SIZE);
        read_string("Enter the phone number: ", e.p, PHONE_SIZE);
 
        result = add_1(&e, c);
        if (!result)
            printf("Error: bad RPC call for add_1.\n");
        else if (*result)
            printf("Error: `%s' not added, error code = %d.\n",
                   e.n, *result);
        else
            printf("`%s' has been added.\n", e.n);
    }

5.3.4 Compiling the Application

The test/examples/phone/oncrpc directory contains a simple Makefile for compiling the phonebook server and client programs. You will need to edit the Makefile slightly in order to suit your build environment. Once that is done, and you have built Flick and the ONC/TCP runtime, you should be able to type make to build the ONC RPC phonebook. Two programs will be created: phoneserver, the server, and phonebook, the client.

5.3.5 Using the Phonebook

To run the application you must first start the phonebook server. Note that unlike the CORBA phonebook server, our ONC RPC server does not require any command line arguments. Once the phoneserver program is running, you can invoke the phonebook program, giving it the name of the host on which the server is running.
    1 marker:~> phoneserver
    Server sendbuf: 0 2 65536
    Server recvbuf: 0 2 65536
 
    # Run a client on the same machine or on a different machine.
 
    3 fast:~> phonebook marker
    Client sendbuf: 0 0 65536
    Client recvbuf: 0 0 65536
 
    (1) Add an entry (2) Remove an entry (3) Find a phone number (4) Exit:
    ...