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.
% ./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,
-
Create a rep socket for the control port
-
Define some command callbacks
-
Call cp_init to set up the control port commands
-
Call zmq_poll to wait for incoming messages on your sockets
-
Call cp_exec whenever a message comes in on the control port
-
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.
#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:
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.