1 /*****************************************************************************
3 * Project ___| | | | _ \| |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
9 * Example application source code using the multi socket interface to
10 * download many files at once.
12 * Written by Jeff Pohlmeyer
14 Requires libevent and a (POSIX?) system that has mkfifo().
16 This is an adaptation of libcurl's "hipev.c" and libevent's "event-test.c"
19 When running, the program creates the named pipe "hiper.fifo"
21 Whenever there is input into the fifo, the program reads the input as a list
22 of URL's and creates some new easy handles to fetch each URL via the
23 curl_multi "hiper" API.
26 Thus, you can try a single URL:
27 % echo http://www.yahoo.com > hiper.fifo
29 Or a whole bunch of them:
30 % cat my-url-list > hiper.fifo
32 The fifo buffer is handled almost instantly, so you can even add more URL's
33 while the previous requests are still being downloaded.
36 For the sake of simplicity, URL length is limited to 1023 char's !
38 This is purely a demo app, all retrieved data is simply discarded by the write
50 #include <curl/curl.h>
57 #define MSG_OUT stdout /* Send info to stdout, change to stderr if you want */
60 /* Global information, common to all connections */
61 typedef struct _GlobalInfo {
62 struct event fifo_event;
63 struct event timer_event;
70 /* Information associated with a specific easy handle */
71 typedef struct _ConnInfo {
75 char error[CURL_ERROR_SIZE];
79 /* Information associated with a specific socket */
80 typedef struct _SockInfo {
92 /* Update the event timer after curl_multi library calls */
93 static int multi_timer_cb(CURLM *multi, long timeout_ms, GlobalInfo *g)
95 struct timeval timeout;
96 (void)multi; /* unused */
98 timeout.tv_sec = timeout_ms/1000;
99 timeout.tv_usec = (timeout_ms%1000)*1000;
100 fprintf(MSG_OUT, "multi_timer_cb: Setting timeout to %ld ms\n", timeout_ms);
101 evtimer_add(&g->timer_event, &timeout);
105 /* Die if we get a bad CURLMcode somewhere */
106 static void mcode_or_die(const char *where, CURLMcode code)
108 if ( CURLM_OK != code ) {
111 case CURLM_CALL_MULTI_PERFORM: s="CURLM_CALL_MULTI_PERFORM"; break;
112 case CURLM_BAD_HANDLE: s="CURLM_BAD_HANDLE"; break;
113 case CURLM_BAD_EASY_HANDLE: s="CURLM_BAD_EASY_HANDLE"; break;
114 case CURLM_OUT_OF_MEMORY: s="CURLM_OUT_OF_MEMORY"; break;
115 case CURLM_INTERNAL_ERROR: s="CURLM_INTERNAL_ERROR"; break;
116 case CURLM_UNKNOWN_OPTION: s="CURLM_UNKNOWN_OPTION"; break;
117 case CURLM_LAST: s="CURLM_LAST"; break;
118 default: s="CURLM_unknown";
120 case CURLM_BAD_SOCKET: s="CURLM_BAD_SOCKET";
121 fprintf(MSG_OUT, "ERROR: %s returns %s\n", where, s);
122 /* ignore this error */
125 fprintf(MSG_OUT, "ERROR: %s returns %s\n", where, s);
132 /* Check for completed transfers, and remove their easy handles */
133 static void check_multi_info(GlobalInfo *g)
142 fprintf(MSG_OUT, "REMAINING: %d\n", g->still_running);
143 while ((msg = curl_multi_info_read(g->multi, &msgs_left))) {
144 if (msg->msg == CURLMSG_DONE) {
145 easy = msg->easy_handle;
146 res = msg->data.result;
147 curl_easy_getinfo(easy, CURLINFO_PRIVATE, &conn);
148 curl_easy_getinfo(easy, CURLINFO_EFFECTIVE_URL, &eff_url);
149 fprintf(MSG_OUT, "DONE: %s => (%d) %s\n", eff_url, res, conn->error);
150 curl_multi_remove_handle(g->multi, easy);
152 curl_easy_cleanup(easy);
160 /* Called by libevent when we get action on a multi socket */
161 static void event_cb(int fd, short kind, void *userp)
163 GlobalInfo *g = (GlobalInfo*) userp;
167 (kind & EV_READ ? CURL_CSELECT_IN : 0) |
168 (kind & EV_WRITE ? CURL_CSELECT_OUT : 0);
170 rc = curl_multi_socket_action(g->multi, fd, action, &g->still_running);
171 mcode_or_die("event_cb: curl_multi_socket_action", rc);
174 if ( g->still_running <= 0 ) {
175 fprintf(MSG_OUT, "last transfer done, kill timeout\n");
176 if (evtimer_pending(&g->timer_event, NULL)) {
177 evtimer_del(&g->timer_event);
184 /* Called by libevent when our timeout expires */
185 static void timer_cb(int fd, short kind, void *userp)
187 GlobalInfo *g = (GlobalInfo *)userp;
192 rc = curl_multi_socket_action(g->multi,
193 CURL_SOCKET_TIMEOUT, 0, &g->still_running);
194 mcode_or_die("timer_cb: curl_multi_socket_action", rc);
200 /* Clean up the SockInfo structure */
201 static void remsock(SockInfo *f)
212 /* Assign information to a SockInfo structure */
213 static void setsock(SockInfo*f, curl_socket_t s, CURL*e, int act, GlobalInfo*g)
216 (act&CURL_POLL_IN?EV_READ:0)|(act&CURL_POLL_OUT?EV_WRITE:0)|EV_PERSIST;
223 event_set(&f->ev, f->sockfd, kind, event_cb, g);
225 event_add(&f->ev, NULL);
230 /* Initialize a new SockInfo structure */
231 static void addsock(curl_socket_t s, CURL *easy, int action, GlobalInfo *g) {
232 SockInfo *fdp = calloc(sizeof(SockInfo), 1);
235 setsock(fdp, s, easy, action, g);
236 curl_multi_assign(g->multi, s, fdp);
239 /* CURLMOPT_SOCKETFUNCTION */
240 static int sock_cb(CURL *e, curl_socket_t s, int what, void *cbp, void *sockp)
242 GlobalInfo *g = (GlobalInfo*) cbp;
243 SockInfo *fdp = (SockInfo*) sockp;
244 const char *whatstr[]={ "none", "IN", "OUT", "INOUT", "REMOVE" };
247 "socket callback: s=%d e=%p what=%s ", s, e, whatstr[what]);
248 if (what == CURL_POLL_REMOVE) {
249 fprintf(MSG_OUT, "\n");
254 fprintf(MSG_OUT, "Adding data: %s\n", whatstr[what]);
255 addsock(s, e, what, g);
259 "Changing action from %s to %s\n",
260 whatstr[fdp->action], whatstr[what]);
261 setsock(fdp, s, e, what, g);
269 /* CURLOPT_WRITEFUNCTION */
270 static size_t write_cb(void *ptr, size_t size, size_t nmemb, void *data)
272 size_t realsize = size * nmemb;
273 ConnInfo *conn = (ConnInfo*) data;
280 /* CURLOPT_PROGRESSFUNCTION */
281 static int prog_cb (void *p, double dltotal, double dlnow, double ult,
284 ConnInfo *conn = (ConnInfo *)p;
288 fprintf(MSG_OUT, "Progress: %s (%g/%g)\n", conn->url, dlnow, dltotal);
293 /* Create a new easy handle, and add it to the global curl_multi */
294 static void new_conn(char *url, GlobalInfo *g )
299 conn = calloc(1, sizeof(ConnInfo));
300 memset(conn, 0, sizeof(ConnInfo));
303 conn->easy = curl_easy_init();
305 fprintf(MSG_OUT, "curl_easy_init() failed, exiting!\n");
309 conn->url = strdup(url);
310 curl_easy_setopt(conn->easy, CURLOPT_URL, conn->url);
311 curl_easy_setopt(conn->easy, CURLOPT_WRITEFUNCTION, write_cb);
312 curl_easy_setopt(conn->easy, CURLOPT_WRITEDATA, &conn);
313 curl_easy_setopt(conn->easy, CURLOPT_VERBOSE, 1L);
314 curl_easy_setopt(conn->easy, CURLOPT_ERRORBUFFER, conn->error);
315 curl_easy_setopt(conn->easy, CURLOPT_PRIVATE, conn);
316 curl_easy_setopt(conn->easy, CURLOPT_NOPROGRESS, 0L);
317 curl_easy_setopt(conn->easy, CURLOPT_PROGRESSFUNCTION, prog_cb);
318 curl_easy_setopt(conn->easy, CURLOPT_PROGRESSDATA, conn);
320 "Adding easy %p to multi %p (%s)\n", conn->easy, g->multi, url);
321 rc = curl_multi_add_handle(g->multi, conn->easy);
322 mcode_or_die("new_conn: curl_multi_add_handle", rc);
324 /* note that the add_handle() will set a time-out to trigger very soon so
325 that the necessary socket_action() call will be called by this app */
328 /* This gets called whenever data is received from the fifo */
329 static void fifo_cb(int fd, short event, void *arg)
334 GlobalInfo *g = (GlobalInfo *)arg;
335 (void)fd; /* unused */
336 (void)event; /* unused */
340 rv=fscanf(g->input, "%1023s%n", s, &n);
343 new_conn(s,arg); /* if we read a URL, go get it! */
345 } while ( rv != EOF);
348 /* Create a named pipe and tell libevent to monitor it */
349 static int init_fifo (GlobalInfo *g)
352 static const char *fifo = "hiper.fifo";
355 fprintf(MSG_OUT, "Creating named pipe \"%s\"\n", fifo);
356 if (lstat (fifo, &st) == 0) {
357 if ((st.st_mode & S_IFMT) == S_IFREG) {
364 if (mkfifo (fifo, 0600) == -1) {
368 sockfd = open(fifo, O_RDWR | O_NONBLOCK, 0);
373 g->input = fdopen(sockfd, "r");
375 fprintf(MSG_OUT, "Now, pipe some URL's into > %s\n", fifo);
376 event_set(&g->fifo_event, sockfd, EV_READ | EV_PERSIST, fifo_cb, g);
377 event_add(&g->fifo_event, NULL);
381 int main(int argc, char **argv)
387 memset(&g, 0, sizeof(GlobalInfo));
390 g.multi = curl_multi_init();
391 evtimer_set(&g.timer_event, timer_cb, &g);
393 /* setup the generic multi interface options we want */
394 curl_multi_setopt(g.multi, CURLMOPT_SOCKETFUNCTION, sock_cb);
395 curl_multi_setopt(g.multi, CURLMOPT_SOCKETDATA, &g);
396 curl_multi_setopt(g.multi, CURLMOPT_TIMERFUNCTION, multi_timer_cb);
397 curl_multi_setopt(g.multi, CURLMOPT_TIMERDATA, &g);
399 /* we don't call any curl_multi_socket*() function yet as we have no handles
403 curl_multi_cleanup(g.multi);