skip to content

In a remote procedure call system, what do the client stub and the server-side skeleton each do during one call?

level: juniorimportance: must knowfreq 45%

answer

  1. looks like a local function
  2. arguments packed into a message
  3. procedure identifier picks the handler
  4. reply matched back by call id

basics

~20 s

The client stub poses as the local procedure: it marshals the arguments into a request and hands it to the transport. The server skeleton unmarshals them, dispatches to the real procedure and marshals the result back.

solid answer

~50 s

An RPC call runs through a pipeline both sides share. The **client stub** has the remote procedure's signature; when called, it marshals the arguments into a request that also names the procedure and carries a call identifier, passes it to the RPC runtime and, in the classic synchronous form, waits for the reply. On the server, the **skeleton** (or a dispatcher plus a per-procedure server stub) reads the procedure identifier, unmarshals the arguments, invokes the real implementation and marshals the result or error into a reply carrying the same identifier. The client stub matches that reply to its call, unmarshals the result and returns it, or raises an error. Both stubs are normally generated from an interface definition, which is what lets the call look local - the stub-based design Birrell and Nelson described in 1984.

code

pseudocode · 18 lines
pseudocode
// client stub for: total = addItems(cartId, items)
function addItems(cartId, items):
    request = marshal(procedure = "addItems", callId = nextCallId(), args = [cartId, items])
    runtime.send(request)
    reply = runtime.awaitReply(request.callId)
    if reply.isError:
        raise RemoteError(reply.error)
    return unmarshal(reply.result)

// server dispatcher
on receive(request):
    handler = registry.lookup(request.procedure)
    if handler is missing:
        runtime.send(errorReply(request.callId, "unknown procedure"))
        return
    args = handler.unmarshalArgs(request.args)
    result = handler.invoke(args)
    runtime.send(reply(request.callId, marshal(result)))

go deeper

for a junior

Walk one call end to end: stub marshals, transport carries, dispatcher picks the handler, skeleton unmarshals and invokes, reply comes back. Naming each piece in order is the answer.

for a middle

Explain what the request must carry - procedure identifier, call identifier, encoded arguments - and why the reply repeats the call identifier when several calls share a connection.

for a senior

Show that you know where the pipeline leaks: latency, partial failure and the missing shared address space are things the stub cannot hide, and they shape interface design.

for a principal

Discuss what an organisation gains and risks by generating stubs that make remote calls look local, and when it should prefer interfaces that make remoteness visible.

## The idea: a remote call dressed as a local one A **remote procedure call (RPC)** lets a program call a procedure that runs in another process, usually on another machine, with the same syntax it would use for a local function. The design that made this practical is usually traced to Birrell and Nelson's 1984 paper *Implementing Remote Procedure Calls*: put a small piece of generated code on each side of the network that translates between "a function call" and "a message", so neither the caller nor the implementation has to touch the network directly. Those pieces are the **stubs**. RFC 5531, the specification of ONC RPC, describes the same model in words that fit every RPC system: "one thread of control logically winds through two processes". The caller sends a **call message** and waits for a **reply message**; the server extracts the parameters, computes the results and sends the reply. ## The pipeline, step by step 1. The application calls `addItems(cartId, items)` on the **client stub**, an object or function with exactly the remote procedure's signature. 2. The stub **marshals** the call: it encodes the procedure identifier, a fresh **call identifier** and the arguments into a request message in the protocol's wire format. 3. The **RPC runtime** hands the bytes to a **transport** (a TCP connection, a datagram, an HTTP request) through a binding that decides how messages travel. 4. On the server, the runtime receives the message and passes it to the **dispatcher**, which reads the procedure identifier and selects the registered handler. 5. The **server stub** or **skeleton** for that procedure **unmarshals** the arguments into local values and invokes the real implementation as an ordinary local call. 6. The skeleton marshals the return value - or an error - into a reply that repeats the call identifier, and the runtime sends it back. 7. The client runtime matches the reply to the waiting call by its identifier; the client stub unmarshals the result and returns it, or raises an error the application can catch. ## Who does what | Component | Side | Job | |---|---|---| | Client stub | Caller | Present the local signature, marshal arguments, unmarshal the result | | RPC runtime | Both | Send and receive messages, match replies to calls | | Binding / transport | Both | Carry the bytes: delimiting, size limits, reliability | | Dispatcher | Callee | Route a request to the handler named by its procedure identifier | | Skeleton / server stub | Callee | Unmarshal arguments, invoke the implementation, marshal the reply | Terminology varies between systems. Some generate one **skeleton** per interface that both dispatches and decodes; others split a generic dispatcher from per-procedure **server stubs**. The division of labour is the same. ## What the messages must carry RFC 5531 lists what the ONC RPC protocol must provide: a **unique specification of the procedure** to be called, a way of **matching responses to requests**, and a way of **authenticating** each side. In ONC RPC that means: - a transaction identifier, `xid`, that the reply repeats - RFC 5531 adds that it serves only to match replies or detect retransmissions, never as a sequence number; - the program, version and procedure numbers (`prog`, `vers`, `proc`) that tell the dispatcher what to run; - a credential and verifier for authentication; - the procedure's parameters, encoded in XDR, the external data representation the protocol uses. Other systems name the procedure with a string or a path instead of numbers, but every one carries some procedure identifier, some correlation, and the encoded arguments. Stubs are normally **generated** from an interface definition so that both sides agree on that encoding; how that definition is managed is a separate subject. ## What the stub cannot hide The stub makes the call look local; it cannot make it behave locally. RFC 5531 itself lists the differences: failures of the server or the network must be handled, the server has no access to the caller's address space, and remote calls run "one or more orders of magnitude slower". Those leaks are why location transparency is a debated goal rather than a solved problem. ## Common confusions - **"Marshalling is only needed across languages."** Even two processes in one language share no memory; arguments always travel as an encoded message. - **"The stub runs the procedure."** The stub only translates; the implementation runs on the server. - **"Replies arrive in call order, so no identifier is needed."** Several calls can be outstanding on one connection, and datagrams can arrive late or twice; the identifier is what pairs them.

  • Why must an RPC request carry a call identifier when the stub already waits for its own reply?
    Because one connection or socket can carry many outstanding calls, and replies may arrive late, out of order or, over datagrams, more than once. The runtime pairs each reply with its call by the identifier. In ONC RPC that is the `xid`; RFC 5531 says the server may use it only as an equality test, to detect retransmissions, never as a sequence number.
  • Is a skeleton the same thing as a dispatcher?
    Not always - it depends on the system's vocabulary. Some generate one skeleton per interface that both routes the call and decodes its arguments; others have a generic dispatcher that routes on the procedure identifier and per-procedure server stubs that decode. Either way the job is to turn a message back into a local call on the implementation.
  • Where do the stubs usually come from?
    They are generated from an interface definition that lists each procedure, its parameters and its result, so the client's encoder and the server's decoder agree by construction. Hand-written stubs are possible, but then nothing but care keeps the two sides in agreement, and a mismatch shows up only at run time as undecodable arguments.

saying these in an interview costs you the question

  • The client stub runs the procedure locally and syncs the result later.
  • Marshalling is only needed when client and server use different languages.
  • Replies always arrive in call order, so a call identifier is unnecessary.
  • The server receives the caller's actual objects, not values decoded from a message.
  • Once a stub is generated, a remote call behaves exactly like a local one.