{"id":20051884,"url":"https://github.com/p-ranav/iris","last_synced_at":"2025-05-05T11:32:04.282Z","repository":{"id":46751898,"uuid":"251463621","full_name":"p-ranav/iris","owner":"p-ranav","description":"Lightweight Component Model and Messaging Framework based on ØMQ","archived":false,"fork":false,"pushed_at":"2020-04-14T13:48:56.000Z","size":22325,"stargazers_count":53,"open_issues_count":0,"forks_count":3,"subscribers_count":10,"default_branch":"master","last_synced_at":"2025-04-08T22:23:24.505Z","etag":null,"topics":["client","components","cpp17","header-only","lightweight-framework","message-passing","message-queue","middleware","mit-license","modern-cpp","notification-queue","ports","publisher","serialization","server","subscriber","task-system","timer","zeromq"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/p-ranav.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-03-31T00:49:42.000Z","updated_at":"2023-09-04T01:02:18.000Z","dependencies_parsed_at":"2022-07-22T09:32:03.449Z","dependency_job_id":null,"html_url":"https://github.com/p-ranav/iris","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/p-ranav%2Firis","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/p-ranav%2Firis/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/p-ranav%2Firis/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/p-ranav%2Firis/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/p-ranav","download_url":"https://codeload.github.com/p-ranav/iris/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252489101,"owners_count":21756263,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["client","components","cpp17","header-only","lightweight-framework","message-passing","message-queue","middleware","mit-license","modern-cpp","notification-queue","ports","publisher","serialization","server","subscriber","task-system","timer","zeromq"],"created_at":"2024-11-13T12:07:29.463Z","updated_at":"2025-05-05T11:31:59.522Z","avatar_url":"https://github.com/p-ranav.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg height=\"150\" src=\"img/logo.png\"/\u003e  \n\u003c/p\u003e\n\n`iris` is a `C++17` header-only library that provides a [component model](https://en.wikipedia.org/wiki/Component-based_software_engineering) and messaging framework based on [ZeroMQ](https://zeromq.org/). \n\n## Prerequisites\n\n* Requires a compiler with `C++17` standard compliance\n* Requires an installation of `libzmq`\n\n## Table of Contents\n\n*   [Component Model](#component-model)\n*   [Getting Started](#getting-started)\n    *   [Time-Triggered Operations](#time-triggered-operations)\n    *   [Publish-Subscribe Interactions](#publish-subscribe-interactions)\n    *   [Synchronous Request-Reply Interactions](#synchronous-request-reply-interactions)\n    *   [Asynchronous Request-Reply Interactions](#asynchronous-request-reply-interactions)\n*   [Building Samples](#building-samples)\n*   [Contributing](#contributing)\n*   [License](#license)\n\n## Component Model\n\nAn `iris::Component` is a building block - A reusable piece of software that can be instantiated and connected with other components. Think LEGO. Large and complex software systems can be assembled by composing small, tested component building blocks. \n\n`iris` components support:\n\n* A variety of communication ports and patterns: _Publisher_, _Subscriber_, _Client_, _Server_, _AsyncServer_, and _Brokers_\n* Periodic and oneshot timers that can trigger the component into action\n* A speedy multi-threaded task system with task stealing\n* [Cereal](https://github.com/USCiLab/cereal)-based serialization and deserialization of complex structures\n* [ZeroMQ](https://zeromq.org/)-based messaging\n\n\u003cp align=\"center\"\u003e\n  \u003cimg height=\"600\" src=\"img/iriscom.png\"/\u003e  \n\u003c/p\u003e \n\n## Getting Started\n\nSimply include `#include \u003ciris/iris.hpp\u003e` and you're good to go. Start by creating an `iris::Component`:\n\n```cpp\niris::Component my_component;\n```\n\nYou can optionally specify the number of threads the component can use in its task system, e.g., this component will spawn 2 executor threads that process records in its respective message queues. \n\n```cpp\niris::Component my_component(iris::threads = 2);\n```\n\n**NOTE:** Here `iris::threads` is a [NamedType](https://github.com/joboccara/NamedType) parameter. It is not necessary to use named parameters but in certain cases they improve code readability.\n\n## Time-Triggered Operations\n\n`iris` components can be triggered periodically by timers. To create a timer, call `component.set_interval`. The following component is triggered every 500ms. Timers are an excellent way to kickstart a communication pattern, e.g., publish messages periodically to multiple sinks.\n\nCall `Component.start()` to start the component - This starts the component executor threads, listener threads, timers etc.\n\n```cpp\n#include \u003ciostream\u003e\n#include \u003ciris/iris.hpp\u003e\nusing namespace iris;\n\nint main() {\n  Component my_component;\n  my_component.set_interval(period = 500,\n                            on_triggered = [] { std::cout \u003c\u003c \"Timer fired!\\n\"; });\n  my_component.start();\n}\n```\n\n### One-shot Timers\n\nUse `component.set_timeout` to create a one-shot timer that triggers the component after a set delay. \n\n```cpp\n// oneshot_timers.cpp\n#include \u003ciris/iris.hpp\u003e\nusing namespace iris;\n#include \u003ciostream\u003e\n\nint main() {\n  Component c;\n  c.set_timeout(delay = 1000,\n                on_triggered = [] { std::cout \u003c\u003c \"1.0 second Timeout!\" \u003c\u003c std::endl; \n                });\n  c.set_timeout(delay = 2500,\n                on_triggered = [] { std::cout \u003c\u003c \"2.5 second Timeout!\" \u003c\u003c std::endl; \n                });\n  c.set_timeout(delay = 5000, \n                on_triggered = [\u0026] {\n                    std::cout \u003c\u003c \"Stopping component\" \u003c\u003c std::endl;\n                    c.stop();\n                });\n  c.start();\n}\n```\n\nNoice that the component is stopped after 5 seconds - `component.stop()` stops the task scheduler from further processing of tasks. \n\n## Publish-Subscribe Interactions\n\nPublish/Subscribe is classic pattern where senders of messages, called publishers, do not program the messages to be sent directly to specific receivers, called subscribers. Messages are published without the knowledge of what or if any subscriber of that knowledge exists.\n\n\u003cp align=\"center\"\u003e\n  \u003cimg height=290 src=\"img/publish_subscribe.png\"/\u003e  \n\u003c/p\u003e\n\nIn this example ([samples/nginx_log_publisher](https://github.com/p-ranav/iris/tree/master/samples/nginx_log_publisher)), we will be parsing an Nginx log file and publishing each log entry. Here's the log file format:\n\n```bash\n[{\"time\": \"17/May/2015:08:05:32 +0000\", \"remote_ip\": \"93.180.71.3\", \"remote_user\": \"-\", \"request\": \"GET /downloads/product_1 HTTP/1.1\", \"response\": 304, \"bytes\": 0, \"referrer\": \"-\", \"agent\": \"Debian APT-HTTP/1.3 (0.8.16~exp12ubuntu10.21)\"},\n{\"time\": \"17/May/2015:08:05:23 +0000\", \"remote_ip\": \"93.180.71.3\", \"remote_user\": \"-\", \"request\": \"GET /downloads/product_1 HTTP/1.1\", \"response\": 304, \"bytes\": 0, \"referrer\": \"-\", \"agent\": \"Debian APT-HTTP/1.3 (0.8.16~exp12ubuntu10.21)\"},\n{\"time\": \"17/May/2015:08:05:24 +0000\", \"remote_ip\": \"80.91.33.133\", \"remote_user\": \"-\", \"request\": \"GET /downloads/product_1 HTTP/1.1\", \"response\": 304, \"bytes\": 0, \"referrer\": \"-\", \"agent\": \"Debian APT-HTTP/1.3 (0.8.16~exp12ubuntu10.17)\"},\n{\"time\": \"17/May/2015:08:05:34 +0000\", \"remote_ip\": \"217.168.17.5\", \"remote_user\": \"-\", \"request\": \"GET /downloads/product_1 HTTP/1.1\", \"response\": 200, \"bytes\": 490, \"referrer\": \"-\", \"agent\": \"Debian APT-HTTP/1.3 (0.8.10.3)\"},\n{\"time\": \"17/May/2015:08:05:09 +0000\", \"remote_ip\": \"217.168.17.5\", \"remote_user\": \"-\", \"request\": \"GET /downloads/product_2 HTTP/1.1\", \"response\": 200, \"bytes\": 490, \"referrer\": \"-\", \"agent\": \"Debian APT-HTTP/1.3 (0.8.10.3)\"},\n{\"time\": \"17/May/2015:08:05:57 +0000\", \"remote_ip\": \"93.180.71.3\", \"remote_user\": \"-\", \"request\": \"GET /downloads/product_1 HTTP/1.1\", \"response\": 304, \"bytes\": 0, \"referrer\": \"-\", \"agent\": \"Debian APT-HTTP/1.3 (0.8.16~exp12ubuntu10.21)\"},\n{\"time\": \"17/May/2015:08:05:02 +0000\", \"remote_ip\": \"217.168.17.5\", \"remote_user\": \"-\", \"request\": \"GET /downloads/product_2 HTTP/1.1\", \"response\": 404, \"bytes\": 337, \"referrer\": \"-\", \"agent\": \"Debian APT-HTTP/1.3 (0.8.10.3)\"},\n{\"time\": \"17/May/2015:08:05:42 +0000\", \"remote_ip\": \"217.168.17.5\", \"remote_user\": \"-\", \"request\": \"GET /downloads/product_1 HTTP/1.1\", \"response\": 404, \"bytes\": 332, \"referrer\": \"-\", \"agent\": \"Debian APT-HTTP/1.3 (0.8.10.3)\"},\n{\"time\": \"17/May/2015:08:05:01 +0000\", \"remote_ip\": \"80.91.33.133\", \"remote_user\": \"-\", \"request\": \"GET /downloads/product_1 HTTP/1.1\", \"response\": 304, \"bytes\": 0, \"referrer\": \"-\", \"agent\": \"Debian APT-HTTP/1.3 (0.8.16~exp12ubuntu10.17)\"},\n{\"time\": \"17/May/2015:08:05:27 +0000\", \"remote_ip\": \"93.180.71.3\", \"remote_user\": \"-\", \"request\": \"GET /downloads/product_1 HTTP/1.1\", \"response\": 304, \"bytes\": 0, \"referrer\": \"-\", \"agent\": \"Debian APT-HTTP/1.3 (0.8.16~exp12ubuntu10.21)\"},\n{\"time\": \"17/May/2015:08:05:12 +0000\", \"remote_ip\": \"217.168.17.5\", \"remote_user\": \"-\", \"request\": \"GET /downloads/product_2 HTTP/1.1\", \"response\": 200, \"bytes\": 3316, \"referrer\": \"-\", \"agent\": \"-\"},\n{\"time\": \"17/May/2015:08:05:49 +0000\", \"remote_ip\": \"188.138.60.101\", \"remote_user\": \"-\", \"request\": \"GET /downloads/product_2 HTTP/1.1\", \"response\": 304, \"bytes\": 0, \"referrer\": \"-\", \"agent\": \"Debian APT-HTTP/1.3 (0.9.7.9)\"},\n{\"time\": \"17/May/2015:08:05:14 +0000\", \"remote_ip\": \"80.91.33.133\", \"remote_user\": \"-\", \"request\": \"GET /downloads/product_1 HTTP/1.1\", \"response\": 304, \"bytes\": 0, \"referrer\": \"-\", \"agent\": \"Debian APT-HTTP/1.3 (0.8.16~exp12ubuntu10.16)\"},\n{\"time\": \"17/May/2015:08:05:45 +0000\", \"remote_ip\": \"46.4.66.76\", \"remote_user\": \"-\", \"request\": \"GET /downloads/product_1 HTTP/1.1\", \"response\": 404, \"bytes\": 318, \"referrer\": \"-\", \"agent\": \"Debian APT-HTTP/1.3 (1.0.1ubuntu2)\"},\n{\"time\": \"17/May/2015:08:05:26 +0000\", \"remote_ip\": \"93.180.71.3\", \"remote_user\": \"-\", \"request\": \"GET /downloads/product_1 HTTP/1.1\", \"response\": 404, \"bytes\": 324, \"referrer\": \"-\", \"agent\": \"Debian APT-HTTP/1.3 (0.8.16~exp12ubuntu10.21)\"},\n{\"time\": \"17/May/2015:08:05:22 +0000\", \"remote_ip\": \"91.234.194.89\", \"remote_user\": \"-\", \"request\": \"GET /downloads/product_2 HTTP/1.1\", \"response\": 304, \"bytes\": 0, \"referrer\": \"-\", \"agent\": \"Debian APT-HTTP/1.3 (0.9.7.9)\"},\n{\"time\": \"17/May/2015:08:05:07 +0000\", \"remote_ip\": \"80.91.33.133\", \"remote_user\": \"-\", \"request\": \"GET /downloads/product_1 HTTP/1.1\", \"response\": 304, \"bytes\": 0, \"referrer\": \"-\", \"agent\": \"Debian APT-HTTP/1.3 (0.8.16~exp12ubuntu10.17)\"},\n{\"time\": \"17/May/2015:08:05:38 +0000\", \"remote_ip\": \"37.26.93.214\", \"remote_user\": \"-\", \"request\": \"GET /downloads/product_2 HTTP/1.1\", \"response\": 404, \"bytes\": 319, \"referrer\": \"-\", \"agent\": \"Go 1.1 package http\"},\n..\n...\n....\n```\n\nFirst we can write a message struct `NginxLogEntry`. `iris` uses [Cereal](https://github.com/USCiLab/cereal) for serialization and deserialization of messages for transport. Our `NginxLogEntry` struct has a `serialize` method for this purpose. \n\n```cpp\n// nginx_log_entry.hpp\n#pragma once\n#include \u003cstring\u003e\n\nstruct NginxLogEntry {\n  std::string time;\n  std::string remote_ip;\n  std::string remote_user;\n  std::string request;\n  unsigned response;\n  unsigned bytes;\n  std::string agent;\n\n  template \u003cclass Archive\u003e void serialize(Archive \u0026ar) {\n    ar(time, remote_ip, remote_user, request, response, bytes, agent);\n  }\n};\n```\n\nWe can start by writing our subscriber.\n\n* Create a subscriber using `component.create_subscriber`\n* The subscriber port timeout is how long the subscriber's `recv()` call will wait before timing out and checking again. Timeouts are essential to keeping the component reactive to commands like component.stop(). See `ZMQ_RCVTIMEO` for more details.\n* The signature of a subscriber callback is `std::function\u003cvoid(Message)\u003e` \n* You can deserialize the received message using `Message.get\u003cT\u003e()`\n* Here, we are receiving log entries and printing select fields\n\n```cpp\n// subscriber.cpp\n#include \u003ciostream\u003e\n#include \u003ciris/iris.hpp\u003e\nusing namespace iris;\n#include \"nginx_log_entry.hpp\"\n\nint main() {\n  Component receiver(threads = 2);\n  receiver.create_subscriber(\n      endpoints = {\"tcp://localhost:5555\"}, \n      timeout = 5000,\n      on_receive = [](Message msg) {\n          auto entry = msg.get\u003cNginxLogEntry\u003e();\n          std::cout \u003c\u003c \"[\" \u003c\u003c entry.time \u003c\u003c \"] \"\n                    \u003c\u003c \"{\" \u003c\u003c entry.remote_ip \u003c\u003c \"} \"\n                    \u003c\u003c \"-\u003e \" \u003c\u003c entry.request\n                    \u003c\u003c \"-\u003e \" \u003c\u003c entry.response \u003c\u003c std::endl;\n      });\n  receiver.start();\n}\n```\n\nNow, for the publisher. When managing state, it is cleaner to inherit from `iris::Component` and write a class.\n\n* Create a class named `NginxLogPublisher` that inherits from `iris::Component`\n* Create a publisher by calling `create_publisher` - We have inherited this method\n* Parse the JSON log file\n* Create a periodic timer using `set_interval` and publish log messages\n* Call `join()` on the class destructor to join on the task system executor threads\n\n```cpp\n// publisher.cpp\n#include \u003ciostream\u003e\n#include \u003ciris/iris.hpp\u003e\nusing namespace iris;\n#include \"nginx_log_entry.hpp\"\n#include \"json.hpp\"\n#include \u003cfstream\u003e\n\nclass NginxLogPublisher : public Component {\n  Publisher pub;\n  nlohmann::json j;\n  nlohmann::json::iterator it;\n\npublic:\n  NginxLogPublisher(const std::string \u0026filename) {\n    // read a JSON file\n    std::ifstream stream(\"nginx_logs.json\");\n    stream \u003e\u003e j;\n    it = j.begin();\n\n    // Craete publisher\n    pub = create_publisher(endpoints = {\"tcp://*:5555\"});\n\n    // Publish periodically\n    set_interval(period = 200, \n                 on_triggered = [this] {\n                     auto element = *it;\n                     std::cout \u003c\u003c \"Published: \" \u003c\u003c element \u003c\u003c std::endl;\n                     pub.send(NginxLogEntry{\n                         .time = element[\"time\"].get\u003cstd::string\u003e(),\n                         .remote_ip = element[\"remote_ip\"].get\u003cstd::string\u003e(),\n                         .remote_user = element[\"remote_user\"].get\u003cstd::string\u003e(),\n                         .request = element[\"request\"].get\u003cstd::string\u003e(),\n                         .response = element[\"response\"].get\u003cunsigned\u003e(),\n                         .bytes = element[\"bytes\"].get\u003cunsigned\u003e(),\n                         .agent = element[\"agent\"].get\u003cstd::string\u003e()\n                     });\n                     ++it;\n                 });\n  }\n\n  ~NginxLogPublisher() {\n    join();\n  }\n\n};\n\nint main() {\n  NginxLogPublisher publisher(\"nginx_logs.json\");\n  publisher.start();\n}\n```\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"img/nginx_log_publisher.gif\"/\u003e  \n\u003c/p\u003e\n\n## Synchronous Request-Reply Interactions\n\nThe client-server model is another basic interaction pattern. Client sends a request to a remote server and waits for a reply. The server receives the request and calls a server-side callback to respond to the client.\n\n\u003cp align=\"center\"\u003e\n  \u003cimg height=230 src=\"img/client_server.png\"/\u003e  \n\u003c/p\u003e\n\nIn this example ([samples/music_tag_server](https://github.com/p-ranav/iris/tree/master/samples/music_tag_server)), we will create a music database server that can be queried for album metadata. Clients can request for album metadata using a key-value pair, e.g., `{\"name\", \"Dark Side of the Moon\"}`. The server will respond with an `std::optional\u003cAlbum\u003e` - If the album is found in its database, it will return it, else it will return `std::nullopt`. \n\nOur JSON database looks like this:\n\n```bash\n[\n    {\n        \"catalog\": \"R2 552927\",\n        \"name\": \"Paranoid\", \n        \"artist\": \"Black Sabbath\", \n        \"year\": 1970, \n        \"genre\": \"Heavy Metal\", \n        \"tracks\": [\n            \"War Pigs\", \n            \"Paranoid\", \n            \"Planet Caravan\", \n            \"Iron Man\", \n            \"Electric Funeral\", \n            \"Hand of Doom\", \n            \"Jack the Stripper / Fairies Wear Boots\"]\n    },\n    {\n        \"catalog\": \"7243 8 35870 2 5\",\n        \"name\": \"The Number of the Beast\",\n...\n...\n...\n```\n\nLet's start with the expected server response - the `Album` struct. \n\n```cpp\n// album.hpp\n#pragma once\n#include \u003ciris/cereal/types/vector.hpp\u003e\n#include \u003cstring\u003e\n#include \u003cvector\u003e\n\nstruct Album {\n  std::string name;\n  std::string artist;\n  int year;\n  std::string genre;\n  std::vector\u003cstd::string\u003e tracks;\n\n  template \u003cclass Archive\u003e void serialize(Archive \u0026ar) {\n    ar(name, artist, year, genre, tracks);\n  }\n};\n```\n\nTo create a server port, call `component.create_server`. \n\n* Server callbacks have the signature `std::function\u003cvoid(Request, Response\u0026)\u003e`\n* The server port timeout is how long the server's `recv()` call will wait before timing out and checking again. Timeouts are essential to keeping the component reactive to commands like `component.stop()`. See `ZMQ_RCVTIMEO` for more details.\n* OK so what's happening here?\n  * The server receives a request that is actually an `std::tuple\u003cstd::string, std::string\u003e`\n  * We deserialize this request using `request.get\u003cT\u003e` \n  * Then we search in our JSON database if this key-value pair exists.\n  * If yes, we construct an `Album` object and set the server callback response\n  * If not, we set the server callback response to `std::nullopt`\n\n```cpp\n// server.cpp\n#include \"album.hpp\"\n#include \u003ciris/iris.hpp\u003e\nusing namespace iris;\n#include \u003ciostream\u003e\n#include \u003cmap\u003e\n#include \"json.hpp\"\n#include \u003cfstream\u003e\n#include \u003ciris/cereal/types/optional.hpp\u003e\n#include \u003ciris/cereal/types/tuple.hpp\u003e\n\nint main() {\n\n  // Load JSON database\n  nlohmann::json j;\n  std::ifstream stream(\"database.json\");\n  stream \u003e\u003e j;\n\n  Component server;\n  server.create_server(\n      endpoints = {\"tcp://*:5510\"}, \n      timeout = 500,\n      on_request = [\u0026](Request request, Response \u0026response) {\n          // Request from client\n          auto kvpair = request.get\u003cstd::tuple\u003cstd::string, std::string\u003e\u003e();\n          auto key = std::get\u003c0\u003e(kvpair);\n          auto value = std::get\u003c1\u003e(kvpair);\n          std::cout \u003c\u003c \"Received request {key: \" \n                    \u003c\u003c key \u003c\u003c \", value: \" \u003c\u003c value \u003c\u003c \"}\\n\";\n\n          // Response to be filled and sent back \n          // Either a valid album struct or empty\n          std::optional\u003cAlbum\u003e album{};\n\n          // Find the album in the JSON database\n          auto it = std::find_if(j.begin(), j.end(), \n            [\u0026key, \u0026value](const auto\u0026 element) {\n              if (key == \"year\")\n                return element[key] == std::stoi(value);\n              else\n                return element[key] == value;\n          });\n\n          // Populate the response fields\n          if (it != j.end()) {\n            album = Album {\n              .name = (*it)[\"name\"].get\u003cstd::string\u003e(),\n              .artist = (*it)[\"artist\"].get\u003cstd::string\u003e(),\n              .year = (*it)[\"year\"].get\u003cunsigned\u003e(),\n              .genre = (*it)[\"genre\"].get\u003cstd::string\u003e(),\n              .tracks = (*it)[\"tracks\"].get\u003cstd::vector\u003cstd::string\u003e\u003e()\n            };\n          }\n\n          // Set response\n          response.set(album);\n      });\n  server.start();\n}\n```\n\nNow, we can write a client that calls this server. Create a client port using `component.create_client`. \n\n***NOTE*** `iris` clients implement the [lazy pirate](http://zguide.zeromq.org/php:chapter4#Client-Side-Reliability-Lazy-Pirate-Pattern) pattern - Rather than doing a blocking receive, `iris` clients:\n\n* Send a request to the server\n* Poll the REQ socket and receive from it only when it's sure a reply has arrived.\n* Resend a request, if no reply has arrived within a timeout period.\n* Abandon the transaction if there is still no reply after several requests.\n\nSo, `iris::Clients` require a _timeout_ (waiting on server response) and a total number of _retries_ when this timeout occurs.\n\n```cpp\n// client.cpp\n#include \"album.hpp\"\n#include \u003ciostream\u003e\n#include \u003coptional\u003e\n#include \u003ciris/iris.hpp\u003e\nusing namespace iris;\n#include \u003ciris/cereal/types/optional.hpp\u003e\n#include \u003ciris/cereal/types/tuple.hpp\u003e\n\nint main(int argc, char *argv[]) {\n\n  std::tuple\u003cstd::string, std::string\u003e request;\n\n  if (argc != 3) {\n    std::cout \u003c\u003c \"Usage: ./\u003cexecutable\u003e key value\\n\";\n    return 0;\n  } else {\n    request = {argv[1], argv[2]};\n  }\n\n  Component c(threads = 1);\n  c.start();\n  auto client = c.create_client(endpoints = {\"tcp://127.0.0.1:5510\"},\n                                timeout = 2500, \n                                retries = 3);\n\n  // Send request to server\n  auto response = client.send(request);\n\n  // Check that the server has called `Response.set()`\n  if (response.has_value()) {\n\n    // Parse response and print result if available\n    auto album = response.get\u003cstd::optional\u003cAlbum\u003e\u003e();\n    if (album.has_value()) {\n      auto metadata = album.value();\n      std::cout \u003c\u003c \"- Received album:\\n\";\n      std::cout \u003c\u003c \"    Name: \" \u003c\u003c metadata.name \u003c\u003c \"\\n\";\n      std::cout \u003c\u003c \"    Artist: \" \u003c\u003c metadata.artist \u003c\u003c \"\\n\";\n      std::cout \u003c\u003c \"    Year: \" \u003c\u003c metadata.year \u003c\u003c \"\\n\";\n      std::cout \u003c\u003c \"    Genre: \" \u003c\u003c metadata.genre \u003c\u003c \"\\n\";\n      std::cout \u003c\u003c \"    Tracks:\\n\";\n      for (size_t i = 0; i \u003c metadata.tracks.size(); ++i) {\n        std::cout \u003c\u003c \"      \" \u003c\u003c i \u003c\u003c \". \" \u003c\u003c metadata.tracks[i] \u003c\u003c \"\\n\";\n      }\n    } else {\n      std::cout \u003c\u003c \"Album not found!\\n\";\n    }\n    \n  } else {\n    std::cout \u003c\u003c \"Response not set by server\\n\";\n  }\n\n  c.stop();\n}\n```\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"img/music_tag_server.gif\"/\u003e  \n\u003c/p\u003e\n\n## Asynchronous Request-Reply Interactions\n\nRather than having one client request work from one worker can we get any number of clients to request work from any number of workers? We could pre load each client with a list of workers and have each client talk directly to a worker. This works, but what if we add or remove workers, we then need to update every client. A better solution would be to have a broker which both clients and workers connect to and is responsible for passing messages back and forth. Brokers can deal with many simultaneous requests and responses using routers and dealers. Routers are like asynchronous response sockets and dealers like asynchronous request sockets.\n\n\u003cp align=\"center\"\u003e\n  \u003cimg height=230 src=\"img/async_client_server_distributed.png\"/\u003e  \n\u003c/p\u003e\n\nFor this example ([samples/number_stats_server](https://github.com/p-ranav/iris/tree/master/samples/number_stats_server)), let's assume we have some servers (workers) that can calculate the mean and standard deviation of an array of numbers. \nThe client will send an array of numbers to the broker. The broker will forward this request to one of many async servers. These async servers/workers are connected to the broker and waiting for work. \n\nHere is the struct we will be passing to the server(s). On the client-side, we will prepare a random array of 3 doubles and pass to the server.\n\n```cpp\n// numbers.hpp\n#pragma once\n#include \u003ciris/cereal/types/vector.hpp\u003e\n#include \u003cvector\u003e\n\nstruct Numbers {\n  std::vector\u003cdouble\u003e values;\n\n  auto size() const { return values.size(); }\n  auto begin() const { return values.begin(); }\n  auto end() const { return values.end(); }\n\n  template \u003cclass Archive\u003e void serialize(Archive \u0026ar) {\n    ar(values);\n  }\n};\n```\n\nand here is the response we expect from the server. A `Statistics` object with mean and standard deviation:\n\n```cpp\n// statistics.hpp\n#pragma once\n\nstruct Statistics {\n  double mean;\n  double stdev;\n\n  template \u003cclass Archive\u003e void serialize(Archive \u0026ar) {\n    ar(mean, stdev);\n  }\n};\n```\n\nOur broker component is very simple. It forwards request on port `5510` to port `5515` where one of our workers will be waiting to receive work. \n\n***NOTE*** Below, we are creating a component with 0 threads - The task system is not needed for pure broker components as there are no tasks to execute. Brokers operate at the ZeroMQ level, simply forwarding requests and responses. \n\n***NOTE*** Broker components, like any other component, can have other communication ports and timers. If you have these, then you need executor threads. \n\n```cpp\n#include \u003ciostream\u003e\n#include \u003ciris/iris.hpp\u003e\nusing namespace iris;\n\nint main() {\n  Component b(threads = 0);\n  b.create_broker(\n    router_endpoints = {\"tcp://*:5510\"},\n    dealer_endpoints = {\"tcp://*:5515\"}\n  );\n  b.start();\n}\n```\n\nHere is our async server. Create workers like this using `Component.create_async_server`. \n\nThis server:\n\n* receives a `Request` object that is deserialized into a `Numbers` structures.\n* calculates the mean and standard deviation of the array.\n* sets the response using `response.set`\n\n***NOTE*** `iris::AsyncServer` is not very different from `iris::Server`. Instead of _binding_ to a ZeroMQ socket and waiting to receive requests, an `AsyncServer` _connects_ with a broker and waits for requests. \n\n```cpp\n#include \u003ciostream\u003e\n#include \"numbers.hpp\"\n#include \"statistics.hpp\"\n#include \u003ciris/iris.hpp\u003e\nusing namespace iris;\n#include \u003calgorithm\u003e\n#include \u003cnumeric\u003e\n#include \u003ccmath\u003e\n\nint main() {\n  Component worker(threads = 3);\n\n  worker.create_async_server(\n      endpoints = {\"tcp://localhost:5515\"}, \n      timeout = 500,\n      on_request = [\u0026](Request request, Response \u0026res) {\n          auto numbers = request.get\u003cNumbers\u003e();\n\n          std::cout \u003c\u003c \"Received numbers: {\" \u003c\u003c numbers.values[0] \n                    \u003c\u003c \", \" \u003c\u003c numbers.values[1] \n                    \u003c\u003c \", \" \u003c\u003c numbers.values[2] \u003c\u003c \"}\\n\";\n\n          // Calculate mean\n          double sum = std::accumulate(numbers.begin(), numbers.end(), 0.0);\n          double mean = sum / numbers.size();\n\n          // Calculate standard deviation\n          double accum = 0.0;\n          std::for_each(numbers.begin(), numbers.end(), [\u0026](const double d) {\n            accum += (d - mean) * (d -mean);\n          });\n          double stdev = std::sqrt(accum / numbers.size());\n\n          // Set the response\n          res.set(Statistics{.mean = mean, .stdev = stdev});\n\n          std::cout \u003c\u003c \"Calculated stats successfully\\n\";\n      });\n  worker.start();\n}\n```\n\nFinally, here's our client. This client:\n\n* Creates an array of numbers and sends it to the broker\n* The broker will forward to one of the async servers at its disposal.\n* One of the servers will respond and the response is forwarded back to this client\n\n```cpp\n#include \u003ciostream\u003e\n#include \"numbers.hpp\"\n#include \"statistics.hpp\"\n#include \u003ciris/iris.hpp\u003e\nusing namespace iris;\n\nint main() {\n  Component c(threads = 2);\n  auto client = c.create_client(endpoints = {\"tcp://localhost:5510\"},\n                                timeout = 2500, \n                                retries = 3);\n\n  double i = 0.0, j = 1.0, k = 2.0;\n\n  c.set_interval(\n      period = 2000, \n      on_triggered = [\u0026] {\n          std::cout \u003c\u003c \"[Sent] numbers = {\" \u003c\u003c i \u003c\u003c \", \" \u003c\u003c j \u003c\u003c \", \" \u003c\u003c k \u003c\u003c \"}\\n\";\n          auto response = client.send(Numbers{.values = {i, j, k}});\n          auto stats = response.get\u003cStatistics\u003e();\n          std::cout \u003c\u003c \"[Received] mean = \" \u003c\u003c stats.mean \n                    \u003c\u003c \"; stdev = \" \u003c\u003c stats.stdev\n                    \u003c\u003c std::endl;\n          i += 0.3;\n          j += 0.5;\n          k += 0.9;\n      });\n  c.start();\n}\n```\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"img/number_stats_server.gif\"/\u003e  \n\u003c/p\u003e\n\n## Building Samples\n\nThere are a number of samples in the `samples/` directory. You can build these samples by running the following commands.\n\n```bash\nmkdir build \u0026\u0026 cd build\ncmake -DIRIS_SAMPLES=ON ..\nmake\n```\n\n## Contributing\nContributions are welcome, have a look at the [CONTRIBUTING.md](CONTRIBUTING.md) document for more information.\n\n## License\nThe project is available under the [MIT](https://opensource.org/licenses/MIT) license.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fp-ranav%2Firis","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fp-ranav%2Firis","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fp-ranav%2Firis/lists"}