Annotation of embedaddon/curl/docs/examples/asiohiper.cpp, revision 1.1.1.1

1.1       misho       1: /***************************************************************************
                      2:  *                                  _   _ ____  _
                      3:  *  Project                     ___| | | |  _ \| |
                      4:  *                             / __| | | | |_) | |
                      5:  *                            | (__| |_| |  _ <| |___
                      6:  *                             \___|\___/|_| \_\_____|
                      7:  *
                      8:  * Copyright (C) 2012 - 2018, Daniel Stenberg, <daniel@haxx.se>, et al.
                      9:  *
                     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.
                     13:  *
                     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.
                     17:  *
                     18:  * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
                     19:  * KIND, either express or implied.
                     20:  *
                     21:  ***************************************************************************/
                     22: 
                     23: /* <DESC>
                     24:  * demonstrate the use of multi socket interface with boost::asio
                     25:  * </DESC>
                     26:  */
                     27: /*
                     28:  * This program is in c++ and uses boost::asio instead of libevent/libev.
                     29:  * Requires boost::asio, boost::bind and boost::system
                     30:  *
                     31:  * This is an adaptation of libcurl's "hiperfifo.c" and "evhiperfifo.c"
                     32:  * sample programs. This example implements a subset of the functionality from
                     33:  * hiperfifo.c, for full functionality refer hiperfifo.c or evhiperfifo.c
                     34:  *
                     35:  * Written by Lijo Antony based on hiperfifo.c by Jeff Pohlmeyer
                     36:  *
                     37:  * When running, the program creates an easy handle for a URL and
                     38:  * uses the curl_multi API to fetch it.
                     39:  *
                     40:  * Note:
                     41:  *  For the sake of simplicity, URL is hard coded to "www.google.com"
                     42:  *
                     43:  * This is purely a demo app, all retrieved data is simply discarded by the
                     44:  * write callback.
                     45:  *
                     46:  * ===========================================================================
                     47:  * WARNING: This example program is known to have flaws:
                     48:  * https://github.com/curl/curl/issues/2407
                     49:  *
                     50:  * It still kept in the example repository with the hope that it might be
                     51:  * useful, and maybe some day someone who knows enough about boost::asio will
                     52:  * read this text, accept the challenge and make the example code work
                     53:  * correctly. Until then: expect this example program to fail occasionally.
                     54:  * ===========================================================================
                     55:  */
                     56: 
                     57: 
                     58: #include <curl/curl.h>
                     59: #include <boost/asio.hpp>
                     60: #include <boost/bind.hpp>
                     61: #include <iostream>
                     62: 
                     63: #define MSG_OUT stdout /* Send info to stdout, change to stderr if you want */
                     64: 
                     65: /* boost::asio related objects
                     66:  * using global variables for simplicity
                     67:  */
                     68: boost::asio::io_service io_service;
                     69: boost::asio::deadline_timer timer(io_service);
                     70: std::map<curl_socket_t, boost::asio::ip::tcp::socket *> socket_map;
                     71: 
                     72: /* Global information, common to all connections */
                     73: typedef struct _GlobalInfo
                     74: {
                     75:   CURLM *multi;
                     76:   int still_running;
                     77: } GlobalInfo;
                     78: 
                     79: /* Information associated with a specific easy handle */
                     80: typedef struct _ConnInfo
                     81: {
                     82:   CURL *easy;
                     83:   char *url;
                     84:   GlobalInfo *global;
                     85:   char error[CURL_ERROR_SIZE];
                     86: } ConnInfo;
                     87: 
                     88: static void timer_cb(const boost::system::error_code & error, GlobalInfo *g);
                     89: 
                     90: /* Update the event timer after curl_multi library calls */
                     91: static int multi_timer_cb(CURLM *multi, long timeout_ms, GlobalInfo *g)
                     92: {
                     93:   fprintf(MSG_OUT, "\nmulti_timer_cb: timeout_ms %ld", timeout_ms);
                     94: 
                     95:   /* cancel running timer */
                     96:   timer.cancel();
                     97: 
                     98:   if(timeout_ms > 0) {
                     99:     /* update timer */
                    100:     timer.expires_from_now(boost::posix_time::millisec(timeout_ms));
                    101:     timer.async_wait(boost::bind(&timer_cb, _1, g));
                    102:   }
                    103:   else if(timeout_ms == 0) {
                    104:     /* call timeout function immediately */
                    105:     boost::system::error_code error; /*success*/
                    106:     timer_cb(error, g);
                    107:   }
                    108: 
                    109:   return 0;
                    110: }
                    111: 
                    112: /* Die if we get a bad CURLMcode somewhere */
                    113: static void mcode_or_die(const char *where, CURLMcode code)
                    114: {
                    115:   if(CURLM_OK != code) {
                    116:     const char *s;
                    117:     switch(code) {
                    118:     case CURLM_CALL_MULTI_PERFORM:
                    119:       s = "CURLM_CALL_MULTI_PERFORM";
                    120:       break;
                    121:     case CURLM_BAD_HANDLE:
                    122:       s = "CURLM_BAD_HANDLE";
                    123:       break;
                    124:     case CURLM_BAD_EASY_HANDLE:
                    125:       s = "CURLM_BAD_EASY_HANDLE";
                    126:       break;
                    127:     case CURLM_OUT_OF_MEMORY:
                    128:       s = "CURLM_OUT_OF_MEMORY";
                    129:       break;
                    130:     case CURLM_INTERNAL_ERROR:
                    131:       s = "CURLM_INTERNAL_ERROR";
                    132:       break;
                    133:     case CURLM_UNKNOWN_OPTION:
                    134:       s = "CURLM_UNKNOWN_OPTION";
                    135:       break;
                    136:     case CURLM_LAST:
                    137:       s = "CURLM_LAST";
                    138:       break;
                    139:     default:
                    140:       s = "CURLM_unknown";
                    141:       break;
                    142:     case CURLM_BAD_SOCKET:
                    143:       s = "CURLM_BAD_SOCKET";
                    144:       fprintf(MSG_OUT, "\nERROR: %s returns %s", where, s);
                    145:       /* ignore this error */
                    146:       return;
                    147:     }
                    148: 
                    149:     fprintf(MSG_OUT, "\nERROR: %s returns %s", where, s);
                    150: 
                    151:     exit(code);
                    152:   }
                    153: }
                    154: 
                    155: /* Check for completed transfers, and remove their easy handles */
                    156: static void check_multi_info(GlobalInfo *g)
                    157: {
                    158:   char *eff_url;
                    159:   CURLMsg *msg;
                    160:   int msgs_left;
                    161:   ConnInfo *conn;
                    162:   CURL *easy;
                    163:   CURLcode res;
                    164: 
                    165:   fprintf(MSG_OUT, "\nREMAINING: %d", g->still_running);
                    166: 
                    167:   while((msg = curl_multi_info_read(g->multi, &msgs_left))) {
                    168:     if(msg->msg == CURLMSG_DONE) {
                    169:       easy = msg->easy_handle;
                    170:       res = msg->data.result;
                    171:       curl_easy_getinfo(easy, CURLINFO_PRIVATE, &conn);
                    172:       curl_easy_getinfo(easy, CURLINFO_EFFECTIVE_URL, &eff_url);
                    173:       fprintf(MSG_OUT, "\nDONE: %s => (%d) %s", eff_url, res, conn->error);
                    174:       curl_multi_remove_handle(g->multi, easy);
                    175:       free(conn->url);
                    176:       curl_easy_cleanup(easy);
                    177:       free(conn);
                    178:     }
                    179:   }
                    180: }
                    181: 
                    182: /* Called by asio when there is an action on a socket */
                    183: static void event_cb(GlobalInfo *g, curl_socket_t s,
                    184:                      int action, const boost::system::error_code & error,
                    185:                      int *fdp)
                    186: {
                    187:   fprintf(MSG_OUT, "\nevent_cb: action=%d", action);
                    188: 
                    189:   if(socket_map.find(s) == socket_map.end()) {
                    190:     fprintf(MSG_OUT, "\nevent_cb: socket already closed");
                    191:     return;
                    192:   }
                    193: 
                    194:   /* make sure the event matches what are wanted */
                    195:   if(*fdp == action || *fdp == CURL_POLL_INOUT) {
                    196:     CURLMcode rc;
                    197:     if(error)
                    198:       action = CURL_CSELECT_ERR;
                    199:     rc = curl_multi_socket_action(g->multi, s, action, &g->still_running);
                    200: 
                    201:     mcode_or_die("event_cb: curl_multi_socket_action", rc);
                    202:     check_multi_info(g);
                    203: 
                    204:     if(g->still_running <= 0) {
                    205:       fprintf(MSG_OUT, "\nlast transfer done, kill timeout");
                    206:       timer.cancel();
                    207:     }
                    208: 
                    209:     /* keep on watching.
                    210:      * the socket may have been closed and/or fdp may have been changed
                    211:      * in curl_multi_socket_action(), so check them both */
                    212:     if(!error && socket_map.find(s) != socket_map.end() &&
                    213:        (*fdp == action || *fdp == CURL_POLL_INOUT)) {
                    214:       boost::asio::ip::tcp::socket *tcp_socket = socket_map.find(s)->second;
                    215: 
                    216:       if(action == CURL_POLL_IN) {
                    217:         tcp_socket->async_read_some(boost::asio::null_buffers(),
                    218:                                     boost::bind(&event_cb, g, s,
                    219:                                                 action, _1, fdp));
                    220:       }
                    221:       if(action == CURL_POLL_OUT) {
                    222:         tcp_socket->async_write_some(boost::asio::null_buffers(),
                    223:                                      boost::bind(&event_cb, g, s,
                    224:                                                  action, _1, fdp));
                    225:       }
                    226:     }
                    227:   }
                    228: }
                    229: 
                    230: /* Called by asio when our timeout expires */
                    231: static void timer_cb(const boost::system::error_code & error, GlobalInfo *g)
                    232: {
                    233:   if(!error) {
                    234:     fprintf(MSG_OUT, "\ntimer_cb: ");
                    235: 
                    236:     CURLMcode rc;
                    237:     rc = curl_multi_socket_action(g->multi, CURL_SOCKET_TIMEOUT, 0,
                    238:                                   &g->still_running);
                    239: 
                    240:     mcode_or_die("timer_cb: curl_multi_socket_action", rc);
                    241:     check_multi_info(g);
                    242:   }
                    243: }
                    244: 
                    245: /* Clean up any data */
                    246: static void remsock(int *f, GlobalInfo *g)
                    247: {
                    248:   fprintf(MSG_OUT, "\nremsock: ");
                    249: 
                    250:   if(f) {
                    251:     free(f);
                    252:   }
                    253: }
                    254: 
                    255: static void setsock(int *fdp, curl_socket_t s, CURL *e, int act, int oldact,
                    256:                     GlobalInfo *g)
                    257: {
                    258:   fprintf(MSG_OUT, "\nsetsock: socket=%d, act=%d, fdp=%p", s, act, fdp);
                    259: 
                    260:   std::map<curl_socket_t, boost::asio::ip::tcp::socket *>::iterator it =
                    261:     socket_map.find(s);
                    262: 
                    263:   if(it == socket_map.end()) {
                    264:     fprintf(MSG_OUT, "\nsocket %d is a c-ares socket, ignoring", s);
                    265:     return;
                    266:   }
                    267: 
                    268:   boost::asio::ip::tcp::socket * tcp_socket = it->second;
                    269: 
                    270:   *fdp = act;
                    271: 
                    272:   if(act == CURL_POLL_IN) {
                    273:     fprintf(MSG_OUT, "\nwatching for socket to become readable");
                    274:     if(oldact != CURL_POLL_IN && oldact != CURL_POLL_INOUT) {
                    275:       tcp_socket->async_read_some(boost::asio::null_buffers(),
                    276:                                   boost::bind(&event_cb, g, s,
                    277:                                               CURL_POLL_IN, _1, fdp));
                    278:     }
                    279:   }
                    280:   else if(act == CURL_POLL_OUT) {
                    281:     fprintf(MSG_OUT, "\nwatching for socket to become writable");
                    282:     if(oldact != CURL_POLL_OUT && oldact != CURL_POLL_INOUT) {
                    283:       tcp_socket->async_write_some(boost::asio::null_buffers(),
                    284:                                    boost::bind(&event_cb, g, s,
                    285:                                                CURL_POLL_OUT, _1, fdp));
                    286:     }
                    287:   }
                    288:   else if(act == CURL_POLL_INOUT) {
                    289:     fprintf(MSG_OUT, "\nwatching for socket to become readable & writable");
                    290:     if(oldact != CURL_POLL_IN && oldact != CURL_POLL_INOUT) {
                    291:       tcp_socket->async_read_some(boost::asio::null_buffers(),
                    292:                                   boost::bind(&event_cb, g, s,
                    293:                                               CURL_POLL_IN, _1, fdp));
                    294:     }
                    295:     if(oldact != CURL_POLL_OUT && oldact != CURL_POLL_INOUT) {
                    296:       tcp_socket->async_write_some(boost::asio::null_buffers(),
                    297:                                    boost::bind(&event_cb, g, s,
                    298:                                                CURL_POLL_OUT, _1, fdp));
                    299:     }
                    300:   }
                    301: }
                    302: 
                    303: static void addsock(curl_socket_t s, CURL *easy, int action, GlobalInfo *g)
                    304: {
                    305:   /* fdp is used to store current action */
                    306:   int *fdp = (int *) calloc(sizeof(int), 1);
                    307: 
                    308:   setsock(fdp, s, easy, action, 0, g);
                    309:   curl_multi_assign(g->multi, s, fdp);
                    310: }
                    311: 
                    312: /* CURLMOPT_SOCKETFUNCTION */
                    313: static int sock_cb(CURL *e, curl_socket_t s, int what, void *cbp, void *sockp)
                    314: {
                    315:   fprintf(MSG_OUT, "\nsock_cb: socket=%d, what=%d, sockp=%p", s, what, sockp);
                    316: 
                    317:   GlobalInfo *g = (GlobalInfo*) cbp;
                    318:   int *actionp = (int *) sockp;
                    319:   const char *whatstr[] = { "none", "IN", "OUT", "INOUT", "REMOVE"};
                    320: 
                    321:   fprintf(MSG_OUT,
                    322:           "\nsocket callback: s=%d e=%p what=%s ", s, e, whatstr[what]);
                    323: 
                    324:   if(what == CURL_POLL_REMOVE) {
                    325:     fprintf(MSG_OUT, "\n");
                    326:     remsock(actionp, g);
                    327:   }
                    328:   else {
                    329:     if(!actionp) {
                    330:       fprintf(MSG_OUT, "\nAdding data: %s", whatstr[what]);
                    331:       addsock(s, e, what, g);
                    332:     }
                    333:     else {
                    334:       fprintf(MSG_OUT,
                    335:               "\nChanging action from %s to %s",
                    336:               whatstr[*actionp], whatstr[what]);
                    337:       setsock(actionp, s, e, what, *actionp, g);
                    338:     }
                    339:   }
                    340: 
                    341:   return 0;
                    342: }
                    343: 
                    344: /* CURLOPT_WRITEFUNCTION */
                    345: static size_t write_cb(void *ptr, size_t size, size_t nmemb, void *data)
                    346: {
                    347:   size_t written = size * nmemb;
                    348:   char *pBuffer = (char *)malloc(written + 1);
                    349: 
                    350:   strncpy(pBuffer, (const char *)ptr, written);
                    351:   pBuffer[written] = '\0';
                    352: 
                    353:   fprintf(MSG_OUT, "%s", pBuffer);
                    354: 
                    355:   free(pBuffer);
                    356: 
                    357:   return written;
                    358: }
                    359: 
                    360: /* CURLOPT_PROGRESSFUNCTION */
                    361: static int prog_cb(void *p, double dltotal, double dlnow, double ult,
                    362:                    double uln)
                    363: {
                    364:   ConnInfo *conn = (ConnInfo *)p;
                    365: 
                    366:   (void)ult;
                    367:   (void)uln;
                    368: 
                    369:   fprintf(MSG_OUT, "\nProgress: %s (%g/%g)", conn->url, dlnow, dltotal);
                    370:   fprintf(MSG_OUT, "\nProgress: %s (%g)", conn->url, ult);
                    371: 
                    372:   return 0;
                    373: }
                    374: 
                    375: /* CURLOPT_OPENSOCKETFUNCTION */
                    376: static curl_socket_t opensocket(void *clientp, curlsocktype purpose,
                    377:                                 struct curl_sockaddr *address)
                    378: {
                    379:   fprintf(MSG_OUT, "\nopensocket :");
                    380: 
                    381:   curl_socket_t sockfd = CURL_SOCKET_BAD;
                    382: 
                    383:   /* restrict to IPv4 */
                    384:   if(purpose == CURLSOCKTYPE_IPCXN && address->family == AF_INET) {
                    385:     /* create a tcp socket object */
                    386:     boost::asio::ip::tcp::socket *tcp_socket =
                    387:       new boost::asio::ip::tcp::socket(io_service);
                    388: 
                    389:     /* open it and get the native handle*/
                    390:     boost::system::error_code ec;
                    391:     tcp_socket->open(boost::asio::ip::tcp::v4(), ec);
                    392: 
                    393:     if(ec) {
                    394:       /* An error occurred */
                    395:       std::cout << std::endl << "Couldn't open socket [" << ec << "][" <<
                    396:         ec.message() << "]";
                    397:       fprintf(MSG_OUT, "\nERROR: Returning CURL_SOCKET_BAD to signal error");
                    398:     }
                    399:     else {
                    400:       sockfd = tcp_socket->native_handle();
                    401:       fprintf(MSG_OUT, "\nOpened socket %d", sockfd);
                    402: 
                    403:       /* save it for monitoring */
                    404:       socket_map.insert(std::pair<curl_socket_t,
                    405:                         boost::asio::ip::tcp::socket *>(sockfd, tcp_socket));
                    406:     }
                    407:   }
                    408: 
                    409:   return sockfd;
                    410: }
                    411: 
                    412: /* CURLOPT_CLOSESOCKETFUNCTION */
                    413: static int close_socket(void *clientp, curl_socket_t item)
                    414: {
                    415:   fprintf(MSG_OUT, "\nclose_socket : %d", item);
                    416: 
                    417:   std::map<curl_socket_t, boost::asio::ip::tcp::socket *>::iterator it =
                    418:     socket_map.find(item);
                    419: 
                    420:   if(it != socket_map.end()) {
                    421:     delete it->second;
                    422:     socket_map.erase(it);
                    423:   }
                    424: 
                    425:   return 0;
                    426: }
                    427: 
                    428: /* Create a new easy handle, and add it to the global curl_multi */
                    429: static void new_conn(char *url, GlobalInfo *g)
                    430: {
                    431:   ConnInfo *conn;
                    432:   CURLMcode rc;
                    433: 
                    434:   conn = (ConnInfo *) calloc(1, sizeof(ConnInfo));
                    435: 
                    436:   conn->easy = curl_easy_init();
                    437:   if(!conn->easy) {
                    438:     fprintf(MSG_OUT, "\ncurl_easy_init() failed, exiting!");
                    439:     exit(2);
                    440:   }
                    441: 
                    442:   conn->global = g;
                    443:   conn->url = strdup(url);
                    444:   curl_easy_setopt(conn->easy, CURLOPT_URL, conn->url);
                    445:   curl_easy_setopt(conn->easy, CURLOPT_WRITEFUNCTION, write_cb);
                    446:   curl_easy_setopt(conn->easy, CURLOPT_WRITEDATA, &conn);
                    447:   curl_easy_setopt(conn->easy, CURLOPT_VERBOSE, 1L);
                    448:   curl_easy_setopt(conn->easy, CURLOPT_ERRORBUFFER, conn->error);
                    449:   curl_easy_setopt(conn->easy, CURLOPT_PRIVATE, conn);
                    450:   curl_easy_setopt(conn->easy, CURLOPT_NOPROGRESS, 1L);
                    451:   curl_easy_setopt(conn->easy, CURLOPT_PROGRESSFUNCTION, prog_cb);
                    452:   curl_easy_setopt(conn->easy, CURLOPT_PROGRESSDATA, conn);
                    453:   curl_easy_setopt(conn->easy, CURLOPT_LOW_SPEED_TIME, 3L);
                    454:   curl_easy_setopt(conn->easy, CURLOPT_LOW_SPEED_LIMIT, 10L);
                    455: 
                    456:   /* call this function to get a socket */
                    457:   curl_easy_setopt(conn->easy, CURLOPT_OPENSOCKETFUNCTION, opensocket);
                    458: 
                    459:   /* call this function to close a socket */
                    460:   curl_easy_setopt(conn->easy, CURLOPT_CLOSESOCKETFUNCTION, close_socket);
                    461: 
                    462:   fprintf(MSG_OUT,
                    463:           "\nAdding easy %p to multi %p (%s)", conn->easy, g->multi, url);
                    464:   rc = curl_multi_add_handle(g->multi, conn->easy);
                    465:   mcode_or_die("new_conn: curl_multi_add_handle", rc);
                    466: 
                    467:   /* note that the add_handle() will set a time-out to trigger very soon so
                    468:      that the necessary socket_action() call will be called by this app */
                    469: }
                    470: 
                    471: int main(int argc, char **argv)
                    472: {
                    473:   GlobalInfo g;
                    474: 
                    475:   (void)argc;
                    476:   (void)argv;
                    477: 
                    478:   memset(&g, 0, sizeof(GlobalInfo));
                    479:   g.multi = curl_multi_init();
                    480: 
                    481:   curl_multi_setopt(g.multi, CURLMOPT_SOCKETFUNCTION, sock_cb);
                    482:   curl_multi_setopt(g.multi, CURLMOPT_SOCKETDATA, &g);
                    483:   curl_multi_setopt(g.multi, CURLMOPT_TIMERFUNCTION, multi_timer_cb);
                    484:   curl_multi_setopt(g.multi, CURLMOPT_TIMERDATA, &g);
                    485: 
                    486:   new_conn((char *)"www.google.com", &g);  /* add a URL */
                    487: 
                    488:   /* enter io_service run loop */
                    489:   io_service.run();
                    490: 
                    491:   curl_multi_cleanup(g.multi);
                    492: 
                    493:   fprintf(MSG_OUT, "\ndone.\n");
                    494: 
                    495:   return 0;
                    496: }

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>