Remote Invocation: RPC & RMI
How middleware makes a call to a procedure or object on another computer look almost like a local call: the request-reply protocol underneath, what can go wrong when messages are lost, the call semantics that result, and the machinery of RPC, RMI and Java RMI. It ends by building the complete Java RMI program that the exam's 10-mark coding question asks for.
- You willtrace a request-reply exchange through a lost request and a lost reply, and name the measure that fixes each.
- You willtell maybe, at-least-once and at-most-once apart, and say which one RR, Sun RPC and Java RMI give.
- You willname every component of RPC and RMI (stubs, proxy, dispatcher, skeleton, remote reference module) and say what it does.
- You willwrite a complete Java RMI client and server that earns every mark in the coding rubric.
Remote Invocation as a Middleware Abstraction exam
In Part III two processes talked through sockets: the programmer opened a connection, turned data into bytes, wrote them, read the answer back and worked out which answer belonged to which question. That works, but every program repeats the same chores, and each chore is a place for bugs. Remote invocation is the idea that removes those chores: the programmer writes something that looks like an ordinary call, and a layer of software underneath turns it into messages.
Remote invocation means invoking a procedure or method in another process, usually on another machine, with (nearly) the same syntax as a local call. Underneath, the middleware turns the call into a request message and a reply message.
Middleware is the layer of software that sits between applications and the operating system and hides the network from the programmer. A process is one running program with its own memory; two processes share no memory, even on the same computer, so messages are the only way they can interact.
The value of the abstraction is clearest when you set the two ways of programming side by side. On the left is what you must write yourself with raw sockets; on the right is the same interaction written as a remote call, with the chores moved into middleware.
So remote invocation hides connection management, message boundaries and marshalling. Marshalling is the process of flattening structured data (numbers, strings, objects) into a sequence of bytes that can be sent in a message; unmarshalling rebuilds the data at the other end. Hiding these means less hand-written code and so fewer errors. Remote invocation also matches replies to requests, deals with retransmission and duplicate messages (together these determine the call semantics, explained in 6.5), and finds the server through a binder or registry, a directory that maps names to servers. On top of all that it separates interface from implementation across the network: the client knows only what operations exist, not how they are written.
Three paradigms, each built on the one below
CDK §5.1 describes three ways of doing remote invocation. They are not rivals: each is built on the one below it, going from the lowest level to the highest.
- Request-reply protocols are a pattern layered on top of plain message passing to support client-server exchanges: the client sends a request, the server sends back a reply. RPC and RMI are both built on them.
- RPC (Remote Procedure Call) extends the familiar procedure call to procedures that live in other processes, so it belongs with procedural languages such as C.
- RMI (Remote Method Invocation) extends object method invocation to objects that live in other processes. "RMI" names the general idea; Java RMI is one product that implements it.
Where remote invocation sits
The layer diagram below shows the whole software stack of a distributed system. Remote invocation sits in the middleware, above the basic interprocess communication (IPC) primitives of Part III and below the applications that use it.
Remote invocation is direct communication: the two parties are coupled in both space and time. Coupled in space means the client must know which server it is talking to; coupled in time means the server must be running at the moment the client calls. Indirect communication (Part VII) removes one or both of these couplings by putting an intermediary between them.
Check yourselfName four chores that remote invocation hides from the programmer, compared with programming raw sockets.
Any four of: managing connections; finding message boundaries; marshalling and unmarshalling the data; matching replies to requests; retransmitting lost requests and dealing with duplicates (which fixes the call semantics); and locating the server through a binder or registry. It also separates interface from implementation across the network.
Check yourselfWhich statement about the three paradigms is correct?- RPC and RMI are alternatives to request-reply protocols.
- RMI is built on the request-reply pattern, just as RPC is.
- "RMI" is the name of Sun's Java product and nothing else.
- RPC is the object-oriented one.
B. Request-reply protocols are the foundation; RPC and RMI are both built on them (so A is wrong). RMI names the general idea of invoking methods on remote objects; Java RMI is one implementation of it (C is wrong). RPC is procedural; RMI is the object-oriented one (D is wrong).
- Remote invocation = calling a procedure or method in another process with nearly local syntax; middleware turns the call into request and reply messages.
- It hides connections, message boundaries, marshalling, reply matching, retransmission and duplicates, and server location.
- Three layered paradigms: request-reply protocols, then RPC, then RMI. All are direct communication, coupled in space and time.
The Request-Reply Protocol exam
Every remote call, whatever it looks like to the programmer, ends up as the same little conversation: the client sends a request saying "please run operation X with these arguments", and the server sends back a reply carrying the result. A request-reply protocol is the set of rules for that conversation (CDK §5.2). RPC and RMI rest on it, so it pays to understand it on its own first.
A request-reply exchange is normally synchronous: the client blocks (stops and waits) until the reply arrives. It can also be reliable, because the reply itself acts as an acknowledgement of the request: if a reply comes back, the request must have arrived.
Why build it on UDP rather than TCP? exam
Two transport protocols are available (Part III). TCP is connection-oriented: it sets up a connection first, then delivers a reliable, ordered stream of bytes, sending acknowledgements (messages saying "I got it") and doing flow control (slowing the sender so it cannot swamp the receiver). UDP just sends independent packets called datagrams, with no connection and no guarantee that they arrive. TCP might seem the obvious choice, but it has three overheads that a request-reply exchange does not need:
- Acknowledgements are redundant, because every request is followed by a reply anyway.
- Connection set-up costs two extra pairs of messages before any useful exchange takes place.
- Flow control is redundant for the small arguments and results that most calls pass.
The price of UDP is that the protocol must now cope with lost and duplicated messages itself, which is what 6.3 is about. (There are also good reasons to use TCP after all; they are in 6.4.)
The three primitives
CDK describes the protocol with three operations, one on the client side and two on the server side:
// client side
public byte[] doOperation(RemoteRef s, int operationId, byte[] arguments)
// server side
public byte[] getRequest();
public void sendReply(byte[] reply, InetAddress clientHost, int clientPort);
doOperationsends a request to the servers, which is identified by its IP address and port. It then blocks inreceiveuntil the reply arrives, and returns the reply bytes.getRequestacquires a client request via the server's port.sendReplysends the reply back to the client at the given host and port.
Notice that doOperation deals only in byte[]: its caller marshals the arguments beforehand and unmarshals the result afterwards. The server, meanwhile, runs a loop: it calls getRequest, selects the operation, executes it and calls sendReply, then goes round again.
getRequest for the next client.The message structure exam
Every request and every reply has the same five fields. Using one layout for both directions keeps the protocol simple.
| Field | Type | Purpose |
|---|---|---|
messageType | int (0 = Request, 1 = Reply) | Says whether this is a request or a reply |
requestId | int | Matches a reply to its request |
remoteReference | RemoteRef | The remote object or server to invoke |
operationId | int or Operation | Which operation to invoke |
arguments | array of bytes | The marshalled arguments (in a request) or results (in a reply) |
Two fields need more explanation. The requestId is generated by doOperation and copied into the reply, so that the client can check that a reply belongs to its current request and is not a delayed reply to an earlier one. The operationId can be a plain number (1, 2, 3 …) or, if both sides are written in the same language, a reflective Method object (6.9 shows Java RMI doing exactly this).
Message identifiers
For duplicate detection to work, every message needs an identifier that is unique across the whole system, not just within one client. It is built from two parts:
The requestId wraps round to 0 after 232−1. That is safe as long as an identifier's lifetime (how long a message carrying it could still be around) is much shorter than the time it takes to use up the whole sequence.
Check yourselfWhy does the server copy the requestId into its reply?
So the client can match the reply to its request. After a timeout and retransmission, a delayed reply to an earlier request could arrive; comparing requestIds lets doOperation ignore it and wait for the reply to the current one.
Check yourselfGive the three TCP overheads that a request-reply protocol over UDP avoids.
(1) Acknowledgements are redundant, since each request is followed by a reply. (2) Connection set-up costs two extra pairs of messages. (3) Flow control is redundant for the small arguments and results most calls pass.
Check yourselfA requestId of 41 arrives from two different clients. Is that a problem for duplicate detection?
No. The requestId is unique only per sender; the server identifies a message by the pair (sender identifier, requestId), where the sender identifier is the client's IP address and port. The two messages have different senders, so they are different messages.
- Request-reply is usually synchronous (the client blocks) and can be reliable (the reply acknowledges the request). It is commonly built on UDP to avoid TCP's redundant acks, connection set-up and flow control.
- Primitives:
doOperation(client, blocks),getRequestandsendReply(server loop). - Each message: messageType, requestId, remoteReference, operationId, arguments. Message id = requestId + sender's IP address and port.
Failures and Fault-Tolerance Measures exam
Running the protocol over UDP saves messages, but UDP promises nothing: a datagram can vanish. When the client hears nothing back, it faces a hard question: did my request get lost, or did the server run the operation and the reply get lost? This section is about the four measures the protocol uses to live with that uncertainty.
The failure model
A failure model states exactly which failures a design must cope with. For request-reply over UDP it is:
- Omission failures: a message is sent but never delivered (it is lost).
- Out-of-order delivery: messages can arrive in a different order from the one in which they were sent.
- Process crashes: a process can crash, but the model assumes it simply stops and stays stopped. There is no Byzantine (arbitrary) behaviour, where a faulty process sends wrong or malicious messages.
The four measures
- Timeout (client). When the client's timeout expires, the reply may have been lost after the operation had already run, so giving up at once would be unusual. Instead,
doOperationretransmits the request until it either gets a reply or is fairly sure the server has failed, and only then throws an exception. - Discard duplicate requests (server). Because the client retransmits, the server may receive the same request twice, for example when it executes more slowly than the client's timeout. It recognises the repeated (client, requestId) and filters out the duplicate. If it has not sent the reply yet, it simply sends it when it has finished.
- Lost reply. If a duplicate arrives after the reply has already been sent, the server has two options. It can re-execute the operation, which is safe only if the operation is idempotent (defined below), or it can retransmit the stored reply.
- History. To retransmit replies without re-executing, the server keeps a history: a record of the replies it has sent, as (requestId, reply, client id) entries.
The two steppers below show the measures in action. First the easy case: the request is lost, so the server never saw it, and a retransmission simply gets it through.
- The client calls
doOperation. It is about to send request number 3 and then block until a reply arrives. - Request id 3 is lost in the network. The server never sees it, so nothing has been executed.
- The timeout expires. The client cannot see inside the network, so it does not know whether the request or the reply was lost. Rather than fail,
doOperationretransmits. - The retransmitted request carries the same id 3. The server has no record of (client, 3), so this is a new request and it executes the operation.
- Reply id 3 reaches the client, which checks that the id matches its current request, and
doOperationreturns. The operation ran exactly once.
The hard case is a lost reply. Now the operation has run, and the retransmitted request is a duplicate. This is the guide's example: a deposit of 10, where running it twice would credit the account twice.
- The account balance is 100 and the server's history is empty.
- Request id 7, "deposit 10", goes from the client's
doOperationto the server. - The server's
getRequestreceives it; the server executes the deposit (balance 110) and stores reply 7 in its history before sending it. sendReplysends reply id 7, which is lost. The deposit has happened, but the client does not know that.- The client's timeout expires. It retransmits the request with the same id 7, because from where it stands the request might have been the message that was lost.
- Request id 7 arrives again. If the server simply ran it, the account would be credited twice.
- The server looks up (C, 7) in its history and finds it: a duplicate, so it does NOT re-execute. The balance stays 110 and the deposit has still run only once.
- Reply id 7 is resent from the history. The client's
doOperationreturns the result. This is the combination that gives at-most-once semantics (6.5).
Duplicates do not only come from lost replies. If the server is simply slow, executing for longer than the client's timeout, the client retransmits while the original is still running. Measure 2 covers this case.
Keeping the history small
A history costs memory, and a busy server with many clients could fill up. Two observations keep the cost down. First, each client has only one outstanding request at a time, so the client's next request acknowledges its previous reply, and the server need keep only the last reply per client. Second, clients that exit never send that next request, so the server discards entries after a time limit.
Idempotent operations exam
An idempotent operation is "an operation that can be performed repeatedly with the same effect as if it had been performed exactly once".
Idempotence is what makes re-execution (option 3a above) safe: if repeating an operation changes nothing more, the server can just run a duplicate again and needs no history. If all of a server's operations are idempotent, as in Sun NFS (Part VIII), the server needs no history at all.
| Idempotent | Not idempotent |
|---|---|
| Adding an element to a set | Appending to a sequence or a file |
| Dictionary search and delete (from the slides) | "Add $10 to the balance" |
| Writing data at a given position in a file | |
| HTTP GET, PUT and DELETE |
Don't confuse idempotent with read-only or stateless. Writing at a fixed file position does change state, but repeating it changes nothing more, so it is idempotent. Appending changes state every time, so it is not.
Check yourselfDefine idempotent and classify: add to a set, append, write at an offset, deposit $10, lift button.
An operation is idempotent if repeating it has the same effect as doing it exactly once. Idempotent: adding to a set, writing at an offset, pressing a lift (elevator) call button, since pressing it again leaves the lift called just once. Not idempotent: appending, depositing $10. Idempotence does not require the operation to be stateless.
Check yourselfOver UDP, what failures affect request-reply, and how are they handled?
Three kinds: omission (lost messages), out-of-order delivery, and server crash. The client uses a timeout: when it expires, it retransmits the request, and if no reply ever comes it eventually raises an exception. The server filters duplicates by (client, requestId). When a reply is lost, the server can re-execute the operation if it is idempotent; otherwise it must resend the reply from a history. To keep the history small it stores only the last reply per client and expires old entries.
Check yourselfA server takes 3 seconds to run an operation, and the client's timeout is 1 second. With no duplicate filtering, what goes wrong with a non-idempotent operation?
The client times out and retransmits (perhaps more than once) while the first execution is still running. Without filtering, each retransmission is treated as a new request and the operation runs several times, for example crediting an account repeatedly. Filtering duplicates by (client, requestId) makes the server ignore the copies and reply once when it finishes.
- Over UDP, messages can be lost or reordered and processes can crash (but not misbehave).
- Four measures: client timeout + retransmit; server duplicate filtering by (client, requestId); on a lost reply either re-execute (idempotent only) or resend the stored reply; a history holding the last reply per client, expired after a time limit.
- Idempotent = repeating it has the same effect as doing it once. If every operation is idempotent, no history is needed.
R, RR and RRA; TCP and HTTP exam
Not every interaction needs the same guarantees. Spector (1982) identified three styles of exchange protocol, which differ in how many messages they send and so in what they can promise. Choosing one is a trade-off between messages sent and certainty gained.
| Protocol | Messages sent | In short |
|---|---|---|
| R (request) | Request | No result or confirmation needed; maybe semantics |
| RR (request-reply) | Request, Reply | Most client-server exchanges |
| RRA (request-reply-acknowledge reply) | Request, Reply, Ack(requestId) | The ack lets the server delete history entries |
R is for when the client needs neither a result nor a confirmation: it sends its request and continues at once. It runs over UDP with no retries, which is why its semantics are maybe (6.5). RR is used for most client-server exchanges. It needs no separate acknowledgements, because the reply acknowledges the request and the client's next request acknowledges the reply.
RRA adds a third message, an acknowledgement carrying the requestId, whose purpose is to let the server delete history entries as soon as the client has its reply, instead of waiting for a time limit. Acks are cumulative: Ack(n) covers every id up to n, so losing one is harmless. The client sends the ack after the reply has been handed to it, so it is not blocked waiting; but the ack does cost an extra message, so it can instead be piggy-backed on (carried inside) the next request.
It depends on which fault-tolerance measures are added (CDK §5.2). Over UDP:
- RR + retransmission + duplicate filtering + reply history masks both lost requests and lost replies and never executes an operation twice, so it gives at-most-once.
- Retransmission without filtering gives at-least-once.
- No retransmission at all gives maybe.
Over TCP the picture is different; see below and the quiz at the end of this section.
Request-reply over TCP
Despite the overheads in 6.2, there are good reasons to run request-reply over TCP:
No multipacket protocol
UDP datagrams are limited, often to about 8 KB, which is too small for arguments of arbitrary size. TCP saves you from building a protocol that splits a message across many packets, and it suits Java serialization, which is stream-based.Reliability for free
TCP is reliable, so the protocol needs no retransmission, duplicate filtering or history, which makes it much simpler. TCP's flow control handles large arguments.Overheads can be spread
The set-up cost is spread by reusing a connection for successive requests, and TCP's acks are cheap when the reply follows soon after the request.A tailored UDP protocol is still the better choice when TCP's features are not needed. Sun NFS is the standard example: it moves fixed-size blocks and its operations are idempotent, so it needs no history.
TCP does not give exactly-once semantics. If the connection breaks in the middle of a call, the client gets an exception and the outcome is unknown to it: the operation may or may not have run.
HTTP as a request-reply protocol (brief)
HTTP, the protocol of the Web, is a familiar request-reply protocol. It runs over TCP and specifies the request and reply messages, the methods, and the marshalling: messages are ASCII text, and data carries MIME types (labels saying what kind of data it is), such as text/html. Unlike RPC, where each service defines its own operations, HTTP has a fixed set of methods for all resources. It also offers content negotiation (client and server agree which representation to send) and password-style authentication, where the server issues a challenge and the client responds with credentials. HTTP/1.1 uses persistent connections (one connection reused for many requests); if a connection closes in the middle of a request, the browser resends the request automatically only if it is idempotent.
method URL-or-path version | headers | body; a reply is version status-code reason | headers | body. The MIME type in Content-Type tells the browser how to interpret the body.| Method | What it does | Idempotent? | Safe (changes nothing)? |
|---|---|---|---|
GET | Fetches a resource or a program's output; any arguments travel in the URL | Yes | Yes |
HEAD | Like GET but returns no data | Yes (safe) | Yes |
POST | Sends body data to a program; may change state | Not necessarily | No |
PUT | Stores data at the URL | Yes | No |
DELETE | Deletes the resource | Yes | No |
OPTIONS | Lists the methods allowed | Yes (safe) | Yes |
TRACE | Echoes the request back | Yes (safe) | Yes |
A safe method changes nothing, so it is idempotent automatically; PUT and DELETE change state but are still idempotent; only POST is neither.
Check yourselfWhat semantics does request-reply over TCP give?
While the connection is up, there is no loss or duplication. If the connection breaks, the client gets an exception and does not know whether the operation ran. If it does not retry, the result is at-most-once. If it retries on a new connection and the server does no filtering, the result is at-least-once; with filtering by (client id, requestId) across connections, it stays at-most-once. Never exactly-once.
Check yourselfWhich protocol fits each case? (a) A sensor reports a temperature every second and does not care if one reading is lost. (b) A server wants to free history memory as soon as each client has its reply.
(a) R: no result or confirmation is needed, and the client continues at once; maybe semantics is acceptable. (b) RRA: the Ack(requestId) tells the server the client has the reply, so the history entry can be deleted at once (and acks are cumulative, so a lost one is harmless).
Check yourselfWhy does an HTTP/1.1 browser resend a request automatically after a dropped connection only if the method is idempotent?
Because the browser cannot tell whether the server ran the request before the connection closed. Repeating an idempotent request (GET, PUT, DELETE) has the same effect as doing it once, so resending is harmless. Repeating a POST might, for example, submit an order twice.
- R = request only (maybe semantics); RR = request + reply (most exchanges, implicit acks); RRA = adds a cumulative Ack(requestId) so the server can delete history entries.
- RR over UDP with retransmission + filtering + history gives at-most-once.
- TCP removes the need for retransmission, filtering and history, but a broken connection still leaves the outcome unknown. HTTP is request-reply over TCP with a fixed set of methods.
Invocation Semantics exam
After a remote call, the caller wants to know one thing: how many times did the remote procedure actually run? For a local call the answer is always "exactly once". For a remote call it depends on how doOperation and the server deal with lost messages. The guarantee a design gives is called its invocation semantics (or call semantics): what the caller can assume about how many times the procedure was executed.
Three design choices, three semantics
The semantics follow from three choices, all of which you met as fault-tolerance measures in 6.3:
- whether to retry the request, retransmitting it until a reply arrives or the server is presumed dead;
- whether to filter duplicates at the server;
- whether to retransmit results from a history instead of re-executing the procedure.
| Retransmit request | Duplicate filtering | Re-execute procedure or retransmit reply | Call semantics |
|---|---|---|---|
| No | Not applicable | Not applicable | Maybe |
| Yes | No | Re-execute procedure | At-least-once |
| Yes | Yes | Retransmit reply | At-most-once |
What each one guarantees
Maybe
The procedure runs once or not at all, and unless a result arrives the caller cannot tell which. With no fault-tolerance measures it suffers both omission failures (a lost request or result) and crash failures (before or after execution). The result may even arrive after the timeout. Use it only where occasional failed calls are acceptable.At-least-once
The caller gets either a result, meaning the procedure ran one or more times, or an exception. Retransmission masks omission failures, but it still suffers crash failures and also arbitrary failures, because repeated execution can store or return wrong values. So it is acceptable only if all operations are idempotent. Sun RPC uses it.At-most-once
The caller gets either a result, meaning the procedure ran exactly once, or an exception, meaning it ran once or not at all. It masks omission failures and never executes more than once. Java RMI and CORBA use it (CORBA'soneway methods have maybe semantics).Why not exactly-once?
Exactly-once is the semantics of local calls (barring a process failure). A remote caller cannot achieve it in general, because when the timeout expires it cannot tell a lost request from a lost reply or from a crashed server. The three situations below look identical from the client's side.
At-least-once is cheaper, since the server needs no history, but it requires idempotent operations. Non-idempotent operations, such as a bank transfer, need at-most-once.
Voters send their votes to a server by remote call. Under maybe semantics, votes can be lost. Under at-least-once, a vote could be counted twice, unless voting is made idempotent: if the server records each voter number, a repeated vote is simply ignored. At-most-once is the safe choice.
The tutors' slides say that on an exception the procedure "was not executed at all". CDK says only that no result arrived: under at-most-once the procedure ran once or not at all, and under at-least-once it may have run any number of times, including zero. Use CDK's version in the exam (see T6-6 in 6.12).
Explain maybe, at-most-once and at-least-once semantics, in terms of a client sending a request to a server. What level of semantics does the RR protocol achieve? What level does Java RMI enforce?
- Maybe: the client sends the request once, with no retransmission. If no reply arrives, the client cannot tell "not executed" (the request was lost, or the server crashed before executing it) from "executed" (the reply was lost, or the server crashed afterwards). So the operation executes 0 or 1 times.
- At-least-once: the client retransmits after each timeout, and the server re-executes every copy it receives, because there is no duplicate filtering. A reply therefore means the operation executed one or more times; otherwise the client gets an exception. Safe only for idempotent operations; Sun RPC uses it.
- At-most-once: the client retransmits, but the server filters duplicates by requestId and resends the saved reply from a history instead of re-executing. A reply means the operation executed exactly once, and an exception means it ran 0 or 1 times. It is never executed twice.
- RR protocol: the reply acknowledges the request and the next request acknowledges the reply. Over UDP with retransmission, duplicate filtering and a reply history, RR gives at-most-once. RR that only retransmits gives at-least-once, and with no retransmission it gives maybe. Over TCP, without a retry after a broken connection, it gives at-most-once.
- Java RMI enforces at-most-once. For comparison, CORBA is also at-most-once (its
onewaymethods give maybe), and Sun RPC is at-least-once.
Check yourselfA call under at-most-once semantics throws an exception. What can the caller conclude?- The procedure was not executed.
- The procedure was executed exactly once.
- The procedure was executed once or not at all.
- The procedure may have been executed several times.
C. At-most-once never runs the procedure twice, but an exception only says no result arrived: it may have run (and the reply been lost) or not. A is the tutors' slide wording, which CDK corrects; B is what a result means; D describes at-least-once.
Check yourselfA bank's "transfer $100 from A to B" is offered over a middleware with at-least-once semantics. What could go wrong, and what should be used instead?
If a reply is lost, the client retransmits and the server, which does no duplicate filtering, executes the transfer again: $200 moves. A transfer is not idempotent, so it needs at-most-once semantics (retransmission + duplicate filtering + reply history), which never executes it twice.
Check yourselfMatch each system to its semantics: Sun RPC, Java RMI, CORBA, a CORBA oneway method, a local procedure call.
Sun RPC: at-least-once. Java RMI: at-most-once. CORBA: at-most-once. CORBA oneway: maybe. Local call: exactly-once (barring process failure).
- Maybe = no retransmission (0 or 1 runs). At-least-once = retransmit and re-execute (result means ≥ 1 run; idempotent operations only). At-most-once = retransmit, filter duplicates, resend reply from history (result means exactly 1; exception means 0 or 1).
- Exactly-once is impossible remotely in general, because a lost request, a lost reply and a crashed server look the same to the client.
- Sun RPC: at-least-once. Java RMI and CORBA: at-most-once.
Remote Procedure Call (RPC) exam
The request-reply protocol moves bytes; the programmer still has to marshal arguments and pick operation numbers. RPC hides even that. It makes calling a procedure in another process look like calling a procedure in your own program.
Remote procedure call lets a client call a procedure in a server process as if it were local. The RPC system hides three things from the programmer: the marshalling of parameters and results, the message passing, and the preservation of the call semantics. Birrell and Nelson introduced it in 1984. Its aim is a high degree of distribution transparency: hiding the fact that the system is distributed.
Building an RPC system raises three design issues: programming with interfaces, call semantics, and transparency. The subsections take them in turn and then show how RPC is implemented.
Design issue 1: programming with interfaces
A service interface specifies the procedures a server offers and the types of their arguments, without saying how they are implemented. Programming against interfaces brings three benefits:
Abstraction
No implementation details are visible to the client.Heterogeneity
Clients need not know the server's language or platform.Evolution
The implementation can change while the interface stays the same, or at least stays compatible.Distribution also imposes three constraints on what an interface can offer, because client and server are separate processes with separate memories:
- No access to another process's variables. That is why CORBA "attributes", which look like variables, are really generated getter and setter methods.
- No call by reference. Instead, each parameter is in (sent in the request), out (returned in the reply), or inout (both).
- No pointers or addresses, because a memory address is meaningless in another process.
Interface Definition Languages (IDLs). An IDL is a language-neutral notation for interfaces in which every parameter has a type and an in/out direction. It lets programs written in different languages call each other, because a compiler maps the IDL to each language. The alternative is to build RPC into a single language, as Java RMI does. Examples are Sun XDR (used for RPC), CORBA IDL (used for RMI), WSDL (web services) and Protocol Buffers. A CORBA IDL declaration looks like this:
void getPerson(in string name, out Person p);
Design issue 2: call semantics
The second issue is which invocation semantics the RPC system offers: maybe, at-least-once or at-most-once. That is decided by how the communication module implements doOperation (retry or not, filter or not, history or not), exactly as set out in 6.5.
Design issue 3: transparency exam
RPC hides marshalling, message passing and retransmission, aiming at least for location transparency (the caller need not know where the server is) and access transparency (local and remote procedures are called the same way). The question is whether it should go further and make remote calls completely indistinguishable from local ones. There are four reasons why remote calls should not be fully transparent:
Partial failure
A remote call involves a network, another computer and another process. A result may never arrive, and the client cannot tell a network failure from a server failure, so it must be able to recover.Latency
A remote call is orders of magnitude slower than a local one, so remote interactions should be minimised. Aborting a slow call (as in Argus) needs rollback on the server side.Parameter passing
Parameters are passed differently: there is no call by reference.Concurrency
Many clients may be using the server at once.Designers have taken different positions. Waldo et al. (1994) argued that the difference belongs in the interface. Argus gave remote calls a different syntax. IDLs can declare exceptions and call semantics.
Use the same syntax for local and remote calls, but express remoteness in the interface. Java's throws RemoteException on every remote method is the standard example: the call looks local, but the compiler forces the caller to handle remote failure.
Implementing RPC exam
An RPC call passes through four kinds of component on its way from client to server and back:
- The client stub procedure, one per procedure, looks like a local procedure to the client. It marshals the procedure id and arguments into a request and unmarshals the results from the reply.
- The communication module, present on both sides, runs the request-reply protocol and implements the call semantics: retransmission, duplicate filtering and reply retransmission.
- The dispatcher uses the procedure id to pick the right server stub.
- The server stub procedure, also one per procedure, unmarshals the arguments, calls the service procedure (the real implementation), and marshals the result into the reply.
The stubs and dispatcher are generated by an interface compiler from the interface definition, and the messages use the layout from 6.2. Step through one call:
- The components of one RPC. Blue is the client's own code, red the server's own code, amber the generated stubs and dispatcher, violet the communication modules.
- The client program makes an ordinary local call,
read(args), which actually calls the client stub procedure forread. - The client stub marshals the procedure id and the arguments into a byte array and hands it to the communication module (via
doOperation). - The communication module sends the Request message over the network. It retransmits on timeout if the chosen semantics say so.
- The server's communication module receives the request, filters duplicates according to the semantics, and passes it to the dispatcher.
- The dispatcher reads the procedure id and selects the matching server stub procedure.
- The server stub unmarshals the arguments and calls the service procedure, which is the real implementation of
read. - The service procedure returns its result to the server stub, which marshals it into a Reply message.
- The marshalled reply goes to the communication module (and, under at-most-once, into its history).
- The Reply message crosses the network back to the client's communication module.
doOperationreturns the reply bytes to the client stub.- The client stub unmarshals the result and returns it to the client program, exactly as a local procedure would.
Case study: Sun RPC (brief)
Sun RPC was designed for Sun NFS, the network file system of Part VIII. It runs over UDP or TCP (over UDP, messages are limited to about 8–9 KB in practice) and uses at-least-once semantics.
- IDL: its IDL is XDR, which the
rpcgencompiler turns into client stubs, a servermaincontaining the dispatcher and server stubs, and XDR marshalling procedures (for C). - No interface names: instead, every request carries a program number and a version number, and each procedure has a procedure number; procedure 0 is the null "alive?" call. Each procedure takes one input parameter (several values must be packed into a struct) and returns one result.
- Binding: each host runs a port mapper on a well-known port. Servers register (program, version, port) with it, and clients ask the port mapper on the server's host.
- Authentication: every request carries credentials, which may be none, a UNIX uid/gid, a shared key, or Kerberos; the server procedure enforces access control.
Check yourselfWhy shouldn't remote calls be completely transparent?
Remote calls differ from local ones in four ways the programmer needs to see. Partial failure: a network failure looks the same as a server failure, so clients must handle getting no result. Latency is orders of magnitude higher, so remote calls should be minimised. There is no call by reference and there are no pointers. Servers are accessed concurrently. The consensus is to keep local syntax but express remoteness in the interface, as Java does with RemoteException.
Check yourselfDraw and explain the components of an RPC implementation.
(Draw the figure in the stepper above.) The client program calls the client stub, which marshals the procedure id and arguments and passes them to the communication module. That module sends the request across the network to the server's communication module, which hands it to the dispatcher. The dispatcher selects the right server stub by procedure id, and the server stub unmarshals the arguments, calls the service procedure, and marshals the result. The reply returns the same way. The communication module is where the call semantics are implemented, and the stubs and dispatcher are generated from the IDL by an interface compiler.
Check yourselfWhich RPC component decides whether a duplicate request is executed again?- The client stub
- The dispatcher
- The communication module
- The service procedure
C. The communication module runs the request-reply protocol and implements the call semantics: retransmission, duplicate filtering and reply retransmission. The dispatcher only selects a stub by procedure id.
Check yourselfWhy can't an RPC interface include a parameter that is a pointer to a struct in the client's memory? What does it use instead?
A pointer is a memory address, and the server is a separate process with a separate memory, so the address is meaningless there. RPC passes values instead: the struct is marshalled and copied as an in parameter, and if the server must change it, it comes back as an out or inout parameter.
- RPC hides marshalling, message passing and call semantics behind a local-looking call. Interfaces give abstraction, heterogeneity and evolution, but allow no shared variables, no call by reference and no pointers (in/out/inout instead). IDLs map one interface to many languages.
- Remote calls should not be fully transparent (partial failure, latency, parameter passing, concurrency); consensus: same syntax, remoteness in the interface.
- Components: client stub, communication module (call semantics), dispatcher, server stub, service procedure; stubs and dispatcher are generated. Sun RPC: XDR + rpcgen, at-least-once, port mapper binding.
RMI and the Distributed Object Model exam
RPC calls procedures. But most modern programs are built from objects: bundles of data with the methods that act on it, referred to by references that can be passed around. RMI brings remote invocation to that world, so that a program's objects can be spread across several processes and still call each other.
Remote method invocation is RPC extended to distributed objects: an object can invoke a method of an object in another process. Every remote object has a system-wide unique reference, and remote object references can be passed as parameters and results.
RPC vs RMI exam
RPC and RMI share a great deal. Both rely on programming with interfaces, both are built on request-reply, both offer at-least-once or at-most-once semantics, and both use local syntax while exposing distribution in the interface through remote exceptions. The table shows where they differ.
| RPC | RMI | |
|---|---|---|
| Model | Procedural | Object-oriented: objects, classes, inheritance, OO design tools |
| Target | A procedure in a server | A method of a specific remote object |
| Identification | Program and procedure ids | System-wide unique remote object reference |
| Parameters | By value (in/out) | By value and by object reference |
| Machinery | Client stub, dispatcher, server stub | Proxy, dispatcher, skeleton, plus a remote reference module |
Passing by object reference matters because passing a reference to a large object avoids copying it: the receiver invokes the object remotely instead.
From objects to distributed objects
RMI extends the ordinary local object model, which has five parts:
Object references
References are first-class: they can be assigned to variables, passed as arguments and returned as results.Interfaces
An interface gives method signatures with no implementation and no constructors.Actions
Things happen through method invocations. An invocation may change the receiver's state, create objects, or invoke other objects, so an action is really a chain of invocations.Exceptions
A method can signal an error by throwing an exception for the caller to handle.Garbage collection
Objects that nothing refers to any more are reclaimed automatically.For distribution, an object's data should be reached only through its methods (encapsulation). A program's state is already split into objects, so distributing those objects is a natural step. Typically servers hold objects and clients invoke them by RMI, and a server object may itself be a client of other servers. Objects may also be replicated, migrated, or copied into a local cache. Because encapsulation is enforced, an object can protect its state against concurrent RMIs using synchronisation, and clients never see the data formats used at each site, which helps with heterogeneity.
Remote and local invocations exam
In the distributed object model, only remote objects can receive remote invocations, while all objects can receive local ones. What makes an invocation remote is crossing a process boundary: an invocation between different processes is remote even when both are on the same computer, and one within a process is local. CDK Fig 5.12 gives the standard example.
The model rests on two fundamental concepts: the remote object reference and the remote interface.
Concept 1: the remote object reference
"An identifier that can be used throughout a distributed system to refer to a particular unique remote object." It names the target of an RMI and can be passed as an argument or result.
An ROR must be unique in space and time and must never be reused, so that invoking a deleted object gives an error instead of silently reaching some other object that happens to have inherited its identifier. A typical format (CDK §4.3.4, see Part III) is:
Concept 2: the remote interface
A remote interface specifies which methods of an object can be invoked remotely. Remote processes can call only these methods, whereas local objects can also call the object's other methods. Like any interface, it has no constructors. It is defined either in CORBA IDL, which is multi-language, or in Java by extending Remote.
What else changes
The rest of the local object model carries over, with three changes.
- Actions may now cross processes. RORs are often obtained as results of RMIs: in Fig 5.12, A could get F's reference from B. A new object lives in the process where it was created, and interfaces have no constructors, so "remote instantiation" is done by calling a factory method: an ordinary remote method whose job is to create a new object in the server and return its ROR (the whiteboard's
newShapein 6.9 is one). - Garbage collection needs a distributed garbage collector that cooperates with the local ones, usually by reference counting (6.8). Without GC, remote objects must be deleted explicitly.
- Exceptions. An RMI must be able to raise distribution exceptions, for a crashed or busy server, a lost message or a timeout, as well as the application's own exceptions.
Check yourselfCompare RPC and RMI.
Shared: both use interfaces, are built on request-reply, offer at-least-once or at-most-once semantics, and use local syntax with failures exposed. RMI goes further in three ways: it adds the full OO model (objects, classes, inheritance) and system-wide remote object references; it passes parameters by reference as well as by value; and its implementation adds the remote reference module and one proxy per remote object.
Check yourselfTwo processes run on the same laptop. Object X in one invokes object Y in the other. Is this a remote invocation?- No, because both are on the same computer.
- Yes, because it crosses a process boundary.
- Only if Y's process uses a different port.
- Only if Y implements a remote interface but X does not.
B. Remote means "between different processes", whatever computer they are on. Y must therefore be a remote object, with a remote interface.
Check yourselfWhy must a remote object reference never be reused, even after its object has been deleted?
Old copies of the reference may still be held by clients. If the identifier were reused for a new object, a client invoking the "deleted" object would silently reach the new one and get wrong results. Never reusing it means such a call produces an error instead. Including the creation time in the ROR is what makes it unique in time.
Check yourselfA client wants a brand-new remote object created on a server. Why can't it just call a constructor remotely, and what does it do instead?
Interfaces (including remote interfaces) have no constructors, and a new object lives in the process that creates it. So the client calls a factory method on an existing remote object; the server creates the new object locally and returns its ROR.
- RMI = RPC for objects: the target is a method of a specific remote object, named by a system-wide unique, never-reused remote object reference, which can itself be passed as an argument or result.
- Remote = crosses a process boundary. Only remote objects receive remote invocations, and only through the methods of their remote interface.
- New objects are made by factory methods; distributed GC and distribution exceptions extend the local model.
Implementing RMI exam
The model says what RMI looks like to the programmer. This section opens the box: which pieces of software sit between object A and remote object B, what each one does, and how the system knows when a remote object can be thrown away.
The components
Communication module
Runs the request-reply protocol and provides the invocation semantics, for example at-most-once. It uses only themessageType, requestId and remoteReference fields. On the server, it gets the local reference from the remote reference module and passes the request to the dispatcher for that object's class.Remote reference module
Translates between local and remote references and creates RORs. It keeps a remote object table with an entry for each remote object the process holds and for each local proxy.Servant
An instance of a class that provides the body of a remote object. It lives in the server and handles the requests passed on by the skeleton.Proxy
Makes RMI transparent by acting as a local stand-in that implements the remote interface. Each method marshals (target ROR, operationId, args), sends the request, awaits the reply, unmarshals it and returns.Dispatcher
Uses the operationId to select the skeleton method, numbering operations the same way as the proxy does.Skeleton
Unmarshals the arguments and invokes the servant, then marshals the result or exception into the reply.| RPC component | RMI equivalent |
|---|---|
| Client stub procedure | Proxy |
| Dispatcher | Dispatcher |
| Server stub procedure | Skeleton |
| Service procedure | Servant |
| Communication module | Communication module |
| (nothing) | Remote reference module: what RMI adds |
- Eight participants. Object A holds a proxy for B, created when A's process first received B's remote object reference.
- A calls
B.m(args). As far as A can tell this is a normal call on a local object: the proxy implements B's remote interface. - The proxy marshals (target ROR, operationId, args) into a request and hands it to the communication module.
- The Request message crosses the network. The communication modules run the request-reply protocol, so this is where at-most-once (retransmission, filtering, history) is enforced.
- The server's communication module asks the remote reference module to translate the ROR in the request into a local reference to the servant, using its remote object table.
- It passes the request to the dispatcher for B's class.
- The dispatcher uses the operationId to select the matching method of the skeleton.
- The skeleton unmarshals the arguments and invokes method
mon the servant, the real object B. - The servant returns its result (or throws an exception) to the skeleton.
- The skeleton marshals the result or exception into the reply and hands it to the communication module.
- The Reply message crosses the network back to the client's communication module.
- The reply bytes are passed back to the proxy, which was waiting in
doOperation. - The proxy unmarshals the result and returns it to A (or throws the exception), completing what looked to A like a local call.
The remote reference module and its table
The remote reference module is what lets references travel. The first time a remote object is passed in a message, the module creates an ROR for it and adds a table entry. When an ROR arrives in a message, it looks the reference up and returns the corresponding local proxy or object; if there is none, a new proxy is created and added to the table.
The rest of the machinery
Interface compiler
The proxy, dispatcher and skeleton classes are produced by an interface compiler, such as a CORBA IDL compiler.Dynamic invocation
If a client receives an ROR whose interface was unknown at compile time, it has no static proxy, so it calls a genericdoOperation(ROR, method, args), which needs method names and argument types (CORBA gets these from the Interface Repository). Less convenient, but it suits types that could not be foreseen, such as new whiteboard shapes. Dynamic skeletons are the server-side equivalent. Java RMI solves the same problem with class downloading and a generic dispatcher.Server program
Contains the servant, dispatcher and skeleton classes. Itsmain creates at least one servant and usually registers one with the binder; that servant then gives access to the rest. Because interfaces have no constructors, other servants are created by factory methods.Client program
Contains the proxy classes and uses the binder to get its first ROR.Binder
A separate service mapping textual names to RORs. Servers register objects with it and clients look them up. Examples: Java's RMIregistry and the CORBA Naming Service.Server threads
Each remote invocation usually gets its own thread, so a slow call does not delay the others. The implementer must therefore make the object's state safe for concurrent execution (Part IV).Activation, persistence and location
Keeping every server running all the time just in case someone calls it is wasteful. Activation starts objects on demand, much as inetd starts servers on demand. An object is active if it can be invoked in a running process, and otherwise passive, in which case it consists of its implementation plus its marshalled state. Activation creates an instance from that passive form. Java provides Activatable for this.
A persistent object is one that survives between activations of processes. A persistent object store keeps the marshalled state on disk, activates objects transparently and passivates them again, for example at the end of a transaction. An object counts as persistent either if it is reachable from a persistent root (as in Persistent Java and JDO) or if it inherits from a persistence class (as in Arjuna).
Object location. If objects can migrate between processes, an ROR is no longer an address. A location service then maps RORs to their probable current locations, for example by caching and broadcast (as in Clouds and Emerald), optionally with forward location pointers left behind when an object moves.
Distributed garbage collection exam
In one Java program, the garbage collector frees an object once nothing refers to it. With RMI, references to an object can be held in other processes, which the local collector cannot see. The aim of distributed garbage collection (CDK §5.4.3) is that a remote object survives while any local or remote reference to it exists, and is reclaimed once none does.
Java's algorithm achieves this with reference counting with leases. A lease is permission granted for a limited period only, which must be renewed to continue. The four steps:
- The server keeps
B.holders, the set of client JVMs holding proxies for B. This can simply be an extra column in the remote object table. - When client C first receives an ROR for B, it calls
addRef(B)and then creates the proxy; the server adds C toB.holders. - When C's local GC finds that the proxy is unreachable, C calls
removeRef(B)and deletes the proxy; the server removes C from the set. - When
B.holdersis empty and there are no local references either, the server's local GC reclaims B.
- Remote object B lives in the server. Nobody outside holds a proxy for it yet, so
B.holdersis empty. - C1 receives an ROR for B (from the registry or as the result of an RMI). Before creating a proxy, it calls
addRef(B), and the server adds C1 toB.holders. The lease for C1 starts now. - Only after
addRefhas succeeded does C1 create its proxy for B. - C2 does the same:
addRef(B), then a proxy. The set is now {C1, C2}. - C1's local garbage collector finds that nothing in C1 uses the proxy any more. C1 calls
removeRef(B)and deletes the proxy; the set is {C2}. - C2 crashes before it can call
removeRef. Reference counting alone would keep B alive for ever, but C2 also stops renewing its lease, so the lease expires and C2 is dropped from the set. B.holdersis empty and the server has no local references to B, so the server's own local GC reclaims B.
Two properties keep the algorithm cheap. Its calls are pairwise request-reply calls between remote reference modules, with at-most-once semantics, so no global synchronisation is needed. And extra calls happen only when proxies are created or deleted, not on every RMI.
Three problems still have to be handled:
- A race. One client's
removeRef(B)may arrive before another client'saddRef(B)and empty the set too early. The fix: when an ROR is sent whileB.holdersis empty, the server adds a temporary entry, which stays until theaddRefarrives. - Communication failure.
addRefandremoveRefare idempotent. IfaddRefthrows an exception, the client creates no proxy and callsremoveRef, which is correct whether or not theaddRefactually took effect. - Client crash or a lost
removeRef. Handled with leases. The server leases each reference for a limited period, starting ataddRef, and stores (client JVM id, lease period). The lease ends either at expiry or atremoveRef. Because clients must renew their leases before expiry, a crashed client's entry simply lapses.
addRef, so B is never unprotected.Jini leases generalise this idea: a resource is granted for a limited time, instead of running "are you still interested?" protocols. Jini leases are negotiable, whereas Java RMI leases are not.
Check yourselfExplain the roles of the proxy, dispatcher, skeleton, remote reference module and communication module.
- The proxy is a local stand-in implementing the remote interface. It marshals (ROR, operationId, args), sends the request, and unmarshals the reply.
- The dispatcher (one per class) selects the skeleton method by operationId.
- The skeleton (one per class) unmarshals the arguments, invokes the servant, and marshals the result or exception.
- The remote reference module keeps the remote object table, translates between local and remote references, and creates proxies.
- The communication module runs the request-reply protocol and provides the invocation semantics.
Check yourselfA server process holds 50 remote objects, all of class Account. A client holds references to 3 of them. How many dispatchers and skeletons does the server need, and how many proxies does the client have?
The server needs one dispatcher and one skeleton, because there is one of each per class. The client has 3 proxies, one per remote object whose reference it holds.
Check yourselfExplain Java distributed garbage collection.
The server keeps B.holders, the set of clients holding proxies for B. A client calls addRef(B) before creating a proxy, and removeRef(B) when its local GC drops the proxy. B is collected when holders is empty and there are no local references to it. The calls are pairwise and at-most-once, so no global synchronisation is needed. A temporary entry covers the race between one client's removeRef and another's addRef, and addRef and removeRef are idempotent. Finally, leases, which clients renew, handle client crashes.
Check yourselfWhy does reference counting alone fail if a client machine crashes, and how do leases fix it?
A crashed client never calls removeRef, so its entry would stay in B.holders for ever and B would never be collected. With leases, each entry is valid only for a limited period and must be renewed; a crashed client stops renewing, its lease expires, and the entry is removed without any message from the client.
- Client: proxy (one per remote object) + remote reference module + communication module. Server: communication module + remote reference module + dispatcher and skeleton (one per class) + servant. The remote reference module (remote object table) is what RMI adds to RPC.
- Binder maps names to RORs; factory methods create further servants; each call usually gets its own server thread; activation and persistent stores let objects sleep on disk.
- Distributed GC:
B.holderswithaddRef/removeRef, a temporary entry for the race, idempotent calls, and renewable leases for crashed clients.
Case Study: Java RMI exam
Everything so far has been the general model. Java RMI is the real system the exam asks you to program, so this section shows how each idea from 6.7 and 6.8 appears in it (CDK §5.5): which interfaces you extend, how arguments travel, what the registry does, and what really happens during a call.
Key characteristics
Local look, not fully transparent
Remote and local calls have the same syntax and type checking. It is deliberately not fully transparent: callers must handleRemoteException, and remote interfaces must extend Remote.Single-language
Java at both ends, so there is no IDL to learn, unlike CORBA. The Java interface is the interface definition.JRMP over TCP
The wire protocol is JRMP (Java Remote Method Protocol) over TCP; marshalling uses Java object serialization.At-most-once
A result means the method ran exactly once; aRemoteException means it ran once or not at all.Runtime threads
Many clients may invoke an object at once, and the RMI runtime runs calls on its own threads, so shared state needssynchronized, locks or concurrent collections.Remote interfaces and parameter passing exam
A remote interface extends java.rmi.Remote, and every method declares throws RemoteException; methods may declare application exceptions too. CDK's running example is a shared whiteboard (CDK Fig 5.16), with two remote interfaces:
import java.rmi.*;
import java.util.Vector;
public interface Shape extends Remote {
int getVersion() throws RemoteException;
GraphicalObject getAllState() throws RemoteException; // returns a COPY (Serializable)
}
public interface ShapeList extends Remote {
Shape newShape(GraphicalObject g) throws RemoteException; // g copied; result is a remote ref
Vector allShapes() throws RemoteException; // Vector of Shape remote refs
int getVersion() throws RemoteException;
}
Reading it line by line: Shape is one shape on the whiteboard, itself a remote object. getVersion returns the version number the shape was created with, and getAllState returns the shape's drawing data as a GraphicalObject, an ordinary serializable class, so the caller receives a copy. ShapeList is the whiteboard as a whole: newShape is a factory method that adds a shape and returns a remote reference to it, allShapes returns a Vector of remote references to every shape, and getVersion returns the whiteboard's current version so clients can tell whether anything new has been drawn.
In Java RMI, arguments are inputs and the result is the single output. Any Serializable object can be passed (a class is serializable if it implements java.io.Serializable, meaning Java knows how to flatten it into bytes); primitives and remote objects count as serializable. How an argument travels depends on its declared type:
| Declared type | Passed | Effect |
|---|---|---|
A remote interface (e.g. Shape) | By reference: serialized as its ROR | The receiver gets a proxy and invokes the original object |
A non-remote Serializable (e.g. GraphicalObject) | By value (a copy) | A new object is created at the receiver and may diverge from the original |
| Neither | Cannot be passed | Marshalling error at run time |
To make this work, Java RMI changes ordinary serialization in two ways. First, a Remote object is replaced by its ROR, which includes the name of its class. Second, class descriptions are annotated with a URL from which the class can be downloaded.
g (a GraphicalObject) goes by value; the result (a Shape) comes back by reference. newShape is a factory method: it is how a client gets a new remote object created.Downloading of classes
If the receiver lacks the class of an object passed by value, or of a proxy, the code is downloaded automatically from the URL in the class annotation. This has two advantages: users need not all keep the same set of classes, and new classes, such as a TextObject subclass of GraphicalObject, are used transparently by the server and by other clients.
The catch is that downloaded code is a security risk, so CDK installs an RMISecurityManager; without one, classes are loaded only from the local classpath. Modern Java (not in CDK): RMISecurityManager is deprecated and codebase loading is off by default, so put the shared classes on both classpaths.
RMIregistry, the Java binder exam
A client needs a first ROR before it can call anything; after that, RORs can arrive as results. The RMIregistry provides the first one. An RMIregistry runs on each server computer hosting remote objects. It maps URL-style names to RORs of the objects on that host, while the objects themselves stay in their servers.
The registry is accessed through java.rmi.Naming, using names of the form:
| Method | Used by | What it does |
|---|---|---|
rebind(String name, Remote obj) | Server | Binds a name to an object, replacing any existing binding |
bind(String name, Remote obj) | Server | Binds a name, but throws an exception if the name is already bound |
unbind(String name) | Server | Removes a binding (CDK shows an extra obj parameter, but the real API takes only the name) |
Remote lookup(String name) | Client | Returns the ROR (a proxy) bound to the name |
String[] list() | Either | Returns the names bound in the registry |
All of these can throw RemoteException. Besides Naming, there is LocateRegistry: LocateRegistry.getRegistry(host, port) returns a Registry, which is itself a remote object with the same methods, and LocateRegistry.createRegistry(port) starts a registry inside the server's own JVM.
bind, rebind and unbind are accepted only from the registry's own host. A server must register its objects with the registry on its own machine; a client anywhere can lookup.
Tracing a Java RMI call exam
Here is the complete life of one call, from the server creating its object to the client receiving the result. Step through it slowly; this trace (or the tutors' version of it in 6.12) is a likely "explain how Java RMI works" answer.
- The five participants. Only the client code and the servant are written by the programmer; the proxy, registry and runtime come with Java.
- Export. The server does
new RemoteObj(). Exporting it makes the RMI runtime listen on a port and enter the object in its object table, which gives the object an ROR. - Bind. The server binds the name
myROto the object's remote reference in the registry. The registry stores only the reference; the object stays in the server. - Lookup. The client calls
lookup("myRO"). This is itself an RMI: the registry is a remote object on a well-known port (1099), which is how the client bootstraps its first ROR. - Proxy. The registry returns the remote reference. Deserializing it creates a proxy whose class implements the remote interface; that class is either generated dynamically or downloaded.
- The client calls
method1(args)on the proxy: an ordinary Java call. - The proxy serializes the method and the arguments (copies, or RORs for remote objects) and sends the request over TCP using JRMP.
- A runtime thread on the server deserializes the request, finds the target object and invokes it through the generic dispatcher, using reflection.
- The servant returns a value or throws an exception.
- The result or exception is serialized and sent back as the reply.
- The proxy returns the value to the client, or re-throws the application exception. If communication failed anywhere, the client gets a
RemoteExceptioninstead.
The slides describe the same path as an eight-step invocation lifecycle:
- The client calls the stub (the proxy).
- The stub serializes the call.
- The dispatcher deserializes it.
- The dispatcher calls the method.
- The method returns.
- The dispatcher serializes the result.
- The stub deserializes it.
- The stub hands the result back to the client.
Design and implementation of Java RMI
Java RMI originally used all the components of 6.8, but two have since been simplified away. Since Java 1.2, reflection (a program's ability to inspect and call its own classes and methods at run time) has provided a generic dispatcher, so skeletons are unnecessary. And before J2SE 5.0, proxies were generated by the rmic compiler from the compiled server class, whereas now they are generated dynamically at run time.
Method object, a single dispatcher can call any method of any servant, which is why modern Java RMI has no skeletons.Class hierarchy. The server-side classes form a chain: RemoteObject ← RemoteServer ← {UnicastRemoteObject, Activatable} ← your servant class. At the top, RemoteObject holds the ROR and provides equals (which compares RORs), toString, and readObject/writeObject.
UnicastRemoteObject (or Activatable) gives it all the remote-object machinery; implementing your remote interface (dashed triangle) says which methods clients may call.Check yourselfHow are parameters passed in Java RMI? Use the whiteboard example.
Arguments are inputs and the result is the single output. Values of a remote-interface type, such as Shape, are passed by reference: the ROR is sent and becomes a proxy at the receiver. Serializable non-remote objects, such as GraphicalObject, are copied, so the copy may diverge from the original. If the receiver is missing a class, it is downloaded via the URL annotation. In the whiteboard, newShape(g) copies g to the server and returns a remote reference to a new ShapeServant.
Check yourselfTrace a Java RMI call.
The server creates and exports the object, then rebinds it in the RMIregistry. The client calls lookup("//host:1099/name") and gets an ROR, which is deserialized as a proxy. When the client invokes a method, the proxy serializes the method and arguments and sends them over JRMP/TCP. The server runtime's generic dispatcher finds the object and invokes it by reflection on a runtime thread. The result or exception is serialized back, and the proxy either returns the result or throws, with a RemoteException on communication failure.
Check yourselfA server calls bind("Dictionary", obj), but the name "Dictionary" is already bound to an old object. What happens, and what should it have called?
bind throws an exception because the name is already bound. rebind would have replaced the old binding, which is why servers usually use rebind.
Check yourselfWhat does registry.lookup("HelloService") return?- A copy of the HelloImpl servant object
- A proxy (stub) that implements the remote interface
- The servant's IP address and port as a string
- The registry itself
B. The registry holds only the remote reference; deserializing it at the client creates a proxy implementing Hello. The servant never leaves the server (A is wrong). That is why the result is cast to the interface type, (Hello) registry.lookup(…).
Check yourselfA client changes a field of the GraphicalObject it got from shape.getAllState(). Does the shape on the server change? What if it called a method on the Shape instead?
No: GraphicalObject is a non-remote Serializable type, so the client received a copy, and changing it changes nothing on the server. A Shape is a remote interface type, so the client holds a proxy, and calling one of its methods invokes the original object on the server.
- Java RMI: same syntax as local calls but deliberately not transparent (
extends Remote,throws RemoteException); single-language, JRMP over TCP, Java serialization, at-most-once, runtime threads. - Remote-interface types pass by reference (ROR → proxy); Serializable types pass by value (copy); classes can be downloaded from an annotated URL.
- RMIregistry maps
//host:port/name(default port 1099) to RORs: serverbind/rebind(local host only), clientlookup. Reflection gives a generic dispatcher (no skeletons); proxies are generated at run time (normic).
Build a Java RMI Program, Mark by Mark 10-mark question
The exam's one 10-mark question asks you to write a complete Java RMI client and server, by hand, from memory. The 2026 practice exam's version (Q11) prints its marking rubric, and this section builds a complete program in the order that rubric rewards, so that every piece you write visibly earns a mark. The example is the guide's Hello program (§6.5.4); at the end you apply the same skeleton to the practice exam's dictionary service.
| Rubric item | Marks | Where you earn it below |
|---|---|---|
| Defining interfaces for the services | 2 | Step 1 · Hello.java |
| Implementing the RMI interfaces | 3 | Step 2 · HelloImpl.java |
| Binding the RMI object to the registry | 1 | Step 3 · HelloServer.java |
| Looking up the remote object and invoking remote methods | 2 | Step 4 · HelloClient.java |
| Handling two distinct types of errors or exceptions | 2 | Step 5 · the catch blocks |
The rubric comes from the official practice paper. The worked answers to that paper (here and in Part XI) are the guide's own, not official: staff released no solutions.
createRegistry), so no separate registry process is needed.Step 1 · The remote interface 2 marks
Start with the contract both programs share. It lists the methods clients may call remotely.
Hello.java · needed by BOTH client and server
// Hello.java -- remote interface, needed by BOTH client and server
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface Hello extends Remote { // marks it as a remote interface
String sayHello(String name) throws RemoteException; // every remote method throws RemoteException
}
extends Remote:java.rmi.Remoteis a marker interface with no methods; extending it is what tells Java RMI thatHello's methods may be invoked from another JVM.throws RemoteExceptionon every method: any remote call can fail in transit, and Java forces the caller to deal with that. This is "remoteness expressed in the interface" from 6.6.- Argument and result types must be serializable or remote:
Stringis serializable, so it is passed by value.
Step 2 · The servant 3 marks
Next, the class that really does the work: the servant, the object that lives in the server and implements the interface.
HelloImpl.java · server only
// HelloImpl.java -- servant
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
public class HelloImpl extends UnicastRemoteObject implements Hello {
private int calls = 0; // shared state
public HelloImpl() throws RemoteException {
super(); // exports the object on an anonymous port
}
@Override
public synchronized String sayHello(String name) { // RMI threads may call concurrently
calls++;
return "Hello, " + name + " (call " + calls + ")";
}
}
extends UnicastRemoteObject: inherits the remote-object machinery (see the class hierarchy in 6.9).implements Hello: promises to provide every method of the remote interface.- The constructor calls
super(), which exports the object on an anonymous port so it can receive remote calls. Exporting can fail, so the constructor must declarethrows RemoteException. synchronized: the RMI runtime may run calls from different clients on different threads at the same time (Part IV), andcalls++is a read-modify-write on shared state, so it must be protected.- The implementation of
sayHellomay leave outthrows RemoteException, because the method body itself throws nothing remote; the interface keeps it for callers.
Step 3 · The server: create, export, bind 1 mark
HelloServer.java · server only
// HelloServer.java
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class HelloServer {
public static void main(String[] args) {
try {
Registry registry = LocateRegistry.createRegistry(1099); // registry inside this JVM
Hello obj = new HelloImpl(); // create and export the servant
registry.rebind("HelloService", obj); // name -> remote reference
System.out.println("Server ready"); // the JVM stays alive: RMI listener threads keep running
} catch (Exception e) {
e.printStackTrace();
}
}
}
createRegistry(1099)starts a registry inside the server's own JVM on the default port, so no separate registry process is needed.new HelloImpl()creates and exports the servant (the constructor in step 2 did the exporting). The variable is typed as the interfaceHello.rebind("HelloService", obj)is the line that earns the binding mark: it maps the name to the servant's remote reference, replacing any old binding.mainthen ends, but the program keeps running: the RMI runtime's listener threads keep the JVM alive, waiting for calls.
Step 4 · The client: look up and invoke 2 marks
HelloClient.java · client only
// HelloClient.java
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class HelloClient {
public static void main(String[] args) {
String host = (args.length > 0) ? args[0] : "localhost";
try {
Registry registry = LocateRegistry.getRegistry(host, 1099); // proxy for the remote registry
Hello stub = (Hello) registry.lookup("HelloService"); // proxy implementing Hello
System.out.println(stub.sayHello("Alice")); // looks local, is an RMI
} catch (RemoteException e) { // communication or server failure
System.err.println("Remote failure: " + e.getMessage());
} catch (NotBoundException e) { // name not registered
System.err.println("Not bound: " + e.getMessage());
}
}
}
getRegistry(host, 1099)returns a proxy for the remote registry on the server's host.lookup("HelloService")returns the remote reference as aRemote, deserialized as a proxy; the cast(Hello)gives it the interface type. The name must match the server'srebindexactly.stub.sayHello("Alice")looks like a local call but is a full remote invocation (6.9). Run against the server above, the first call printsHello, Alice (call 1).
Step 5 · Two distinct kinds of exception 2 marks
The rubric wants two distinct kinds of error handled, so make each catch handle a different kind of failure and say so in a comment. The client above already does: RemoteException is a communication failure and NotBoundException is a naming failure. An application exception declared in the interface would be a third kind.
RemoteException does not tell you: whether the method ran.If you also declare an application exception that extends Exception, list the more specific exceptions first. A catch (Exception e) before the others would swallow everything and leave the later blocks unreachable (a compile error), and one generic catch does not show two distinct kinds.
Compile and run
Since J2SE 5.0 there is no rmic step: proxies are generated at run time. Compile everything and start the server before the client:
javac *.java java HelloServer # terminal 1: prints "Server ready" and keeps running java HelloClient localhost # terminal 2: prints "Hello, Alice (call 1)"
Variations worth knowing
The equivalent java.rmi.Naming calls use the URL-style names from 6.9:
Naming.rebind("//localhost:1099/HelloService", obj); // server
Hello stub = (Hello) Naming.lookup("rmi://host/HelloService"); // client
Either style is acceptable in the exam; with Naming a registry must already be running on the server's host.
If the servant must extend some other class, it cannot extend UnicastRemoteObject. Export it explicitly instead, which is the style CDK's whiteboard uses:
ShapeList stub = (ShapeList) UnicastRemoteObject.exportObject(servant, 0); // 0 = anonymous port
UnicastRemoteObject objects live only as long as their process. Activatable objects outlive it: they can be passivated and re-activated on demand (6.8).
- Interface
extends Remote; every methodthrows RemoteException. - Servant
extends UnicastRemoteObject implements YourInterface; constructorthrows RemoteExceptionand callssuper(). - Shared state in the servant is thread-safe (
synchronizedor a concurrent collection). - Server:
createRegistry(1099)(orgetRegistry),newservant,rebind("Name", servant). - Client:
getRegistry(host, 1099),(YourInterface) registry.lookup("Name")with the same name, then call the methods. - At least two distinct
catchblocks, each commented with the kind of failure (communication, naming, application).
Check yourselfSpot the four mistakes in this attempt:
public interface Counter {
int next();
}
public class CounterImpl implements Counter {
private int n = 0;
public int next() { return ++n; }
}
// server: registry.rebind("Counter", new CounterImpl());
// client: Counter c = (Counter) registry.lookup("counter");
public interface Counter {
int next();
}
public class CounterImpl implements Counter {
private int n = 0;
public int next() { return ++n; }
}
// server: registry.rebind("Counter", new CounterImpl());
// client: Counter c = (Counter) registry.lookup("counter");Counterdoes not extendRemote, so it is not a remote interface.next()does not declarethrows RemoteException.CounterImplneither extendsUnicastRemoteObject(with a constructor thatthrows RemoteException) nor is exported withexportObject, so it can never receive remote calls. Also,++non shared state should besynchronized.- The client looks up
"counter"but the server bound"Counter": names are case-sensitive, solookupthrowsNotBoundException. (And neither side handles any exception.)
Exam rehearsal · 10 marks2026 practice exam Q11. Write a simple Java RMI program that demonstrates the invocation of remote object services, implementing a dictionary service with three operations: ADD, which adds a new word and its meaning; SEARCH, which returns the meaning of a word passed as an argument; and DELETE, which deletes the word passed as an argument. Write both the server and the client programs. Marks: binding the RMI object to the registry (1); defining interfaces for the services (2); implementing the RMI interfaces (3); looking up the remote object and invoking remote methods (2); handling two distinct types of errors or exceptions (2). Try it on paper first.
Worked answer (the guide's, not official). It follows the shape of the Hello program exactly; the comment on each file says which rubric item it earns. The guide reports that the five files compile and run as written (checked with Java 21). In the exam you can shorten imports and comments, but keep extends Remote, throws RemoteException, the registry calls and the catch blocks, because those are what the marks are for.
// DictionaryService.java -- remote interface, needed by BOTH sides [interfaces: 2 marks]
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface DictionaryService extends Remote {
boolean add(String word, String meaning) throws RemoteException; // ADD: false if present
String search(String word) // SEARCH: the meaning
throws RemoteException, WordNotFoundException;
boolean delete(String word) throws RemoteException; // DELETE: false if absent
}
// WordNotFoundException.java -- application exception, sent back to the client by RMI
public class WordNotFoundException extends Exception { // Exception is Serializable
public WordNotFoundException(String word) {
super("No such word: " + word);
}
}
// DictionaryServant.java -- implements the remote interface [implementation: 3 marks]
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.concurrent.ConcurrentHashMap;
public class DictionaryServant extends UnicastRemoteObject implements DictionaryService {
// RMI may run calls from different clients on different threads: use a thread-safe map
private final ConcurrentHashMap<String, String> words = new ConcurrentHashMap<>();
public DictionaryServant() throws RemoteException {
super(); // exports the object to receive remote calls
}
@Override
public boolean add(String word, String meaning) {
return words.putIfAbsent(word.toLowerCase(), meaning) == null; // atomic check-then-add
}
@Override
public String search(String word) throws WordNotFoundException {
String meaning = words.get(word.toLowerCase());
if (meaning == null) throw new WordNotFoundException(word); // re-thrown at client
return meaning;
}
@Override
public boolean delete(String word) {
return words.remove(word.toLowerCase()) != null;
}
}
// DictionaryServer.java -- creates the servant and binds it [binding: 1 mark]
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class DictionaryServer {
public static void main(String[] args) {
try {
Registry registry = LocateRegistry.createRegistry(1099); // registry in this JVM
DictionaryService dict = new DictionaryServant(); // create and export
registry.rebind("Dictionary", dict); // name -> remote ref
System.out.println("Dictionary server ready");
} catch (RemoteException e) { // e.g. port 1099 in use, export failed
System.err.println("Server failed to start: " + e.getMessage());
}
}
}
// DictionaryClient.java [lookup and invocation: 2 marks; errors and exceptions: 2 marks]
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class DictionaryClient {
public static void main(String[] args) {
String host = (args.length > 0) ? args[0] : "localhost";
try {
Registry registry = LocateRegistry.getRegistry(host, 1099);
DictionaryService dict =
(DictionaryService) registry.lookup("Dictionary"); // returns a stub
System.out.println(dict.add("socket", "an endpoint of a two-way link")
? "Added" : "Already in the dictionary");
System.out.println("socket: " + dict.search("socket"));
System.out.println(dict.delete("socket") ? "Deleted" : "Not in the dictionary");
System.out.println("socket: " + dict.search("socket")); // throws: word gone
} catch (WordNotFoundException e) { // application error: no such word
System.err.println(e.getMessage());
} catch (NotBoundException e) { // naming error: nothing bound as "Dictionary"
System.err.println("Service not registered: " + e.getMessage());
} catch (RemoteException e) { // communication failure: server or registry down
System.err.println("Remote failure: " + e.getMessage());
}
}
}
Why it is built this way, a sentence each if you have room:
- The interface is the contract both programs share. Every method declares
RemoteException, because any remote call can fail in transit. SEARCH also declares an application exception, which RMI marshals back to the client and re-throws there. - The servant extends
UnicastRemoteObject, whose constructor exports the object. Calls from different clients may run on different threads at once, so the words are kept in aConcurrentHashMap, and ADD usesputIfAbsentso that the check and the insert happen as one atomic step. - The server starts a registry inside its own JVM with
createRegistryand binds the servant as "Dictionary".Naming.rebind("//localhost/Dictionary", dict)would be equally acceptable. - The client gets a stub from
lookupand calls ADD, SEARCH and DELETE as if it were a local object. - The kinds of error are kept apart.
RemoteExceptionis a distribution failure (registry or server down, connection lost), after which the client cannot tell whether the operation ran (at-most-once).NotBoundExceptionis a naming failure.WordNotFoundExceptionis a third, application-level error.
- Interface (
extends Remote, every methodthrows RemoteException) → servant (extends UnicastRemoteObject, constructorthrows RemoteException, thread-safe state) → server (createRegistry,new,rebind) → client (getRegistry, castlookup, call). - Catch at least two distinct kinds:
RemoteException(communication),NotBoundException(naming), and optionally an application exception. - Rubric: interface 2, implementation 3, binding 1, lookup + invoke 2, exceptions 2.
Callbacks: The Shared Whiteboard exam
RMI as described so far is one-way: clients call servers. But often a client wants to know when something changes on the server, for example when another user draws on a shared whiteboard. The whiteboard example shows the two ways to find out: polling (keep asking) and callbacks (be told).
The application
Users share a drawing surface. The server stores every shape with a version number, incremented for each new shape, so clients can fetch only the shapes they lack. On the server, ShapeListServant implements ShapeList, keeping the shapes in a Vector. Its newShape(g) is a factory method: it increments the version, creates a ShapeServant(g, version) remote object, stores it, and returns its reference. The server's main creates the servant, exports it and rebinds it.
A polling client works in four steps:
- It calls
lookupto get theShapeList. - It calls
allShapes(), thengetAllState()on each shape to display it. - It calls
newShape(g)when the user draws. - It periodically calls
getVersion()to detect shapes added by other users.
With callbacks, instead of clients polling, the server notifies the clients when the event occurs. In RMI this takes three steps:
- The client creates a callback object, a remote object with a method the server can call. The client thereby also becomes an RMI server.
- The server offers a
registermethod, through which clients give it the RORs of their callback objects, and it keeps a list of them. - When the event happens (here, a new shape), the server invokes every registered callback.
The callback code (CDK's version, corrected):
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
public interface WhiteboardCallback extends Remote { // CDK prints "implements Remote", a typo
void callback(int version) throws RemoteException;
}
// Added to ShapeList: int register(WhiteboardCallback cb) throws RemoteException; // returns callbackId
// void deregister(int callbackId) throws RemoteException;
class WhiteboardCallbackImpl extends UnicastRemoteObject implements WhiteboardCallback {
WhiteboardCallbackImpl() throws RemoteException { super(); } // exported: client acts as a server
public void callback(int version) { // runs on an RMI thread in the client
System.out.println("New shapes up to version " + version);
}
}
// Client: int id = shapeList.register(new WhiteboardCallbackImpl()); ... shapeList.deregister(id);
// Server: on each new shape, call cb.callback(version) for every registered cb;
// on RemoteException, remove that client from the list.
WhiteboardCallbackis an ordinary remote interface, so it must extendRemote(CDK's "implements" is a typo: an interface cannot implement another).WhiteboardCallbackImplis a servant that lives in the client. Its constructor exports it, which is exactly what turns the client into an RMI server.registerpasses the callback object to the server. Its declared type is a remote interface, so it travels by reference: the server receives a proxy it can call back on.callbackruns on an RMI runtime thread inside the client, not on the client's main thread.- On the server, a
RemoteExceptionfrom a callback means that client has gone, so it is removed from the list.
- Three whiteboard clients and the server. The whiteboard is at version 3 and the server's callback list is empty.
- Each client creates and exports a callback object. From now on each client is also an RMI server, able to receive calls.
- Each client calls
register(cb). The callback object travels by reference, so the server's list now holds three remote references to callback objects. - C1's user draws: C1 calls
newShape(g)and the version becomes 4. - The server invokes
callback(4)on every registered callback object, one after another. These are synchronous RMIs, so a slow client (say C2) delays everyone after it in the list. - C3 had exited without deregistering: its entry was stale. The callback throws
RemoteException, and the server removes C3 from the list. (The cleaner fix for stale entries is leases.)
| Polling | Callbacks | |
|---|---|---|
| Who starts the exchange | The client, repeatedly | The server, when the event occurs |
| Drawbacks | Server load; late updates | Stale callback list; a slow client delays the rest |
Polling has two drawbacks: constant polling degrades server performance, and clients cannot update users promptly. Callbacks bring two problems of their own. First, the server's callback list goes stale when clients exit without deregistering; the fix is leases. Second, the server makes a series of synchronous RMIs, one after another, so a slow client delays the rest; the fixes are threads, asynchronous calls (CORBA oneway) or indirect communication (Part VII).
Check yourselfWhat are callbacks, why use them, and what are their problems?
With callbacks, the client exports a callback object and registers it with the server, which invokes it when an event occurs. This avoids the load that polling puts on the server and gives timely updates. There are two problems: registrations can go stale (fixed with leases), and the server must make sequential synchronous RMIs to many clients (fixed with threads, asynchronous calls or indirect communication).
Check yourselfIn the callback design, which statement is true?- The server polls the clients for changes.
- The callback object is copied to the server by value.
- The client must export a remote object, so it acts as an RMI server too.
- Callbacks remove the need for the server to know which clients exist.
C. The callback object is a remote object exported by the client. It is passed to register by reference (B is wrong), and the server keeps a list of these references, so it does know its clients (D is wrong).
- Polling: clients call
getVersion()periodically; costs server load and gives late updates. - Callbacks: client exports a callback object (becoming an RMI server),
registers it, and the server callscallback(version)on every registered object when a shape is added. - Callback problems: stale registrations (fix: leases) and a series of synchronous RMIs (fix: threads, asynchronous calls, indirect communication).
Pitfalls and Tutorial Answers
This last section collects the mistakes that most often cost marks on this topic, and the official tutorial answers for Weeks 7 and 8.
- At-most-once is not exactly-once: after an exception, the procedure ran once or not at all. Nor does TCP make RPC exactly-once.
- In Java RMI, only remote objects are passed by reference; Serializable objects are copied.
- The registry holds references, not objects.
- There is a proxy for each remote object, but a dispatcher and a skeleton for each class.
- Idempotent is not read-only: a write at a fixed position is idempotent even though it changes state.
- "Remote" means a different process, not necessarily a different computer.
Tutorial 6 part 2 and Tutorial 7 official answers
These answers come from the tutors' official solutions on Ed: Tutorial 06 (Week 7, on remote invocation) and Tutorial 07 (Week 8, which repeats the Java RMI question). Staff have confirmed that tutorial content is examinable. Part 1 of Tutorial 06 (operating systems) is in Part V.
T6-5 · officialExplain three types of protocol used for remote invocation.
- Request (R): the client sends a message.
- Request-reply (RR): the client sends a message and the server sends a reply.
- Request-reply-acknowledgement (RRA): works as RR does, but the client acknowledges the reply once it receives it.
Details are in 6.4.
The tutors list three issues, each with two options:
| Issue | Option 1 | Option 2 |
|---|---|---|
| Request timeout (no reply arrives) | Retry the request after the timeout | Do nothing |
| Reply lost | Retransmit the result after a timeout | Do nothing |
| Duplicate requests or replies | Discard the duplicates | Perform the action again, if the operation is idempotent |
The combination of options chosen determines the invocation semantics (the table in 6.5).
T6-6 · officialExplain the different invocation semantics.
Invocation semantics define what the client can assume about how many times the remote procedure was executed.
- Maybe: the procedure was executed once or not at all. Unless the caller receives a result, it cannot tell whether the procedure ran.
- At-least-once: a result means the procedure executed at least once; otherwise the caller gets an exception. Suitable for idempotent operations.
- At-most-once: a result means the procedure executed exactly once; otherwise the caller gets an exception. Suitable for non-idempotent operations.
Be precise about the exception case. The tutors' slides say that on an exception the procedure "was not executed at all". CDK says the caller only learns that no result arrived: under at-most-once the procedure ran once or not at all, and under at-least-once it may have run any number of times, including zero. Use CDK's version.
T6-7 · officialExplain remote procedure call (RPC) and describe its key components.
RPC lets a client execute procedures in server processes through a defined service interface. It is used with procedural languages such as C, Fortran and Go. It has four key components:
- The communication module implements the chosen design: retransmitting requests, handling duplicates, and retransmitting results.
- The client stub procedure looks like a local procedure to the client. It marshals the procedure identifier and arguments and hands them to the communication module, then unmarshals the result in the reply.
- The dispatcher selects the server stub using the procedure identifier and forwards the request to it.
- The server stub procedure unmarshals the arguments and calls the service procedure, then marshals the result into the reply.
Explain how to build a distributed system with Java RMI: what must be compiled, and which parts of the system must be used. No code is required.
- Define the remote interface using the RMI system's interface definition mechanism. In Java this is an interface that extends
Remote, and every method throwsRemoteException. - Compile the interface. In generic RMI (CDK), this generates the proxy, dispatcher and skeleton classes. In current Java, proxies are generated dynamically at run time and skeletons are unnecessary (6.9), so mention both.
- Write the server program. Implement and compile the remote object classes, create and initialise the remote objects, and register them with the binder (the RMI registry).
- Write the client program. It contains the invoking code, and it uses the binder to look up remote objects, receiving proxies for them.
The tutors' diagram shows the same steps as a call sequence:
Code demos (Weeks 7–8). The tutorials also demonstrated an RMI client and server, and sending or receiving a file over sockets; 6.10 covers the RMI program.
Final check
Check yourselfWhich component exists in RMI but has no counterpart in RPC?- The dispatcher
- The communication module
- The remote reference module
- The client stub
C. The remote reference module translates between local and remote object references and keeps the remote object table. The dispatcher and communication module exist in both; the client stub is RMI's proxy.
Check yourselfA server's only operation is "set temperature to t". Which invocation semantics is the cheapest safe choice, and why?
At-least-once. Setting a value to t is idempotent: running it twice leaves the same state as running it once. So re-executing duplicates is harmless, and the server can skip duplicate filtering and the reply history, which makes at-least-once cheaper than at-most-once.
- Know the official tutorial lists word for word: R/RR/RRA; the three request-reply issues and their two options each; the three semantics; the four RPC components; the four steps to build a Java RMI system.
- When the tutors' wording and CDK differ (the exception case; skeleton vs dispatcher), give CDK's version and mention both.