1 /***************************************************************************
3 * Project ___| | | | _ \| |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
8 * Copyright (C) 1998 - 2016, 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 https://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 API usage with libevent 2
26 /* Example application source code using the multi socket interface to
27 download many files at once.
29 Written by Jeff Pohlmeyer
31 Requires libevent version 2 and a (POSIX?) system that has mkfifo().
33 This is an adaptation of libcurl's "hipev.c" and libevent's "event-test.c"
36 When running, the program creates the named pipe "hiper.fifo"
38 Whenever there is input into the fifo, the program reads the input as a list
39 of URL's and creates some new easy handles to fetch each URL via the
40 curl_multi "hiper" API.
43 Thus, you can try a single URL:
44 % echo http://www.yahoo.com > hiper.fifo
46 Or a whole bunch of them:
47 % cat my-url-list > hiper.fifo
49 The fifo buffer is handled almost instantly, so you can even add more URL's
50 while the previous requests are still being downloaded.
53 For the sake of simplicity, URL length is limited to 1023 char's !
55 This is purely a demo app, all retrieved data is simply discarded by the write
67 #include <curl/curl.h>
68 #include <event2/event.h>
74 #define MSG_OUT stdout /* Send info to stdout, change to stderr if you want */
77 /* Global information, common to all connections */
78 typedef struct _GlobalInfo
80 struct event_base *evbase;
81 struct event *fifo_event;
82 struct event *timer_event;
89 /* Information associated with a specific easy handle */
90 typedef struct _ConnInfo
95 char error[CURL_ERROR_SIZE];
99 /* Information associated with a specific socket */
100 typedef struct _SockInfo
102 curl_socket_t sockfd;
113 /* Update the event timer after curl_multi library calls */
114 static int multi_timer_cb(CURLM *multi, long timeout_ms, GlobalInfo *g)
116 struct timeval timeout;
117 (void)multi; /* unused */
119 timeout.tv_sec = timeout_ms/1000;
120 timeout.tv_usec = (timeout_ms%1000)*1000;
121 fprintf(MSG_OUT, "multi_timer_cb: Setting timeout to %ld ms\n", timeout_ms);
122 evtimer_add(g->timer_event, &timeout);
126 /* Die if we get a bad CURLMcode somewhere */
127 static void mcode_or_die(const char *where, CURLMcode code)
129 if(CURLM_OK != code) {
132 case CURLM_BAD_HANDLE: s="CURLM_BAD_HANDLE"; break;
133 case CURLM_BAD_EASY_HANDLE: s="CURLM_BAD_EASY_HANDLE"; break;
134 case CURLM_OUT_OF_MEMORY: s="CURLM_OUT_OF_MEMORY"; break;
135 case CURLM_INTERNAL_ERROR: s="CURLM_INTERNAL_ERROR"; break;
136 case CURLM_UNKNOWN_OPTION: s="CURLM_UNKNOWN_OPTION"; break;
137 case CURLM_LAST: s="CURLM_LAST"; break;
138 default: s="CURLM_unknown";
140 case CURLM_BAD_SOCKET: s="CURLM_BAD_SOCKET";
141 fprintf(MSG_OUT, "ERROR: %s returns %s\n", where, s);
142 /* ignore this error */
145 fprintf(MSG_OUT, "ERROR: %s returns %s\n", where, s);
152 /* Check for completed transfers, and remove their easy handles */
153 static void check_multi_info(GlobalInfo *g)
162 fprintf(MSG_OUT, "REMAINING: %d\n", g->still_running);
163 while((msg = curl_multi_info_read(g->multi, &msgs_left))) {
164 if(msg->msg == CURLMSG_DONE) {
165 easy = msg->easy_handle;
166 res = msg->data.result;
167 curl_easy_getinfo(easy, CURLINFO_PRIVATE, &conn);
168 curl_easy_getinfo(easy, CURLINFO_EFFECTIVE_URL, &eff_url);
169 fprintf(MSG_OUT, "DONE: %s => (%d) %s\n", eff_url, res, conn->error);
170 curl_multi_remove_handle(g->multi, easy);
172 curl_easy_cleanup(easy);
180 /* Called by libevent when we get action on a multi socket */
181 static void event_cb(int fd, short kind, void *userp)
183 GlobalInfo *g = (GlobalInfo*) userp;
187 (kind & EV_READ ? CURL_CSELECT_IN : 0) |
188 (kind & EV_WRITE ? CURL_CSELECT_OUT : 0);
190 rc = curl_multi_socket_action(g->multi, fd, action, &g->still_running);
191 mcode_or_die("event_cb: curl_multi_socket_action", rc);
194 if(g->still_running <= 0) {
195 fprintf(MSG_OUT, "last transfer done, kill timeout\n");
196 if(evtimer_pending(g->timer_event, NULL)) {
197 evtimer_del(g->timer_event);
204 /* Called by libevent when our timeout expires */
205 static void timer_cb(int fd, short kind, void *userp)
207 GlobalInfo *g = (GlobalInfo *)userp;
212 rc = curl_multi_socket_action(g->multi,
213 CURL_SOCKET_TIMEOUT, 0, &g->still_running);
214 mcode_or_die("timer_cb: curl_multi_socket_action", rc);
220 /* Clean up the SockInfo structure */
221 static void remsock(SockInfo *f)
232 /* Assign information to a SockInfo structure */
233 static void setsock(SockInfo*f, curl_socket_t s, CURL*e, int act, GlobalInfo*g)
236 (act&CURL_POLL_IN?EV_READ:0)|(act&CURL_POLL_OUT?EV_WRITE:0)|EV_PERSIST;
243 f->ev = event_new(g->evbase, f->sockfd, kind, event_cb, g);
245 event_add(f->ev, NULL);
250 /* Initialize a new SockInfo structure */
251 static void addsock(curl_socket_t s, CURL *easy, int action, GlobalInfo *g)
253 SockInfo *fdp = calloc(sizeof(SockInfo), 1);
256 setsock(fdp, s, easy, action, g);
257 curl_multi_assign(g->multi, s, fdp);
260 /* CURLMOPT_SOCKETFUNCTION */
261 static int sock_cb(CURL *e, curl_socket_t s, int what, void *cbp, void *sockp)
263 GlobalInfo *g = (GlobalInfo*) cbp;
264 SockInfo *fdp = (SockInfo*) sockp;
265 const char *whatstr[]={ "none", "IN", "OUT", "INOUT", "REMOVE" };
268 "socket callback: s=%d e=%p what=%s ", s, e, whatstr[what]);
269 if(what == CURL_POLL_REMOVE) {
270 fprintf(MSG_OUT, "\n");
275 fprintf(MSG_OUT, "Adding data: %s\n", whatstr[what]);
276 addsock(s, e, what, g);
280 "Changing action from %s to %s\n",
281 whatstr[fdp->action], whatstr[what]);
282 setsock(fdp, s, e, what, g);
290 /* CURLOPT_WRITEFUNCTION */
291 static size_t write_cb(void *ptr, size_t size, size_t nmemb, void *data)
293 size_t realsize = size * nmemb;
294 ConnInfo *conn = (ConnInfo*) data;
301 /* CURLOPT_PROGRESSFUNCTION */
302 static int prog_cb (void *p, double dltotal, double dlnow, double ult,
305 ConnInfo *conn = (ConnInfo *)p;
309 fprintf(MSG_OUT, "Progress: %s (%g/%g)\n", conn->url, dlnow, dltotal);
314 /* Create a new easy handle, and add it to the global curl_multi */
315 static void new_conn(char *url, GlobalInfo *g)
320 conn = calloc(1, sizeof(ConnInfo));
321 memset(conn, 0, sizeof(ConnInfo));
324 conn->easy = curl_easy_init();
326 fprintf(MSG_OUT, "curl_easy_init() failed, exiting!\n");
330 conn->url = strdup(url);
331 curl_easy_setopt(conn->easy, CURLOPT_URL, conn->url);
332 curl_easy_setopt(conn->easy, CURLOPT_WRITEFUNCTION, write_cb);
333 curl_easy_setopt(conn->easy, CURLOPT_WRITEDATA, conn);
334 curl_easy_setopt(conn->easy, CURLOPT_VERBOSE, 1L);
335 curl_easy_setopt(conn->easy, CURLOPT_ERRORBUFFER, conn->error);
336 curl_easy_setopt(conn->easy, CURLOPT_PRIVATE, conn);
337 curl_easy_setopt(conn->easy, CURLOPT_NOPROGRESS, 0L);
338 curl_easy_setopt(conn->easy, CURLOPT_PROGRESSFUNCTION, prog_cb);
339 curl_easy_setopt(conn->easy, CURLOPT_PROGRESSDATA, conn);
341 "Adding easy %p to multi %p (%s)\n", conn->easy, g->multi, url);
342 rc = curl_multi_add_handle(g->multi, conn->easy);
343 mcode_or_die("new_conn: curl_multi_add_handle", rc);
345 /* note that the add_handle() will set a time-out to trigger very soon so
346 that the necessary socket_action() call will be called by this app */
349 /* This gets called whenever data is received from the fifo */
350 static void fifo_cb(int fd, short event, void *arg)
355 GlobalInfo *g = (GlobalInfo *)arg;
356 (void)fd; /* unused */
357 (void)event; /* unused */
361 rv=fscanf(g->input, "%1023s%n", s, &n);
364 new_conn(s, arg); /* if we read a URL, go get it! */
371 /* Create a named pipe and tell libevent to monitor it */
372 static const char *fifo = "hiper.fifo";
373 static int init_fifo (GlobalInfo *g)
376 curl_socket_t sockfd;
378 fprintf(MSG_OUT, "Creating named pipe \"%s\"\n", fifo);
379 if(lstat (fifo, &st) == 0) {
380 if((st.st_mode & S_IFMT) == S_IFREG) {
387 if(mkfifo (fifo, 0600) == -1) {
391 sockfd = open(fifo, O_RDWR | O_NONBLOCK, 0);
396 g->input = fdopen(sockfd, "r");
398 fprintf(MSG_OUT, "Now, pipe some URL's into > %s\n", fifo);
399 g->fifo_event = event_new(g->evbase, sockfd, EV_READ|EV_PERSIST, fifo_cb, g);
400 event_add(g->fifo_event, NULL);
404 static void clean_fifo(GlobalInfo *g)
406 event_free(g->fifo_event);
411 int main(int argc, char **argv)
417 memset(&g, 0, sizeof(GlobalInfo));
418 g.evbase = event_base_new();
420 g.multi = curl_multi_init();
421 g.timer_event = evtimer_new(g.evbase, timer_cb, &g);
423 /* setup the generic multi interface options we want */
424 curl_multi_setopt(g.multi, CURLMOPT_SOCKETFUNCTION, sock_cb);
425 curl_multi_setopt(g.multi, CURLMOPT_SOCKETDATA, &g);
426 curl_multi_setopt(g.multi, CURLMOPT_TIMERFUNCTION, multi_timer_cb);
427 curl_multi_setopt(g.multi, CURLMOPT_TIMERDATA, &g);
429 /* we don't call any curl_multi_socket*() function yet as we have no handles
432 event_base_dispatch(g.evbase);
434 /* this, of course, won't get called since only way to stop this program is
435 via ctrl-C, but it is here to show how cleanup /would/ be done. */
437 event_free(g.timer_event);
438 event_base_free(g.evbase);
439 curl_multi_cleanup(g.multi);