RPC Example - protobuf-c/protobuf-c GitHub Wiki

RPC Example

A Simple Client and Server

The .proto file

package foo;

message Person {
  required string name = 1;
  required int32 id = 2;
  optional string email = 3;

  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }

  message PhoneNumber {
    required string number = 1;
    optional PhoneType type = 2 [default = HOME];
  }

  repeated PhoneNumber phone = 4;
}

message LookupResult
{
  optional Person person = 1;
}

message Name {
  optional string name = 1;
};

service DirLookup {
  rpc ByName (Name) returns (LookupResult);
}

The server code

#include <ctype.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include "test.pb-c.h"
#include <protobuf-c-rpc/protobuf-c-rpc.h>

static unsigned database_size;
static Foo__Person *database;		/* sorted by name */

static void
die (const char *format, ...)
{
  va_list args;
  va_start (args, format);
  vfprintf (stderr, format, args);
  va_end (args);
  fprintf (stderr, "\n");
  exit (1);
}

static void
usage (void)
{
  die ("usage: example-server [--port=NUM | --unix=PATH] --database=INPUT\n"
       "\n"
       "Run a protobuf server as specified by the DirLookup service\n"
       "in the test.proto file in the protobuf-c distribution.\n"
       "\n"
       "Options:\n"
       "  --port=NUM       Port to listen on for RPC clients.\n"
       "  --unix=PATH      Unix-domain socket to listen on.\n"
       "  --database=FILE  data which the server will use to answer requests.\n"
       "\n"
       "The database file is a sequence of stanzas, one per person:\n"
       "\n"
       "dave\n"
       " email [email protected]\n"
       " mobile (123)123-1234\n"
       " id 666\n"
       "\n"
       "notes:\n"
       "- each stanza begins with a single unindented line, the person's name.\n");
}

static void *xmalloc (size_t size)
{
  void *rv;
  if (size == 0)
    return NULL;
  rv = malloc (size);
  if (rv == NULL)
    die ("out-of-memory allocating %u bytes", (unsigned) size);
  return rv;
}

static void *xrealloc (void *a, size_t size)
{
  void *rv;
  if (size == 0)
    {
      free (a);
      return NULL;
    }
  if (a == NULL)
    return xmalloc (size);
  rv = realloc (a, size);
  if (rv == NULL)
    die ("out-of-memory re-allocating %u bytes", (unsigned) size);
  return rv;
}

static char *xstrdup (const char *str)
{
  if (str == NULL)
    return NULL;
  return strcpy (xmalloc (strlen (str) + 1), str);
}

static char *peek_next_token (char *buf)
{
  while (*buf && !isspace (*buf))
    buf++;
  while (*buf && isspace (*buf))
    buf++;
  return buf;
}

static protobuf_c_boolean is_whitespace (const char *text)
{
  while (*text != 0)
    {
      if (!isspace (*text))
        return 0;
      text++;
    }
  return 1;
}
static void chomp_trailing_whitespace (char *buf)
{
  unsigned len = strlen (buf);
  while (len > 0)
    {
      if (!isspace (buf[len-1]))
        break;
      len--;
    }
  buf[len] = 0;
}
static protobuf_c_boolean starts_with (const char *str, const char *prefix)
{
  return memcmp (str, prefix, strlen (prefix)) == 0;
}

static int
compare_persons_by_name (const void *a, const void *b)
{
  return strcmp (((const Foo__Person*)a)->name, ((const Foo__Person*)b)->name);
}
static void
load_database (const char *filename)
{
  FILE *fp = fopen (filename, "r");
  char buf[2048];
  unsigned n_people = 0;
  unsigned people_alloced = 32;
  unsigned line_no;
  Foo__Person *people = xmalloc (sizeof (Foo__Person) * people_alloced);
  if (fp == NULL)
    die ("error opening %s: %s", filename, strerror (errno));
  line_no = 0;
  while (fgets (buf, sizeof (buf), fp) != NULL)
    {
      line_no++;
      if (buf[0] == '#')
    continue;
      if (is_whitespace (buf))
        continue;
      chomp_trailing_whitespace (buf);
      if (isspace (buf[0]))
        {
      Foo__Person *person;
          char *start = buf + 1;
      if (n_people == 0)
        die ("error on %s, line %u: line began with a space, but no person's name preceded it",
             filename, line_no);
          person = people + (n_people - 1);
          while (*start && isspace (*start))
            start++;
          if (starts_with (start, "id "))
            person->id = atoi (peek_next_token (start));
          else if (starts_with (start, "email "))
            person->email = xstrdup (peek_next_token (start));
          else if (starts_with (start, "mobile ")
              ||   starts_with (start, "home ")
              ||   starts_with (start, "work "))
            {
              Foo__Person__PhoneNumber *pn = xmalloc (sizeof (Foo__Person__PhoneNumber));
              Foo__Person__PhoneNumber tmp = FOO__PERSON__PHONE_NUMBER__INIT;
              tmp.has_type = 1;
              tmp.type = start[0] == 'm' ? FOO__PERSON__PHONE_TYPE__MOBILE
                       : start[0] == 'h' ? FOO__PERSON__PHONE_TYPE__HOME
                       : FOO__PERSON__PHONE_TYPE__WORK;
              tmp.number = xstrdup (peek_next_token (start));
              person->phone = xrealloc (person->phone, sizeof (Foo__Person__PhoneNumber*) * (person->n_phone+1));
              *pn = tmp;
              person->phone[person->n_phone++] = pn;
            }
          else
            die ("error on %s, line %u: unrecognized field starting with %s", filename, line_no, start);
    }
      else
        {
      Foo__Person *person;
      if (n_people == people_alloced)
        {
              people_alloced *= 2;
              people = xrealloc (people, people_alloced * sizeof (Foo__Person));
        }
      person = people + n_people++;
      foo__person__init (person);
      person->name = xstrdup (buf);
    }
    }
  if (n_people == 0)
    die ("empty database: insufficiently interesting to procede");
  
  qsort (people, n_people, sizeof (Foo__Person), compare_persons_by_name);

  database = people;
  database_size = n_people;
  fclose (fp);
}

static int
compare_name_to_person (const void *a, const void *b)
{
  return strcmp (a, ((const Foo__Person*)b)->name);
}
static void
example__by_name (Foo__DirLookup_Service    *service,
                  const Foo__Name           *name,
                  Foo__LookupResult_Closure  closure,
                  void                      *closure_data)
{
  (void) service;
  if (name == NULL || name->name == NULL)
    closure (NULL, closure_data);
  else
    {
      Foo__LookupResult result = FOO__LOOKUP_RESULT__INIT;
      Foo__Person *rv = bsearch (name->name, database, database_size,
                                 sizeof (Foo__Person), compare_name_to_person);
      if (rv != NULL)
        result.person = rv;
      closure (&result, closure_data);
    }
}

static Foo__DirLookup_Service the_dir_lookup_service =
  FOO__DIR_LOOKUP__INIT(example__);

int main(int argc, char**argv)
{
  ProtobufC_RPC_Server *server;
  ProtobufC_RPC_AddressType address_type=0;
  const char *name = NULL;
  unsigned i;

  for (i = 1; i < (unsigned) argc; i++)
    {
      if (starts_with (argv[i], "--port="))
        {
          address_type = PROTOBUF_C_RPC_ADDRESS_TCP;
          name = strchr (argv[i], '=') + 1;
        }
      else if (starts_with (argv[i], "--unix="))
        {
          address_type = PROTOBUF_C_RPC_ADDRESS_LOCAL;
          name = strchr (argv[i], '=') + 1;
        }
      else if (starts_with (argv[i], "--database="))
        {
          load_database (strchr (argv[i], '=') + 1);
        }
      else
        usage ();
    }

  if (database_size == 0)
    die ("missing --database=FILE (try --database=example.database)");
  if (name == NULL)
    die ("missing --port=NUM or --unix=PATH");
  
  server = protobuf_c_rpc_server_new (address_type, name, (ProtobufCService *) &the_dir_lookup_service, NULL);

  for (;;)
    protobuf_c_rpc_dispatch_run (protobuf_c_rpc_dispatch_default ());
  return 0;
}

The client code

#include <ctype.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include "test.pb-c.h"
#include <protobuf-c-rpc/protobuf-c-rpc.h>

static void
die (const char *format, ...)
{
  va_list args;
  va_start (args, format);
  vfprintf (stderr, format, args);
  va_end (args);
  fprintf (stderr, "\n");
  exit (1);
}

static void
usage (void)
{
  die ("usage: example-client [--tcp=HOST:PORT | --unix=PATH]\n"
       "\n"
       "Run a protobuf client as specified by the DirLookup service\n"
       "in the test.proto file in the protobuf-c distribution.\n"
       "\n"
       "Options:\n"
       "  --tcp=HOST:PORT  Port to listen on for RPC clients.\n"
       "  --unix=PATH      Unix-domain socket to listen on.\n"
      );
}

static void *xmalloc (size_t size)
{
  void *rv;
  if (size == 0)
    return NULL;
  rv = malloc (size);
  if (rv == NULL)
    die ("out-of-memory allocating %u bytes", (unsigned) size);
  return rv;
}

static protobuf_c_boolean is_whitespace (const char *text)
{
  while (*text != 0)
    {
      if (!isspace (*text))
        return 0;
      text++;
    }
  return 1;
}
static void chomp_trailing_whitespace (char *buf)
{
  unsigned len = strlen (buf);
  while (len > 0)
    {
      if (!isspace (buf[len-1]))
        break;
      len--;
    }
  buf[len] = 0;
}
static protobuf_c_boolean starts_with (const char *str, const char *prefix)
{
  return memcmp (str, prefix, strlen (prefix)) == 0;
}

static void
handle_query_response (const Foo__LookupResult *result,
                       void *closure_data)
{
  if (result == NULL)
    printf ("Error processing request.\n");
  else if (result->person == NULL)
    printf ("Not found.\n");
  else
    {
      Foo__Person *person = result->person;
      unsigned i;
      printf ("%s\n"
              " %u\n", person->name, person->id);
      if (person->email)
        printf (" %s\n", person->email);
      for (i = 0; i < person->n_phone; i++)
        {
          const ProtobufCEnumValue *ev;
          ev = protobuf_c_enum_descriptor_get_value (&foo__person__phone_type__descriptor, person->phone[i]->type);
          printf (" %s %s\n",
                  ev ? ev->name : "???",
                  person->phone[i]->number);
        }
    }

  * (protobuf_c_boolean *) closure_data = 1;
}

int main(int argc, char**argv)
{
  ProtobufCService *service;
  ProtobufC_RPC_Client *client;
  ProtobufC_RPC_AddressType address_type=0;
  const char *name = NULL;
  unsigned i;

  for (i = 1; i < (unsigned) argc; i++)
    {
      if (starts_with (argv[i], "--tcp="))
        {
          address_type = PROTOBUF_C_RPC_ADDRESS_TCP;
          name = strchr (argv[i], '=') + 1;
        }
      else if (starts_with (argv[i], "--unix="))
        {
          address_type = PROTOBUF_C_RPC_ADDRESS_LOCAL;
          name = strchr (argv[i], '=') + 1;
        }
      else
        usage ();
    }

  if (name == NULL)
    die ("missing --tcp=HOST:PORT or --unix=PATH");
  
  service = protobuf_c_rpc_client_new (address_type, name, &foo__dir_lookup__descriptor, NULL);
  if (service == NULL)
    die ("error creating client");
  client = (ProtobufC_RPC_Client *) service;

  fprintf (stderr, "Connecting... ");
  while (!protobuf_c_rpc_client_is_connected (client))
    protobuf_c_rpc_dispatch_run (protobuf_c_rpc_dispatch_default ());
  fprintf (stderr, "done.\n");

  for (;;)
    {
      char buf[1024];
      Foo__Name query = FOO__NAME__INIT;
      protobuf_c_boolean is_done = 0;
      fprintf (stderr, ">> ");
      if (fgets (buf, sizeof (buf), stdin) == NULL)
        break;
      if (is_whitespace (buf))
        continue;
      chomp_trailing_whitespace (buf);
      query.name = buf;
      foo__dir_lookup__by_name (service, &query, handle_query_response, &is_done);
      while (!is_done)
        protobuf_c_rpc_dispatch_run (protobuf_c_rpc_dispatch_default ());
    }
  return 0;
}

Running the code

In one shell (or backgrounded), run:

./example-server --unix=socket --database=example.database

And in another shell (or in the foreground), run:

./example-client --unix=socket

The client will greet you with a prompt:

>>

Type dave:

>> dave

And it will respond, (as given in the example.database file):

dave
 666
 [email protected]
 MOBILE (123)123-1234

Where is this code?

Directory Lookup example

Other examples are here

⚠️ **GitHub.com Fallback** ⚠️