kganser.com

Asynchronous DNS lookups using libeio and libev

Here's a simple example that uses a thread pool from libeio inside libev's event loop to do asynchronous DNS lookups using the blocking getaddrinfo function. A timer in the loop should intersperse ticks in the program's output as the DNS lookups are in progress.

#include <ev.h>
#include <eio.h>
#include <string.h>
#include <stdio.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>

// To compile: cc <file> -leio -lev

typedef struct {
  const char *host;
  struct addrinfo *address;
} resolve_t;

struct ev_loop *loop;
ev_idle idle_watcher;
ev_async async_watcher;
ev_timer timer_watcher;

// This is only executed when eio_poll returns -1
void idle_cb(struct ev_loop *loop, ev_idle *w, int revents) {
  if (eio_poll() != -1)
    ev_idle_stop(loop, w);
}

void async_cb(struct ev_loop *loop, ev_async *w, int revents) {
  if (eio_poll() == -1) {
    ev_idle_start(loop, &idle_watcher);
  } else if (!eio_nreqs()) {
    ev_timer_stop(loop, &timer_watcher);
    ev_async_stop(loop, &async_watcher);
  }
}

// Need to call eio_poll, but not from this function.
// Signal async_watcher instead and call from there.
void want_poll(void) {
  ev_async_send(loop, &async_watcher);
}

int on_resolve(eio_req *req) {
  char ipstr[INET6_ADDRSTRLEN];
  resolve_t *r = (resolve_t *)req->data;
  struct addrinfo *p;
  
  for (p = r->address; p; p = p->ai_next) {
    void *addr;

    if (p->ai_family == AF_INET) { // IPv4
      struct sockaddr_in *ipv4 = (struct sockaddr_in *)p->ai_addr;
      addr = &ipv4->sin_addr;
    } else { // IPv6
      struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)p->ai_addr;
      addr = &ipv6->sin6_addr;
    }

    inet_ntop(p->ai_family, addr, ipstr, sizeof ipstr);
    printf("  %s: %s\n", r->host, ipstr);
  }
  
  freeaddrinfo(r->address);
  return 0;
}

void resolve(eio_req *req) {
  resolve_t *r = (resolve_t *)req->data;
  struct addrinfo hints;

  memset(&hints, 0, sizeof hints);
  hints.ai_family = AF_UNSPEC;
  hints.ai_socktype = SOCK_STREAM;

  printf("resolving %s...\n", r->host);
  req->result = getaddrinfo(r->host, "80", &hints, &r->address);
}

void tick(struct ev_loop *loop, ev_timer *w, int revents) {
  puts("tick");
  ev_timer_again(loop, w);
}

int main(int argc, char *argv[]) {
  
  if (argc < 2) {
    fprintf(stderr, "  Usage: %s host ...\n", argv[0]);
    return 1;
  }
  
  loop = EV_DEFAULT;
  resolve_t r[argc-1];
  
  ev_idle_init(&idle_watcher, idle_cb);
  ev_async_init(&async_watcher, async_cb);
  ev_timer_init(&timer_watcher, tick, 0., .001);
  eio_init(want_poll, 0);
  
  ev_async_start(loop, &async_watcher);
  ev_timer_again(loop, &timer_watcher);
  
  int i = 0;
  while (i < argc-1) {
    r[i].host = argv[i+1];
    eio_custom(resolve, 0, on_resolve, r+i++);
  }
  
  ev_run(loop, 0);
  
  return 0;
}