1 /*****************************************************************************
3 * Project ___| | | | _ \| |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
8 * $Id: hiperfifo.c,v 1.7 2008-05-22 21:20:09 danf Exp $
10 * Example application source code using the multi socket interface to
11 * download many files at once.
13 * Written by Jeff Pohlmeyer
15 Requires libevent and a (POSIX?) system that has mkfifo().
17 This is an adaptation of libcurl's "hipev.c" and libevent's "event-test.c"
20 When running, the program creates the named pipe "hiper.fifo"
22 Whenever there is input into the fifo, the program reads the input as a list
23 of URL's and creates some new easy handles to fetch each URL via the
24 curl_multi "hiper" API.
27 Thus, you can try a single URL:
28 % echo http://www.yahoo.com > hiper.fifo
30 Or a whole bunch of them:
31 % cat my-url-list > hiper.fifo
33 The fifo buffer is handled almost instantly, so you can even add more URL's
34 while the previous requests are still being downloaded.
37 For the sake of simplicity, URL length is limited to 1023 char's !
39 This is purely a demo app, all retrieved data is simply discarded by the write
51 #include <curl/curl.h>
58 #define MSG_OUT stdout /* Send info to stdout, change to stderr if you want */
61 /* Global information, common to all connections */
62 typedef struct _GlobalInfo
{
63 struct event fifo_event
;
64 struct event timer_event
;
72 /* Information associated with a specific easy handle */
73 typedef struct _ConnInfo
{
77 char error
[CURL_ERROR_SIZE
];
81 /* Information associated with a specific socket */
82 typedef struct _SockInfo
{
94 /* Update the event timer after curl_multi library calls */
95 static int multi_timer_cb(CURLM
*multi
, long timeout_ms
, GlobalInfo
*g
)
97 struct timeval timeout
;
98 (void)multi
; /* unused */
100 timeout
.tv_sec
= timeout_ms
/1000;
101 timeout
.tv_usec
= (timeout_ms
%1000)*1000;
102 fprintf(MSG_OUT
, "multi_timer_cb: Setting timeout to %ld ms\n", timeout_ms
);
103 evtimer_add(&g
->timer_event
, &timeout
);
107 /* Die if we get a bad CURLMcode somewhere */
108 static void mcode_or_die(const char *where
, CURLMcode code
)
110 if ( CURLM_OK
!= code
) {
113 case CURLM_CALL_MULTI_PERFORM
: s
="CURLM_CALL_MULTI_PERFORM"; break;
114 case CURLM_OK
: s
="CURLM_OK"; break;
115 case CURLM_BAD_HANDLE
: s
="CURLM_BAD_HANDLE"; break;
116 case CURLM_BAD_EASY_HANDLE
: s
="CURLM_BAD_EASY_HANDLE"; break;
117 case CURLM_OUT_OF_MEMORY
: s
="CURLM_OUT_OF_MEMORY"; break;
118 case CURLM_INTERNAL_ERROR
: s
="CURLM_INTERNAL_ERROR"; break;
119 case CURLM_UNKNOWN_OPTION
: s
="CURLM_UNKNOWN_OPTION"; break;
120 case CURLM_LAST
: s
="CURLM_LAST"; break;
121 default: s
="CURLM_unknown";
123 case CURLM_BAD_SOCKET
: s
="CURLM_BAD_SOCKET";
124 fprintf(MSG_OUT
, "ERROR: %s returns %s\n", where
, s
);
125 /* ignore this error */
128 fprintf(MSG_OUT
, "ERROR: %s returns %s\n", where
, s
);
135 /* Check for completed transfers, and remove their easy handles */
136 static void check_run_count(GlobalInfo
*g
)
138 if (g
->prev_running
> g
->still_running
) {
146 fprintf(MSG_OUT
, "REMAINING: %d\n", g
->still_running
);
148 I am still uncertain whether it is safe to remove an easy handle
149 from inside the curl_multi_info_read loop, so here I will search
150 for completed transfers in the inner "while" loop, and then remove
151 them in the outer "do-while" loop...
155 while ((msg
= curl_multi_info_read(g
->multi
, &msgs_left
))) {
156 if (msg
->msg
== CURLMSG_DONE
) {
157 easy
=msg
->easy_handle
;
158 res
=msg
->data
.result
;
163 curl_easy_getinfo(easy
, CURLINFO_PRIVATE
, &conn
);
164 curl_easy_getinfo(easy
, CURLINFO_EFFECTIVE_URL
, &eff_url
);
165 fprintf(MSG_OUT
, "DONE: %s => (%d) %s\n", eff_url
, res
, conn
->error
);
166 curl_multi_remove_handle(g
->multi
, easy
);
168 curl_easy_cleanup(easy
);
173 g
->prev_running
= g
->still_running
;
178 /* Called by libevent when we get action on a multi socket */
179 static void event_cb(int fd
, short kind
, void *userp
)
181 GlobalInfo
*g
= (GlobalInfo
*) userp
;
183 (void)kind
; /* unused */
186 rc
= curl_multi_socket(g
->multi
, fd
, &g
->still_running
);
187 } while (rc
== CURLM_CALL_MULTI_PERFORM
);
188 mcode_or_die("event_cb: curl_multi_socket", rc
);
190 if ( g
->still_running
<= 0 ) {
191 fprintf(MSG_OUT
, "last transfer done, kill timeout\n");
192 if (evtimer_pending(&g
->timer_event
, NULL
)) {
193 evtimer_del(&g
->timer_event
);
200 /* Called by libevent when our timeout expires */
201 static void timer_cb(int fd
, short kind
, void *userp
)
203 GlobalInfo
*g
= (GlobalInfo
*)userp
;
209 rc
= curl_multi_socket(g
->multi
, CURL_SOCKET_TIMEOUT
, &g
->still_running
);
210 } while (rc
== CURLM_CALL_MULTI_PERFORM
);
211 mcode_or_die("timer_cb: curl_multi_socket", rc
);
217 /* Clean up the SockInfo structure */
218 static void remsock(SockInfo
*f
)
229 /* Assign information to a SockInfo structure */
230 static void setsock(SockInfo
*f
, curl_socket_t s
, CURL
*e
, int act
, GlobalInfo
*g
)
233 (act
&CURL_POLL_IN
?EV_READ
:0)|(act
&CURL_POLL_OUT
?EV_WRITE
:0)|EV_PERSIST
;
240 event_set(&f
->ev
, f
->sockfd
, kind
, event_cb
, g
);
242 event_add(&f
->ev
, NULL
);
247 /* Initialize a new SockInfo structure */
248 static void addsock(curl_socket_t s
, CURL
*easy
, int action
, GlobalInfo
*g
) {
249 SockInfo
*fdp
= calloc(sizeof(SockInfo
), 1);
252 setsock(fdp
, s
, easy
, action
, g
);
253 curl_multi_assign(g
->multi
, s
, fdp
);
256 /* CURLMOPT_SOCKETFUNCTION */
257 static int sock_cb(CURL
*e
, curl_socket_t s
, int what
, void *cbp
, void *sockp
)
259 GlobalInfo
*g
= (GlobalInfo
*) cbp
;
260 SockInfo
*fdp
= (SockInfo
*) sockp
;
261 const char *whatstr
[]={ "none", "IN", "OUT", "INOUT", "REMOVE" };
264 "socket callback: s=%d e=%p what=%s ", s
, e
, whatstr
[what
]);
265 if (what
== CURL_POLL_REMOVE
) {
266 fprintf(MSG_OUT
, "\n");
271 fprintf(MSG_OUT
, "Adding data: %s\n", whatstr
[what
]);
272 addsock(s
, e
, what
, g
);
276 "Changing action from %s to %s\n",
277 whatstr
[fdp
->action
], whatstr
[what
]);
278 setsock(fdp
, s
, e
, what
, g
);
286 /* CURLOPT_WRITEFUNCTION */
287 static size_t write_cb(void *ptr
, size_t size
, size_t nmemb
, void *data
)
289 size_t realsize
= size
* nmemb
;
290 ConnInfo
*conn
= (ConnInfo
*) data
;
297 /* CURLOPT_PROGRESSFUNCTION */
298 static int prog_cb (void *p
, double dltotal
, double dlnow
, double ult
,
301 ConnInfo
*conn
= (ConnInfo
*)p
;
305 fprintf(MSG_OUT
, "Progress: %s (%g/%g)\n", conn
->url
, dlnow
, dltotal
);
310 /* Create a new easy handle, and add it to the global curl_multi */
311 static void new_conn(char *url
, GlobalInfo
*g
)
316 conn
= calloc(1, sizeof(ConnInfo
));
317 memset(conn
, 0, sizeof(ConnInfo
));
320 conn
->easy
= curl_easy_init();
322 fprintf(MSG_OUT
, "curl_easy_init() failed, exiting!\n");
326 conn
->url
= strdup(url
);
327 curl_easy_setopt(conn
->easy
, CURLOPT_URL
, conn
->url
);
328 curl_easy_setopt(conn
->easy
, CURLOPT_WRITEFUNCTION
, write_cb
);
329 curl_easy_setopt(conn
->easy
, CURLOPT_WRITEDATA
, &conn
);
330 curl_easy_setopt(conn
->easy
, CURLOPT_VERBOSE
, 1L);
331 curl_easy_setopt(conn
->easy
, CURLOPT_ERRORBUFFER
, conn
->error
);
332 curl_easy_setopt(conn
->easy
, CURLOPT_PRIVATE
, conn
);
333 curl_easy_setopt(conn
->easy
, CURLOPT_NOPROGRESS
, 0L);
334 curl_easy_setopt(conn
->easy
, CURLOPT_PROGRESSFUNCTION
, prog_cb
);
335 curl_easy_setopt(conn
->easy
, CURLOPT_PROGRESSDATA
, conn
);
337 "Adding easy %p to multi %p (%s)\n", conn
->easy
, g
->multi
, url
);
338 rc
=curl_multi_add_handle(g
->multi
, conn
->easy
);
339 mcode_or_die("new_conn: curl_multi_add_handle", rc
);
341 rc
= curl_multi_socket_all(g
->multi
, &g
->still_running
);
342 } while (CURLM_CALL_MULTI_PERFORM
== rc
);
343 mcode_or_die("new_conn: curl_multi_socket_all", rc
);
347 /* This gets called whenever data is received from the fifo */
348 static void fifo_cb(int fd
, short event
, void *arg
)
353 GlobalInfo
*g
= (GlobalInfo
*)arg
;
354 (void)fd
; /* unused */
355 (void)event
; /* unused */
359 rv
=fscanf(g
->input
, "%1023s%n", s
, &n
);
362 new_conn(s
,arg
); /* if we read a URL, go get it! */
364 } while ( rv
!= EOF
);
367 /* Create a named pipe and tell libevent to monitor it */
368 static int init_fifo (GlobalInfo
*g
)
371 static const char *fifo
= "hiper.fifo";
374 fprintf(MSG_OUT
, "Creating named pipe \"%s\"\n", fifo
);
375 if (lstat (fifo
, &st
) == 0) {
376 if ((st
.st_mode
& S_IFMT
) == S_IFREG
) {
383 if (mkfifo (fifo
, 0600) == -1) {
387 sockfd
= open(fifo
, O_RDWR
| O_NONBLOCK
, 0);
392 g
->input
= fdopen(sockfd
, "r");
394 fprintf(MSG_OUT
, "Now, pipe some URL's into > %s\n", fifo
);
395 event_set(&g
->fifo_event
, sockfd
, EV_READ
| EV_PERSIST
, fifo_cb
, g
);
396 event_add(&g
->fifo_event
, NULL
);
400 int main(int argc
, char **argv
)
407 memset(&g
, 0, sizeof(GlobalInfo
));
410 g
.multi
= curl_multi_init();
411 evtimer_set(&g
.timer_event
, timer_cb
, &g
);
412 curl_multi_setopt(g
.multi
, CURLMOPT_SOCKETFUNCTION
, sock_cb
);
413 curl_multi_setopt(g
.multi
, CURLMOPT_SOCKETDATA
, &g
);
414 curl_multi_setopt(g
.multi
, CURLMOPT_TIMERFUNCTION
, multi_timer_cb
);
415 curl_multi_setopt(g
.multi
, CURLMOPT_TIMERDATA
, &g
);
417 rc
= curl_multi_socket_all(g
.multi
, &g
.still_running
);
418 } while (CURLM_CALL_MULTI_PERFORM
== rc
);
420 curl_multi_cleanup(g
.multi
);