libcurlのCURLOPT_WRITEFUNCTIONコールバックはどのタイミングで呼ばれるのか?

こちらの記事を参考にして、C言語からlibcurlを使ってHTTP GETするコードを書いて動かしてみました。
"cURL と libcurl を使ってインターネット経由でやりとりする"

さて取得したHTTP Bodyを変数に格納するためにコールバックを使うのですが、コールバックの中身が思ったよりも複雑になっていたので、その理由を調べてみました。

C言語+libcurlだとなぜこんな複雑になるの?

PHPだと $content = file_get_contents($url); みたいなのでいけるのに、C言語+libcurlだとなぜこんな複雑になるのだろう?
ひょっとして、このコールバックは1回のRequest内で複数呼ばれるのではないか?と思って 標準エラー出力にログを吐いてみたら(fprintfのところ)、やはりそうでした。

1回のリクエストで17回もコールバックが呼ばれてしました。
wr_index = 824
wr_index = 2226
wr_index = 3566
wr_index = 3620
wr_index = 5022
wr_index = 6424
wr_index = 7662
wr_index = 7818
wr_index = 9220
wr_index = 10622
wr_index = 11758
wr_index = 12016
wr_index = 13418
wr_index = 14820
wr_index = 15854
wr_index = 16451
wr_index = 16517
で、このコールバックはどのタイミングでどこから呼ばれるのだろう?と思ってstraceしてみたらこんな感じでした。
sendto(3, "GET / HTTP/1.1\r\nHost: www.yahoo."..., 54, MSG_NOSIGNAL, NULL, 0) = 54
poll([{fd=3, events=POLLIN|POLLPRI|POLLRDNORM|POLLRDBAND}], 1, 0) = 0 (Timeout)
poll([{fd=3, events=POLLIN|POLLPRI|POLLRDNORM|POLLRDBAND}], 1, 0) = 0 (Timeout)
poll([{fd=3, events=POLLIN|POLLPRI|POLLRDNORM|POLLRDBAND}], 1, 1000) = 1 ([{fd=3, revents=POLLIN|POLLRDNORM}])
poll([{fd=3, events=POLLIN|POLLPRI|POLLRDNORM|POLLRDBAND}], 1, 0) = 1 ([{fd=3, revents=POLLIN|POLLRDNORM}])
recvfrom(3, "HTTP/1.1 200 OK\r\nServer: nginx\r\n"..., 16384, 0, NULL, NULL) = 1402
write(2, "wr_index = 824\n", 15wr_index = 824
)        = 15
poll([{fd=3, events=POLLIN|POLLPRI|POLLRDNORM|POLLRDBAND}], 1, 1000) = 1 ([{fd=3, revents=POLLIN|POLLRDNORM}])
poll([{fd=3, events=POLLIN|POLLPRI|POLLRDNORM|POLLRDBAND}], 1, 0) = 1 ([{fd=3, revents=POLLIN|POLLRDNORM}])
recvfrom(3, "e:inherit;font:100%;}\npre,code,k"..., 16384, 0, NULL, NULL) = 1402
write(2, "wr_index = 2226\n", 16wr_index = 2226
)       = 16
poll([{fd=3, events=POLLIN|POLLPRI|POLLRDNORM|POLLRDBAND}], 1, 1000) = 1 ([{fd=3, revents=POLLIN|POLLRDNORM}])
poll([{fd=3, events=POLLIN|POLLPRI|POLLRDNORM|POLLRDBAND}], 1, 0) = 1 ([{fd=3, revents=POLLIN|POLLRDNORM}])
recvfrom(3, "http://auctions.yahoo.co.jp/\" al"..., 16384, 0, NULL, NULL) = 1402
write(2, "wr_index = 3566\n", 16wr_index = 3566
)       = 16
"poll" というシステムコールが使われていました。
http://linuxjm.sourceforge.jp/html/LDP_man-pages/man2/poll.2.html
poll() は select(2) と同様の仕事を行う、つまり、ファイルディスクリプタ集合のいずれか一つが I/O を実行可能な状態になるのを待つ。
ということだそうです。

まとめ

  • libcurlのCURLOPT_WRITEFUNCTIONコールバックは1リクエストで複数回呼ばれる。
  • pollシステムコールが発行されたタイミングで呼ばれる
  • libcurlはHTTPコンテンツを受信する際にpollシステムコールを使っている。

実験に使ったソースコード

/*
 * Simple curl application to do HTTP GET
 * forked from http://www.ibm.com/developerworks/jp/opensource/library/os-curl/#N100C4
 */
#include <stdio.h>
#include <string.h>
#include <curl/curl.h>

#define MAX_BUF 65536

char wr_buf[MAX_BUF+1];
int  wr_index;

/*
 * callback function (called many times in one request)
 */
size_t mycallback(void *buffer, size_t size, size_t num_of_members, void *userp)
{
    int segsize = size * num_of_members;

    /* Check to see if this data exceeds the size of our buffer. If so,
     * set the user-defined context value and return 0 to indicate a
     * problem to curl.
     */
    if (wr_index + segsize > MAX_BUF) {
        *(int *)userp = 1;
        return 0;
    }

    /* Copy the data from the curl buffer into our buffer */
    memcpy((void *)&wr_buf[wr_index], buffer, (size_t)segsize);

    wr_index += segsize;     /* Update the write index */
    wr_buf[wr_index] = '\0'; /* Null terminate the buffer */

    fprintf(stderr, "wr_index = %d\n", wr_index);

    return segsize;     /* Return the number of bytes received, indicating to curl that all is okay */
}

int main(int argc, char *argv[])
{
    char *url;
    CURL *curl;
    CURLcode ret;
    int  wr_error;

    if (argc < 2) {
        fprintf(stderr, "argument not given\n");
        return 1;
    }

    url = argv[1];

    wr_error = 0;
    wr_index = 0;

    /* First step, init curl */
    curl = curl_easy_init();
    if (!curl) {
        printf("couldn't init curl\n");
        return 0;
    }

    /* Tell curl the URL of the file we're going to retrieve */
    curl_easy_setopt(curl, CURLOPT_URL, url);

    /* Tell curl that we'll receive data to the function mycallback, and
     * also provide it with a context pointer for our error return.
     */
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&wr_error);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, mycallback);

    /* Allow curl to perform the action */
    ret = curl_easy_perform(curl);

    if (ret == 0) {
        /* Emit the page if curl indicates that no errors occurred */
        printf("%s\n", wr_buf);
    } else {
        printf("ret = %d (write_error = %d)\n", ret, wr_error);
    }

    curl_easy_cleanup(curl);
    return 0;
}
カテゴリ:

人気記事