Part VI

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.
6.1

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.

Definition · Remote invocation

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.

CLIENT PROCESS · YOUR CODE 1 · open and manage the connection 2 · marshal the arguments into bytes 3 · find where each message ends 4 · match each reply to its request 5 · retransmit lost requests, discard duplicates 6 · find out where the server is 7 · unmarshal the result
Raw sockets: you write every chore. Each red card is code the application programmer has to get right, in every program.
CLIENT PROCESS your code: one line result = server.op(args); Middleware does the chores connections · marshalling · message boundaries matching replies · retransmission · duplicates locating the server (binder or registry) to the network
Remote invocation: one call. The same chores still happen, but once, inside middleware (violet), and the programmer sees only a call.

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.

  1. 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.
  2. 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.
  3. 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.
RMI · remote method invocation methods of objects in other processes (e.g. Java RMI) RPC · remote procedure call procedures in other processes (procedural languages such as C) Request-reply protocols a message pattern for client-server exchanges, on top of message passing
Read from the bottom up. Each layer uses the one beneath it: an RMI call is carried out as an RPC-like call, which is carried out as a request message and a reply message. That is why this Part starts with the request-reply protocol.

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.

Applications, services Remote invocation, indirect communication IPC primitives sockets, message passing, multicast, overlays UDP and TCP middleware this Part
The middle two layers are middleware (violet). This Part lives in the highlighted layer; indirect communication, its neighbour, is Part VII.

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?
  1. RPC and RMI are alternatives to request-reply protocols.
  2. RMI is built on the request-reply pattern, just as RPC is.
  3. "RMI" is the name of Sun's Java product and nothing else.
  4. 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).

In short
  • 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.
6.2

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.
client server connection set-up: 2 extra pairs of messages request ack: redundant reply ack: redundant + flow control: redundant for small arguments
Over TCP. The faint messages and the flow-control machinery cost time but add nothing a request-reply exchange needs.
client server request reply the reply acknowledges the request next request acknowledges the previous reply no set-up, no separate acks
Over UDP. Two messages per call. The protocol gets its acknowledgements for free from the messages it sends anyway.

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);
  • doOperation sends a request to the server s, which is identified by its IP address and port. It then blocks in receive until the reply arrives, and returns the reply bytes.
  • getRequest acquires a client request via the server's port.
  • sendReply sends 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.

client server doOperation (blocked in receive) returns bytes continuation Request message getRequest select operation execute sendReply Reply message loop
One client primitive, two server primitives. The client is blocked for the whole round trip. The server's loop (faint arrow) goes back to 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.

FieldTypePurpose
messageTypeint (0 = Request, 1 = Reply)Says whether this is a request or a reply
requestIdintMatches a reply to its request
remoteReferenceRemoteRefThe remote object or server to invoke
operationIdint or OperationWhich operation to invoke
argumentsarray of bytesThe marshalled arguments (in a request) or results (in a reply)
the communication module reads only these three messageType int: 0 = Request, 1 = Reply requestId int remoteReference RemoteRef operationId int or Operation arguments array of bytes made by doOperation, copied into the reply 1, 2, 3 … or a reflective Method if both share a language marshalled arguments or results
Shaded fields are header; white is payload. The communication module needs only the first three to deliver and match messages (6.8); the operation id and arguments are for the code that actually runs the operation.

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:

requestId increasing integer sequence + sender identifier the sender's IP address and port unique per sender together: unique in the whole system 0, 1, 2, … , 232 − 1, then wraps round to 0
Two halves, two kinds of uniqueness. The counter alone could clash with another client's counter; adding the sender's address and port removes the clash.

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.

In short
  • 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), getRequest and sendReply (server loop).
  • Each message: messageType, requestId, remoteReference, operationId, arguments. Message id = requestId + sender's IP address and port.
6.3

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

  1. 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, doOperation retransmits the request until it either gets a reply or is fairly sure the server has failed, and only then throws an exception.
  2. 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.
  3. 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.
  4. 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.

A lost request, one message at a time
client server 1 · Request id 3 lost in the network timeout expires: no reply (was the request or the reply lost?) 2 · Request id 3 (retransmitted) new (client, 3): execute it 3 · Reply id 3 returns
  1. The client calls doOperation. It is about to send request number 3 and then block until a reply arrives.
  2. Request id 3 is lost in the network. The server never sees it, so nothing has been executed.
  3. 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, doOperation retransmits.
  4. 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.
  5. Reply id 3 reaches the client, which checks that the id matches its current request, and doOperation returns. 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.

A lost reply, answered from the history
client server SERVER STATE balance = 100 balance = 110 times executed: 0 times executed: 1 history client requestId reply (empty) C 7 ok, 110 (C, 7) found: a duplicate resend the stored reply 1 · Request id 7: deposit(10) execute, store reply 2 · Reply id 7 lost timeout expires: retransmit, same id 3 · Request id 7 again duplicate: NOT re-run 4 · Reply id 7 (from history) doOperation returns
  1. The account balance is 100 and the server's history is empty.
  2. Request id 7, "deposit 10", goes from the client's doOperation to the server.
  3. The server's getRequest receives it; the server executes the deposit (balance 110) and stores reply 7 in its history before sending it.
  4. sendReply sends reply id 7, which is lost. The deposit has happened, but the client does not know that.
  5. 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.
  6. Request id 7 arrives again. If the server simply ran it, the account would be credited twice.
  7. 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.
  8. Reply id 7 is resent from the history. The client's doOperation returns 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.

client server 1 · Request id 4 executing slowly… timeout expires 2 · Request id 4 (retransmitted) duplicate of (C, 4): discard it finished 3 · Reply id 4 (sent once)
A duplicate while the original is still running. The server has not sent a reply yet, so it just drops the copy and replies when it finishes. Without the requestId it could not tell the copy from a new request.

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.

History before client idrequestIdreply A12reply to 12 B3reply to 3 A sends request 13 (this acks reply 12) History after client idrequestIdreply A13reply to 13 B3reply to 3 A: replaced, still one entry per client B has exited and will never send a next request: its entry is discarded after a time limit entries are (requestId, reply, client id); one outstanding request per client, so only the last reply per client needs to be kept
Two rules bound the history. A client's next request makes its old entry useless, so it is overwritten (green); an entry for a client that has gone quiet is dropped after a time limit (faded).

Idempotent operations exam

Definition · Idempotent operation

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: add x to a set {a} add(x) {a, x} add(x) again {a, x} twice = same effect as once safe to re-execute a duplicate
Repeating changes nothing more. A duplicate can simply be run again.
Not idempotent: deposit $10 balance 100 +10 110 +10 again 120 twice = twice the effect needs duplicate filtering and a history
Repeating changes the state again. A duplicate must be answered from the history instead.
IdempotentNot idempotent
Adding an element to a setAppending 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
Common mistake

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.

In short
  • 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.
6.4

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.

ProtocolMessages sentIn short
R (request)RequestNo result or confirmation needed; maybe semantics
RR (request-reply)Request, ReplyMost client-server exchanges
RRA (request-reply-acknowledge reply)Request, Reply, Ack(requestId)The ack lets the server delete history entries
R · request client server Request no reply: maybe semantics; the client continues at once RR · request-reply client server Request Reply reply acks the request; next request acks the reply RRA · request-reply-ack client server Request Reply Ack(requestId) the ack lets the server delete history entries
One, two or three messages. Each extra message buys something: the reply gives a result and confirms the request; the acknowledgement (teal) tells the server it can forget the reply.

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.

client C server Ack(4) lost Request 5 Reply 5 Ack(5) history for client C requestIdreply 4reply to 4 5reply to 5 Ack(5) deletes 5 and every lower id, so the lost Ack(4) did no harm
Cumulative acks forgive losses. Because one ack covers all lower ids, the server's history is cleaned up correctly even though an earlier ack vanished.
Exam · What semantics does RR give?

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:

Size

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.
Simplicity

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.
Cost

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.

Pitfall

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.

request GETmethod /index.htmlURL or path HTTP/1.1version Host: …headers (empty)message body reply HTTP/1.1version 200status code OKreason Content-Type: text/htmlheaders <html>…</html>message body ASCII text, sent over a TCP connection
Same shape both ways. A request is 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.
MethodWhat it doesIdempotent?Safe (changes nothing)?
GETFetches a resource or a program's output; any arguments travel in the URLYesYes
HEADLike GET but returns no dataYes (safe)Yes
POSTSends body data to a program; may change stateNot necessarilyNo
PUTStores data at the URLYesNo
DELETEDeletes the resourceYesNo
OPTIONSLists the methods allowedYes (safe)Yes
TRACEEchoes the request backYes (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.

In short
  • 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.
6.5

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 requestDuplicate filteringRe-execute procedure or retransmit replyCall semantics
NoNot applicableNot applicableMaybe
YesNoRe-execute procedureAt-least-once
YesYesRetransmit replyAt-most-once
Retransmit request? Filter duplicates? AT-MOST-ONCE resend reply from history e.g. Java RMI, CORBA MAYBE no fault tolerance at all e.g. CORBA oneway methods AT-LEAST-ONCE re-execute the procedure e.g. Sun RPC yes yes no no
Two questions decide the semantics. Red is weakest, green strongest. Each step to the right adds one fault-tolerance measure and one guarantee.

What each one guarantees

No measures

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.
Retransmit

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.
All three measures

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's oneway methods have maybe semantics).
a result arrives no result (exception) times the procedure may have run times the procedure may have run Maybe 0 1 2+ 0 1 2+ At-least-once 0 1 2+ 0 1 2+ At-most-once 0 1 2+ 0 1 2+ Local call (exactly-once) 0 1 2+ (barring process failure)
Teal chips are the possible execution counts. Only at-most-once makes a result mean "exactly once" and an exception mean "at most once". Under at-least-once, an exception tells you almost nothing (CDK: it may have run any number of times, including zero).

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.

(a) request lost client server timeout, no reply server ran it: no (b) server crashes client server crash timeout, no reply server ran it: maybe (c) reply lost client server timeout, no reply server ran it: yes
Same view, different truth. In all three the client sees only "timeout, no reply". Since it cannot tell which one happened, no remote protocol can promise "exactly once" in general.
The trade-off

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.

Example · An election service (CDK Ex. 5.14)

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.

Be precise about the exception case

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).

Past exam 2016 Q4(c) · 5 marks

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?

Model answer
  • 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 oneway methods 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?
  1. The procedure was not executed.
  2. The procedure was executed exactly once.
  3. The procedure was executed once or not at all.
  4. 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).

In short
  • 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.
6.6

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.

Definition · RPC

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:

1

Abstraction

No implementation details are visible to the client.
2

Heterogeneity

Clients need not know the server's language or platform.
3

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:

  1. No access to another process's variables. That is why CORBA "attributes", which look like variables, are really generated getter and setter methods.
  2. No call by reference. Instead, each parameter is in (sent in the request), out (returned in the reply), or inout (both).
  3. No pointers or addresses, because a memory address is meaningless in another process.
client server in: copied into the request out: copied back in the reply inout: both ways
Values are copied, never shared. Since there is no call by reference, "changing an argument" remotely means the server sends a new value back, which is what out and inout parameters do.

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);
interface definition (IDL, e.g. Sun XDR) interface compiler e.g. rpcgen client stub procedures server stubs + dispatcher marshalling procedures client program server program used by both programs
Nobody writes stubs by hand. The interface compiler generates them (amber) from the interface definition, which is why the stubs on both sides always agree on how to marshal each procedure's arguments.

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:

1

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.
2

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.
3

Parameter passing

Parameters are passed differently: there is no call by reference.
4

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.

The consensus

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:

One RPC call through the components
CLIENT HOST SERVER HOST NETWORK client programx = read(args) client stub proceduremarshals id + args, unmarshals results communication modulerequest-reply protocol, call semantics service procedurethe real implementation of read server stub procedureunmarshals args, marshals result dispatcherpicks stub by procedure id communication modulefilters duplicates, may resend reply 1 · read(args) 2 · proc id + args 3 · Request 4 5 · select stub 6 · call 7 8 9 · Reply 10 11
  1. 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.
  2. The client program makes an ordinary local call, read(args), which actually calls the client stub procedure for read.
  3. The client stub marshals the procedure id and the arguments into a byte array and hands it to the communication module (via doOperation).
  4. The communication module sends the Request message over the network. It retransmits on timeout if the chosen semantics say so.
  5. The server's communication module receives the request, filters duplicates according to the semantics, and passes it to the dispatcher.
  6. The dispatcher reads the procedure id and selects the matching server stub procedure.
  7. The server stub unmarshals the arguments and calls the service procedure, which is the real implementation of read.
  8. The service procedure returns its result to the server stub, which marshals it into a Reply message.
  9. The marshalled reply goes to the communication module (and, under at-most-once, into its history).
  10. The Reply message crosses the network back to the client's communication module.
  11. doOperation returns the reply bytes to the client stub.
  12. 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 rpcgen compiler turns into client stubs, a server main containing 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.
CLIENT HOST SERVER HOST client port mapperon a well-known port Sun RPC serverlistening on its own port 1 · register (program, version, port) 2 · which port? 3 · port number 4 · call (program, version, procedure)
The port mapper is Sun RPC's binder. Only its port is well known; every server's own port is discovered at run time (steps 2–3) before the real call (step 4).
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?
  1. The client stub
  2. The dispatcher
  3. The communication module
  4. 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.

In short
  • 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.
6.7

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.

Definition · RMI

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.

RPCRMI
ModelProceduralObject-oriented: objects, classes, inheritance, OO design tools
TargetA procedure in a serverA method of a specific remote object
IdentificationProgram and procedure idsSystem-wide unique remote object reference
ParametersBy value (in/out)By value and by object reference
MachineryClient stub, dispatcher, server stubProxy, dispatcher, skeleton, plus a remote reference module
RPC: a procedure in a server client SERVER PROCESS read(…) write(…) delete(…) proc id, args parameters by value (in / out) RMI: a method of a specific object client SERVER PROCESS object BROR of B object CROR of C object DROR of D ROR, method, args parameters by value and by object reference
The target gets more precise. RPC names a procedure; RMI names one particular object (by its remote object reference, teal) and then a method of it.

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:

1

Object references

References are first-class: they can be assigned to variables, passed as arguments and returned as results.
2

Interfaces

An interface gives method signatures with no implementation and no constructors.
3

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.
4

Exceptions

A method can signal an error by throwing an exception for the caller to handle.
5

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.

PROCESS 1 PROCESS 2 PROCESS 3 remote remote A B C D E F remote invocation (crosses a process boundary) local invocation
Remote objects are the ones that receive remote invocations: here B and F (teal). C, D and E are only ever invoked from inside their own process, so they need not be remote objects. Process 2 might even run on the same computer as process 1; the A-to-B call would still be remote.

The model rests on two fundamental concepts: the remote object reference and the remote interface.

Concept 1: the remote object reference

Definition · Remote object reference (ROR)

"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:

IP address port creation time local object number remote interface the process that created it which object; unique in time too, never reused lets the receiver build the right proxy an ROR works as an address only if the object never moves to another process
Where, when, which, and what type. Including the creation time is what stops a restarted process on the same address and port from producing an identical reference for a different object.

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.

remote object data (reached only through methods) remote interface other methods m1 m2 m3 m4 m5 m6 remote processm1–m3 only local objectmay call all six
The remote interface is a window onto the object (CDK Fig 5.13). Only the teal methods are visible from other processes; the local object, inside the same process, can also use m4–m6 (and m1–m3, not drawn).

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 newShape in 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?
  1. No, because both are on the same computer.
  2. Yes, because it crosses a process boundary.
  3. Only if Y's process uses a different port.
  4. 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.

In short
  • 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.
6.8

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

CLIENT PROCESS SERVER PROCESS object A proxy for Bone per remote object remote referencemodule communicationmodule communicationmodule remote referencemodule dispatcherone per class skeletonone per class servantremote object B Request Reply
Colour key for every RMI figure in this Part: blue = the client's own code, red = the servant (the server's own code), amber = generated code (proxy, dispatcher, skeleton), violet = communication modules, teal = remote reference modules. Notice the counts: a proxy per remote object, but a dispatcher and a skeleton per class.
Both sides

Communication module

Runs the request-reply protocol and provides the invocation semantics, for example at-most-once. It uses only the messageType, 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.
Both sides

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.
Server

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.
Client · one per remote object

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.
Server · one per class

Dispatcher

Uses the operationId to select the skeleton method, numbering operations the same way as the proxy does.
Server · one per class

Skeleton

Unmarshals the arguments and invokes the servant, then marshals the result or exception into the reply.
RPC componentRMI equivalent
Client stub procedureProxy
DispatcherDispatcher
Server stub procedureSkeleton
Service procedureServant
Communication moduleCommunication module
(nothing)Remote reference module: what RMI adds
One remote method invocation, A calls B.m(args)
NET object A proxyfor B comm moduleclient comm moduleserver remote refmodule dispatcher skeleton servant B B.m(args) marshalled Request ROR? local ref request opId m(args) result marshalled reply Reply bytes result
  1. Eight participants. Object A holds a proxy for B, created when A's process first received B's remote object reference.
  2. 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.
  3. The proxy marshals (target ROR, operationId, args) into a request and hands it to the communication module.
  4. 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.
  5. 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.
  6. It passes the request to the dispatcher for B's class.
  7. The dispatcher uses the operationId to select the matching method of the skeleton.
  8. The skeleton unmarshals the arguments and invokes method m on the servant, the real object B.
  9. The servant returns its result (or throws an exception) to the skeleton.
  10. The skeleton marshals the result or exception into the reply and hands it to the communication module.
  11. The Reply message crosses the network back to the client's communication module.
  12. The reply bytes are passed back to the proxy, which was waiting in doOperation.
  13. 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.

an ROR arrives ROR of X remote reference module remote object reflocal reference ROR of Bservant B ROR of Xproxy for X the remote object table: one entry per remote object held and per local proxy in the table return its local proxy or object not in the table create a new proxy and add an entry the other direction: a remote object passed out for the first time gets a new ROR and an entry
Translation both ways. Outgoing local remote objects become RORs; incoming RORs become proxies (or, if the object is local, the object itself).

The rest of the machinery

Generation

Interface compiler

The proxy, dispatcher and skeleton classes are produced by an interface compiler, such as a CORBA IDL compiler.
Unknown types

Dynamic invocation

If a client receives an ROR whose interface was unknown at compile time, it has no static proxy, so it calls a generic doOperation(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

Server program

Contains the servant, dispatcher and skeleton classes. Its main 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

Client program

Contains the proxy classes and uses the binder to get its first ROR.
Naming

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.
Concurrency

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.

passive object implementation + marshalled state activate activator registers passive objects starts server processes and activates objects in them tracks where they are running SERVER PROCESS active object can be invoked passivate (e.g. a persistent store at the end of a transaction)
Objects can sleep on disk. The activator (violet, part of the middleware) brings a passive object to life in a server process when it is needed; a persistent object store can passivate it again.

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:

  1. 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.
  2. When client C first receives an ROR for B, it calls addRef(B) and then creates the proxy; the server adds C to B.holders.
  3. 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.
  4. When B.holders is empty and there are no local references either, the server's local GC reclaims B.
Distributed garbage collection of remote object B
CLIENT JVM C1 CLIENT JVM C2 SERVER JVM B.holders (client JVM id, lease period) { } { C1 } { C1, C2 } { C2 } { } remote object B no local references receives an ROR for B addRef(B) proxy for B receives an ROR for B addRef(B) proxy for B local GC: proxy unreachable, proxy deleted removeRef(B) C2 crashes: no removeRef is ever sent C2's lease expires reclaimed by the server's local GC
  1. Remote object B lives in the server. Nobody outside holds a proxy for it yet, so B.holders is empty.
  2. 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 to B.holders. The lease for C1 starts now.
  3. Only after addRef has succeeded does C1 create its proxy for B.
  4. C2 does the same: addRef(B), then a proxy. The set is now {C1, C2}.
  5. 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}.
  6. 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.
  7. B.holders is 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's addRef(B) and empty the set too early. The fix: when an ROR is sent while B.holders is empty, the server adds a temporary entry, which stays until the addRef arrives.
  • Communication failure. addRef and removeRef are idempotent. If addRef throws an exception, the client creates no proxy and calls removeRef, which is correct whether or not the addRef actually took effect.
  • Client crash or a lost removeRef. Handled with leases. The server leases each reference for a limited period, starting at addRef, and stores (client JVM id, lease period). The lease ends either at expiry or at removeRef. Because clients must renew their leases before expiry, a crashed client's entry simply lapses.
C1 server C2 removeRef(B) holders = { } ROR of B no holders, no local refs: B is reclaimed addRef(B) too late: B is gone
Without the fix. For a moment nobody is recorded as holding B, and the server's local GC strikes in that gap.
C1 server C2 removeRef(B) holders = { } ROR of B sent while empty, so add a temporary entry for C2 addRef(B) holders = {C2}
With the fix. The temporary entry covers the gap between sending the reference and receiving the addRef, so B is never unprotected.
client C crashes server addRef renew renew last lease expires: C removed from B.holders each bar is one lease period
Silence is enough. A live client renews before each lease (teal bar) runs out; a crashed one just stops, and the server needs no message to notice.

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.

In short
  • 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.holders with addRef/removeRef, a temporary entry for the race, idempotent calls, and renewable leases for crashed clients.
6.9

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

Syntax

Local look, not fully transparent

Remote and local calls have the same syntax and type checking. It is deliberately not fully transparent: callers must handle RemoteException, and remote interfaces must extend Remote.
Language

Single-language

Java at both ends, so there is no IDL to learn, unlike CORBA. The Java interface is the interface definition.
Wire

JRMP over TCP

The wire protocol is JRMP (Java Remote Method Protocol) over TCP; marshalling uses Java object serialization.
Semantics

At-most-once

A result means the method ran exactly once; a RemoteException means it ran once or not at all.
Concurrency

Runtime threads

Many clients may invoke an object at once, and the RMI runtime runs calls on its own threads, so shared state needs synchronized, 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 typePassedEffect
A remote interface (e.g. Shape)By reference: serialized as its RORThe 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
NeitherCannot be passedMarshalling error at run time
Remote type (Shape): by reference SENDER JVM RECEIVER JVM Shape objectthe original proxyimplements Shape ROR calls one object: every call reaches the original
By reference. Only the reference travels; the object stays put and is invoked remotely.
Serializable (GraphicalObject): by value SENDER JVM RECEIVER JVM gthe original g′a new copy copy two objects: they may diverge
By value. The whole object is serialized and rebuilt; changing one copy does not change the other.

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.

client ShapeList servant 1 · newShape(g): g is copied version++ 2 · new ShapeServant(g, version) ShapeServant 3 · remote ref, typed Shape the client now holds a proxy implementing Shape
Both kinds of passing in one call. The argument 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.

CLIENT JVM TextObject ta new subclass of GraphicalObject SERVER JVM serverhas no TextObject class yet newShape(t) class annotated with a URL web server at the URL (the codebase) download TextObject
Code follows data. The server learns where to fetch an unknown class from the annotation in the serialized stream, so a new subclass works without redeploying the server.

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.

CLIENT HOST SERVER HOST clientneeds its first ROR RMIregistryport 1099 "HelloService" → ROR server processholds the servant (the object itself) 1 · rebind local host only 2 · lookup("HelloService") 3 · ROR (proxy) 4 · invoke the servant directly
The registry holds references, not objects. It is consulted once, to bootstrap (steps 2–3); every later call goes straight to the server process (step 4, teal). The registry (green) is itself a remote object on a well-known port.

The registry is accessed through java.rmi.Naming, using names of the form:

rmi: // computerName :port /objectName optional default: local host default: 1099 the bound name e.g. //localhost:1099/HelloService
Only the object name is compulsory. Leave out the host and port and you get the local host on port 1099.
MethodUsed byWhat it does
rebind(String name, Remote obj)ServerBinds a name to an object, replacing any existing binding
bind(String name, Remote obj)ServerBinds a name, but throws an exception if the name is already bound
unbind(String name)ServerRemoves a binding (CDK shows an extra obj parameter, but the real API takes only the name)
Remote lookup(String name)ClientReturns the ROR (a proxy) bound to the name
String[] list()EitherReturns 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.

Pitfall

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.

A Java RMI call, from export to result
client code proxy RMI registry server RMI runtime+ dispatcher RemoteObjservant 1 · new RemoteObj(),exported 2 · bind myRO 3 · lookup("myRO") 4 · remote ref → proxy 5 · method1(args) 6 · request, JRMP over TCP 7 · invoke (reflection) 8 · value or exception 9 · serialized reply 10 · return / throw
  1. The five participants. Only the client code and the servant are written by the programmer; the proxy, registry and runtime come with Java.
  2. 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.
  3. Bind. The server binds the name myRO to the object's remote reference in the registry. The registry stores only the reference; the object stays in the server.
  4. 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.
  5. 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.
  6. The client calls method1(args) on the proxy: an ordinary Java call.
  7. The proxy serializes the method and the arguments (copies, or RORs for remote objects) and sends the request over TCP using JRMP.
  8. A runtime thread on the server deserializes the request, finds the target object and invokes it through the generic dispatcher, using reflection.
  9. The servant returns a value or throws an exception.
  10. The result or exception is serialized and sent back as the reply.
  11. The proxy returns the value to the client, or re-throws the application exception. If communication failed anywhere, the client gets a RemoteException instead.

The slides describe the same path as an eight-step invocation lifecycle:

  1. The client calls the stub (the proxy).
  2. The stub serializes the call.
  3. The dispatcher deserializes it.
  4. The dispatcher calls the method.
  5. The method returns.
  6. The dispatcher serializes the result.
  7. The stub deserializes it.
  8. 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.

proxy Method m Object[] args marshalled generic dispatcher 1 · unmarshal the Method and args 2 · target = local ref (remote ref module) 3 · method.invoke(target, args) 4 · marshal the result or exception servant of any class one dispatcher serves every class, so per-class skeletons are no longer needed
Reflection replaces generated code. Because the request carries a 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.

RemoteObject holds the ROR equals() toString() read/writeObject() RemoteServer UnicastRemoteObject Activatable HelloImpl «interface»Remote «interface»Hello implements extends
Your servant sits at the bottom. Extending 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?
  1. A copy of the HelloImpl servant object
  2. A proxy (stub) that implements the remote interface
  3. The servant's IP address and port as a string
  4. 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.

In short
  • 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: server bind/rebind (local host only), client lookup. Reflection gives a generic dispatcher (no skeletons); proxies are generated at run time (no rmic).
6.10

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.

Exam · The rubric (2026 practice exam Q11)
Rubric itemMarksWhere you earn it below
Defining interfaces for the services2Step 1 · Hello.java
Implementing the RMI interfaces3Step 2 · HelloImpl.java
Binding the RMI object to the registry1Step 3 · HelloServer.java
Looking up the remote object and invoking remote methods2Step 4 · HelloClient.java
Handling two distinct types of errors or exceptions2Step 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.

CLIENT JVM SERVER JVM HelloClient.javagetRegistry · lookup · sayHello · two catches Hello.javathe same remote interface HelloServer.javacreateRegistry · new · rebind HelloImpl.javathe servant registry, port 1099created inside this JVM Hello.java«interface» extends Remote rebind implements lookup sayHello("Alice") 1 1 2 3 4 5 where the 10 marks are 1 · interface2 marks 2 · implementation3 marks 3 · bind1 mark 4 · lookup + invoke2 marks 5 · two exceptions2 marks each segment's width is proportional to its marks
Four files, five rubric items. Gold badges are the build steps below. The interface file appears on both sides because both programs are compiled against it. The registry here lives inside the server's JVM (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.Remote is a marker interface with no methods; extending it is what tells Java RMI that Hello's methods may be invoked from another JVM.
  • throws RemoteException on 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: String is 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 declare throws RemoteException.
  • synchronized: the RMI runtime may run calls from different clients on different threads at the same time (Part IV), and calls++ is a read-modify-write on shared state, so it must be protected.
  • The implementation of sayHello may leave out throws 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 interface Hello.
  • 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.
  • main then 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 a Remote, deserialized as a proxy; the cast (Hello) gives it the interface type. The name must match the server's rebind exactly.
  • stub.sayHello("Alice") looks like a local call but is a full remote invocation (6.9). Run against the server above, the first call prints Hello, 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.

registrylookup("HelloService") network, server or registry servantthe method body NotBoundExceptionnaming: nothing bound under that name RemoteExceptioncommunication: down, unreachable, link lost application exceptione.g. WordNotFoundException clientone catch block per kind after a RemoteException the client cannot tell whether the method ran (at-most-once: once or not at all)
Three sources, three kinds. Any two of these, caught separately with a comment naming the kind, earn the 2 marks. Note what a RemoteException does not tell you: whether the method ran.
Catch-order pitfall

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).

Exam checklist · before you move on
  1. Interface extends Remote; every method throws RemoteException.
  2. Servant extends UnicastRemoteObject implements YourInterface; constructor throws RemoteException and calls super().
  3. Shared state in the servant is thread-safe (synchronized or a concurrent collection).
  4. Server: createRegistry(1099) (or getRegistry), new servant, rebind("Name", servant).
  5. Client: getRegistry(host, 1099), (YourInterface) registry.lookup("Name") with the same name, then call the methods.
  6. At least two distinct catch blocks, 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");
  1. Counter does not extend Remote, so it is not a remote interface.
  2. next() does not declare throws RemoteException.
  3. CounterImpl neither extends UnicastRemoteObject (with a constructor that throws RemoteException) nor is exported with exportObject, so it can never receive remote calls. Also, ++n on shared state should be synchronized.
  4. The client looks up "counter" but the server bound "Counter": names are case-sensitive, so lookup throws NotBoundException. (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 a ConcurrentHashMap, and ADD uses putIfAbsent so that the check and the insert happen as one atomic step.
  • The server starts a registry inside its own JVM with createRegistry and binds the servant as "Dictionary". Naming.rebind("//localhost/Dictionary", dict) would be equally acceptable.
  • The client gets a stub from lookup and calls ADD, SEARCH and DELETE as if it were a local object.
  • The kinds of error are kept apart. RemoteException is a distribution failure (registry or server down, connection lost), after which the client cannot tell whether the operation ran (at-most-once). NotBoundException is a naming failure. WordNotFoundException is a third, application-level error.
In short
  • Interface (extends Remote, every method throws RemoteException) → servant (extends UnicastRemoteObject, constructor throws RemoteException, thread-safe state) → server (createRegistry, new, rebind) → client (getRegistry, cast lookup, 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.
6.11

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:

  1. It calls lookup to get the ShapeList.
  2. It calls allShapes(), then getAllState() on each shape to display it.
  3. It calls newShape(g) when the user draws.
  4. 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:

  1. The client creates a callback object, a remote object with a method the server can call. The client thereby also becomes an RMI server.
  2. The server offers a register method, through which clients give it the RORs of their callback objects, and it keeps a list of them.
  3. When the event happens (here, a new shape), the server invokes every registered callback.
Polling client server getVersion() 3 getVersion() 3 getVersion() 3 wasted another user draws: v4 getVersion() 4: now fetch the new shape
The client keeps asking. Most calls bring nothing new (server load), and a change is seen only at the next poll (late updates).
Callbacks client server register(cb) callbackId no traffic while nothing changes another user draws: v4 callback(4) told at once, no wasted calls
The server tells the client. The arrow from server to client (teal) is an RMI on the client's callback object, so the client is acting as a server.

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.
  • WhiteboardCallback is an ordinary remote interface, so it must extend Remote (CDK's "implements" is a typo: an interface cannot implement another).
  • WhiteboardCallbackImpl is a servant that lives in the client. Its constructor exports it, which is exactly what turns the client into an RMI server.
  • register passes 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.
  • callback runs on an RMI runtime thread inside the client, not on the client's main thread.
  • On the server, a RemoteException from a callback means that client has gone, so it is removed from the list.
Callbacks with three clients
CLIENT C1 CLIENT C2 CLIENT C3 SERVER ShapeListServant version 3 version 4 callback list (empty) callback object callback object callback object exported:now an RMI server register(cb) register(cb) register(cb) ROR of C1's callback ROR of C2's callback ROR of C3's callback newShape(g) 1st: callback(4) 2nd: callback(4) 3rd: callback(4) RemoteException: remove C3 exited without deregistering
  1. Three whiteboard clients and the server. The whiteboard is at version 3 and the server's callback list is empty.
  2. Each client creates and exports a callback object. From now on each client is also an RMI server, able to receive calls.
  3. Each client calls register(cb). The callback object travels by reference, so the server's list now holds three remote references to callback objects.
  4. C1's user draws: C1 calls newShape(g) and the version becomes 4.
  5. 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.
  6. 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.)
PollingCallbacks
Who starts the exchangeThe client, repeatedlyThe server, when the event occurs
DrawbacksServer load; late updatesStale 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?
  1. The server polls the clients for changes.
  2. The callback object is copied to the server by value.
  3. The client must export a remote object, so it acts as an RMI server too.
  4. 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).

In short
  • 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 calls callback(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).
6.12

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.

Common pitfalls
  • 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.

Official · Issues in request-reply, and how they can be handled

The tutors list three issues, each with two options:

IssueOption 1Option 2
Request timeout (no reply arrives)Retry the request after the timeoutDo nothing
Reply lostRetransmit the result after a timeoutDo nothing
Duplicate requests or repliesDiscard the duplicatesPerform 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.
T6-8 / T7-1 · official · Building a system with Java RMI

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.

Official answer
  1. Define the remote interface using the RMI system's interface definition mechanism. In Java this is an interface that extends Remote, and every method throws RemoteException.
  2. 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.
  3. 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).
  4. 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:

Client Proxyclient side RMI registry Skeletonserver side ServerRemoteObj 1 · new RemoteObj() 2 · bind("myRO", remote ref) 3 · lookup("myRO") 4 · RemoteObj proxy 5 · method1() 6 · method1() request, network 7 · method1() 8 · return value 9 · reply, network 10 · return value
The tutors draw a skeleton, not a dispatcher. In CDK's model both are there (the dispatcher selects the method, the skeleton unmarshals and invokes), so name the skeleton when you reproduce this figure. The registry stores only the name-to-reference binding; the object stays on the server, and the client always talks to it through the proxy.

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?
  1. The dispatcher
  2. The communication module
  3. The remote reference module
  4. 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.

In short
  • 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.