{"id":19973228,"url":"https://github.com/zeromq/zproto","last_synced_at":"2025-05-16T14:08:24.837Z","repository":{"id":14901485,"uuid":"17625362","full_name":"zeromq/zproto","owner":"zeromq","description":"A protocol framework for ZeroMQ","archived":false,"fork":false,"pushed_at":"2024-03-10T13:44:24.000Z","size":1180,"stargazers_count":240,"open_issues_count":5,"forks_count":100,"subscribers_count":34,"default_branch":"master","last_synced_at":"2025-05-14T06:56:49.838Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":"blackheaven/pgdayfr","license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zeromq.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":"AUTHORS","dei":null,"publiccode":null,"codemeta":null}},"created_at":"2014-03-11T09:44:14.000Z","updated_at":"2025-05-10T21:00:20.000Z","dependencies_parsed_at":"2024-06-20T17:34:24.467Z","dependency_job_id":"7083433c-cb31-453b-b84c-f622c5f085bb","html_url":"https://github.com/zeromq/zproto","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zeromq%2Fzproto","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zeromq%2Fzproto/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zeromq%2Fzproto/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zeromq%2Fzproto/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zeromq","download_url":"https://codeload.github.com/zeromq/zproto/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254544146,"owners_count":22088807,"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":[],"created_at":"2024-11-13T03:10:42.250Z","updated_at":"2025-05-16T14:08:24.787Z","avatar_url":"https://github.com/zeromq.png","language":"C","funding_links":[],"categories":["Utilities","公用事业","C","🥧 Browse Series: 52 articles"],"sub_categories":["YAML"],"readme":"\n# zproto - a protocol framework for ZeroMQ\n\n## Contents\n\n\n**[Man Page](#man-page)**\n\n**[The Codec Generator](#the-codec-generator)**\n\n**[The Server Generator](#the-server-generator)**\n\n**[Quick Background](#quick-background)**\n\n**[The State Machine Model](#the-state-machine-model)**\n\n**[The zproto Server Model](#the-zproto-server-model)**\n\n**[Superstates](#superstates)**\n\n**[Client and Server Properties](#client-and-server-properties)**\n\n**[Client Connection Expiry](#client-connection-expiry)**\n\n**[Server Configuration File](#server-configuration-file)**\n\n**[CZMQ Reactor Integration](#czmq-reactor-integration)**\n\n**[The Client Generator](#the-client-generator)**\n\n**[The zproto Client Model](#the-zproto-client-model)**\n\n**[Heartbeating](#heartbeating)**\n\n**[Superstates](#superstates)**\n\n**[Before/After State Actions](#beforeafter-state-actions)**\n\n**[Client Properties](#client-properties)**\n\n**[Client Expiry Timer](#client-expiry-timer)**\n\n**[Method Framework](#method-framework)**\n\n**[Custom hand-written methods](#custom-hand-written-methods)**\n\n**[Protocol Design Notes](#protocol-design-notes)**\n\n**[Heartbeating and Client Expiry](#heartbeating-and-client-expiry)**\n\n**[For More Information](#for-more-information)**\n\n**[This document](#this-document)**\n\n## Man Page\n\nzproto is a set of code generators that can produce:\n\n* fast and efficient binary codecs for ZeroMQ-based protocols.\n\n* full-featured protocol servers based on high-level state machine models.\n\n* full-featured protocol clients based on high-level state machine models.\n\nTo use zproto, clone the repository at https://github.com/zeromq/zproto.\n\nBuild and test using the usual commands:\n\n    ./autogen.sh\n    ./configure\n    make check\n\nAnd then install the code generators:\n\n    make install\n\nNext, read the src/zproto_example.xml file to learn how to write your own protocol specifications. The binary codec has the same name, and is src/zproto_example.c and include/zproto_example.h.\n\nTo rebuild the codec, first build and install https://github.com/zeromq/gsl. Then run these commands:\n\n    cd src\n    make code check\n\nTo use zproto as the base for your own projects, create a new project with [zproject](http://github.com/zeromq/zproject) which nicely integrates with zproto.\n\n## The Codec Generator\n\nGoals of the codec generator:\n\n* Best performance on high-volume low-complexity data.\n* Full flexibility on often-changing data (headers).\n* Portable to any programming language.\n* Built for ZeroMQ.\n* Easy to use.\n\nThe origin of this project is [Chapter 7 of the ZeroMQ Guide](http://zguide.zeromq.org/page:all#Serializing-Your-Data).\n\nTo use this tool, please build and install the [GSL code generator](https://github.com/zeromq/gsl).\n\nTo contribute patches back to this code, please send GitHub pull requests, adding your name to AUTHORS. The full contract for contributions is ZeroMQ RFC 22, http://rfc.zeromq.org/spec:22, with the change of using the MIT license.\n\nTo use:\n\n* Write your protocol as an XML file, using src/zproto_example.xml as a starting point.\n* Generate your protocol, using src/generate as a starting point.\n* Add the generated .h and .c files to your git repository.\n* Don't modify generated codecs. Change the model, and regenerate.\n\n## The Server Generator\n\nWhile ZeroMQ gives you a powerful communications engine to use in many different ways, building a conventional server is still fairly heavy work. We use the ROUTER socket for that, managing the state for each individual client connection. The zproto project includes a tool that generates whole servers, in C, from state machine models.\n\n### Quick Background\n\nBy Pieter Hintjens.\n\nzproto is based Chapter 7 of my ZeroMQ book, originally used in FileMQ, Zyre, and several other projects.\n\nMy company iMatix used to do code generation as our main business. We got... very good at it. There are lots of ways to generate code, and the most powerful and sophisticated code generator ever built by mankind lives on Github.com at [imatix/gsl](https://github.com/imatix/gsl). (Development continues at [zeromq/gsl](https://github.com/zeromq/gsl).) It's an interpreter for programs that eat models (self-describing documents) and spew out text of any shape and form.\n\nThe only problem with sophisticated magic like GSL is that it quickly excludes other people. So in ZeroMQ I've been very careful to not do a lot of code generation, only opening that mysterious black box when there was real need.\n\nThe first case was in CZMQ, to generate the classes for ZeroMQ socket options. Then in CZMQ, to [generate project files](https://github.com/zeromq/czmq/tree/master/model) (for various build systems) from [a single project.xml](https://github.com/zeromq/czmq/blob/master/src/project.xml) file. Yes, we still use XML models. It's actually a good use case for simple schema-free XML.\n\nThen I started generating binary codecs for protocols, [starting with FILEMQ](https://github.com/zeromq/filemq/blob/master/src/fmq_msg.xml). We used these codecs for a few different projects and they started to be quite solid. Something like a protobufs for ZeroMQ. You can see that the generated code [looks as good](https://github.com/zeromq/filemq/blob/master/include/fmq_msg.h) as hand-written code. It's actually better: [more consistent, with fewer errors](https://github.com/zeromq/filemq/blob/master/src/fmq_msg.c).\n\nFinally, I drew back on an even older iMatix speciality, which was state machines. My first free software tool was [Libero](http://legacy.imatix.com/html/libero/), a great tool for designing code as state machines and producing lovely, robust engines in pretty much any language. Libero predates GSL, so isn't as flexible. However it uses a very elegant and simple state-event-action model.\n\nThe Libero model is especially good at designing server-side logic, where you want to capture the exact state machine for a client connection, from start to end. This happens to be one of the heavier problems in ZeroMQ architecture: how to build capable protocol servers. We do a lot of this, it turns out. You can do only so much with low-level patterns like pub-sub and push-pull. Quite soon you need to implement stateful services.\n\nSo this is what I made: a GSL code generator that takes a finite-state machine model inspired by Libero, and turns out a full working server. The current code generator produces C (that builds on CZMQ). In this article I'll explain briefly how this works, and how to use it.\n\n### The State Machine Model\n\nState machines are a little unusual, conceptually. If you're not familiar with them it'll take a few days before they click. The Libero model is fairly simple and high level, meant to be designed and understood by humans:\n\n* The machine exists in one of a number of named *states*. By convention the machine starts in the first state.\n\n* In each state, the machine accepts one of a set of named *events*. Unhandled events are either ignored or cause the machine to die, depending on your taste.\n\n* Given an event in a state, the machine executes a list of *actions*, which correspond to your code.\n\n* After executing the actions the machine moves to the *next state*. An empty next state means \"stay in same state\".\n\n* In the next state, the machine either continues with an *internal event* produced by the previous actions, or waits for an *external event* coming as a protocol command.\n\n* Any action can set an *exception event* that interrupts the flow through the action list and to the next state.\n\n### The zproto Server Model\n\nThe zproto_server_c.gsl code generator outputs a single .inc file called an *engine* that does the hard work. If needed, it'll also generate you a skeleton .c file for your server, which you edit and build. It doesn't re-create that file again, though it will append new action stubs.\n\nThe server is a \"actor\" built on the CZMQ/zactor class. CZMQ zactors use a simple, consistent API based on message passing:\n\n    zactor_t *server = zactor_new (some_server, \"myserver\");\n    zstr_send (server, \"VERBOSE\");\n    zstr_sendx (server, \"BIND\", endpoint, NULL);\n    ...\n    zactor_destroy (\u0026server);\n\nWhere \"myserver\" is used in logging. Note that a zactor is effectively a background thread with a socket API, and you can pass zactor_t instances to all CZMQ message passing methods. The generated zactor accepts these messages:\n\n    VERBOSE\n    LOAD configfile\n    SET configpath value\n    SAVE configfile\n    BIND localendpoint\n    $TERM\n\nRather than run the server as a main program, you write a main program that creates and works with server actors. These run as background services, accepting clients on a ZMQ ROUTER port. The bind method exposes that port to the outside world.\n\nYour input to the code generator is two XML files (without schemas, DTDs, entity encodings!) that defines a set of 'states', and the protocol messages as used to generate the codec. Here is a minimal 'hello_server.xml' example that defines a Hello, World server:\n\n    \u003cclass\n        name = \"hello_server\"\n        title = \"Hello server\"\n        script = \"zproto_server_c\"\n        protocol_class = \"hello_msg\"\n        protocol_dir = \"\"\n        package_dir = \"../include\"\n        source_dir = \".\"\n        project_header = \"hello.h\"\n        \u003e\n        A Hello, World server\n\n        \u003cstate name = \"start\"\u003e\n            \u003cevent name = \"HELLO\"\u003e\n                \u003caction name = \"send\" message = \"WORLD\" /\u003e\n            \u003c/event\u003e\n        \u003c/state\u003e\n    \u003c/class\u003e\n\nYou will also need a minimal 'hello_msg.xml' as below. It's name is defined line 5 of 'hello_server.xml', as 'protocol_class'. Variable 'protocol_dir' allows developer to split protocol from client/server implementation to different projects.\n\n    \u003cclass\n        name = \"hello_msg\"\n        signature = \"0\"\n        title = \"hello msg protocol\"\n        script = \"zproto_codec_c\"\n        package_dir = \"../include\"\n        source_dir = \".\"\n        \u003e\n\n        \u003cmessage name = \"HELLO\" /\u003e\n        \u003cmessage name = \"WORLD\" /\u003e\n    \u003c/class\u003e\n\nNames of states, events, and actions are case insensitive. By convention however we use uppercase for protocol events. Protocol events can also contain embedded spaces or hyphens, which are mapped to underscores. In this case, HELLO and WORLD are two messages that must be defined in the hello_msg.xml file.\n\nThe generated server manages clients automatically. To build this, do:\n\n    gsl -q -trace:1 hello_server.xml\n\nThe first time you do this, you'll get a hello_server.c source file. You can edit that; it won't be regenerated. The generated code goes, instead, into hello_server_engine.inc. Take a look if you like.\n\nThe trace option shows all protocol messages received and sent.\n\nThere are two predefined actions: \"send\", which sends a specific protocol message, and \"terminate\", which ends the client connection. Other actions map to functions in your own code, e.g.:\n\n    \u003cstate name = \"start\"\u003e\n        \u003cevent name = \"HELLO\" next = \"start\"\u003e\n            \u003caction name = \"tell the user hello too\" /\u003e\n            \u003caction name = \"send\" message = \"WORLD\" /\u003e\n        \u003c/event\u003e\n    \u003c/state\u003e\n\n    ...\n\n    static void\n    tell_the_user_hello_too (client_t *self)\n    {\n        puts (\"Hello, World!\");\n    }\n\nYour server code (the actions) gets a small API to work with:\n\n    //  Set the next event, needed in at least one action in an internal\n    //  state; otherwise the state machine will wait for a message on the\n    //  router socket and treat that as the event.\n    static void\n    engine_set_next_event (client_t *self, event_t event);\n\n    //  Raise an exception with 'event', halting any actions in progress.\n    //  Continues execution of actions defined for the exception event.\n    static void\n    engine_set_exception (client_t *self, event_t event);\n\n    //  Set wakeup alarm after 'delay' msecs. The next state should\n    //  handle the wakeup event. The alarm is cancelled on any other\n    //  event.\n    static void\n    engine_set_wakeup_event (client_t *self, size_t delay, event_t event);\n\n    //  Execute 'event' on specified client. Use this to send events to\n    //  other clients. Cancels any wakeup alarm on that client.\n    static void\n    engine_send_event (client_t *self, event_t event);\n\n    //  Execute 'event' on all clients known to the server. If you pass a\n    //  client argument, that client will not receive the broadcast. If you\n    //  want to pass any arguments, store them in the server context.\n    static void\n    engine_broadcast_event (server_t *server, client_t *client, event_t event);\n\n    //  Set log file prefix; this string will be added to log data, to make\n    //  log data more searchable. The string is truncated to ~20 chars.\n    static void\n    engine_set_log_prefix (client_t *client, const char *string);\n\n    //  Set a configuration value in the server's configuration tree.\n    //  The properties this engine uses are: server/timeout, and\n    //  server/background. You can also configure other abitrary properties.\n    static void\n    engine_configure (server_t *server, const char *path, const char *value);\n\n### Superstates\n\nSuperstates are a shorthand to reduce the amount of error-prone repetition in a state machine. Here is the same state machine using a superstate:\n\n    \u003cstate name = \"start\" inherit = \"defaults\"\u003e\n        \u003cevent name = \"HELLO\" next = \"waiting\"\u003e\n            \u003caction name = \"wait on other internal event\" /\u003e\n        \u003c/event\u003e\n    \u003c/state\u003e\n\n    \u003cstate name = \"waiting\" inherit = \"defaults\"\u003e\n        \u003cevent name = \"ok\" next = \"start\"\u003e\n            \u003caction name = \"tell the user hello too\" /\u003e\n            \u003caction name = \"send\" message = \"WORLD\" /\u003e\n        \u003c/event\u003e\n        \u003cevent name = \"error\" next = \"start\"\u003e\n            \u003caction name = \"terminate\" /\u003e\n        \u003c/event\u003e\n    \u003c/state\u003e\n\n    \u003cstate name = \"defaults\"\u003e\n        \u003cevent name = \"PING\"\u003e\n            \u003caction name = \"send\" message = \"PONG\" /\u003e\n        \u003c/event\u003e\n    \u003c/state\u003e\n\nNote the logic of PING, which says, \"when the client sends PING, respond by sending PONG, and then stay in the same state.\"\n\nFor complex protocols you can collect error handling together using the wildcard event, \"*\", which means \"all other protocol events in this state\". For example:\n\n    \u003cstate name = \"defaults\"\u003e\n        \u003cevent name = \"PING\"\u003e\n            \u003caction name = \"send\" message = \"PONG\" /\u003e\n        \u003c/event\u003e\n        \u003cevent name = \"*\"\u003e\n            \u003caction name = \"log unexpected client message\" /\u003e\n            \u003caction name = \"terminate\" /\u003e\n        \u003c/event\u003e\n    \u003c/state\u003e\n\n### Client and Server Properties\n\nIn your server code, you have two structures, client_t and server_t. Note that the client_t structure MUST always start with these variables (the message uses whatever protocol name you defined):\n\n    server_t *server;           //  Reference to parent server\n    hello_msg_t *message;       //  Message from/to clients\n\nAnd the server_t structure MUST always start with these variables:\n\n    zsock_t *pipe;              //  Actor pipe back to caller\n    zconfig_t *config;          //  Current loaded configuration\n\n### Client Connection Expiry\n\nIf you define an \"expired\" event anywhere in your dialog, the server will automatically expire idle clients after a timeout, which defaults to 60 seconds. It's smart to put this into a superstate:\n\n    \u003cstate name = \"defaults\"\u003e\n        \u003cevent name = \"PING\"\u003e\n            \u003caction name = \"send\" message = \"PONG\" /\u003e\n        \u003c/event\u003e\n        \u003cevent name = \"expired\"\u003e\n            \u003caction name = \"terminate\" /\u003e\n        \u003c/event\u003e\n    \u003c/state\u003e\n\nTo tune the expiry time, use this method (e.g. to set to 5 second):\n\n    hello_server_set (self, \"server/timeout\", \"5000\");\n\nThe server timeout can also come from a configuration file, see below. It is good practice to do heartbeating by sending a PING from the client and responding to that with a PONG or suchlike. Do not heartbeat from the server to clients; that is fragile.\n\n### Server Configuration File\n\nYou can call the 'configure' method on the server object to configure it, and you can also call the 'set' method later to change individual configuration options. The configuration file format is ZPL (ZeroMQ RFC 5), which looks like this:\n\n    #   Default zbroker configuration\n    #\n    hello_server\n        echo = I: starting hello service on tcp://*:8888\n        bind\n            endpoint = tcp://*:8888\n\n    #   Apply to all servers that load this config file\n    server\n        timeout = 10        #   Client connection timeout\n        background = 1      #   Run as background process\n        workdir = .         #   Working directory for daemon\n\n'echo' and 'bind' in the 'hello_server' section are executed automatically.\n\n### CZMQ Reactor Integration\n\nThe generated engine offers zloop integration so you can monitor your own sockets for activity and execute callbacks when messages arrive on them. Use this API method:\n\n    //  Poll socket for activity, invoke handler on any received message.\n    //  Handler must be a CZMQ zloop_fn function; receives server as arg.\n    static void\n    engine_handle_socket (server_t *server, void *socket, zloop_fn handler);\n\nThe engine invokes the handler with the 'server' as the argument. Here is the general style of using such a handler. First, in the 'server_initialize' function:\n\n    engine_handle_socket (self, self-\u003esome_socket, some_handler);\n\nWhere 'some_socket' is a ZeroMQ socket, and where 'some_handler' looks like this:\n\n    static int\n    some_handler (zloop_t *loop, zmq_pollitem_t *item, void *argument)\n    {\n        server_t *self = (server_t *) argument;\n        zmsg_t *msg = zmsg_recv (self-\u003esome_socket);\n        if (!msg)\n            return 0;               //  Interrupted; do nothing\n        zmsg_dump (msg);            //  Nice during development\n        ... process the message\n        return 0;                   //  0 = continue, -1 = end reactor\n    }\n\nSimilarly you can tell the engine to call a 'monitor' function at some specific interval, e.g. once per second. Use this API method:\n\n    //  Register monitor function that will be called at regular intervals\n    //  by the server engine\n    static void\n    engine_set_monitor (server_t *server, size_t interval, zloop_timer_fn monitor);\n\nCall this in the 'server_initialize' function:\n\n    engine_set_monitor (self, 1000, some_monitor);\n\nWere 'some_monitor' looks like this:\n\n    static int\n    some_monitor (zloop_t *loop, int timer_id, void *argument)\n    {\n        server_t *self = (server_t *) argument;\n        ... do regular server maintenance\n        return 0;                   //  0 = continue, -1 = end reactor\n    }\n\n## The Client Generator\n\nThe zproto project lets you generate full asynchronous client stacks in C to talk to the server engines. Overall the model and toolchain is similar to that used for servers. See the zproto_client_c.gsl code generator. The main differences is that:\n\n* A client manages one connection to a server;\n* We generate a full CLASS API that wraps the client actor with conventional methods;\n* The client XML model has a language for defining these methods.\n\n### The zproto Client Model\n\nYour input to the code generator is two XML files that defines a set of 'states', and the protocol messages as used to generate the codec. Here is a minimal 'hello_client.xml' example that defines a Hello, World client:\n\n    \u003cclass\n        name = \"hello_client\"\n        title = \"Hello Client\"\n        script = \"zproto_client_c\"\n        protocol_class = \"hello_msg\"\n        project_header = \"czmq.h\"\n        package_dir = \".\"\n        \u003e\n        \u003cstate name = \"start\"\u003e\n            \u003cevent name = \"constructor\" next = \"connected\"\u003e\n                \u003caction name = \"connect to server\" /\u003e\n                \u003caction name = \"send\" message = \"HELLO\" /\u003e\n            \u003c/event\u003e\n        \u003c/state\u003e\n        \u003cstate name = \"connected\"\u003e\n            \u003cevent name = \"WORLD\" next = \"connected\"\u003e\n                \u003caction name = \"terminate\" /\u003e\n            \u003c/event\u003e\n        \u003c/state\u003e\n        \u003cmethod name = \"constructor\"\u003e\n        Connect to server.\n            \u003cfield name = \"endpoint\" type = \"string\" /\u003e\n        \u003c/method\u003e\n    \u003c/class\u003e\n\nThe zproto_client_c.gsl code generator produces:\n\n* A .h file that acts as the public API for your client\n* An .inc file called an *engine* that runs the state machine\n* The first time, a skeleton .c file for your client class\n\nThe client is a \"actor\" built on the CZMQ/zactor class. The generated zactor accepts these messages:\n\n    VERBOSE\n    $TERM\n\nPlus one message for each method defined in the model, including the pre-defined \"constructor\" and \"destructor\" methods (called after, and before construction and destruction respectively).\n\nThe client stack is then wrapped in a classic CLASS API like this:\n\n    //  Create a new hello_client\n    hello_client_t *\n        hello_client_new (const char *endpoint);\n\n    //  Destroy the hello_client\n    void\n        hello_client_destroy (hello_client_t **self_p);\n\n    //  Enable verbose logging of client activity\n    void\n        hello_client_verbose (hello_client_t *self);\n\n    //  Return actor for low-level command control and polling\n    zactor_t *\n        hello_client_actor (hello_client_t *self);\n\nNames are case insensitive. By convention however we use uppercase for protocol events and methods. Protocol events can also contain embedded spaces or hyphens, which are mapped to underscores. In this case, HELLO and WORLD are two messages that must be defined in the hello_msg.xml file.\n\nTo build this, do:\n\n    gsl -q -trace:1 hello_client.xml\n\nThe first time you do this, you'll get a hello_client.c source file. You can edit that; it won't be regenerated. The generated code goes, instead, into hello_client_engine.inc and hello_client.h. Take a look if you like.\n\nThe trace option shows all protocol messages received and sent.\n\nThere are two predefined actions: \"send\", which sends a specific protocol message, and \"terminate\", which ends the client. Other actions map to functions in your own code, e.g.:\n\n    \u003cstate name = \"start\"\u003e\n        \u003cevent name = \"HELLO\" next = \"start\"\u003e\n            \u003caction name = \"tell the user hello too\" /\u003e\n            \u003caction name = \"send\" message = \"WORLD\" /\u003e\n        \u003c/event\u003e\n    \u003c/state\u003e\n\n    ...\n\n    static void\n    tell_the_user_hello_too (client_t *self)\n    {\n        puts (\"Hello, World!\");\n    }\n\nYour client code (the actions) gets a small API to work with:\n\n    //  Set the next event, needed in at least one action in an internal\n    //  state; otherwise the state machine will wait for a message on the\n    //  dealer socket and treat that as the event.\n    static void\n    engine_set_next_event (client_t *client, event_t event);\n\n    //  Raise an exception with 'event', halting any actions in progress.\n    //  Continues execution of actions defined for the exception event.\n    static void\n    engine_set_exception (client_t *client, event_t event);\n\n    //  Set wakeup alarm after 'delay' msecs. The next state should\n    //  handle the wakeup event. The alarm is cancelled on any other\n    //  event.\n    static void\n    engine_set_wakeup_event (client_t *self, size_t delay, event_t event);\n\n    //  Set a heartbeat timer. The interval is in msecs and must be\n    //  non-zero. The state machine must handle the \"heartbeat\" event.\n    //  The heartbeat happens every interval no matter what traffic the\n    //  client is sending or receiving.\n    static void\n    engine_set_heartbeat (client_t *self, size_t heartbeat);\n\n    //  Set expiry timer. Setting a non-zero expiry causes the state machine\n    //  to receive an \"expired\" event if is no incoming traffic for that many\n    //  milliseconds. This cycles over and over until/unless the code sets a\n    //  zero expiry. The state machine must handle the \"expired\" event.\n    static void\n    engine_set_expiry (client_t *client, size_t expiry);\n\n    //  Set connected to true/false. The client must call this if it wants\n    //  to provide the API with the connected status.\n    static void\n    engine_set_connected (client_t *client, bool connected);\n\n    //  Poll socket for activity, invoke handler on any received message.\n    //  Handler must be a CZMQ zloop_fn function; receives server as arg.\n    static void\n    engine_handle_socket (client_t *client, zsock_t *socket, zloop_reader_fn handler);\n\n### Heartbeating\n\nUse the engine_set_heartbeat method to generate a regular \"heartbeat\" event when connected, and send a PING each time. The server needs to respond with a PONG. Then, set an expiry timeout of 2 or 3 times the heartbeat interval, and use this to detect a dead server.\n\n### Superstates\n\nSuperstates are a shorthand to reduce the amount of error-prone repetition in a state machine. Here is the same state machine using a superstate:\n\n    \u003cstate name = \"subscribing\" inherit = \"defaults\"\u003e\n        \u003cevent name = \"SUBSCRIBE OK\" next = \"connected\"\u003e\n            \u003caction name = \"signal success\" /\u003e\n        \u003c/event\u003e\n    \u003c/state\u003e\n\n    \u003cstate name = \"disconnecting\" inherit = \"defaults\"\u003e\n        \u003cevent name = \"GOODBYE OK\"\u003e\n            \u003caction name = \"terminate\" /\u003e\n        \u003c/event\u003e\n        \u003cevent name = \"expired\"\u003e\n            \u003caction name = \"terminate\" /\u003e\n        \u003c/event\u003e\n    \u003c/state\u003e\n\n   \u003cstate name = \"defaults\"\u003e\n        \u003c!-- Server didn't respond for some time --\u003e\n        \u003cevent name = \"expired\" next = \"reconnecting\"\u003e\n            \u003caction name = \"use connect timeout\" /\u003e\n            \u003caction name = \"send\" message = \"HELLO\" /\u003e\n        \u003c/event\u003e\n        \u003c!-- Server lost our connection state --\u003e\n        \u003cevent name = \"INVALID\" next = \"reconnecting\"\u003e\n            \u003caction name = \"use connect timeout\" /\u003e\n            \u003caction name = \"send\" message = \"HELLO\" /\u003e\n        \u003c/event\u003e\n        \u003cevent name = \"*\"\u003e\n            \u003c!-- Discard any other incoming events --\u003e\n        \u003c/event\u003e\n    \u003c/state\u003e\n\nFor complex protocols you can collect error handling together using the wildcard event, \"*\", which means \"all other protocol events in this state\". For example:\n\n    \u003cstate name = \"defaults\"\u003e\n        \u003cevent name = \"PING\"\u003e\n            \u003caction name = \"send\" message = \"PONG\" /\u003e\n        \u003c/event\u003e\n        \u003cevent name = \"*\"\u003e\n            \u003caction name = \"log unexpected server message\" /\u003e\n            \u003caction name = \"terminate\" /\u003e\n        \u003c/event\u003e\n    \u003c/state\u003e\n\n### Before/After State Actions\n\nAs another way to reduce error-prone repetition, it is possible to add actions to be executed for any event that transitions to or from a given state. This is modelled with a before or after element containing one or more action elements inside of a state element. The given actions will be executed only when the state of the machine changes to or from that state (due to an event that has the \"next\" attribute defined).\n\n    \u003cstate name = \"active\" inherit = \"defaults\"\u003e\n        \u003cbefore\u003e\n            \u003caction name = \"start underlying service\" /\u003e\n        \u003c/before\u003e\n        \u003cevent name = \"REQUEST\"\u003e\n            \u003caction name = \"store metadata\" /\u003e\n            \u003caction name = \"forward to underlying service\" /\u003e\n        \u003c/event\u003e\n        \u003cevent name = \"reply from underlying service\"\u003e\n            \u003caction name = \"send\" message = \"REPLY\" /\u003e\n            \u003caction name = \"clear metadata\" /\u003e\n        \u003c/event\u003e\n        \u003cafter\u003e\n            \u003caction name = \"stop underlying service\" /\u003e\n        \u003c/after\u003e\n    \u003c/state\u003e\n\n### Client Properties\n\nIn your client code, you have a client_t structure. Note that the client_t structure MUST always start with these variables (the msgout and msgin will use whatever protocol name you defined):\n\n    zsock_t *pipe;              //  Actor pipe back to caller\n    zsock_t *dealer;            //  Socket to talk to server\n    my_msg_t *msgout;           //  Message to send to server\n    my_msg_t *msgin;            //  Message received from server\n\n### Client Expiry Timer\n\nIf you define an \"expired\" event anywhere in your dialog, the client will automatically execute an expired_event after a timeout. To define the expiry timeout, use engine_set_expiry (). The expired event will repeat whenever there is no activity from the server, until you set a expiry of zero (which ends it).\n\n### Method Framework\n\nTo simplify the delivery of a conventional non-actor API, you can define methods in your state machine. Here are some examples taken from real projects:\n\n    \u003cmethod name = \"constructor\" return = \"status\"\u003e\n    Connect to server endpoint, with specified timeout in msecs (zero means\n    wait forever). Constructor succeeds if connection is successful.\n        \u003cfield name = \"endpoint\" type = \"string\" /\u003e\n        \u003cfield name = \"timeout\" type = \"number\" /\u003e\n        \u003caccept reply = \"SUCCESS\" /\u003e\n        \u003caccept reply = \"FAILURE\" /\u003e\n    \u003c/method\u003e\n\n    \u003cmethod name = \"subscribe\" return = \"status\"\u003e\n    Subscribe to all messages sent to matching addresses. The expression is a\n    regular expression using the CZMQ zrex syntax. The most useful elements\n    are: ^ and $ to match the start and end, . to match any character,\n    \\s and \\S to match whitespace and non-whitespace, \\d and \\D to match a\n    digit and non-digit, \\a and \\A to match alphabetic and non-alphabetic,\n    \\w and \\W to match alphanumeric and non-alphanumeric, + for one or more\n    repetitions, * for zero or more repetitions, and ( ) to create groups.\n    Returns 0 if subscription was successful, else -1.\n        \u003cfield name = \"expression\" type = \"string\" /\u003e\n        \u003caccept reply = \"SUCCESS\" /\u003e\n        \u003caccept reply = \"FAILURE\" /\u003e\n    \u003c/method\u003e\n\n    \u003cmethod name = \"publish\"\u003e\n    Publish a message on the server, using a logical address. All subscribers\n    to that address will receive a copy of the message. The server does not\n    store messages. If a message is published before subscribers arrive, they\n    will miss it. Currently only supports string contents. Does not return a\n    status value; publish commands are asynchronous and unconfirmed.\n        \u003cfield name = \"address\" type = \"string\" /\u003e\n        \u003cfield name = \"content\" type = \"string\" /\u003e\n    \u003c/method\u003e\n\n    \u003cmethod name = \"recv\" return = \"content\" immediate = \"1\" virtual =\"1\"\u003e\n    Receive next message from server. Returns the message content, as a string,\n    if any. The caller should not modify or free this string. This method is\n    defined as \"immediate\" and so does not send any message to the client actor.\n    Virtual 1 means recv method returns msg part of incomming message. Virtual 0 means\n    recv returns 0/-1 and message parts are accessible using client methods.\n        \u003caccept reply = \"MESSAGE\" /\u003e\n    \u003c/method\u003e\n\n    \u003c!-- These are the replies from the actor to the API --\u003e\n    \u003creply name = \"SUCCESS\"\u003e\n        \u003cfield name = \"status\" type = \"number\" /\u003e\n    \u003c/reply\u003e\n\n    \u003creply name = \"FAILURE\"\u003e\n        \u003cfield name = \"status\" type = \"number\" /\u003e\n        \u003cfield name = \"reason\" type = \"string\" /\u003e\n    \u003c/reply\u003e\n\n    \u003creply name = \"MESSAGE\"\u003e\n        \u003cfield name = \"sender\" type = \"string\" /\u003e\n        \u003cfield name = \"address\" type = \"string\" /\u003e\n        \u003cfield name = \"content\" type = \"string\" /\u003e\n    \u003c/reply\u003e\n\nEach method is implemented as a classic CLASS method, with the public API in the generated .h header, and the body in the generated .inc engine. For example:\n\n    //  ------------------------------------------------------------------\n    //  Subscribe to all messages sent to matching addresses...\n\n    int\n    mlm_client_subscribe (mlm_client_t *self, const char *stream, const char *pattern)\n    {\n        assert (self);\n        zsock_send (self-\u003eactor, \"ss\", \"SUBSCRIBE\", stream, pattern);\n        if (s_accept_reply (self, \"SUCCESS\", \"FAILURE\", NULL))\n            return -1;              //  Interrupted or timed-out\n        return 0;\n    }\n\nWhen the calling application uses the method, this can do any or several of these things:\n\n* Send a message to the client actor, if the method does not have the 'immediate = \"1\"' attribute. This generates an event in the client state machine, corresponding to the method name.\n\n* Wait for one of a set of replies from the actor, and store reply properties in the client object. These are defined by one or more \u003caccept\u003e tags.\n\n* Return a propery to the caller. This is defined by the \"return\" attribute of the \u003cmethod\u003e tag.\n\nAll possible replies are defined as \u003creply\u003e objects. The actor's replies are always several frames. The first is the reply name, and the following are the reply fields.\n\nWe currently support only two field types: string, and number, which map to char * and int.\n\nThe fields you pass in a method are accessible to client state machine actions via the self-\u003eargs structure. For example:\n\n    if (zsock_connect (self-\u003edealer, \"%s\", self-\u003eargs-\u003eendpoint)) {\n        engine_set_exception (self, error_event);\n        zsys_warning (\"could not connect to %s\", self-\u003eargs-\u003eendpoint);\n    }\n\n### Custom hand-written methods\n\nWithin your state model you can include another XML file with custom hand-written methods to easily extend the state maschine. To do so include the following:\n\n    \u003ccustom filename = \"hello_client_custom.xml\" language = \"C\" /\u003e\n\nThe hello_client_custom.xml can contain three sections `\u003cheader\u003e`, `\u003csource\u003e` and `\u003capi\u003e`. The contents of the `\u003cheader\u003e` and `\u003csource\u003e` section will be placed into the hello_client.(h|c) files. The `\u003capi\u003e` defines a zproject API for your custom code and can only be used in conjunction with zproject. Please note that `\u003cheader\u003e` and `\u003capi\u003e` are exclusive as the header will be generated from the API by zproject. The hello_client_custom.xml may look like:\n\n    \u003cheader\u003e\n        //  Print the attributes of this class\n        void\n            hello_client_print (hello_client_t *self)\n    \u003c/header\u003e\n    \u003csource\u003e\n        //  -------------------------------------------------------------------\n        //  Print the attributes of this class\n\n        void\n        hello_client_print (hello_client_t *self)\n        {\n            printf (\"hello_client {\\n\");\n            printf (\"  last command %s\\n\", self-\u003ecommand);\n            ...\n            printf (\"}\\n\");\n        }\n    \u003c/source\u003e\n\nWhen using zprojects API you'll need to escape '\u003c' and '\u003e' for now:\n\n    \u003capi\u003e\n        \u0026lt;method name = \"print\"\u0026gt;\n            Print the attributes of this class\n        \u0026lt;/method\u0026gt;\n    \u003c/api\u003e\n    \u003csource\u003e\n        //  -------------------------------------------------------------------\n        //  Print the attributes of this class\n\n        void\n        hello_client_print (client_t *client)\n        {\n            if (client) {\n                s_client_t *self = (s_client_t *) client;\n                printf (\"hello_client {\n                printf (\"  heartbeat %lu\\n\", self-\u003eheartbeat);\n                printf (\"  current event %lu\\n\", self-\u003eevent);\n                printf (\"  current state %lu\\n\", self-\u003estate);\n                ...\n                printf (\"}\\n\");\n            }\n        }\n    \u003c/source\u003e\n\n## Protocol Design Notes\n\nThis section covers some learned experience designing protocols, using zproto and more generally:\n\n### Heartbeating and Client Expiry\n\nThe simplest and most robust heartbeat / connection expiry model appears to be the following:\n\n* The server disconnects unresponsive clients after some expiry timeout, which you can set using the SET message and the \"server/timeout\" property. A good expiry timeout is perhaps 3 to 10 seconds. The minimum allowed is 2000 milliseconds.\n\n* The client heartbeats the server by sending a PING heartbeat every second if there is no other activity. You can do this by calling \"engine_set_timeout (self, 1000);\" in the client and in expired_event handling, send a PING command (or similar).\n\n* The server responds to PING commands with a PING-OK (or similar), when it is in a valid connected state. When the server does not consider the client as connected, it responds with INVALID (or similar).\n\n* The client accepts and discards PING-OK. If it receives INVALID, it re-starts the protocol session by sending OPEN (or similar).\n\n* The server accepts OPEN in all external states and always treats this as a request to start a new protocol session.\n\nThis approach resolves stale TCP connections, as well as dead clients and dead servers. It makes the heartbeating interval a client-side decision, and client expiry a server-side decision (this seems best in both cases).\n\n## For More Information\n\nThough [the Libero documentation](http://legacy.imatix.com/html/libero/) is quite old now, it's useful as a guide to what's possible with state machines. The Libero model added superstates, substates, and other useful ways to manage larger state machines.\n\nThe current working example of the zproto server generator is the [zeromq/zbroker](https://github.com/zeromq/zbroker) project, and specifically the zpipes_server class.\n\nYou can find [GSL on Github](https://github.com/zeromq/gsl) and there's a [old backgrounder](http://download.imatix.com/mop/) for the so-called \"model oriented programming\" we used at iMatix.\n\n## This document\n\n_This documentation was generated from zproto/README.txt using [Gitdown](https://github.com/zeromq/gitdown)_\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzeromq%2Fzproto","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzeromq%2Fzproto","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzeromq%2Fzproto/lists"}