C++ Quickstart — from Install to a First Request¶
What you get from this chapter
You can install the packages and run a minimal project where two processes call each other.
The project lives at
framework/languages/cpp/quickstart/. The code blocks
below are read from those files when the site is built. Without a location store, two processes
name each other's endpoint directly and exchange one request/reply.
0. Clone the examples repository¶
This chapter's project is quickstart/ in the zlink-cpp-examples repository; tutorial/,
the program read by the feature guides, and samples/ live beside it.
main is the latest release plus the fixes merged since, with package versions pinned to that
release. An older release is the tag vA.B.C (git checkout vA.B.C). Send issues and PRs to
zlink-systems/zlink.
1. Installation¶
- CMake 3.20 or later, a C++20 compiler. The framework uses C++20 coroutines. The
IDE path reads the preset the bootstrap writes and needs 3.21 or later;
bootstrap.cmakeneeds 3.24 - nlohmann_json, Boost, liblz4, libprotobuf, OpenSSL, opentelemetry-cpp
zlink is not in the official vcpkg registry or ConanCenter yet. This repository carries an
overlay port and Conan recipes as well, so there are three installation paths. The GitHub
Release path is the one verified end to end; the bootstrap.cmake in quickstart/, tutorial/
and samples/ of zlink-cpp-examples automates it.
| Path | Where it stands |
|---|---|
| GitHub Release | Downloads and extracts this platform's one framework prebuilt. Core, the C++ binding, the framework libraries and nlohmann_json share one prefix |
| vcpkg overlay port | Refreshed by sync-recipes at every release. Outside what this page verifies |
| Conan recipe | Refreshed by sync-recipes at every release. Outside what this page verifies |
The commands of the archive path are in the project's
README.md.
1.1 vcpkg¶
Outside the verified path
This path is refreshed at every release but is not the one this page verified. Use GitHub Release for a first install.
git clone https://github.com/zlink-systems/zlink.git
vcpkg install zlink zlink-cpp zlink-framework \
--overlay-ports=zlink/vcpkg/ports --triplet=x64-linux
The consumer project uses the vcpkg toolchain.
1.2 Conan¶
Outside the verified path
This path is refreshed at every release but is not the one this page verified. Use GitHub Release for a first install.
git clone https://github.com/zlink-systems/zlink.git
conan create zlink/core/packaging/conan --build=missing -s compiler.cppstd=gnu20
conan create zlink/bindings/cpp/packaging/conan --build=missing -s compiler.cppstd=gnu20
conan create zlink/framework/languages/cpp/packaging/conan --build=missing -s compiler.cppstd=gnu20
Put zlink-framework/0.16.0 in the consumer's conanfile.txt and run conan install.
1.3 GitHub Release¶
Three archives built and installed in order — core/vX.Y.Z → cpp/vX.Y.Z →
framework-cpp/vA.B.C. The order and the exact commands are in the project's
README.md.
On this path the third-party dependencies are installed by hand. nlohmann_json, Boost, liblz4,
libprotobuf and OpenSSL come from distribution packages; opentelemetry-cpp has none and is
built from source. The framework links only opentelemetry-cpp::api, so
-DOTELCPP_WITH_API_ONLY=ON is a header-only build.
1.4 Targets to add when you need them¶
| Target | When to add it |
|---|---|
zlink::framework_locations_redis |
When using the Redis location store for auto-connect (Location) |
zlink::framework_codec_protobuf · _messagepack |
To use instead of the default JSON codec (Handlers and Message Processing) |
zlink::stream_connector |
When building an external client (a game client, mobile) (STREAM) |
zlink::http_client |
When the server calls out over HTTP (HTTP Client guide) |
The license differs by layer — core/binding is MPL-2.0, framework is FSL-1.1-ALv2, and
zlink::http_client is Apache-2.0. There is no cost to building and selling a service.
2. The consumer's CMake¶
Once the three-stage install is done, this is all the consumer writes.
cmake_minimum_required(VERSION 3.24)
project(zlink_quickstart_cpp LANGUAGES CXX)
# Everything the two processes need comes from this one package. The prefix
# it is found under comes from bootstrap.cmake (.zlink/install) or from the
# command line; see README.md.
find_package(zlink_framework CONFIG REQUIRED)
add_executable(quickstart_server Server/main.cpp)
target_link_libraries(quickstart_server PRIVATE zlink::framework)
target_compile_features(quickstart_server PRIVATE cxx_std_20)
add_executable(quickstart_client Client/main.cpp)
target_link_libraries(quickstart_client PRIVATE zlink::framework)
target_compile_features(quickstart_client PRIVATE cxx_std_20)
foreach(_target quickstart_server quickstart_client)
if(MSVC)
# The framework headers are large enough to exhaust MSVC's default object
# file section limit, and they carry UTF-8 source that the compiler reads
# as the system code page without /utf-8.
target_compile_options(${_target} PRIVATE /bigobj /utf-8)
endif()
if(WIN32)
# Windows has no RPATH: the loader looks next to the executable. Core is a
# DLL (zlink.dll) that no package manager deploys for us, so copy every
# runtime DLL this target links, Core included, beside it.
add_custom_command(TARGET ${_target} POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E copy_if_different
$<TARGET_RUNTIME_DLLS:${_target}> $<TARGET_FILE_DIR:${_target}>
COMMAND_EXPAND_LISTS)
endif()
endforeach()
3. Shared contract¶
Message types need NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE. The default JSON serializer finds
to_json/from_json by ADL, so a bare struct does not serialize.
#pragma once
#include <nlohmann/json.hpp>
#include <string>
// Request/reply contract shared by the server and client processes.
struct hello_t
{
static constexpr const char *packet_name = "Hello";
std::string name;
};
struct greeting_t
{
static constexpr const char *packet_name = "Greeting";
std::string text;
};
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE (hello_t, name)
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE (greeting_t, text)
4. The handling side¶
object_role defaults to none; this configuration states it explicitly because it is a
channel-only node. A routing_id is required. This example binds to 127.0.0.1, so only processes on the same host connect
and Windows shows no firewall prompt. When other hosts or containers must connect, bind to a reachable
address and advertise it with advertise_host.
#include "../Shared/messages.hpp"
#include <zlink/framework.hpp>
namespace fw = zlink::framework;
// Handles one request on the "greeting" channel.
class hello_handler_t
{
public:
using request_type = hello_t;
using reply_type = greeting_t;
reply_type handle (const request_type &request) { return greeting_t{"hello, " + request.name}; }
};
int main (int argc, char **argv)
{
auto app = fw::app_t::create ();
app.add_zlink_framework ([] (fw::zlink_framework_options_t &options) {
// Names the mesh and opens this process's endpoint for peers to connect to.
auto mesh = options.add_route_mesh ("services")
.listen ("tcp://127.0.0.1:7301")
// No Object Server/Client role, so no Location Store is required for
// this endpoint-only quickstart.
.set_object_role (fw::object_role_t::none)
.set_routing_id (zlink::routing_id_t::from ("quickstart-server"))
.set_advertise_host ("127.0.0.1");
// This process handles the "greeting" channel.
mesh.channel_name ("greeting")
.server ()
.add_request_handler<hello_handler_t, hello_t, greeting_t> ();
});
return app.run (argc, argv);
}
5. The calling side¶
An HTTP handler does not receive route parameters as arguments. It takes an
http_request_t and reads request.route_values.
#include "../Shared/messages.hpp"
#include <zlink/framework.hpp>
namespace fw = zlink::framework;
// Serves GET /hello/{name}: calls "greeting" on the mesh and returns the reply.
class hello_http_handler_t
{
public:
explicit hello_http_handler_t (fw::route_client_t &routes) : _routes (routes) {}
fw::task_t<fw::http_response_t> handle (const fw::http_request_t &request)
{
const auto name = request.route_values.at ("name");
auto reply = co_await _routes.request_to_channel ("greeting", hello_t{name})
.template async<greeting_t> ();
co_return fw::http_response_t{200, "\"" + reply.text + "\"", "application/json"};
}
private:
fw::route_client_t &_routes;
};
int main (int argc, char **argv)
{
auto app = fw::app_t::create ();
app.add_zlink_framework ([] (fw::zlink_framework_options_t &options) {
// This process also needs its own endpoint.
auto mesh = options.add_route_mesh ("services")
.listen ("tcp://127.0.0.1:7302")
.set_object_role (fw::object_role_t::none)
.set_routing_id (zlink::routing_id_t::from ("quickstart-client"))
.set_advertise_host ("127.0.0.1");
// This side only calls; it does not handle "greeting".
mesh.channel_name ("greeting").client ();
// Manual connection -- the server's endpoint is given directly.
mesh.peer_connections ().connect ("tcp://127.0.0.1:7301");
options.http ()
.listen ("http://127.0.0.1:5083")
.map_get<hello_http_handler_t> ("/hello/{name}");
});
return app.run (argc, argv);
}
6. Run¶
bootstrap.cmake replaces the installation in §1 and configures this project into build/. It
downloads this platform's framework prebuilt and extracts it into .zlink/install/; it builds
nothing. If tutorial/ was bootstrapped first, reuse its prefix with
cmake -DZLINK_ROOT=../tutorial/.zlink -P bootstrap.cmake.
cd zlink-cpp-examples/quickstart
cmake -P bootstrap.cmake
cmake --build build --parallel
# Two terminals. Start the server first.
./build/quickstart_server
./build/quickstart_client
curl http://127.0.0.1:5083/hello/world
If you installed the three stages of §1 yourself, configure with
cmake -S . -B build -DCMAKE_PREFIX_PATH=<framework install prefix> instead of the bootstrap.
The response is "hello, world" with status 200.
7. Opening it in an IDE¶
It is a CMake project, so an IDE reads CMakeLists.txt and the preset as they are. bootstrap.cmake
records the exact arguments it configured this folder with (framework install prefix, C++
standard, generator) as the preset zlink in CMakeUserPresets.json, so run
§6's cmake -P bootstrap.cmake once, open the folder and pick that preset — that is all. No
environment variable is needed. The preset configures into the same build/ as §6, so the IDE
continues from the terminal build. CMakeUserPresets.json is rewritten by every bootstrap and
ignored by git; it is not edited by hand — to change a value, run the bootstrap again. The
Windows preset is Release only: the prebuilt is built Release, and a Debug consumer cannot link
against it.
7.1 Visual Studio 2022 or 2026¶
The Desktop development with C++ workload with its C++ CMake tools for Windows component is required.
- Run
cmake -P bootstrap.cmakein a terminal (§6). - File → Open → Folder on
quickstart/. No solution file is needed. - Pick
zlink bootstrap (Release)in the configuration drop-down. - Build → Build All.
-
Choose
quickstart_server.exeas the startup item and run it, then runquickstart_client.exeand check from a terminal.
7.2 Rider · CLion¶
The JetBrains IDEs (Rider with C++ support, CLion) also recognise the folder as a CMake project and read the preset as a profile.
- Run
cmake -P bootstrap.cmakein a terminal (§6); run it inside WSL to build there. - File → Open on the
quickstart/folder. - Settings → Build, Execution, Deployment → CMake lists the preset
zlinkas a profile. Enable it. The toolchain is Visual Studio on Windows, or the WSL toolchain when building under WSL. -
After the CMake load, run configurations
quickstart_serverandquickstart_clientappear. Runquickstart_server, thenquickstart_client, and check from a terminal.
Stop with the IDE's Stop button.
8. What to check when the first run fails¶
| Symptom | What to check |
|---|---|
find_package fails |
Check that cmake -P bootstrap.cmake ended with bootstrap complete and that .zlink/install/lib/cmake/zlink_framework/ exists. If you installed by hand, check that CMAKE_PREFIX_PATH points at the framework install prefix |
| The server requires a location store | Check that set_object_role is set to none |
| Startup fails | Check that both processes name the same mesh and that routing_id is set |
| A message does not serialize | Check that the message type carries NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE |
| A call ends with no target | Check that the receiving side registered that channel name in the server role, and that the two processes are connected as peers |
9. What to carry over¶
| File | Content |
|---|---|
CMakeLists.txt |
find_package(zlink_framework CONFIG REQUIRED) and linking zlink::framework |
Shared/messages.hpp |
packet_name and NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE |
Server/main.cpp |
add_route_mesh → listen → set_object_role(none), set_routing_id, set_advertise_host → handler registration |
Client/main.cpp |
The client role, peer_connections().connect(...), request_to_channel(...) |
10. What to read next¶
These two processes connect by writing each other's endpoint directly. Keeping the calling code unchanged while servers are added or restarted at another address needs automatic connection, and that is covered by Location.
- To go over the concepts first — Core Concepts
- The path that calls by name — Channel Messaging
- State objects called by id — Spot · Actor
- To see a complete business flow — Picking a Sample