Remote procedure call
A remote procedure call (RPC) is a protocol that enables a computer program to execute a subroutine or procedure on a remote machine across a network, presenting the interaction to the programmer as if it were a local procedure call by abstracting away the details of network communication, parameter passing, and control transfer.[1] The concept of RPC emerged in the late 1970s as a way to simplify distributed programming, with early discussions appearing in technical literature around 1976.[1] The term "remote procedure call" was coined by Bruce J. Nelson in his 1981 PhD thesis at Carnegie-Mellon University.[2] A foundational implementation was developed in 1983–1984 by Andrew D. Birrell and Bruce J. Nelson at Xerox PARC for the Cedar programming environment, which ran on Dorado computers connected via Ethernet; this system emphasized semantics matching local calls, use of stubs for interface generation, and optimizations for performance and security within a protected network.[1] In 1988, Sun Microsystems formalized a widely adopted version through RFC 1057, defining the Open Network Computing (ONC) RPC protocol version 2, which specified message formats using External Data Representation (XDR), support for multiple transports like UDP and TCP, program versioning, and basic authentication mechanisms.[3] RPC systems operate on core principles including client-side and server-side stubs that marshal arguments into network messages and unmarshal results, ensuring no shared address space between caller and callee to maintain isolation.[1] Unlike local procedure calls, which assume reliable, instantaneous execution in the same address space, RPCs must handle network-induced issues such as latency (often orders of magnitude slower), partial failures, and exceptions for communication errors, without access to the caller's globals or side effects.[1][3] These mechanisms have made RPC a cornerstone of distributed computing, facilitating client-server architectures in operating systems, file services, and middleware.[3]Fundamentals
Definition and Overview
Remote procedure call (RPC) is a protocol that enables a program to execute a subroutine or procedure on a remote server as if it were a local call, by suspending the caller, transferring control and parameters across a network, executing the procedure remotely, and returning results transparently.[4] This mechanism abstracts the underlying network complexities, such as packet transmission, retransmissions, and acknowledgments, allowing programmers to focus on application logic rather than low-level communication details.[4] In distributed computing, RPC plays a central role by facilitating client-server interactions in networked environments, where distributed applications can leverage a familiar procedure call interface to build scalable systems without explicit handling of inter-machine communication.[2] The core components include the client (the calling program), the server (the remote procedure executor), and stub routines: the client stub marshals arguments into network messages and invokes the transport layer, while the server stub unmarshals incoming messages and dispatches the call to the actual procedure.[4] A high-level example illustrates RPC's transparency; a local procedure call might appear asresult = add(a, b);, and the remote equivalent uses identical syntax, with the client stub handling the network transmission invisibly to the programmer.[4]
Unlike direct socket programming, which requires explicit management of byte streams, connections, and data serialization, RPC provides location transparency by mimicking local calls and hiding these details through stubs and runtime support.[5]
Core Principles
Remote procedure calls (RPCs) are designed to provide a level of transparency that allows developers to invoke remote procedures as if they were local, abstracting away the complexities of distributed computing. Location transparency hides the physical location or address of the server from the client, enabling procedure calls without specifying network details. Access transparency conceals the differences in data representation and communication protocols between client and server, ensuring seamless interoperability across heterogeneous systems. Failure transparency masks errors such as network partitions or server crashes, presenting them in a manner similar to local exceptions, while performance transparency aims to mitigate the impact of network latency and variability, though it often requires additional mechanisms like caching or retries to approximate local call speeds.[6] At the heart of RPC's abstraction layers are client and server stubs, which serve as intermediaries to handle the distribution details. The client stub packs procedure parameters into a network message through a process known as marshaling, transmitting them to the server, while the server stub unpacks these parameters via unmarshaling before invoking the actual procedure. Conversely, upon completion, the server stub marshals the results, and the client stub unmarshals them for return to the caller. This stub-based approach ensures that the programmer interacts only with familiar local procedure semantics, without direct involvement in network communication.[7] Traditional RPC implementations employ synchronous, blocking calls as the default model, where the client thread suspends execution until the server responds, mirroring the behavior of local procedure calls to maintain semantic consistency. This blocking nature simplifies programming but can introduce latency issues in high-latency networks. Non-blocking or asynchronous variants, where the client continues execution after issuing the call and later retrieves the result via callbacks or polling, address these limitations by improving concurrency and responsiveness, particularly in event-driven or multi-threaded applications.[7][8] To ensure cross-platform compatibility, RPC systems handle data representation through standardized formats that convert machine-dependent data into a neutral, external form. External Data Representation (XDR), for instance, defines a canonical encoding for basic types like integers and strings, independent of host byte order or architecture, allowing parameters to be accurately interpreted on diverse systems during marshaling and unmarshaling.[9] RPC provides semantic guarantees regarding execution to manage the uncertainties of distributed environments, typically adhering to at-most-once semantics where, if a response is received, the procedure has executed exactly once, but retries may result in no execution if failures occur. Achieving exactly-once semantics is challenging due to potential duplicate requests from network issues, often requiring idempotency in procedures or additional state management.[7]Historical Development
Origins in Early Computing
The development of remote procedure calls (RPC) emerged from the broader context of early distributed computing in the 1970s, where time-sharing systems and wide-area networks like ARPANET highlighted the need for efficient resource sharing across disparate machines. Time-sharing systems, such as Multics and the Compatible Time-Sharing System (CTSS), enabled multiple users to access centralized computing resources interactively, but as ARPANET connected remote sites starting in 1969, researchers sought mechanisms to extend this sharing beyond local boundaries. The primary motivation was to overcome the limitations of batch processing and isolated mainframes by allowing programs on one computer to access data or services on another, fostering collaborative environments in academic and military research funded by ARPA.[10] A pivotal early proposal for RPC as a subroutine-level abstraction originated in 1976 at Xerox PARC, where Jim White advanced concepts for remote invocation within the Distributed Programming System (DPS) integrated into SRI's NLS text editor. This work built on ARPANET discussions, including Jack Haverty's RFC 722, which outlined a request-response discipline for network servers, emphasizing synchronous communication to mimic local procedure calls. The motivations centered on simplifying distributed programming in an era of expanding networks, where low-level message passing proved cumbersome; RPC drew inspiration from familiar procedure calls in sequential languages like Mesa (similar to Pascal) and C, aiming to provide transparent semantics for parameters, exceptions, and control flow across address spaces. Bruce Jay Nelson's 1981 dissertation further formalized these ideas, evaluating prototypes like Envoy and Diplomat to demonstrate RPC's efficiency over Ethernet, achieving round-trip times as low as 149 microseconds on high-speed processors.[2] Initial prototypes materialized in the Cedar/Mesa environment at Xerox PARC, where Andrew Birrell and Bruce Nelson implemented RPC in 1984 to support networked applications on Dorado workstations. This system generated stubs to handle marshaling and unmarshaling, ensuring at-most-once semantics and integrating with the Mesa language for seamless local-remote transitions, with simple calls completing in about 1 millisecond over Ethernet. The design prioritized ease of use for programmers, abstracting network details like binding and failure recovery.[1] In the 1980s, RPC concepts gained formalization through Xerox's Courier protocol, introduced in 1981 as part of the Xerox Network Systems (XNS), which defined a standardized RPC layer for application protocols like remote filing and printing over PUP and Ethernet. Courier emphasized type-safe data representation and idempotency, influencing subsequent systems. Concurrently, early UNIX implementations, such as rexec and rsh in 4.2BSD (1983), provided rudimentary RPC-like remote execution, using TCP for command invocation and output return, though lacking advanced features like stubs or encryption. These efforts solidified RPC as a foundational abstraction for distributed systems.[1][11]Key Milestones and Evolution
The 1980s marked the emergence of standardized RPC implementations that enabled practical distributed computing. Sun Microsystems introduced ONC RPC in 1984 as part of its Network File System (NFS) project, providing a lightweight protocol for remote invocations over UDP or TCP, which became one of the first widely adopted RPC systems in Unix environments. This binary protocol emphasized simplicity and performance, influencing subsequent standards. Building on these foundations, the Open Software Foundation (OSF) released DCE RPC in 1990 as a core component of its Distributed Computing Environment (DCE), offering enhanced features like secure authentication, directory services, and support for heterogeneous networks through an interface definition language (IDL). In the 1990s, RPC evolved toward object-oriented paradigms to support more complex distributed applications. The Object Management Group (OMG) integrated RPC mechanisms into the Common Object Request Broker Architecture (CORBA) with its 1.1 specification in 1991, enabling method invocations on distributed objects across languages and platforms.[12] Similarly, Microsoft extended its Component Object Model (COM) with Distributed COM (DCOM) in 1996, adapting DCE RPC for Windows-based object invocations and facilitating enterprise integration in proprietary ecosystems. The 2000s saw RPC adapt to web-based architectures, shifting from proprietary binary protocols to XML and HTTP for broader interoperability. XML-RPC, proposed by Dave Winer in 1998, introduced a simple XML-over-HTTP format for remote calls, paving the way for web services.[13] This culminated in SOAP, standardized by the W3C in 2000 as a protocol for exchanging structured information in web services, supporting RPC-style operations while incorporating WS-Security and other extensions for enterprise use.[14] Recent developments through 2025 have focused on high-performance, cloud-native RPC suited for microservices. Google open-sourced gRPC in 2015, leveraging HTTP/2 for multiplexing and Protocol Buffers for efficient serialization, which has become a staple for low-latency inter-service communication in scalable systems.[15] In the 2020s, cloud providers enhanced RPC support; for instance, Amazon Web Services added native gRPC integration to API Gateway in 2021, enabling serverless RPC deployments with automatic scaling. Overall, RPC has transitioned from binary, platform-specific protocols to HTTP-centric and RESTful alternatives, yet retains prominence in microservices architectures where gRPC offers superior efficiency over REST for internal service meshes.[16]Operational Mechanics
Message Passing Fundamentals
In remote procedure calls (RPC), communication between client and server relies on an exchange of structured messages that abstract the underlying network transport. The fundamental message types are the request (or call) message and the reply message. A request message contains the procedure identifier—typically a numeric value specifying the remote program, version, and specific procedure—along with the parameters to be passed to that procedure. These parameters are encoded in a platform-independent format to ensure interoperability across heterogeneous systems. The reply message, in turn, carries the procedure's results or an indication of failure, including any output values generated by the execution. This bidirectional message model mimics local procedure invocation while handling the distributed nature of the interaction.[17] Central to message passing in RPC is the marshaling process, which serializes parameters and results into a byte stream suitable for transmission over the network. Marshaling involves converting machine-specific data representations into a canonical external data representation (XDR), a standard defined for ONC RPC that supports both primitive types like integers and strings, as well as complex types such as arrays, structures, and unions. For instance, an array of structures would be encoded by first serializing the array's length, followed by each element's fields in a defined order, ensuring no padding or endianness issues arise during deserialization on the receiving end. This process, performed by client stubs before sending and by server stubs upon receipt, enables transparent handling of data without shared memory assumptions. The original RPC implementation emphasized efficient marshaling to minimize overhead, fitting typical messages into single packets where possible.[18][19] RPC messages are typically transported over reliable protocols like TCP for connection-oriented delivery or UDP for lightweight, datagram-based exchanges, with the choice influencing reliability semantics. Over TCP, messages are framed with length prefixes to delineate records in the byte stream, providing inherent ordering and delivery guarantees. UDP, being connectionless, requires explicit client-side retransmission logic for lost packets, often using timeouts to trigger retries. Error handling is embedded in the message protocol through status codes in replies; for example, accept errors include codes for program unavailability (1), procedure unavailability (3), invalid arguments (4, e.g., garbage parameters), and system errors (5), while reject errors cover RPC version mismatches (0) or authentication failures (1). Timeouts are managed at the application or transport layer, with replies indicating failures like invalid parameters via these codes to inform the client without further negotiation.[17] To support robust operation in unreliable networks, RPC incorporates idempotency considerations, primarily through at-most-once invocation semantics enabled by a unique transaction identifier (xid) in each message. Clients include the xid in requests and retransmit with the same value if no timely reply is received, allowing servers to detect duplicates via a cache of recent xids and avoid re-executing non-idempotent procedures that could cause side effects, such as duplicate database updates. While exactly-once semantics are not guaranteed due to potential server crashes or network partitions, this mechanism ensures retries do not amplify errors, with the original RPC design prioritizing exactly-once under normal conditions when a reply is returned.[17][19]Sequence of Events in RPC
The sequence of events in a remote procedure call (RPC) begins when a client process invokes a remote procedure, abstracting the distributed nature of the interaction to resemble a local subroutine call. This process relies on client and server stubs—automatically generated code that handles the low-level details of communication—to maintain transparency for the programmer. The invocation triggers a series of steps involving argument preparation, network transmission, execution on the server, and result return, all orchestrated by the RPC runtime system.[4] First, the client process makes a local call to the client stub, passing the procedure name and arguments as if calling a local function. The client stub then marshals these arguments—converting them into a network-transmittable format, such as byte streams or packets—along with the procedure specification, ensuring compatibility across machines. This marshaled request is packaged into one or more packets and handed to the RPC runtime for transmission over the network to the server's address, which is assumed to be resolved prior to invocation. The client process typically blocks at this point, suspending execution until the response arrives, mimicking synchronous local calls.[4][20] Upon receipt, the server's RPC runtime delivers the packets to the server stub. The server stub unmarshals the arguments, reconstructing them into the appropriate format for the server's address space, and then dispatches a local call to the actual server procedure with these parameters. The server procedure executes as a standard local function, performing the requested computation without awareness of its remote origin. Once execution completes, the procedure returns its results (including any output parameters or exceptions) to the server stub.[4][20] The server stub then marshals the results into a reply packet and passes it to the RPC runtime for transmission back across the network to the client. On the client side, the RPC runtime receives the reply, and the client stub unmarshals the results, converting them back into the client's format before returning the value to the invoking process. This resumes the client's execution, delivering the outcome as if from a local procedure. The entire round-trip aims for at-most-once semantics, where the call executes no more than once despite potential network issues.[4][20] RPC systems must handle exceptions arising from network unreliability, such as lost packets, timeouts, or server failures, integrated into the sequence to preserve reliability. If packets are lost during transmission, the RPC runtime detects this via acknowledgments or sequence numbers and initiates retransmissions of the request or reply, ensuring idempotency to avoid duplicate executions. Timeouts occur if no response arrives within a predefined interval, prompting the client stub to raise an exception (e.g., a communication failure) to the client process, similar to how local calls might handle deadlocks. Probe packets may be sent periodically to detect server crashes or network partitions, notifying the client via exceptions without altering the core sequence. These mechanisms do not guarantee exactly-once execution but provide robust failure detection and recovery.[4]Binding and Contact Mechanisms
In remote procedure calls (RPC), binding refers to the process of establishing a logical connection between a client and a server, enabling the client to locate and invoke procedures on the remote server. This mechanism resolves the client's reference to the server's interface, typically involving the specification of network addresses, ports, and identifiers for the target procedure. Binding is essential prior to invocation, as it handles the variability in server locations and configurations across distributed systems.[19] Static binding occurs at compile-time or link-time, where the client's code is resolved to fixed server addresses or ports hardcoded into the application. This approach simplifies development by embedding server details directly, avoiding runtime discovery overhead, but it limits flexibility since changes to server locations require recompilation. In early RPC designs, static binding was favored for environments with stable, known server endpoints.[19] Dynamic binding, in contrast, resolves server details at runtime, allowing clients to discover available servers without prior knowledge of their exact locations. This is achieved through intermediary services that map abstract identifiers to concrete network endpoints. For instance, in the Open Network Computing (ONC) RPC protocol, the rpcbind service (formerly portmapper) operates on well-known TCP/UDP port 111, where servers register their program numbers and versions upon startup, and clients query this port to obtain the corresponding dynamic port assignments for the desired service. This enables transport-independent binding using universal addresses in ASCII string format.[21] Naming services provide a higher-level abstraction for dynamic binding, associating human-readable or hierarchical names with server objects or interfaces. In the Common Object Request Broker Architecture (CORBA), the Naming Service maintains a namespace of bindings within naming contexts, where clients use operations likebind to register object references and resolve to retrieve them by name, supporting nested hierarchies for scalable resolution. Similarly, in the Distributed Computing Environment (DCE) RPC, universal unique identifiers (UUIDs) uniquely identify interfaces and optional object endpoints; servers register these with an endpoint mapper, which clients query to complete partial bindings lacking endpoint details.[22][23]
Universal Resource Locators (URLs) and Domain Name System (DNS) resolution extend naming services to RPC endpoints by mapping symbolic names to IP addresses and ports, facilitating location transparency in internet-scale deployments. For example, DNS-based service discovery allows clients to resolve RPC server hostnames to addresses dynamically, often combined with port mappers for full endpoint binding.[21]
To support scalability and reliability, binding mechanisms incorporate load balancing and failover through multiple server replicas. In DCE RPC, servers can register vectors of endpoints with the mapper, enabling clients to select from available replicas for distributing load or redirecting to healthy instances upon failure. ONC RPC similarly allows multiple registrations under the same program number, permitting clients to query and bind to alternate ports for balanced invocation across replicas. These techniques ensure continued availability without single points of failure in the binding phase.[23][21]
Implementations and Analogues
Language-Specific Variants
Remote procedure call (RPC) implementations vary across programming languages, often integrating with native syntax, libraries, and tools to facilitate seamless remote invocations while addressing language-specific concerns like type marshalling and stub generation. These variants typically leverage interface definition languages (IDLs) or built-in mechanisms to handle data serialization, ensuring compatibility within the language ecosystem and, where possible, across languages through standardized encodings.[24] In C and C++, ONC RPC, originally developed by Sun Microsystems, provides a foundational implementation using the rpcgen tool to automate stub generation from interface definitions written in XDR (eXternal Data Representation), a language-agnostic IDL that standardizes type handling for cross-language compatibility by specifying encodings like big-endian integers and padded structures.[25][26] The rpcgen compiler processes .x files to produce client stubs, server stubs, header files, and XDR routines, enabling developers to write RPC programs as if they were local functions; for example, a simple procedure definition in XDR can generate C code for remote calls over UDP or TCP.[25] This approach emphasizes static compilation for performance, with XDR ensuring portable data representation across heterogeneous systems.[26] Java's Remote Method Invocation (RMI), introduced in JDK 1.1 in 1997, supports RPC through interface-based remote objects, where developers define remote interfaces extending java.rmi.Remote to specify callable methods, and implement them in classes that extend java.rmi.server.UnicastRemoteObject for automatic stub export.[27][28] The RMI registry, accessed via java.rmi.registry.Registry and LocateRegistry, serves as a naming service for binding and looking up remote objects by name, typically on port 1099, allowing clients to obtain stubs dynamically for method invocation.[28] Java RMI handles type marshalling via object serialization, passing remote references for objects implementing Remote while serializing primitives and non-remote objects by value.[28] Python offers RPC support through standard libraries like xmlrpc.client, which provides a dynamic proxy for invoking methods on XML-RPC servers over HTTP, where ServerProxy creates an object that maps method calls to XML-encoded requests without requiring predefined stubs.[29] For more advanced object-oriented RPC, the third-party Pyro5 library enables dynamic proxies that allow transparent remote access to Python objects, supporting features like method invocation, attribute access, and even callbacks with minimal boilerplate, using serialization for type handling.[30] Pyro5 proxies can be created from URIs or object IDs, facilitating ad-hoc remote calls while handling exceptions and one-way invocations. Go's net/rpc package, part of the standard library, implements RPC using exported methods on server objects, where clients dial connections to call procedures synchronously or asynchronously, defaulting to gob encoding for type-safe marshalling.[31] For broader compatibility, the net/rpc/jsonrpc subpackage supports JSON-RPC 1.0 over HTTP, allowing clients to send JSON-formatted requests via DialHTTP and integrating with Go's http package for server setup using HandleHTTP.[31] This enables cross-language use where JSON handles diverse types like structs and slices, though Go-specific conventions require methods to follow a receiver-arguments-error signature.[31] Differences in type handling across these variants often stem from language-specific IDLs or serialization strategies; for instance, ONC RPC's XDR provides explicit, cross-language type mappings for fixed and variable arrays, while Java RMI relies on JVM serialization for object graphs, and Python's xmlrpc uses XML schemas for basic types, potentially limiting complex structures without extensions like Pyro5's pickle-based serialization.[24][26] These approaches balance ease of use within the language against interoperability, with IDLs like XDR promoting standardization for multi-language environments.[24]Application and Framework-Specific Uses
In database systems, remote procedure calls facilitate distributed querying and execution across interconnected instances. PostgreSQL's dblink extension enables connections to remote databases, allowing the execution of SQL statements, including calls to stored procedures, as if they were local. For instance, dblink_exec can invoke a remote procedure by passing the procedure call as a SQL command string, supporting data retrieval and modification across servers.[32] Similarly, Oracle Database uses database links to access PL/SQL objects on remote databases, where procedures are invoked by appending the link name to the procedure identifier, such asprocedure_name@dblink, enabling seamless execution of remote logic within local transactions.[33]
In web services and microservices architectures, RPC frameworks like Thrift and gRPC integrate deeply to handle inter-service communication. Thrift, originally developed by Facebook in 2007, provides a scalable, cross-language RPC system for defining and invoking services, emphasizing efficient serialization and transport for high-throughput applications such as social network backends.[34] gRPC, built on HTTP/2, is commonly deployed in service meshes like Istio, where it supports proxyless integration for direct gRPC service discovery, traffic routing, and load balancing among microservices, reducing latency in containerized environments.[35]
For operating systems, Microsoft Windows employs the Microsoft Remote Procedure Call (MSRPC) protocol to enable distributed system calls across networked machines. MSRPC extends the DCE/RPC standard, allowing client applications to invoke server procedures transparently, as seen in components like Active Directory for authentication and management tasks.[36]
In cloud applications, RPC patterns underpin serverless and container orchestration workflows. AWS Lambda functions can be invoked synchronously via gRPC protocols, often through API Gateway integrations that translate gRPC requests into Lambda execution events, supporting high-performance, event-driven architectures for scalable processing.[37] In Kubernetes, service calls frequently leverage gRPC for efficient pod-to-pod communication, with built-in load balancing ensuring even distribution of RPC traffic across replicas to maintain reliability in distributed deployments.[38]
A notable case study is Hadoop's use of RPC for high-performance distributed data processing. Hadoop's framework employs a custom RPC layer, based on abstractions like the IPC (Inter-Process Communication) protocol, to coordinate tasks between the NameNode, DataNodes, and client applications, enabling fault-tolerant execution of MapReduce jobs across clusters handling petabyte-scale datasets. This RPC mechanism ensures low-latency communication for metadata operations and data transfers, contributing to Hadoop's scalability in big data environments.[39]