diff --git a/README.md b/README.md index fac50688..f32402d0 100644 --- a/README.md +++ b/README.md @@ -91,63 +91,9 @@ Examples are placed into `build/examples/zenohc` and `build/examples/zenohpico` ## Running the examples -Change current directory to the variant you want (`examples/zenohc` or `examples/zenohpico` in the build directory). +See information about running examples [here](./examples/README.md). -See example sources for command line arguments (key expression, value, router address). - -`zenohc` examples can work standalone, but for `zenohpico` examples the working zenoh router is required. So to run `zenohpico` examples download [zenoh] project and run the router ([Rust](rust-lang) should be installed): - -```bash -git clone https://github.com/eclipse-zenoh/zenoh -cd zenoh -cargo run -``` - -### Basic Pub/Sub Example - -```bash -./z_sub -``` - -```bash -./z_pub -``` - -The `z_pub` should receive message sent by `z_sub`. - -### Queryable and Query Example - -```bash -./z_queryable -``` - -```bash -./z_get -``` - -### Queryable and Querier Example - -```bash -./z_queryable -``` - -```bash -./z_querier -``` - -The `z_querier` should continuously send queries and receive replies from `z_queryable`. - -### Throughput Examples - -```bash -./z_sub_thr_cpp -``` - -```bash -./z_pub_thr_cpp 1024 -``` - -After 30-40 seconds delay the `z_sub_thr` will start to show the throughput measure results. +Examples of linking [zenoh-cpp] to an external project can be found [here](./examples/simple/Readme.md). ## Library usage diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 00000000..708bbf20 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,325 @@ +# Zenoh CPP examples + +## Start instructions + + Depending on the backend used, the examples will be available either in `examples/zenohc` or `examples/zenohpico` (or in both, if zenoh-cpp is built for both backends) in the build directory. + + Each example accepts the `-h` or `--help` option that provides a description of its arguments and their default values. + + `zenohc` examples can work standalone, but for `zenohpico` examples the working zenoh router is required, so you will need to download [zenoh](https://github.com/eclipse-zenoh/zenoh) project and run the router ([Rust](https://www.rust-lang.org) should be installed): + + ```bash + git clone https://github.com/eclipse-zenoh/zenoh + cd zenoh + cargo run + ``` + + If you run the tests against the Zenoh router running in a Docker container, you need to add the + `-e tcp/localhost:7447` option to your examples. That's because Docker doesn't support UDP multicast + transport, and therefore the Zenoh scouting and discovery mechanism cannot work with. + +## Examples description + +Note: few examples might not be available under one of the backends, due to unsupportrd APIs. + +### z_scout + + Scouts for Zenoh peers and routers available on the network. + + Typical usage: + + ```bash + z_scout + ``` + +### z_info + + Gets information about the Zenoh session. + + Typical usage: + + ```bash + z_info + ``` + +### z_put + + Puts a path/value into Zenoh. + The path/value will be received by all matching subscribers, for instance the [z_sub](#z_sub) + and [z_storage](#z_storage) examples. + + Typical usage: + + ```bash + z_put + ``` + + or + + ```bash + z_put -k demo/example/test -v 'Hello World' + ``` + +### z_pub + + Declares a key expression and a publisher. Then writes values periodically on the declared key expression. + The published value will be received by all matching subscribers, for instance the [z_sub](#z_sub) and [z_storage](#z_storage) examples. + + Typical usage: + + ```bash + z_pub + ``` + + or + + ```bash + z_pub -k demo/example/test -v 'Hello World' + ``` + +### z_sub + + Declares a key expression and a subscriber. + The subscriber will be notified of each `put` or `delete` made on any key expression matching the subscriber key expression, and will print this notification. + + Typical usage: + + ```bash + z_sub + ``` + + or + + ```bash + z_sub -k 'demo/**' + ``` + +### z_pull + + Declares a key expression and a pull subscriber. + On each pull, the pull subscriber will be notified of the last N `put` or `delete` made on each key expression matching the subscriber key expression, and will print this notification. + + Typical usage: + + ```bash + z_pull + ``` + + or + + ```bash + z_pull -k demo/** --size 3 + ``` + +### z_get, z_get_channel and z_get_channel_non_blocking + + Sends a query message for a selector. + The queryables with a matching path or selector (for instance [z_queryable](#z_queryable) and [z_storage](#z_storage)) + will receive this query and reply with paths/values that will be received by the receiver stream. + + Typical usage: + + ```bash + z_get + ``` + + or + + ```bash + z_get -s 'demo/**' + ``` + +### z_querier + + Continuously sends query messages for a selector. + The queryables with a matching path or selector (for instance [z_queryable](#z_queryable) and [z_storage](#z_storage)) + will receive these queries and reply with paths/values that will be received by the querier. + + Typical usage: + + ```bash + z_querier + ``` + + or + + ```bash + z_querier -s 'demo/**' + ``` + +### z_queryable + + Declares a queryable function with a path. + This queryable function will be triggered by each call to get + with a selector that matches the path, and will return a value to the querier. + + Typical usage: + + ```bash + z_queryable + ``` + + or + + ```bash + z_queryable -k demo/example/queryable -v 'This is the result' + ``` + +### z_storage + + Trivial implementation of a storage in memory. + This example declares a subscriber and a queryable on the same selector. + The subscriber callback will store the received paths/values in a hashmap. + The queryable callback will answer to queries with the paths/values stored in the hashmap + and that match the queried selector. + + Typical usage: + + ```bash + z_storage + ``` + + or + + ```bash + z_storage -k 'demo/**' + ``` + +### z_pub_shm & z_sub + + A pub/sub example involving the shared-memory feature. + Note that on subscriber side, the same `z_sub` example than for non-shared-memory example is used. + + Typical Subscriber usage: + + ```bash + z_sub + ``` + + Typical Publisher usage: + + ```bash + z_pub_shm + ``` + +### z_pub_thr & z_sub_thr + + Pub/Sub throughput test. + This example allows performing throughput measurements between a publisher performing + put operations and a subscriber receiving notifications of those puts. + + Typical Subscriber usage: + + ```bash + z_sub_thr + ``` + + Typical Publisher usage: + + ```bash + z_pub_thr 1024 + ``` + +### z_ping & z_pong + + Pub/Sub roundtrip time test. + This example allows performing roundtrip time measurements. The z_ping example + performs a put operation on a first key expression, waits for a reply from the pong + example on a second key expression and measures the time between the two. + The pong application waits for samples on the first key expression and replies by + writing back the received data on the second key expression. + + :warning: z_pong needs to start first to avoid missing the kickoff from z_ping. + + Typical Pong usage: + + ```bash + z_pong + ``` + + Typical Ping usage: + + ```bash + z_ping 1024 + ``` + +### z_pub_shm_thr & z_sub_thr + + Pub/Sub throughput test involving the shared-memory feature. + This example allows performing throughput measurements between a publisher performing + put operations with the shared-memory feature and a subscriber receiving notifications + of those puts. + Note that on subscriber side, the same `z_sub_thr` example than for non-shared-memory example is used. + + Typical Subscriber usage: + + ```bash + z_sub_thr + ``` + + Typical Publisher usage: + + ```bash + z_pub_shm_thr + ``` + +### z_liveliness + + Declares a liveliness token on a given key expression (`group1/zenoh-rs` by default). + This token will be seen alive by the `z_get_liveliness` and `z_sub_liveliness` until + user explicitly drops the token by pressing `'d'` or implicitly dropped by terminating + or killing the `z_liveliness` example. + + Typical usage: + + ```bash + z_liveliness + ``` + + or + + ```bash + z_liveliness -k 'group1/member1' + ``` + +### z_get_liveliness + + Queries all the currently alive liveliness tokens that match a given key expression + (`group1/**` by default). Those tokens could be declared by the `z_liveliness` example. + + Typical usage: + + ```bash + z_get_liveliness + ``` + + or + + ```bash + z_get_liveliness -k 'group1/**' + ``` + +### z_sub_liveliness + + Subscribe to all liveliness changes (liveliness tokens getting alive or + liveliness tokens being dropped) that match a given key expression + (`group1/**` by default). Those tokens could be declared by the `z_liveliness` + example. + Note: the `z_sub_liveliness` example will not receive information about + matching liveliness tokens that were alive before it's start. + + Typical usage: + + ```bash + z_sub_liveliness + ``` + + or + + ```bash + z_sub_liveliness -k 'group1/**' + ``` + +### z_bytes + + Show how to serialize different message types into ZBytes, and then deserialize from ZBytes to the original message types. diff --git a/examples/universal/z_storage.cxx b/examples/universal/z_storage.cxx new file mode 100644 index 00000000..06dc0530 --- /dev/null +++ b/examples/universal/z_storage.cxx @@ -0,0 +1,106 @@ +// +// Copyright (c) 2025 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// +#include + +#include +#include +#include +#include +#include + +#include "../getargs.hxx" +#include "zenoh.hxx" + +using namespace zenoh; +using namespace std::chrono_literals; + +const char *kind_to_str(SampleKind kind) { + switch (kind) { + case SampleKind::Z_SAMPLE_KIND_PUT: + return "PUT"; + case SampleKind::Z_SAMPLE_KIND_DELETE: + return "DELETE"; + default: + return "UNKNOWN"; + } +} + +int _main(int argc, char **argv) { + auto &&[config, args] = + ConfigCliArgParser(argc, argv) + .named_value({"k", "key"}, "KEY_EXPRESSION", "The selection of resources to store", "demo/example/**") + .named_flag({"complete"}, "Flag to indicate whether the storage is complete w.r.t. the key expression") + .run(); + KeyExpr keyexpr(args.value("key")); + + std::cout << "Opening session..." << std::endl; + auto session = Session::open(std::move(config)); + std::unordered_map storage; + std::mutex storage_mutex; + + auto sub_handler = [&storage, &storage_mutex](Sample &sample) { + std::lock_guard lock(storage_mutex); + std::cout << ">> [Subscriber] Received " << kind_to_str(sample.get_kind()) << " ('" + << sample.get_keyexpr().as_string_view() << "' : '" + sample.get_payload().as_string() << "')\n"; + + switch (sample.get_kind()) { + case SampleKind::Z_SAMPLE_KIND_PUT: + // Note: it is safe to use string_view as a key, since it references internal sample data, + // so it is guaranteed to stay valid as long as corresponding map value exists. + storage.erase(sample.get_keyexpr().as_string_view()); + storage.insert({sample.get_keyexpr().as_string_view(), std::move(sample)}); + break; + case SampleKind::Z_SAMPLE_KIND_DELETE: + storage.erase(sample.get_keyexpr().as_string_view()); + break; + } + }; + + std::cout << "Declaring Subscriber on '" << keyexpr.as_string_view() << "'..." << std::endl; + auto subscriber = session.declare_subscriber(keyexpr, sub_handler, closures::none); + + auto qbl_handler = [&storage, &storage_mutex](Query &query) { + std::lock_guard lock(storage_mutex); + std::cout << ">> [Queryable ] Received Query '" << query.get_keyexpr().as_string_view() << "?" + << query.get_parameters() << "'\n"; + for (const auto &[k, v] : storage) { + if (query.get_keyexpr().intersects(v.get_keyexpr())) { + query.reply(v.get_keyexpr(), v.get_payload().clone()); + } + } + }; + + std::cout << "Declaring Queryable on '" << keyexpr.as_string_view() << "'..." << std::endl; + Session::QueryableOptions opts; + opts.complete = args.flag("complete"); + auto queryable = session.declare_queryable(keyexpr, qbl_handler, closures::none, std::move(opts)); + + std::cout << "Press CTRL-C to quit...\n"; + while (true) { + std::this_thread::sleep_for(1s); + } + + return 0; +} + +int main(int argc, char **argv) { + try { +#ifdef ZENOHCXX_ZENOHC + init_log_from_env_or("error"); +#endif + _main(argc, argv); + } catch (ZException e) { + std::cout << "Received an error :" << e.what() << "\n"; + } +} diff --git a/examples/universal/z_sub_thr.cxx b/examples/universal/z_sub_thr.cxx index ceb8dce9..39b9e8e0 100644 --- a/examples/universal/z_sub_thr.cxx +++ b/examples/universal/z_sub_thr.cxx @@ -75,7 +75,7 @@ int _main(int argc, char **argv) { auto &&[config, args] = ConfigCliArgParser(argc, argv) .named_value({"s", "samples"}, "MESUREMENTS", "Number of throughput measurements", "10") .named_value({"n", "number"}, "NUM_MESSAGES", - "Number of messages in each throughput measurements", "1000000") + "Number of messages in each throughput measurement", "1000000") .run(); auto samples = std::atoi(args.value("samples").data()); @@ -84,20 +84,16 @@ int _main(int argc, char **argv) { std::cout << "Opening session...\n"; auto session = Session::open(std::move(config)); - KeyExpr keyexpr = session.declare_keyexpr(KeyExpr("test/thr")); - Stats stats(samples, num_messages); auto on_receive = [&stats](const Sample &s) { stats(s); }; auto on_drop = [&stats]() { stats(); }; - session.declare_background_subscriber(keyexpr, on_receive, on_drop); + session.declare_background_subscriber("test/thr", on_receive, on_drop); std::cout << "Press CTRL-C to quit...\n"; while (true) { std::this_thread::sleep_for(1s); } - session.undeclare_keyexpr(std::move(keyexpr)); - return 0; } diff --git a/include/zenoh/api/config.hxx b/include/zenoh/api/config.hxx index 812865a6..cee25323 100644 --- a/include/zenoh/api/config.hxx +++ b/include/zenoh/api/config.hxx @@ -19,7 +19,7 @@ #include "base.hxx" namespace zenoh { -/// A Zenoh Session config +/// A Zenoh Session config. class Config : public Owned<::z_owned_config_t> { Config(zenoh::detail::null_object_t) : Owned(nullptr){}; diff --git a/include/zenoh/api/keyexpr.hxx b/include/zenoh/api/keyexpr.hxx index 1dd22eec..0e2f6ada 100644 --- a/include/zenoh/api/keyexpr.hxx +++ b/include/zenoh/api/keyexpr.hxx @@ -68,6 +68,13 @@ class KeyExpr : public Owned<::z_owned_keyexpr_t> { KeyExpr(const char* key_expr, bool autocanonize = true, ZResult* err = nullptr) : KeyExpr(std::string_view(key_expr), autocanonize, err){}; + /// @brief Copy constructor. + KeyExpr(const KeyExpr& other) : KeyExpr(zenoh::detail::null_object) { + ::z_keyexpr_clone(&this->_0, interop::as_loaned_c_ptr(other)); + }; + + KeyExpr(KeyExpr&& other) = default; + /// @name Methods /// @brief Get underlying key expression string. std::string_view as_string_view() const { @@ -186,6 +193,17 @@ class KeyExpr : public Owned<::z_owned_keyexpr_t> { /// @return ``false`` if both key expressions are equal (i.e. they represent the same set of resources), ``true`` /// otherwise. bool operator!=(const KeyExpr& other) const { return !(*this == other); } + + /// @brief Assignment operator. + KeyExpr& operator=(const KeyExpr& other) { + if (this != &other) { + ::z_drop(z_move(this->_0)); + ::z_keyexpr_clone(&this->_0, interop::as_loaned_c_ptr(other)); + } + return *this; + }; + + KeyExpr& operator=(KeyExpr&& other) = default; }; } // namespace zenoh \ No newline at end of file