1 /***************************************************************************
3 * Project ___| | | | _ \| |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
8 * Copyright (C) 1998 - 2015, Daniel Stenberg, <daniel@haxx.se>, et al.
10 * This software is licensed as described in the file COPYING, which
11 * you should have received as part of this distribution. The terms
12 * are also available at http://curl.haxx.se/docs/copyright.html.
14 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15 * copies of the Software, and permit persons to whom the Software is
16 * furnished to do so, under the terms of the COPYING file.
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
21 ***************************************************************************/
23 * multi socket interface together with libev
26 /* Example application source code using the multi socket interface to
27 * download many files at once.
29 * This example features the same basic functionality as hiperfifo.c does,
30 * but this uses libev instead of libevent.
32 * Written by Jeff Pohlmeyer, converted to use libev by Markus Koetter
34 Requires libev and a (POSIX?) system that has mkfifo().
36 This is an adaptation of libcurl's "hipev.c" and libevent's "event-test.c"
39 When running, the program creates the named pipe "hiper.fifo"
41 Whenever there is input into the fifo, the program reads the input as a list
42 of URL's and creates some new easy handles to fetch each URL via the
43 curl_multi "hiper" API.
46 Thus, you can try a single URL:
47 % echo http://www.yahoo.com > hiper.fifo
49 Or a whole bunch of them:
50 % cat my-url-list > hiper.fifo
52 The fifo buffer is handled almost instantly, so you can even add more URL's
53 while the previous requests are still being downloaded.
56 For the sake of simplicity, URL length is limited to 1023 char's !
58 This is purely a demo app, all retrieved data is simply discarded by the write
70 #include <curl/curl.h>
76 #define DPRINT(x...) printf(x)
78 #define MSG_OUT stdout /* Send info to stdout, change to stderr if you want */
81 /* Global information, common to all connections */
82 typedef struct _GlobalInfo
85 struct ev_io fifo_event;
86 struct ev_timer timer_event;
93 /* Information associated with a specific easy handle */
94 typedef struct _ConnInfo
99 char error[CURL_ERROR_SIZE];
103 /* Information associated with a specific socket */
104 typedef struct _SockInfo
106 curl_socket_t sockfd;
115 static void timer_cb(EV_P_ struct ev_timer *w, int revents);
117 /* Update the event timer after curl_multi library calls */
118 static int multi_timer_cb(CURLM *multi, long timeout_ms, GlobalInfo *g)
120 DPRINT("%s %li\n", __PRETTY_FUNCTION__, timeout_ms);
121 ev_timer_stop(g->loop, &g->timer_event);
124 double t = timeout_ms / 1000;
125 ev_timer_init(&g->timer_event, timer_cb, t, 0.);
126 ev_timer_start(g->loop, &g->timer_event);
128 timer_cb(g->loop, &g->timer_event, 0);
132 /* Die if we get a bad CURLMcode somewhere */
133 static void mcode_or_die(const char *where, CURLMcode code)
135 if ( CURLM_OK != code )
140 case CURLM_BAD_HANDLE: s="CURLM_BAD_HANDLE"; break;
141 case CURLM_BAD_EASY_HANDLE: s="CURLM_BAD_EASY_HANDLE"; break;
142 case CURLM_OUT_OF_MEMORY: s="CURLM_OUT_OF_MEMORY"; break;
143 case CURLM_INTERNAL_ERROR: s="CURLM_INTERNAL_ERROR"; break;
144 case CURLM_UNKNOWN_OPTION: s="CURLM_UNKNOWN_OPTION"; break;
145 case CURLM_LAST: s="CURLM_LAST"; break;
146 default: s="CURLM_unknown";
148 case CURLM_BAD_SOCKET: s="CURLM_BAD_SOCKET";
149 fprintf(MSG_OUT, "ERROR: %s returns %s\n", where, s);
150 /* ignore this error */
153 fprintf(MSG_OUT, "ERROR: %s returns %s\n", where, s);
160 /* Check for completed transfers, and remove their easy handles */
161 static void check_multi_info(GlobalInfo *g)
170 fprintf(MSG_OUT, "REMAINING: %d\n", g->still_running);
171 while ((msg = curl_multi_info_read(g->multi, &msgs_left))) {
172 if (msg->msg == CURLMSG_DONE) {
173 easy = msg->easy_handle;
174 res = msg->data.result;
175 curl_easy_getinfo(easy, CURLINFO_PRIVATE, &conn);
176 curl_easy_getinfo(easy, CURLINFO_EFFECTIVE_URL, &eff_url);
177 fprintf(MSG_OUT, "DONE: %s => (%d) %s\n", eff_url, res, conn->error);
178 curl_multi_remove_handle(g->multi, easy);
180 curl_easy_cleanup(easy);
188 /* Called by libevent when we get action on a multi socket */
189 static void event_cb(EV_P_ struct ev_io *w, int revents)
191 DPRINT("%s w %p revents %i\n", __PRETTY_FUNCTION__, w, revents);
192 GlobalInfo *g = (GlobalInfo*) w->data;
195 int action = (revents&EV_READ?CURL_POLL_IN:0)|
196 (revents&EV_WRITE?CURL_POLL_OUT:0);
197 rc = curl_multi_socket_action(g->multi, w->fd, action, &g->still_running);
198 mcode_or_die("event_cb: curl_multi_socket_action", rc);
200 if ( g->still_running <= 0 )
202 fprintf(MSG_OUT, "last transfer done, kill timeout\n");
203 ev_timer_stop(g->loop, &g->timer_event);
207 /* Called by libevent when our timeout expires */
208 static void timer_cb(EV_P_ struct ev_timer *w, int revents)
210 DPRINT("%s w %p revents %i\n", __PRETTY_FUNCTION__, w, revents);
212 GlobalInfo *g = (GlobalInfo *)w->data;
215 rc = curl_multi_socket_action(g->multi, CURL_SOCKET_TIMEOUT, 0, &g->still_running);
216 mcode_or_die("timer_cb: curl_multi_socket_action", rc);
220 /* Clean up the SockInfo structure */
221 static void remsock(SockInfo *f, GlobalInfo *g)
223 printf("%s \n", __PRETTY_FUNCTION__);
227 ev_io_stop(g->loop, &f->ev);
234 /* Assign information to a SockInfo structure */
235 static void setsock(SockInfo*f, curl_socket_t s, CURL*e, int act, GlobalInfo*g)
237 printf("%s \n", __PRETTY_FUNCTION__);
239 int kind = (act&CURL_POLL_IN?EV_READ:0)|(act&CURL_POLL_OUT?EV_WRITE:0);
245 ev_io_stop(g->loop, &f->ev);
246 ev_io_init(&f->ev, event_cb, f->sockfd, kind);
249 ev_io_start(g->loop, &f->ev);
254 /* Initialize a new SockInfo structure */
255 static void addsock(curl_socket_t s, CURL *easy, int action, GlobalInfo *g)
257 SockInfo *fdp = calloc(sizeof(SockInfo), 1);
260 setsock(fdp, s, easy, action, g);
261 curl_multi_assign(g->multi, s, fdp);
264 /* CURLMOPT_SOCKETFUNCTION */
265 static int sock_cb(CURL *e, curl_socket_t s, int what, void *cbp, void *sockp)
267 DPRINT("%s e %p s %i what %i cbp %p sockp %p\n",
268 __PRETTY_FUNCTION__, e, s, what, cbp, sockp);
270 GlobalInfo *g = (GlobalInfo*) cbp;
271 SockInfo *fdp = (SockInfo*) sockp;
272 const char *whatstr[]={ "none", "IN", "OUT", "INOUT", "REMOVE"};
275 "socket callback: s=%d e=%p what=%s ", s, e, whatstr[what]);
276 if ( what == CURL_POLL_REMOVE )
278 fprintf(MSG_OUT, "\n");
284 fprintf(MSG_OUT, "Adding data: %s\n", whatstr[what]);
285 addsock(s, e, what, g);
289 "Changing action from %s to %s\n",
290 whatstr[fdp->action], whatstr[what]);
291 setsock(fdp, s, e, what, g);
298 /* CURLOPT_WRITEFUNCTION */
299 static size_t write_cb(void *ptr, size_t size, size_t nmemb, void *data)
301 size_t realsize = size * nmemb;
302 ConnInfo *conn = (ConnInfo*) data;
309 /* CURLOPT_PROGRESSFUNCTION */
310 static int prog_cb (void *p, double dltotal, double dlnow, double ult,
313 ConnInfo *conn = (ConnInfo *)p;
317 fprintf(MSG_OUT, "Progress: %s (%g/%g)\n", conn->url, dlnow, dltotal);
322 /* Create a new easy handle, and add it to the global curl_multi */
323 static void new_conn(char *url, GlobalInfo *g )
328 conn = calloc(1, sizeof(ConnInfo));
329 memset(conn, 0, sizeof(ConnInfo));
332 conn->easy = curl_easy_init();
335 fprintf(MSG_OUT, "curl_easy_init() failed, exiting!\n");
339 conn->url = strdup(url);
340 curl_easy_setopt(conn->easy, CURLOPT_URL, conn->url);
341 curl_easy_setopt(conn->easy, CURLOPT_WRITEFUNCTION, write_cb);
342 curl_easy_setopt(conn->easy, CURLOPT_WRITEDATA, conn);
343 curl_easy_setopt(conn->easy, CURLOPT_VERBOSE, 1L);
344 curl_easy_setopt(conn->easy, CURLOPT_ERRORBUFFER, conn->error);
345 curl_easy_setopt(conn->easy, CURLOPT_PRIVATE, conn);
346 curl_easy_setopt(conn->easy, CURLOPT_NOPROGRESS, 0L);
347 curl_easy_setopt(conn->easy, CURLOPT_PROGRESSFUNCTION, prog_cb);
348 curl_easy_setopt(conn->easy, CURLOPT_PROGRESSDATA, conn);
349 curl_easy_setopt(conn->easy, CURLOPT_LOW_SPEED_TIME, 3L);
350 curl_easy_setopt(conn->easy, CURLOPT_LOW_SPEED_LIMIT, 10L);
353 "Adding easy %p to multi %p (%s)\n", conn->easy, g->multi, url);
354 rc = curl_multi_add_handle(g->multi, conn->easy);
355 mcode_or_die("new_conn: curl_multi_add_handle", rc);
357 /* note that the add_handle() will set a time-out to trigger very soon so
358 that the necessary socket_action() call will be called by this app */
361 /* This gets called whenever data is received from the fifo */
362 static void fifo_cb(EV_P_ struct ev_io *w, int revents)
367 GlobalInfo *g = (GlobalInfo *)w->data;
372 rv=fscanf(g->input, "%1023s%n", s, &n);
376 new_conn(s,g); /* if we read a URL, go get it! */
378 } while ( rv != EOF );
381 /* Create a named pipe and tell libevent to monitor it */
382 static int init_fifo (GlobalInfo *g)
385 static const char *fifo = "hiper.fifo";
386 curl_socket_t sockfd;
388 fprintf(MSG_OUT, "Creating named pipe \"%s\"\n", fifo);
389 if ( lstat (fifo, &st) == 0 )
391 if ( (st.st_mode & S_IFMT) == S_IFREG )
399 if ( mkfifo (fifo, 0600) == -1 )
404 sockfd = open(fifo, O_RDWR | O_NONBLOCK, 0);
410 g->input = fdopen(sockfd, "r");
412 fprintf(MSG_OUT, "Now, pipe some URL's into > %s\n", fifo);
413 ev_io_init(&g->fifo_event, fifo_cb, sockfd, EV_READ);
414 ev_io_start(g->loop, &g->fifo_event);
418 int main(int argc, char **argv)
425 memset(&g, 0, sizeof(GlobalInfo));
426 g.loop = ev_default_loop(0);
429 g.multi = curl_multi_init();
431 ev_timer_init(&g.timer_event, timer_cb, 0., 0.);
432 g.timer_event.data = &g;
433 g.fifo_event.data = &g;
434 curl_multi_setopt(g.multi, CURLMOPT_SOCKETFUNCTION, sock_cb);
435 curl_multi_setopt(g.multi, CURLMOPT_SOCKETDATA, &g);
436 curl_multi_setopt(g.multi, CURLMOPT_TIMERFUNCTION, multi_timer_cb);
437 curl_multi_setopt(g.multi, CURLMOPT_TIMERDATA, &g);
439 /* we don't call any curl_multi_socket*() function yet as we have no handles
443 curl_multi_cleanup(g.multi);