cURL / Mailing Lists / curl-library / Single Mail

curl-library

Socket callback not firing

From: Steve Ellis <steve_at_changesciences.com>
Date: Mon, 19 Oct 2009 15:52:03 -0400

I'm new to libcurl and am hoping someone can point me in the right
direction.

My goal is to use curl_multi_socket_action and libevent. I've studied
the hiperfifo.c example. But unlike the sample, I would like to add
handles without piping them through a fifo.

Here's how I'm expecting this to work:
1) init libvent and curl
2) add handles by calling curl_multi_add_handle (hiperfifo.c has this
running through a fifo which I'd rather not do)
3) curl makes requests and notifies the sockets it creates to handle the
requests about progress
4) libevent picks up action on those sockets and fires the curl-defined
callbacks

Unfortunately I can't get the socket callback (sock_cb) to fire. Adding
the easy handle doesn't appear to be enough to start the request. I'd
like to try calling curl_multi_socket_action directly but it requires a
   reference to a socket that I don't have, implying that it should only
be called in one of the callback functions. The multi timer callback
(multi_timer_cb) does fire, but the app returns immediately.

What am I missing?

I've attached some code duplicating the problem (based on hiperfifo.c).

Thanks!

Steve

/*****************************************************************************
Based on http://curl.haxx.se/libcurl/c/hiperfifo.html.

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>
#include <sys/poll.h>
#include <curl/curl.h>
#include <event.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>

#define MSG_OUT stdout /* Send info to stdout, change to stderr if you want */

#define BUFSIZE 1024

/* Global information, common to all connections */
typedef struct _GlobalInfo {
  struct event timer_event;
  CURLM *multi;
  int prev_running;
  int still_running;
} GlobalInfo;


/* Information associated with a specific easy handle */
typedef struct _ConnInfo {
  CURL *easy;
  char *url;
  GlobalInfo *global;
  char error[CURL_ERROR_SIZE];
} ConnInfo;


/* Information associated with a specific socket */
typedef struct _SockInfo {
  curl_socket_t sockfd;
  CURL *easy;
  int action;
  long timeout;
  struct event ev;
  int evset;
  GlobalInfo *global;
} SockInfo;



/* Update the event timer after curl_multi library calls */
static int multi_timer_cb(CURLM *multi, long timeout_ms, GlobalInfo *g)
{
  struct timeval timeout;
  (void)multi; /* unused */

  timeout.tv_sec = timeout_ms/1000;
  timeout.tv_usec = (timeout_ms%1000)*1000;
  fprintf(MSG_OUT, "multi_timer_cb: Setting timeout to %ld ms\n", timeout_ms);
  evtimer_add(&g->timer_event, &timeout);
  return 0;
}

/* Die if we get a bad CURLMcode somewhere */
static void mcode_or_die(const char *where, CURLMcode code)
{
  if ( CURLM_OK != code ) {
    const char *s;
    switch (code) {
      case CURLM_CALL_MULTI_PERFORM: s="CURLM_CALL_MULTI_PERFORM"; break;
      case CURLM_OK: s="CURLM_OK"; break;
      case CURLM_BAD_HANDLE: s="CURLM_BAD_HANDLE"; break;
      case CURLM_BAD_EASY_HANDLE: s="CURLM_BAD_EASY_HANDLE"; break;
      case CURLM_OUT_OF_MEMORY: s="CURLM_OUT_OF_MEMORY"; break;
      case CURLM_INTERNAL_ERROR: s="CURLM_INTERNAL_ERROR"; break;
      case CURLM_UNKNOWN_OPTION: s="CURLM_UNKNOWN_OPTION"; break;
      case CURLM_LAST: s="CURLM_LAST"; break;
      default: s="CURLM_unknown";
        break;
    case CURLM_BAD_SOCKET: s="CURLM_BAD_SOCKET";
      fprintf(MSG_OUT, "ERROR: %s returns %s\n", where, s);
      /* ignore this error */
      return;
    }
    fprintf(MSG_OUT, "ERROR: %s returns %s\n", where, s);
    exit(code);
  }
}


/* Check for completed transfers, and remove their easy handles. */
static void check_run_count(GlobalInfo *g)
{
  if (g->prev_running > g->still_running) {
    char *eff_url=NULL;
    CURLMsg *msg;
    int msgs_left;
    ConnInfo *conn=NULL;
    CURL*easy;
    CURLcode res;

    fprintf(MSG_OUT, "REMAINING: %d\n", g->still_running);
    do {
      easy=NULL;
      while ((msg = curl_multi_info_read(g->multi, &msgs_left))) {
        if (msg->msg == CURLMSG_DONE) {
          easy=msg->easy_handle;
          res=msg->data.result;
          break;
        }
      }
      if (easy) {
          curl_easy_getinfo(easy, CURLINFO_PRIVATE, &conn);
          curl_easy_getinfo(easy, CURLINFO_EFFECTIVE_URL, &eff_url);
          fprintf(MSG_OUT, "DONE: %s => (%d) %s\n", eff_url, res, conn->error);

          curl_multi_remove_handle(g->multi, easy);
          free(conn->url);
          curl_easy_cleanup(easy);
          free(conn);
      }
    } while ( easy );
  }
  g->prev_running = g->still_running;
}


/* Called by libevent when we get action on a multi socket */
static void event_cb(int fd, short kind, void *userp)
{
  GlobalInfo *g = (GlobalInfo*) userp;
  CURLMcode rc;

  int action =
    (kind&EV_READ?CURL_CSELECT_IN:0)|
    (kind&EV_WRITE?CURL_CSELECT_OUT:0);

  do {
    rc = curl_multi_socket_action(g->multi, fd, action, &g->still_running);
  } while (rc == CURLM_CALL_MULTI_PERFORM);

  mcode_or_die("event_cb: curl_multi_socket_action", rc);

  check_run_count(g);
  if ( g->still_running <= 0 ) {
    fprintf(MSG_OUT, "last transfer done, kill timeout\n");
    if (evtimer_pending(&g->timer_event, NULL)) {
      evtimer_del(&g->timer_event);
    }
  }
}

/* Called by libevent when our timeout expires */
static void timer_cb(int fd, short kind, void *userp)
{
  GlobalInfo *g = (GlobalInfo *)userp;
  CURLMcode rc;
  (void)fd;
  (void)kind;

  do {
    rc = curl_multi_socket_action(g->multi,
                                  CURL_SOCKET_TIMEOUT, 0, &g->still_running);
  } while (rc == CURLM_CALL_MULTI_PERFORM);
  mcode_or_die("timer_cb: curl_multi_socket_action", rc);
  check_run_count(g);
}

/* Clean up the SockInfo structure Called in sock_cb.*/
static void remsock(SockInfo *f)
{
  if (f) {
    if (f->evset)
      event_del(&f->ev);
    free(f);
  }
}

/* Assign information to a SockInfo structure Called in sock_cb. */
static void setsock(SockInfo*f, curl_socket_t s, CURL*e, int act, GlobalInfo*g)
{
  int kind =
     (act&CURL_POLL_IN?EV_READ:0)|(act&CURL_POLL_OUT?EV_WRITE:0)|EV_PERSIST;

  f->sockfd = s;
  f->action = act;
  f->easy = e;
  if (f->evset)
    event_del(&f->ev);
  event_set(&f->ev, f->sockfd, kind, event_cb, g);
  f->evset=1;
  event_add(&f->ev, NULL);
}

/* Initialize a new SockInfo structure. Called in sock_cb. */
static void addsock(curl_socket_t s, CURL *easy, int action, GlobalInfo *g) {
  SockInfo *fdp = calloc(sizeof(SockInfo), 1);

  fdp->global = g;
  setsock(fdp, s, easy, action, g);
  curl_multi_assign(g->multi, s, fdp);
}

/* CURLMOPT_SOCKETFUNCTION This is set in main.*/
static int sock_cb(CURL *e, curl_socket_t s, int what, void *cbp, void *sockp)
{
  GlobalInfo *g = (GlobalInfo*) cbp;
  SockInfo *fdp = (SockInfo*) sockp;
  const char *whatstr[]={ "none", "IN", "OUT", "INOUT", "REMOVE" };

  fprintf(MSG_OUT,
          "socket callback: s=%d e=%p what=%s ", s, e, whatstr[what]);
  if (what == CURL_POLL_REMOVE) {
    fprintf(MSG_OUT, "\n");
    remsock(fdp);
  }
  else {
    if (!fdp) {
      fprintf(MSG_OUT, "Adding data: %s\n", whatstr[what]);
      addsock(s, e, what, g);
    }
    else {
      fprintf(MSG_OUT,
              "Changing action from %s to %s\n",
              whatstr[fdp->action], whatstr[what]);
      setsock(fdp, s, e, what, g);
    }
  }
  return 0;
}

/* CURLOPT_WRITEFUNCTION Called in new_conn */
static size_t write_cb(void *ptr, size_t size, size_t nmemb, void *data)
{
  size_t realsize = size * nmemb;
  ConnInfo *conn = (ConnInfo*) data;
  (void)ptr;
  (void)conn;
  return realsize;
}

/* CURLOPT_PROGRESSFUNCTION Called in new_conn */
static int prog_cb (void *p, double dltotal, double dlnow, double ult,
                    double uln)
{
  ConnInfo *conn = (ConnInfo *)p;
  (void)ult;
  (void)uln;

  fprintf(MSG_OUT, "Progress: %s (%g/%g)\n", conn->url, dlnow, dltotal);
  return 0;
}


/* Create a new easy handle, and add it to the global curl_multi */
static void new_conn(char *url, GlobalInfo *g )
{
  ConnInfo *conn;
  CURLMcode rc;

  conn = calloc(1, sizeof(ConnInfo));
  memset(conn, 0, sizeof(ConnInfo));
  conn->error[0]='\0';

  conn->easy = curl_easy_init();
  if (!conn->easy) {
    fprintf(MSG_OUT, "curl_easy_init() failed, exiting!\n");
    exit(2);
  }
  conn->global = g;
  conn->url = strdup(url);
  curl_easy_setopt(conn->easy, CURLOPT_URL, conn->url);
  curl_easy_setopt(conn->easy, CURLOPT_WRITEFUNCTION, write_cb);
  curl_easy_setopt(conn->easy, CURLOPT_WRITEDATA, &conn);
  curl_easy_setopt(conn->easy, CURLOPT_VERBOSE, 1L);
  curl_easy_setopt(conn->easy, CURLOPT_ERRORBUFFER, conn->error);
  curl_easy_setopt(conn->easy, CURLOPT_PRIVATE, conn);
  curl_easy_setopt(conn->easy, CURLOPT_NOPROGRESS, 0L);
  curl_easy_setopt(conn->easy, CURLOPT_PROGRESSFUNCTION, prog_cb);
  curl_easy_setopt(conn->easy, CURLOPT_PROGRESSDATA, conn);
  fprintf(MSG_OUT,
          "Adding easy %p to multi %p (%s)\n", conn->easy, g->multi, url);
  rc = curl_multi_add_handle(g->multi, conn->easy);
  mcode_or_die("new_conn: curl_multi_add_handle", rc);

  /* From the manual on curl_multi_add_handle
     (http://curl.haxx.se/libcurl/c/curl_multi_add_handle.html):
     "Adds a standard easy handle to the multi stack. This function call will
     make this multi_handle control the specified easy_handle. Furthermore,
     libcurl now *initiates* the connection associated with the specified
     easy_handle."

     But from http://curl.haxx.se/libcurl/c/libcurl-multi.html:
     "Adding the easy handle to the multi handle does not start the transfer.
         Remember that one of the main ideas with this interface is to let your
     application drive. You drive the transfers by invoking
     curl_multi_perform(3)."

     Doesn't this mean that event_cb will be called immediately since
     starting the connection would create a socket action that would be
     detected by libevent? Apparently not. But what am I missing? */
}

int main(int argc, char **argv)
{
  // See: http://curl.haxx.se/mail/lib-2008-10/0332.html
  GlobalInfo g;
  (void)argc;
  (void)argv;

  memset(&g, 0, sizeof(GlobalInfo));

  event_init();

  g.multi = curl_multi_init();

  evtimer_set(&g.timer_event, timer_cb, &g);

  curl_multi_setopt(g.multi, CURLMOPT_SOCKETFUNCTION, sock_cb);
  curl_multi_setopt(g.multi, CURLMOPT_SOCKETDATA, &g);
  curl_multi_setopt(g.multi, CURLMOPT_TIMERFUNCTION, multi_timer_cb);
  curl_multi_setopt(g.multi, CURLMOPT_TIMERDATA, &g);

  /* I'd like to be able to call new_conn from within my code instead of
     invoking it through the fifo. I'm expecting this to start the transfer.
     But instead main returns immediately. */
  new_conn("http://www.yahoo.com", &g);

  curl_multi_cleanup(g.multi);

  return 0;
}

-------------------------------------------------------------------
List admin: http://cool.haxx.se/list/listinfo/curl-library
Etiquette: http://curl.haxx.se/mail/etiquette.html
Received on 2009-10-19