zcontrol

The zcontrol library is a small API for embedding an administrative control port into a ØMQ program. The control port is an interactive command shell — like a Unix shell, but limited to those commands that you define yourself as C callback functions. Typically these are monitoring or control commands.

Example of using a control port
% ./zcon -e tcp://127.0.0.1:3333
Connecting to tcp://127.0.0.1:3333.
zcon> help
help                 this text
version              version info
shutdown             shutdown server
quit                 close session

zcon> version
Current 0MQ version is 3.0.3

zcon> quit

zcon

Use zcon to connect to a control port and issue commands. It’s a simple command line client that comes with the zcontrol library.

Prerequisites

The zcontrol library is designed for:

  • ØMQ programs written in C on Linux or similar

  • ØMQ versions 2.x or 3.x

  • ØMQ programs based on a zmq_poll main loop

How it works

The control port is a ØMQ req-rep socket. Your ØMQ application should,

  1. Create a rep socket for the control port

  2. Define some command callbacks

  3. Call cp_init to set up the control port commands

  4. Call zmq_poll to wait for incoming messages on your sockets

  5. Call cp_exec whenever a message comes in on the control port

  6. Call cp_free at program termination

Note that the zcontrol library does not create any ØMQ sockets itself, nor does it do any polling on its own. It relies on the application to provide a rep socket, and to poll it. When a a message is available, the application invokes cp_exec. At this point the zcontrol library internally handles the request/reply messaging on that socket.

Complete example

Here’s a complete ØMQ application that embeds a control port. In particular it defines a couple of application-specific commands.

An application that embeds a control port
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <zmq.h>
#include "zcontrol.h"

#define ZCON_DEF_ENDPOINT "tcp://127.0.0.1:3333"
struct _CF {
  /* application state */
  int verbose;
  int request_exit;
  /* zmq related */
  void *zmq_context;
  void *zcontrol_socket;
  char *zcontrol_endpoint;
  void *zcontrol;
} CF = {
  .zcontrol_endpoint = ZCON_DEF_ENDPOINT,
};

int version_cmd(void *cp, cp_arg_t *arg, void *data) {
  int major, minor, patch;
  zmq_version (&major, &minor, &patch);
  cp_printf(cp,"Current 0MQ version is %d.%d.%d\n", major, minor, patch);
  return 0;
}

int shutdown_cmd(void *cp, cp_arg_t *arg, void *data) {
  cp_printf(cp,"Shutting down\n");
  CF.request_exit=1;
  return 0;
}

cp_cmd_t cmds[] = {
  {"version",      version_cmd,      "version info"},
  {"shutdown",     shutdown_cmd,     "shutdown server"},
  {NULL,           NULL,             NULL},
};

void usage(char *prog) {
  fprintf(stderr, "usage: %s [-v] [-e endpoint]\n", prog);
  exit(-1);
}

int setup_zmq() {
  int zero=0;
  if (( (CF.zmq_context = zmq_init(1)) == NULL)                               ||
      ( (CF.zcontrol_socket  = zmq_socket(CF.zmq_context, ZMQ_REP )) == NULL) ||
      ( zmq_setsockopt(CF.zcontrol_socket , ZMQ_LINGER, &zero, sizeof(zero))) ||
      ( zmq_bind(CF.zcontrol_socket, CF.zcontrol_endpoint ) != 0)) {
    fprintf(stderr,"zeromq setup failed: %s\n", zmq_strerror(errno));
    return -1;
  }
  return 0;
}

#define adim(x) (sizeof(x)/sizeof(*x))

void msg_loop() {

  zmq_pollitem_t items [] = {
    { CF.zcontrol_socket, 0, ZMQ_POLLIN, 0 },
    /* add your other zmq sockets here */
  };

  while (CF.request_exit==0) {
    zmq_poll (items, adim(items), -1);

    if (items[0].revents & ZMQ_POLLIN) {
      cp_exec(CF.zcontrol, CF.zcontrol_socket);
    }

    /* handle other socket types here ... */
  }
}

int main(int argc, char *argv[]) {
  int opt;

  while ( (opt = getopt(argc, argv, "v+e:")) != -1) {
    switch (opt) {
      case 'v': CF.verbose++; break;
      case 'e': CF.zcontrol_endpoint=strdup(optarg); break;
      default: usage(argv[0]); break;
    }
  }

  if (setup_zmq() == -1) goto program_exit;
  CF.zcontrol = cp_init(cmds,NULL);

  msg_loop();

 program_exit:
  if (CF.zcontrol_socket) zmq_close(CF.zcontrol_socket);
  if (CF.zmq_context) zmq_term(CF.zmq_context);
  if (CF.zcontrol) cp_free(CF.zcontrol);
  return 0;
}

This example is included with the zcontrol library in samples/sample1.c. To try it, build the zcontrol library then run make in the samples/ directory. Test it out by running sample1 in one terminal and zcon in another:

./sample1 -e tcp://127.0.0.1:3333
zcon -e tcp://127.0.0.1:3333

Built-in commands

The help and quit commands are always built-in to the control port. The quit command disconnects zcon from the control port.

Building and installing

Obtain the source code and simply run make to build libzcontrol.a.

git clone git://github.com/troydhanson/zcontrol.git
cd zcontrol
make
sudo make install

The install target installs zcon, libzcontrol.a and zcontrol.h in the /usr/local/ subdirectories bin, lib and include, respectively.

API

The control port library has these API functions as listed in zcontrol.h:

Table 1. API

cp_init

Set up a data structure for running a control port

cp_add_cmd

Add commands to a control port

cp_exec

Call when zmq_poll says the control port is readable

cp_free

Call when terminating the program to release memory

cp_printf

Called only within a command callback to add response text

See zcontrol.h for the full prototypes.

Command callbacks

The control port commands you define must have this prototype:

int (cp_cmd_f)(void *cp, cp_arg_t *arg, void *data);

The first argument is there as an opaque pointer which you can pass along to cp_printf when forming a command response. The second argument contains the parameters to the command, as a conventional C argc/argv list (with the addition of a lenv which has the length of each argument so strlen is not necessary). Note that zcon turns double-quoted strings into a single argument (similar to a traditional shell).

typedef struct {
  int     argc;
  char  **argv;
  size_t *lenv;
} cp_arg_t;

The final command argument, data, is just the opaque value that you passed to cp_init or cp_add_cmd when registering the command.

The return value is currently not used but good convention is to return 0 on success.

Contact/News

Feel free to send comments, questions or bug reports to the author at tdh@tkhanson.net. News is posted to the author’s blog at http://tkhanson.net/blog.