Goals
Your goal is to design a simple "on-the-wire" format for calling C++ functions remotely. In other words, you will specify the format and content of the request and response messages.
Assumptions
We'll worry only about a simplified version of the C++ language. Your design must support function calls that have as their argument types:
- int (assume 32 bit signed)
- char (assume 8 bit ASCII)
- bool (true/false)
- float (assume 32 bit)
- string (the C++ type with lengths that vary at runtime)
- array (C-style arrays with size fixed at compile time -- members may be of any type listed here including structs or arrays)
- struct (with members of any type listed including other structs)
- (optional) std::vector
(c++ arrays with size set at run time -- can be any type listed including array, struct or vector
This is the same set of types you will be supporting in your RPC project.
Assume that the signature of each method is available when preparing the
code to send or receive a message. For example,
if the function to be remoted is isOldPerson
:
struct person { char name[20]; bool isMale; int age; } bool isOldPerson(person p);
Then you know that the supplied name is 20 characters, and that isMale is a boolean and age is an integer.
You must not assume that the compiler and structure mappings are the same at both ends of the connection. Otherwiswe, for this simple case, you could just send the above person struct in the form it's stored in the source machine. You must come up with an on-the-wire representation that conveys each field. You can assume that both ends of the connection have knowledge of the function signature and associated types.
What to do: getting started
Start by thinking about a simple method like this:
int product(int n, int m);
which takes two integers and (we can guess) returns their product. What would you put on the wire for the request? For the response?
Now consider each of the types separately. How will you handle arrays?
int average(int numbers[12]); // takes array of 12 ints ... which we would call like this... int nums[12] = {2,4,6,8,10,12,14,16, 18,20,22,24}; cout << average(nums);
You can assume (if you like) that both sides know that that array size is 12 before the message is ever sent.
Now think about structs? What about arrays of structs?
What to do: the general case
The above was to get you thinking, but what you really need is a general approach that will handle even complex combinations like this (or worse):
struct Person { string name; bool isMale; int age; }; struct Company { int numberOfEmployees; Person employees[100]; }; int countMaleEmployees(Company c);
What's the general approach? How would you write code to generate a request message at runtime?
How would you convey the length of the string? Are there special characters that could show up in the name field
that would cause you problems? How would you parse it and call the countMaleEmployees routine?
How will you handle the variable length name
string?
Things to Think About
Some things to think about as you design your protocol:
- Text-only or binary format?
- How will you convey which function to call?
- How will you represent complex data structures, e.g. arrays of structures containing arrays of structures?
- Should you name arguments (like "n" and "m") explicitly in your messages, or is that unnecessary?
- How would things get harder if we allowed pointers?