RPC Example - protobuf-c/protobuf-c GitHub Wiki
RPC Example
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);
}
#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;
}
#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;
}
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
Other examples are here