This repository shows a simple Remote Procedure Call (RPC) communication using gRPC in C#:
csgrpcserver: Provides RPC methodscsgrpcclient: Calls the methods over the networkProtos/SimpleCommunication.proto: Contract (interface) between client and server
The project demonstrates how to design function calls between two processes/applications so they behave like local method calls.
Instead of just sending bytes over a socket connection, gRPC provides:
- clearly defined interfaces,
- serialized data types,
- and strongly typed calls between client and server.
This repository uses submodules (CSgRPCServer and CSgRPCClient). Clone it like this:
git clone --recurse-submodules https://github.com/Ano-sys/CSgRPCService.git
cd CSgRPCServiceIf you already cloned the repository without submodules:
git submodule update --init --recursiveRPC (Remote Procedure Call) means:
A function is called in another process (often on another machine) as if it were local.
Technically, the call is a network request, but from a developer perspective it looks similar to a normal method call:
- The client calls a method (e.g.
GetMessage(...)) - The request is serialized and sent over the network
- The server executes the method
- The response is serialized and sent back
- The client receives the result
- based on HTTP/2
- uses Protocol Buffers (compact, fast)
- strongly typed contracts via
.proto - automatic code generation for client/server stubs
RPC is especially useful when:
- Application logic should run separately (client and service can be deployed separately)
- Multiple clients (desktop, web, backend) should use the same service
- Scalability matters (server can be scaled horizontally on its own)
- Clear API contracts are required (versioning via protos)
- Cross-language communication is relevant (e.g. C#, Go, Java, Python)
- Internal service communication is needed in distributed systems
Typical use cases:
- microservices
- central business services
- internal enterprise APIs
- high-performance service-to-service communication
- Method-level abstraction instead of manually handling a byte protocol
- Strongly typed through
.protocontracts - Automatic code generation reduces boilerplate
- Good interoperability across languages/platforms
- Scalable and deployable as its own service
- Uniform contract for all clients
- Network dependency (latency, timeouts, outages)
- More complex operating model than pure in-process calls
- Versioning discipline required (avoid breaking changes)
- Distributed debugging is often more complex than local code
- Maximum control over protocol and data flow
- Very flexible for special cases
- Potentially very efficient if implemented well
- High development effort (framing, serialization, error cases)
- More error-prone due to custom protocol design
- Poor maintainability if the specification is not maintained strictly
- No standardized stub/contract workflow like gRPC
- When a proprietary low-level protocol is required
- When extremely specific transport requirements exist
This means: The C# program references a DLL and calls functions directly in the same process.
- Very fast (no network, no external serialization)
- Simple debugging in the same process
- Low runtime complexity
- Tight coupling between the calling app and the library
- Deployment coupling (versions must match)
- Weaker isolation: an error/crash in the DLL can affect the whole process
- Limited scaling to the local process/host
- For local, high-performance function libraries
- When no process or machine separation is required
This means: The main program starts a separate tool/program, passes parameters, and waits for a result (exit code, output, file).
- Strong isolation between the main process and the tool
- Can reuse large existing tools
- Errors in the tool usually affect the host process less directly
- Startup cost per call (process startup is expensive)
- Cumbersome data transfer (arguments, stdout, files, pipes)
- Limited interactivity for many small calls
- Operations/monitoring become complex with many tool calls
- For infrequent, heavy batch tasks
- When existing CLI tools must be integrated
- gRPC/RPC: Best choice for stable, typed communication between separate applications/services.
- Raw TCP: Only when you deliberately need your own protocol and full low-level control.
- Local DLL: Best choice for maximum local performance and tight integration in the same process.
- External process call: Good for loosely coupled tool integration and isolated batch steps with higher runtime overhead.
Ask yourself for new features:
- Does the function need to be usable over the network/between processes? -> more likely RPC
- Must it run extremely fast in the same process? -> more likely DLL
- Does an existing large tool only need to be used occasionally? -> more likely external process
- Do you need a custom binary protocol with special requirements? -> possibly direct TCP
csgrpcclient/CSgRPCClient/Program.cs: Example clientcsgrpcserver/CSgRPCServer/Program.cs: Example serverProtos/SimpleCommunication.proto: RPC contract
Unlike a local function call, RPC should always consider:
- timeouts/deadlines
- retries (controlled)
- idempotency
- clean error codes
- telemetry/logging for distributed error analysis
These points in particular are a core difference between a "local call" and a "remote call".
RPC is ideal when functions should be provided as a service; local DLL calls are ideal for in-process performance, and external process calls are suited for loosely coupled tool integration with higher runtime overhead.