A minimal two-process example using the Canopy RPC library. A server exposes a
i_greeter interface over TCP; a client connects, calls three methods, and exits.
- Canopy checked out at
../Canopy(adjacent to this directory) - clang / clang++ (default; see
CMakePresets.jsonto override) - Ninja
- OpenSSL, liburing (same requirements as Canopy coroutine builds)
cmake --preset Coroutine
cmake --build build_coroutine --target server clientBinaries land in build_coroutine/output/.
Start the server in one terminal, then run the client in another.
./build_coroutine/output/server [options]
Press Ctrl+C to shut down.
./build_coroutine/output/client [options]
The client connects, calls greet, add, and get_server_info, prints the
results, then exits cleanly.
Both server and client register Canopy network arguments via
canopy::network_config::add_network_args(parser).
These options define the Canopy zone address allocated for the local process:
| Option | Meaning |
|---|---|
--va-name <identifier> |
Virtual address name. |
| `--va-type <local | ipv4 |
--va-prefix <prefix> |
Routing prefix. If omitted, Canopy auto-detects it. |
--va-subnet-bits <n> |
Subnet field width. Default in Canopy is 64. |
--va-subnet <value> |
Initial subnet value. Default in Canopy is 0. |
--va-object-id-bits <n> |
Object-id field width. Default in Canopy is 64. |
--va-object-id <value> |
Initial object-id value. Default in Canopy is 0. |
These options define the TCP addresses used for listening and connecting:
| Option | Meaning |
|---|---|
--listen [va-name:]addr:port |
Server bind/listen endpoint. |
--connect [va-name:]addr:port |
Client connect endpoint. |
-h, --help |
Print usage and exit. |
Address family is inferred from the endpoint format:
- IPv4:
127.0.0.1:8080 - IPv6:
[::1]:8080 - Port-only listen form:
9090
This repo adds defaults before parsing CLI arguments, so the programs run without manually specifying Canopy network flags.
If you do not pass any --va-* options, server injects:
--va-name=server
--va-type=ipv4
--va-prefix=127.0.0.1
--va-subnet-bits=32
--va-object-id-bits=32
--va-subnet=1If you do not pass --listen, server injects:
--listen=server:127.0.0.1:8080If you do not pass any --va-* options, client injects:
--va-name=client
--va-type=ipv4
--va-prefix=127.0.0.1
--va-subnet-bits=32
--va-object-id-bits=32
--va-subnet=100If you do not pass --connect, client injects:
--connect=client:127.0.0.1:8080Default (loopback, port 8080):
# terminal 1
./build_coroutine/output/server
# terminal 2
./build_coroutine/output/clientCustom port:
./build_coroutine/output/server --listen=server:127.0.0.1:9000
./build_coroutine/output/client --connect=client:127.0.0.1:9000Custom virtual-address prefixes and endpoint port:
./build_coroutine/output/server \
--va-name=server --va-type=ipv4 --va-prefix=192.168.1.0 \
--va-subnet-bits=32 --va-object-id-bits=32 --va-subnet=1 \
--listen=server:192.168.1.10:9000
./build_coroutine/output/client \
--va-name=client --va-type=ipv4 --va-prefix=192.168.1.0 \
--va-subnet-bits=32 --va-object-id-bits=32 --va-subnet=100 \
--connect=client:192.168.1.10:9000IPv6:
./build_coroutine/output/server \
--va-name=server --va-type=ipv6 --va-prefix=2001:db8:: \
--listen=server:[::1]:8080
./build_coroutine/output/client \
--va-name=client --va-type=ipv6 --va-prefix=2001:db8:: \
--connect=client:[::1]:8080Defined in idl/greeting/greeting.idl:
namespace greeting_app
{
interface i_greeter
{
int greet([in] const std::string& name, [out] std::string& response);
int add(int a, int b, [out] int& result);
int get_server_info([out] std::string& info);
};
}
Generated code appears in build_coroutine/generated/ and is never written
back into the source tree.
test_app/
├── CMakeLists.txt root build file; pulls in Canopy via add_subdirectory
├── CMakePresets.json Coroutine (Debug) and Release_Coroutine presets
├── idl/
│ ├── CMakeLists.txt CanopyGenerate() call
│ └── greeting/
│ └── greeting.idl i_greeter interface definition
└── src/
├── server.cpp greeter_impl + TCP listener; runs until Ctrl+C
└── client.cpp connects, calls all three methods, exits