分类: C/C++
2011-08-30 21:10:13
Hopefully this strikes you as a neat idea, and I'm not sure why you don't hear about RPCs more often. My guess is that programmers don't like to think about the new failure modes that are introduced. All of a sudden, what looks like a simple function call can fail due to any of the myriad issues that plague networks: broken links, long ping times, low bandwidth, malicious users, and so on. Our systems professors try to get it through our skulls that, no matter what,every operation can fail, but shamefully we acknowledge this only about as far as checking for -1 after a getline or recv. If a call is going to fail, the thinking goes, it had better look like one of these. That's my theory anyway. But if you're comfortable checking errors outside your I/O subsystem, read on and reap the benefits of networking without system calls or the obscure data structures they tend to require.
Several different RPC implementations exist; I'll be using the one provided with glibc on Linux. There are also several ways to use RPCs, and I won't be able to go into all the details here. Hopefully I'll be able to get you up and running, and if you want more, O'Reilly has a pretty good book on the subject called . I'm also planning a follow-up article showing how to deal with some of the shortcomings of RPCs by using themasynchronously, so stay tuned.
Let's begin. RPCs could be used for any network application in theory, but are particularly attractive for distributed computation—when you've got a huge amount of scientific data to crunch and systems programmers are in short supply. Like most network applications, a program using RPC can be understood with the client/server model. The client is a program making a call, and the "RPC server" is the receiver that actually performs the function. In other words, an RPC server provides the service of executing some code for you. Simple enough, but before we can actually make an RPC, we have to wonder: how do you pass arguments over a network? Sending a primitive type like an int should be easy enough, but you also might want to pass a pointer to a data structure in memory. Sending the pointer over the network won't do anybody any good: it's an address in your memory after all, which will not be valid on some other machine.
RPC solves this problem for you, using a mechanism called XDR. XDR is able to take your data structure, serialize it behind the scenes, send it over the network, and reassemble it properly on the other end. This service is one of the major benefits of using RPC; not only do you not have to program sockets, you don't have to write tedious and error-prone functions to serialize your linked structures either. In fact, XDR works by reading a description of your data structures and generating such code for you. The program that does this is called an RPC compiler, and its language for describing data structures is similar to C. You describe a procedure call interface along with the data specifications, and the RPC compiler generates client stubs (which you call on the client side to issue an RPC) and server stubs (which will be called on the server side when a request comes in).
Skip this paragraphBig tangent: XDR is cool because it almost solves an interesting and important open problem in programming practice, which is to create a framework for dynamically sharing structured data, which would, among other things, implement a standard interface for serializing such data. XDR lets you describe data, but unfortunately, this metadata does not persist at run time; you're still stuck with all your code having to assume particular data structures at some point. Technologies like XML, gconf, the GIMP's procedural database, plugins, extension languages, and SQL all sort of chip away at the issue from different angles, but none of them decisively nail it.Invoke the RPC compiler using "rpcgen llist.x". rpcgen spits a slew of files back out at you:
Looking at the bottom of llist.h, we find function prototypes:
#define PRINT_LIST 1 extern int * print_list_1(list *, CLIENT *); extern int * print_list_1_svc(list *, struct svc_req *); #define SUM_LIST 2 extern int * sum_list_1(list *, CLIENT *); extern int * sum_list_1_svc(list *, struct svc_req *);Names ending in "_svc" are prototypes for the server functions we need to fill in. The other function in each pair is the client stub, already written for us. "CLIENT" is a data type specifying a connection to an RPC server, and "svc_req" gives a server function some information about an incoming request.