免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
楼主: 雨过白鹭洲
打印 上一主题 下一主题

源码阅读第二期之curl-7.22.0 [复制链接]

论坛徽章:
0
11 [报告]
发表于 2011-10-20 16:06 |只看该作者
CU源码阅读7: curl之大块头operate函数(3)



在继续之前,我们先总结一下curl是如何处理多个URL的,由于curl命令可以指定多个URL,每个URL又可以是glob,表示一组相关的URL。另外如果涉及到上传(-T选项),上传的文件也可能是glob。这样的话循环套循环,代码流程比较难于理解。

1. curl的总体处理流程如下:
  1. while() // config->url_list中的每个URL(可能是glob,也可能是普通URL)
  2. {
  3.     // 如果不需要上传,进入循环而且只执行一次,在里面处理一个URL
  4.     // 如果需要上传,根据输入文件infiles是否glob,循环处理或执行一次处理
  5.     for()
  6.     {
  7.         // 处理glob URL中的每一个URL,如果不是glob,则只执行一次处理一个URL
  8.         for ()
  9.         {
  10.             // 循环处理网络数据传输
  11.             for()
  12.             {
  13.             }
  14.         }
  15.     }
  16. }
复制代码
现在再看curl是如何处理所有URL请求的,应该会更加清晰一些了。


2. 在src/main.c文件的4955行,CURL使用了下面for循环
  1. /* 这个循环用来上传glob字符串中的多个文件
  2.    如果不需要上传,循环总是执行一次 */
  3. for(up = 0;
  4.     (!up && !infiles) ||
  5.     ((uploadfile = inglob?glob_next_url(inglob):(!up?strdup(infiles):NULL)) != NULL);
  6.     up++)
  7. {
  8. }
复制代码
这个循环比较绕,主要是中间那个"||"和两个"?:"条件语句给整的,结合C语言逻辑语句的短路规则,我给解释下:
(1). 如果up等于0并且infiles为NULL,表达式为true,表示不需要上传,直接进入循环;
(2). 否则进一步判断inglob,如果inglob不为NULL,表示infiles是一个glob字符串,glob_next_url(inglob)获取第一个infile并赋值给uploadfile,然后进入循环处理。循环的退出条件是glob_next_url取完了最后一个infile,uploadfile=NULL;
(3). 如果inglob为NULL,表示infiles是单个文件字符串,直接把infiles拷贝到uploadfile,然后进入循环处理。由于up++,因此!up?为false,从而uploadfile=NULL,然后退出循环。

总结起来说,上面循环首先处理不需要上传,也就是下载的请求;如果命令使用了-T之类的上传选项,这个循环还根据输入文件是否glob,从而执行多次上传,或者只执行一次上传操作。


3.进入循环后,首先判断是否启用glob URL,并计算需处理的URL数量
  1.       if(!config->globoff) {
  2.         /* 展开'{...}' and '[...]'为多个URL,urlnum存储需处理的URL数量 */
  3.         res = glob_url(&urls, dourl, &urlnum,
  4.                        config->showerror?config->errors:NULL);
  5.         if(res != CURLE_OK) {
  6.           break; // glob URL非法
  7.         }
  8.       }
  9.       else
  10.         urlnum = 1; /* 关闭了glob,不管是否带有'{...}'和'[...]',都当做一个URL */
复制代码
这里urls就是当前正在处理的这个URL得到的glob,可能是NULL


4. 如果需要传输多个URL到stdout,则插入分隔符
  1.       // 如果outfiles为NULL(默认为stdout),或者outfiles为"-"(指定为stdout),并且URL数量大于1
  2.       separator= ((!outfiles || curlx_strequal(outfiles, "-")) && urlnum > 1);
复制代码
5. 又来一个循环,这里面对glob url中的每个URL进行处理
  1. for (i = 0;
  2.     ((url = urls?glob_next_url(urls):(i?NULL:strdup(url))) != NULL);
  3.     i++)
  4. {
  5. }
复制代码
如果是glob url,则循环处理直到所有URL处理完毕
如果不是glob url,则直接处理url,循环执行一次即退出
现在我们进入到了处理glob url中每个URL的循环中,这里面就是对单个URL的处理了。


6. 如果我们指定了存储结果的文件名,或者指定使用远程URL中的文件名,则对本地文件进行相应的处理
-o, --output <file> 指定本地保存的文件名
如果使用{}或[]获取多个文件,可以使用#1扩展到多个文件名,#1会被替换成glob URL中的对应字符串
  1. curl http://{one,two}.site.com -o "file_#1.txt"   得到"file_one.txt" "file_two.txt"
  2. curl http://{site,host}.host[1-5].com -o "#1_#2"  得到"site_1" ~ "site_5"; "host_1" ~ "host_5"
复制代码
-o选项还可以结合--create-dirs选项在本地创建目录,本地目录和文件均由-o的参数指定

-O, --remote-name 指定使用远程URL的文件名,文件名直接从URL中提取
此时只保留文件名,所有目录前缀都被裁掉,文件保存在当前目录
-J, --remote-header-name 选项则表示不从URL中提取文件名,而是使用服务器指定的Content-Disposition文件名

代码如下:
  1.         // 如果指定使用远程URL的文件名,或者
  2.         // 指定了输出文件,并且输出文件不是"-"(stdout)
  3.         if((urlnode->flags&GETOUT_USEREMOTE) ||
  4.            (outfile && !curlx_strequal("-", outfile)) ) {
  5.          
  6.           if(!outfile) { // outfile为NULL
  7.             /* 从URL中获得文件名 */
  8.             outfile = get_url_file_name(url);
  9.             // config->content_disposition表示使用服务器指定的Content-Disposition文件名
  10.             // 而不是从URL中提取文件名
  11.             if((!outfile || !*outfile) && !config->content_disposition) {
  12.             // 出错并break到下一次循环
  13.               break;
  14.             }
  15. #if defined(MSDOS) || defined(WIN32)
  16.             // DOS和WIN32,需要对文件名中的特殊字符做一些处理
  17.             outfile = sanitize_dos_name(outfile);
  18. #endif
  19.           }
  20.           else if(urls) { // outfile不为NULL,而且是glob url
  21.             /* 从URL的glob模式填充'#1' ... '#9'的文件 */
  22.             outfile = glob_match_url(storefile, urls);
  23.             if(!outfile) {
  24.               // glob url 非法
  25.               // 出错并break到下一次循环
  26.             }
  27.           }

  28.           // 如果需要创建目录层次结构
  29.           if(config->create_dirs &&
  30.              (-1 == create_dir_hierarchy(outfile, config->errors))) {
  31.             // 出错并break到下一次循环
  32.           }
  33.          
  34.           // -C, --continue-at <offset>选项表示在指定位置继续文件的传输
  35.           // <offset>指定了"-",表示自动获取重传的文件位置
  36.           if(config->resume_from_current) {
  37.             // 获得文件的大小,并以append模式打开文件
  38.             struct_stat fileinfo;
  39.             /* VMS -- Danger, the filesize is only valid for stream files */
  40.             if(0 == stat(outfile, &fileinfo))
  41.               config->resume_from = fileinfo.st_size; // 设置偏移位置为文件的当前大小
  42.             else
  43.               config->resume_from = 0; // 设置偏移位置为0
  44.           }
  45.          
  46.           // -C, --continue-at指定了重传位置,因此从指定位置开始重传
  47.           if(config->resume_from) {
  48.             outs.init = config->resume_from;
  49.             /* 打开文件用于输出 */
  50.           }
  51.           else {
  52.             outs.stream = NULL; /* 需要时再打开 */
  53.             outs.bytes = 0;     /* 重置字节计数器 */
  54.           }
  55.         }
复制代码
7. 如果是上传文件,则对需要上传的文件进行相应的处理,包括从stdin获取数据上传
stdin可以指定为"-"或".",使用"."表示非阻塞模式,在stdin正在上传时,仍然可以读取服务器的输出
  1.         // 指定了上传文件,并且不是"-"(stdin)
  2.         if(uploadfile && !stdin_upload(uploadfile)) {
  3.           // 把文件添加到原始URL,并使用新生成的URL
  4.           url = add_file_name_to_url(curl, url, uploadfile);

  5.           // 打开文件并获取文件的stat信息
  6.           // VMS读取二进制文件又有特殊的问题,感兴趣的可以看看源代码里的注释
  7.           struct_stat fileinfo;
  8.           infd= open(uploadfile, O_RDONLY | O_BINARY);
  9.           if((infd == -1) || fstat(infd, &fileinfo)) {
  10.            // 出错,跳过当前url,处理下一个
  11.             goto quit_urls;
  12.           }
  13.          
  14.           // 如果不是普通文件,如字符/块设置、sockets等等,忽略文件的大小
  15.           if(S_ISREG(fileinfo.st_mode))
  16.             uploadfilesize=fileinfo.st_size;
  17.         }
  18.         // 指定了上传文件,并且使用stdin
  19.         else if(uploadfile && stdin_upload(uploadfile)) {
  20.           // 检查config->authtype至少设置了一位验证掩码位
  21.           int authbits = 0;
  22.           int bitcheck = 0;
  23.           while(bitcheck < 32) {
  24.             if(config->authtype & (1 << bitcheck++)) {
  25.               authbits++;
  26.               if(authbits > 1) {
  27.                 /* 找到了! */
  28.                 break;
  29.               }
  30.             }
  31.           }

  32.           /*
  33.            * --anyauth: 让curl自动找到并使用最安全的验证方式
  34.            * curl会发起一个请求并检查返回头,因此可能需要额外的request/response来回
  35.            *
  36.            * --proxy-anyauth: 让curl与代理通信时自动选择适当的验证方式,
  37.            * 可能需要一个额外的request/response来回
  38.            *
  39.            * 使用stdin上传,如果指定了--anyauth或--proxy-anyauth
  40.            * 则操作很可能会失败,因为可能要求数据发送两次,需要客户端支持rewind(回绕)
  41.            */
  42.           if(config->proxyanyauth || (authbits>1)) {
  43.             warnf(config,
  44.                   "Using --anyauth or --proxy-anyauth with upload from stdin"
  45.                   " involves a big risk of it not working. Use a temporary"
  46.                   " file or a fixed auth type instead!\n");
  47.           }
  48.          
  49.           // 设置二进制模式
  50.           SET_BINMODE(stdin);
  51.           infd = STDIN_FILENO;
  52.           // stdin指定为"."表示非阻塞模式,在上传时也可以接收服务器的输出
  53.           if(curlx_strequal(uploadfile, ".")) {
  54.             if(curlx_nonblock((curl_socket_t)infd, TRUE) < 0)
  55.               warnf(config,
  56.                     "fcntl failed on fd=%d: %s\n", infd, strerror(errno));
  57.           }
  58.         }
复制代码
8. 继续对输入输出文件做准备工作,代码如下,我简单注释一下:
  1.         // curl自动得到重传的文件偏移位置
  2.         if(uploadfile && config->resume_from_current)
  3.           config->resume_from = -1; /* -1 will then force get-it-yourself */
  4.          
  5.         // 输出至tty,关闭进度表
  6.         if(output_expected(url, uploadfile)
  7.            && outs.stream && isatty(fileno(outs.stream)))
  8.           /* we send the output to a tty, therefore we switch off the progress
  9.              meter */
  10.           config->noprogress = config->isatty = TRUE;
  11.          
  12.         // config->mute表示是否安静模式
  13.         if(urlnum > 1 && !(config->mute)) {
  14.           fprintf(config->errors, "\n[%d/%d]: %s --> %s\n",
  15.                   i+1, urlnum, url, outfile ? outfile : "<stdout>");
  16.           if(separator)
  17.             printf("%s%s\n", CURLseparator, url);
  18.         }
  19.         
  20.         // 使用HTTP GET而不是POST,下面代码就是给URL加上?httpgetfields
  21.         if(httpgetfields) {
  22.           // ...
  23.         }
  24.         
  25.         // 如果config->errors为空,确保设置为stderr
  26.         if(!config->errors)
  27.           config->errors = stderr;
  28.          
  29.         // -B, --use-ascii 选项启用FTP或LDAP的ASCII传输
  30.         // WIN32输出至stdout时将使用text模式
  31.         // 如果outfile为空,输出文件是"-"(stdout),并且未指定ascii模式
  32.         if((!outfile || !strcmp(outfile, "-")) && !config->use_ascii) {
  33.           /* We get the output to stdout and we have not got the ASCII/text
  34.              flag, then set stdout to be binary */
  35.           // 则设置为二进制模式
  36.           SET_BINMODE(stdout);
  37.         }
复制代码
在建立连接并开始传输数据之前,curl还要大量地调用my_setopt,后者又再调用curl_easy_setopt来设置libcurl的各种选项,我们之前说过curl只是一个命令行工具,它的所有网络功能都是使用libcurl来实现的。

关于libcurl,后面我们单独对它进行分析,对于一个通用库来说,我觉得可以分:"总体介绍"、"接口"、"使用"、"实现" 几个部分来分析它。

未完待续!

论坛徽章:
0
12 [报告]
发表于 2011-10-25 14:14 |只看该作者
拜读。
谢谢老牛啦
谢谢老牛们的无私奉献精神
一年嵌入式编程经验,去年比较闲的时候也曾经想看linux源码却没有看进去

论坛徽章:
0
13 [报告]
发表于 2011-10-25 17:39 |只看该作者
最近跟libcurl打交道比较多,说实话,这个库写的不错,很精干,尤其是setopt,尼妹的,给了我很多思路

论坛徽章:
0
14 [报告]
发表于 2011-10-26 15:18 |只看该作者
楼主还是挺不错的。

论坛徽章:
0
15 [报告]
发表于 2011-10-26 17:16 |只看该作者
最近跟libcurl打交道比较多,说实话,这个库写的不错,很精干,尤其是setopt,尼妹的,给了我很多思路
embededboy 发表于 2011-10-25 17:39



    呵呵,之前也用這個做過個小程序,設置選項那個方法確實很強大

论坛徽章:
0
16 [报告]
发表于 2011-10-27 10:32 |只看该作者
本帖最后由 雨过白鹭洲 于 2011-10-27 10:34 编辑

惭愧,这两天忙着项目的上线,没时间更新

上周硬盘又挂了,花了一整天用EasyRecovery好不容易恢复回来,备份还是很重要啊。。还好我用了DropBox和EverNote这两个好软件!

这两天闲了接着分析,大家也可以积极地发表自己的分析和见解啊,欢迎欢迎!

论坛徽章:
0
17 [报告]
发表于 2011-10-27 16:48 |只看该作者
本帖最后由 雨过白鹭洲 于 2011-10-27 16:50 编辑

CU源码阅读8: curl之大块头operate函数(4)

接上一篇,我们继续来阅读和分析curl-7.22.0这个工具,src/main.c文件

1. 现在curl开始要调用libcurl的"curl_easy_setopt"函数来设置网络通信的各种选项,首先看下curl_easy_setopt的使用方式:
  1. CURLcode curl_easy_setopt(CURL *handle, CURLoption option, parameter); // 来自于libcurl man page
复制代码
实际上curl_easy_setopt的函数定义使用了可变数量参数:
  1. CURLcode curl_easy_setopt(CURL *curl, CURLoption tag, ...)
复制代码
但是在include/curl.h中暴露接口时,却又定义了如下宏:
  1. #define curl_easy_setopt(handle,opt,param) curl_easy_setopt(handle,opt,param)
复制代码
这个宏替换成为完全一样的函数调用,作用是确保应用代码使用三个参数来调用curl_easy_setopt函数。

curl_easy_setopt用来设置curl easy handle的各种选项,从而控制和修改libcurl的行为。每次只能设置一个选项,因此典型的应用需要调用许多次这个函数。libcurl定义了非常多的option,不同选项输入的parameter类型不相同,目前有四种类型:long, 函数指针, 对象指针, curl_off_t。

详细的用法和选项列表请参考:http://curl.haxx.se/libcurl/c/curl_easy_setopt.html


2. 回到curl的代码,curl封装了_my_setopt函数来调用curl_easy_setopt,里面根据不同的选项类型进行一些处理
  1. static CURLcode _my_setopt(CURL *curl, bool str, struct Configurable *config,
  2.                            const char *name, CURLoption tag, ...)
复制代码
作者写这个辅助函数,主要目的也是调试和日志。这个函数根据选项的四种类型,分别取出相应的parameter,然后调用curl_easy_setopt进行设置。同时会把设置的选项和参数信息生成字符串,记录日志便于调试。

然后定义了以下两个宏以方便使用:
  1. #define my_setopt(x,y,z) _my_setopt(x, FALSE, config, #y, y, z)         // 参数不是字符串
  2. #define my_setopt_str(x,y,z) _my_setopt(x, TRUE, config, #y, y, z)     // 参数是字符串
复制代码
3. curl在调用curl_easy_setopt函数上真是毫不吝啬!几乎把所有选项都调了个遍,差不多用了400行代码。
  1.         // 设置(1)或清除(0) TCP_NODELAY选项,连接建立后设置没有作用
  2.         // 设置该选项将禁用TCP的Nagle算法
  3.         if(config->tcp_nodelay)
  4.           my_setopt(curl, CURLOPT_TCP_NODELAY, 1);

  5.         // 传递给文件写入函数的数据指针
  6.         // 如果我们设置了CURLOPT_WRITEFUNCTION选项,这个指针就是回调函数的输入
  7.         // 如果我们没有设置CURLOPT_WRITEFUNCTION选项,就使用libcurl内部的写入函数,这个指针必须是FILE *
  8.         my_setopt(curl, CURLOPT_WRITEDATA, &outs);
  9.         // 文件写入回调函数
  10.         // size_t function( char *ptr, size_t size, size_t nmemb, void *userdata);
  11.         // libcurl接收到数据需要存储时,就会自动调用这个函数
  12.         my_setopt(curl, CURLOPT_WRITEFUNCTION, my_fwrite);

  13.         /* for uploads */
  14.         input.fd = infd;
  15.         input.config = config;
  16.         // 传递给文件读取函数的数据指针
  17.         // 如果我们设置了CURLOPT_READFUNCTION选项,这个指针就是回调函数的输入
  18.         // 如果我们没有设置CURLOPT_READFUNCTION选项,就使用libcurl内部的读取函数,这个指针必须是FILE *
  19.         my_setopt(curl, CURLOPT_READDATA, &input);
  20.         /* what call to read */
  21.         if((outfile && !curlx_strequal("-", outfile)) ||
  22.             !checkprefix("telnet:", url))
  23.           // 同上,这里指定了文件读取回调函数
  24.           my_setopt(curl, CURLOPT_READFUNCTION, my_fread);

  25.         /* in 7.18.0, the CURLOPT_SEEKFUNCTION/DATA pair is taking over what
  26.         CURLOPT_IOCTLFUNCTION/DATA pair previously provided for seeking */
  27.         // 这两个和上面也是类似的,当libcurl需要定位到文件的特定位置时
  28.         // 就会调用相应的回调函数,并传递这里设置的输入
  29.         my_setopt(curl, CURLOPT_SEEKDATA, &input);
  30.         my_setopt(curl, CURLOPT_SEEKFUNCTION, my_seek);

  31.         if(config->recvpersecond)
  32.           /* tell libcurl to use a smaller sized buffer as it allows us to
  33.           make better sleeps! 7.9.9 stuff! */
  34.           // 设置libcurl的接收缓冲区大小,小的缓冲区会导致更频繁地调用写入回调函数
  35.           my_setopt(curl, CURLOPT_BUFFERSIZE, config->recvpersecond);

  36.         /* size of uploaded file: */
  37.         if(uploadfilesize != -1)
  38.           // 上传文件时,这个选项告诉libcurl上传文件的大小
  39.           my_setopt(curl, CURLOPT_INFILESIZE_LARGE, uploadfilesize);

  40.         // 要处理的URL地址
  41.         my_setopt_str(curl, CURLOPT_URL, url);     /* what to fetch */
  42.         // 要使用的代理
  43.         my_setopt_str(curl, CURLOPT_PROXY, config->proxy); /* proxy to use */
  44.         if(config->proxy)
  45.           // 代理类型
  46.           my_setopt(curl, CURLOPT_PROXYTYPE, config->proxyver);

  47.         // libcurl的进度测量表
  48.         my_setopt(curl, CURLOPT_NOPROGRESS, config->noprogress);
  49.         if(config->no_body) {
  50.           // 在输出中不包含body部分,只对header和body分离的协议有效
  51.           // 对HTTP服务器来说,libcurl将发起HEAD请求
  52.           my_setopt(curl, CURLOPT_NOBODY, 1);
  53.           // 在输出中包含header
  54.           my_setopt(curl, CURLOPT_HEADER, 1);
  55.         }
  56.         else
  57.           my_setopt(curl, CURLOPT_HEADER, config->include_headers);

  58.         // 返回的HTTP code大于400时,自动失败;默认是忽略错误,正常返回页面内容
  59.         my_setopt(curl, CURLOPT_FAILONERROR, config->failonerror);
  60.         // 告诉libcurl准备上传
  61.         my_setopt(curl, CURLOPT_UPLOAD, uploadfile?TRUE:FALSE);
  62.         // 告诉libcurl只列出目录的文件名列表,而不包含文件大小、数据等
  63.         my_setopt(curl, CURLOPT_DIRLISTONLY, config->dirlistonly);
  64.         // 添加到远程文件末尾,而不是覆盖远程文件
  65.         my_setopt(curl, CURLOPT_APPEND, config->ftp_append);

  66.         if(config->netrc_opt)
  67.           // 使用~/.netrc中的用户和密码,还是URL中的用户和密码
  68.           my_setopt(curl, CURLOPT_NETRC, CURL_NETRC_OPTIONAL);
  69.         else if(config->netrc || config->netrc_file)
  70.           my_setopt(curl, CURLOPT_NETRC, CURL_NETRC_REQUIRED);
  71.         else
  72.           my_setopt(curl, CURLOPT_NETRC, CURL_NETRC_IGNORED);

  73.         if(config->netrc_file)
  74.           // 指定自定义的.netrc文件
  75.           my_setopt(curl, CURLOPT_NETRC_FILE, config->netrc_file);

  76.         my_setopt(curl, CURLOPT_FOLLOWLOCATION, config->followlocation);
  77.         my_setopt(curl, CURLOPT_UNRESTRICTED_AUTH, config->unrestricted_auth);
  78.         my_setopt(curl, CURLOPT_TRANSFERTEXT, config->use_ascii);
  79.         my_setopt_str(curl, CURLOPT_USERPWD, config->userpwd);
  80.         my_setopt_str(curl, CURLOPT_PROXYUSERPWD, config->proxyuserpwd);
  81.         my_setopt(curl, CURLOPT_NOPROXY, config->noproxy);
  82.         my_setopt_str(curl, CURLOPT_RANGE, config->range);
  83.         my_setopt(curl, CURLOPT_ERRORBUFFER, errorbuffer);
  84.         my_setopt(curl, CURLOPT_TIMEOUT, config->timeout);

  85.         switch(config->httpreq) {
  86.         case HTTPREQ_SIMPLEPOST:
  87.           my_setopt_str(curl, CURLOPT_POSTFIELDS, config->postfields);
  88.           my_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE, config->postfieldsize);
  89.           break;
  90.         case HTTPREQ_POST:
  91.           my_setopt(curl, CURLOPT_HTTPPOST, config->httppost);
  92.           break;
  93.         default:
  94.           break;
  95.         }
  96.         my_setopt_str(curl, CURLOPT_REFERER, config->referer);
  97.         my_setopt(curl, CURLOPT_AUTOREFERER, config->autoreferer);
  98.         my_setopt_str(curl, CURLOPT_USERAGENT, config->useragent);
  99.         my_setopt_str(curl, CURLOPT_FTPPORT, config->ftpport);
  100.         my_setopt(curl, CURLOPT_LOW_SPEED_LIMIT,
  101.                   config->low_speed_limit);
  102.         my_setopt(curl, CURLOPT_LOW_SPEED_TIME, config->low_speed_time);
  103.         my_setopt(curl, CURLOPT_MAX_SEND_SPEED_LARGE,
  104.                   config->sendpersecond);
  105.         my_setopt(curl, CURLOPT_MAX_RECV_SPEED_LARGE,
  106.                   config->recvpersecond);
  107.         my_setopt(curl, CURLOPT_RESUME_FROM_LARGE,
  108.                   config->use_resume?config->resume_from:0);
  109.         my_setopt_str(curl, CURLOPT_COOKIE, config->cookie);
  110.         my_setopt(curl, CURLOPT_HTTPHEADER, config->headers);
  111.         my_setopt(curl, CURLOPT_SSLCERT, config->cert);
  112.         my_setopt_str(curl, CURLOPT_SSLCERTTYPE, config->cert_type);
  113.         my_setopt(curl, CURLOPT_SSLKEY, config->key);
  114.         my_setopt_str(curl, CURLOPT_SSLKEYTYPE, config->key_type);
  115.         my_setopt_str(curl, CURLOPT_KEYPASSWD, config->key_passwd);

  116.         ............................
  117.         ............................

  118.         /* new in curl 7.16.1 */
  119.         if(config->ftp_ssl_ccc)
  120.           my_setopt(curl, CURLOPT_FTP_SSL_CCC, config->ftp_ssl_ccc_mode);

  121.         /* new in curl 7.11.1, modified in 7.15.2 */
  122.         if(config->socksproxy) {
  123.           my_setopt_str(curl, CURLOPT_PROXY, config->socksproxy);
  124.           my_setopt(curl, CURLOPT_PROXYTYPE, config->socksver);
  125.         }

  126. #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)
  127.         /* new in curl 7.19.4 */
  128.         if(config->socks5_gssapi_service)
  129.           my_setopt_str(curl, CURLOPT_SOCKS5_GSSAPI_SERVICE,
  130.                         config->socks5_gssapi_service);

  131.         /* new in curl 7.19.4 */
  132.         if(config->socks5_gssapi_nec)
  133.           my_setopt_str(curl, CURLOPT_SOCKS5_GSSAPI_NEC,
  134.                         config->socks5_gssapi_nec);
  135. #endif
  136.         /* curl 7.13.0 */
  137.         my_setopt_str(curl, CURLOPT_FTP_ACCOUNT, config->ftp_account);

  138.         my_setopt(curl, CURLOPT_IGNORE_CONTENT_LENGTH, config->ignorecl);

  139.         /* curl 7.14.2 */
  140.         my_setopt(curl, CURLOPT_FTP_SKIP_PASV_IP, config->ftp_skip_ip);

  141.         /* curl 7.15.1 */
  142.         my_setopt(curl, CURLOPT_FTP_FILEMETHOD, config->ftp_filemethod);

  143.         /* curl 7.15.2 */
  144.         if(config->localport) {
  145.           my_setopt(curl, CURLOPT_LOCALPORT, config->localport);
  146.           my_setopt_str(curl, CURLOPT_LOCALPORTRANGE,
  147.                         config->localportrange);
  148.         }

  149.         /* curl 7.15.5 */
  150.         my_setopt_str(curl, CURLOPT_FTP_ALTERNATIVE_TO_USER,
  151.                       config->ftp_alternative_to_user);

  152.         /* curl 7.16.0 */
  153.         if(config->disable_sessionid)
  154.           my_setopt(curl, CURLOPT_SSL_SESSIONID_CACHE,
  155.                     !config->disable_sessionid);

  156.         /* curl 7.16.2 */
  157.         if(config->raw) {
  158.           my_setopt(curl, CURLOPT_HTTP_CONTENT_DECODING, FALSE);
  159.           my_setopt(curl, CURLOPT_HTTP_TRANSFER_DECODING, FALSE);
  160.         }

  161.         /* curl 7.17.1 */
  162.         if(!config->nokeepalive) {
  163.           my_setopt(curl, CURLOPT_SOCKOPTFUNCTION, sockoptcallback);
  164.           my_setopt(curl, CURLOPT_SOCKOPTDATA, config);
  165.         }

  166.         /* curl 7.19.1 (the 301 version existed in 7.18.2) */
  167.         my_setopt(curl, CURLOPT_POSTREDIR, config->post301 |
  168.                   (config->post302 ? CURL_REDIR_POST_302 : FALSE));

  169.         /* curl 7.20.0 */
  170.         if(config->tftp_blksize)
  171.           my_setopt(curl, CURLOPT_TFTP_BLKSIZE, config->tftp_blksize);

  172.         if(config->mail_from)
  173.           my_setopt_str(curl, CURLOPT_MAIL_FROM, config->mail_from);

  174.         if(config->mail_rcpt)
  175.           my_setopt(curl, CURLOPT_MAIL_RCPT, config->mail_rcpt);

  176.         /* curl 7.20.x */
  177.         if(config->ftp_pret)
  178.           my_setopt(curl, CURLOPT_FTP_USE_PRET, TRUE);

  179.         if(config->proto_present)
  180.           my_setopt(curl, CURLOPT_PROTOCOLS, config->proto);
  181.         if(config->proto_redir_present)
  182.           my_setopt(curl, CURLOPT_REDIR_PROTOCOLS, config->proto_redir);

  183.         if((urlnode->flags & GETOUT_USEREMOTE)
  184.            && config->content_disposition) {
  185.           my_setopt(curl, CURLOPT_HEADERFUNCTION, header_callback);
  186.           my_setopt(curl, CURLOPT_HEADERDATA, &outs);
  187.         }
  188.         else {
  189.           /* if HEADERFUNCTION was set to something in the previous loop, it
  190.              is important that we set it (back) to NULL now */
  191.           my_setopt(curl, CURLOPT_HEADERFUNCTION, NULL);
  192.           my_setopt(curl, CURLOPT_HEADERDATA, config->headerfile?&heads:NULL);
  193.         }

  194.         if(config->resolve)
  195.           /* new in 7.21.3 */
  196.           my_setopt(curl, CURLOPT_RESOLVE, config->resolve);

  197.         /* new in 7.21.4 */
  198.         my_setopt_str(curl, CURLOPT_TLSAUTH_USERNAME, config->tls_username);
  199.         my_setopt_str(curl, CURLOPT_TLSAUTH_PASSWORD, config->tls_password);

  200.         /* new in 7.22.0 */
  201.         if(config->gssapi_delegation)
  202.           my_setopt_str(curl, CURLOPT_GSSAPI_DELEGATION,
  203.                         config->gssapi_delegation);
复制代码
我的那个亲娘嘞,这段设置选项的代码实在是太多了,大家可以参考http://curl.haxx.se/libcurl/c/curl_easy_setopt.html,对应起来看,我们这里就不多加注释了。


4. 我们接着往下看,curl使用了一个 for( ; ; ) 循环处理一个URL,循环一进来就是调用curl_easy_perform(),我们看看文档怎么说的:
  1. CURLcode curl_easy_perform(CURL * handle );
复制代码
CURL handle初始化之后,调用curl_easy_setopt设置选项完成,就可以使用curl_easy_perform执行传输。
我们可以使用相同handle调用curl_easy_perform多次,如果我们需要传输多个文件,这是推荐的做法。因为libcurl会为接下来的传输尝试重用相同的连接,因此达到加快速度、降低CPU使用、使用更少的网络资源等目的。不过在多次curl_easy_perform调用之间,可能需要使用curl_easy_setopt重新设置CURL的选项。
相同handle不能并行地调用,并行时必须使用不同的curl handle。

  1.           res = curl_easy_perform(curl);
  2.           if(!curl_slist_append(easycode, "ret = curl_easy_perform(hnd);")) {
  3.             res = CURLE_OUT_OF_MEMORY;
  4.             break;
  5.           }
复制代码
调用curl_easy_perform
  1.           if(config->content_disposition && outs.stream && !config->mute &&
  2.              outs.filename)
  3.             printf("curl: Saved to filename '%s'\n", outs.filename);
复制代码
记录保存的文件名

然后根据retry重试次数去处理网络传输,要么continue继续curl_easy_perform,要么break结束当前URL的处理
  1.           /* retry_numretries是剩余的重试次数 */         
  2.           /* if retry-max-time非0, 确保我们没有超过这个时间 */
  3.           if(retry_numretries &&
  4.              (!config->retry_maxtime ||
  5.               (cutil_tvdiff(cutil_tvnow(), retrystart)<
  6.                config->retry_maxtime*1000)) ) {
  7.             enum {
  8.               RETRY_NO,
  9.               RETRY_TIMEOUT,
  10.               RETRY_HTTP,
  11.               RETRY_FTP,
  12.               RETRY_LAST /* not used */
  13.             } retry = RETRY_NO; // 用来记录重试的原因
  14.             
  15.             long response;
  16.             // curl_easy_perform返回超时
  17.             if(CURLE_OPERATION_TIMEDOUT == res)
  18.               /* retry timeout always */
  19.               retry = RETRY_TIMEOUT;
  20.             else if((CURLE_OK == res) ||
  21.                     (config->failonerror &&
  22.                      (CURLE_HTTP_RETURNED_ERROR == res))) {
  23.               /* 如果curl_easy_perform返回OK,启用了failonerror
  24.               由于出现HTTP错误而失败, 检查HTTP transient错误并重试 */
  25.               char *this_url=NULL;
  26.               curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL, &this_url);
  27.               if(this_url &&
  28.                  checkprefix("http", this_url)) {
  29.                 /* HTTP(S) */
  30.                 curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response);

  31.                 // 检查HTTP的错误
  32.                 switch(response) {
  33.                 case 500: /* Internal Server Error */
  34.                 case 502: /* Bad Gateway */
  35.                 case 503: /* Service Unavailable */
  36.                 case 504: /* Gateway Timeout */
  37.                   retry = RETRY_HTTP;
  38.                   /*
  39.                    * At this point, we have already written data to the output
  40.                    * file (or terminal). If we write to a file, we must rewind
  41.                    * or close/re-open the file so that the next attempt starts
  42.                    * over from the beginning.
  43.                    *
  44.                    * TODO: similar action for the upload case. We might need
  45.                    * to start over reading from a previous point if we have
  46.                    * uploaded something when this was returned.
  47.                    */
  48.                   break;
  49.                 }
  50.               }
  51.             } /* if CURLE_OK */
  52.             else if(res) {
  53.               curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response);

  54.               if(response/100 == 4)
  55.                 /*
  56.                  * This is typically when the FTP server only allows a certain
  57.                  * amount of users and we are not one of them.  All 4xx codes
  58.                  * are transient.
  59.                  */
  60.                 retry = RETRY_FTP;
  61.             }

  62.             // 重试
  63.             if(retry) {
  64.               static const char * const m[]={
  65.                 NULL, "timeout", "HTTP error", "FTP error"
  66.               };
  67.               warnf(config, "Transient problem: %s "
  68.                     "Will retry in %ld seconds. "
  69.                     "%ld retries left.\n",
  70.                     m[retry], retry_sleep/1000, retry_numretries);
  71.               
  72.               // 先睡一下
  73.               go_sleep(retry_sleep);
  74.               retry_numretries--;
  75.               if(!config->retry_delay) {
  76.                 retry_sleep *= 2;
  77.                 if(retry_sleep > RETRY_SLEEP_MAX)
  78.                   retry_sleep = RETRY_SLEEP_MAX;
  79.               }
  80.               if(outs.bytes && outs.filename) {
  81.                 /* We have written data to a output file, we truncate file
  82.                  */
  83.                 if(!config->mute)
  84.                   fprintf(config->errors, "Throwing away %"
  85.                           CURL_FORMAT_CURL_OFF_T " bytes\n",
  86.                           outs.bytes);
  87.                 fflush(outs.stream);
  88.                 /* truncate file at the position where we started appending */
  89. #ifdef HAVE_FTRUNCATE
  90.                 if(ftruncate( fileno(outs.stream), outs.init)) {
  91.                   /* when truncate fails, we can't just append as then we'll
  92.                      create something strange, bail out */
  93.                   if(!config->mute)
  94.                     fprintf(config->errors,
  95.                             "failed to truncate, exiting\n");
  96.                   break;
  97.                 }
  98.                 /* now seek to the end of the file, the position where we
  99.                    just truncated the file in a large file-safe way */
  100.                 fseek(outs.stream, 0, SEEK_END);
  101. #else
  102.                 /* ftruncate is not available, so just reposition the file
  103.                    to the location we would have truncated it. This won't
  104.                    work properly with large files on 32-bit systems, but
  105.                    most of those will have ftruncate. */
  106.                 fseek(outs.stream, (long)outs.init, SEEK_SET);
  107. #endif
  108.                 outs.bytes = 0; /* clear for next round */
  109.               }
  110.               continue;
  111.             }
  112.           } /* if retry_numretries */
复制代码
5. 从循环里面出来之后,就是当前URL处理的收尾工作了,如下:
处理一下进度条
打印一下错误输出
关闭一下相关的文件
释放一下相关的资源


curl的大致流程就是这样了,咱们今天先到这里,回头有时间再分析curl的其它部分,以及最核心的libcurl

论坛徽章:
0
18 [报告]
发表于 2011-10-31 11:57 |只看该作者
补上我翻译的一段 man 手册

下面是一堆 选项说明,实在太多了,看着都头大。不过人家作者能做出来这么多功能,能够写出来man,
我们没有理由不自习读下,把它翻译过来。呵呵

=================================================
-# --progress-bar
如果你喜欢一个 bar型进度条,而不是普通的,那么 -#选项能够提供这个功能.
说的通俗些,默认的进度是类似与top那种风格,或者你看到的watch -d ifconfig eth0这种格式
而bar型进度,就是windows下安装文件或者拷贝文件那种条形进度条,用#符号拼成的。
例子如下:
默认的
curl  http://www.kernel.org/pub/linux/ ... nux-3.1-rc4.tar.bz2 -o 1.tar.gz
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0 73.6M    0 85176    0     0  63254      0  0:20:20  0:00:01  0:20:19 89182

-#选项后输出
curl  http://www.kernel.org/pub/linux/ ... nux-3.1-rc4.tar.bz2 -o 1.tar.gz -#
#####                                                                      7.7%

===============================================
-0        --http1.0
        使用http1.0版本的协议,而不是内置默认的http1.1版本

-1        --tlsv1
        强制curl在使用TLS(Transport Layer Security)协议与TLS服务器进行通讯商榷时,使用TSL版本问1

-2        --sslv2
        curl 在与远程 SSL 服务器商榷阶段,强制使用sslv2协议

-3        --sslv3
        同上,只不过使用sslv3协议

-4        --ipv4
        curl在解析主机名的过程中会把对应的IP地址为ipv4和ipv6的都解析出来,如果添加此选项,只解析ipv4的地址。
       
-6        --ipv6
        同上,只解析ipv6地址

-a        --append
        追加方式,当使用FTP/SFTP上传文件时,如果不指定该选项,并且目标文件存在,则会覆盖目标文件,如果设置了该选项,则会追加写到目标文件。
        目标文件不存在的话,会创建。一些SSH服务器(包括openssh)会忽略该选项。

-A, --user-agent <agent string>

        设置发送到http服务器的user-agent串。有一些处理不太好的CGI会在该项没被设置为"Mozilla/4.0"时失败。为了能够处理该串中的空格,
        需要用单引号把包含空格的串括起来。这一项同样可以通过 -H --header来设置.
        如果这个选项设置多次的话,curl只使用最后一个设置的值。

--anyauth
        http告知curl自己確定一個驗證方法,這個驗證方法是目標站點支持的所有驗證方法中最安全的一個。當然,要獲取站點支持的驗證方法,就可能需要首先給
        目標站點發送一個請求,然後從返回的頭中解析信息,因此,增加了一個 curl 到 目標站點的往復通訊。除此之外,可以通過 --basic, --digest, --ntlm 和
        --negotiate 這些選項來指定驗證方法.

        注意,如果你從標準輸入上傳數據的話,不建議使用選項 --anyauth, 因為這個操作可能需要數據發送兩次,這樣的話,客戶端就必須能夠反繞(rewind?),
        如果從標準輸入上傳數據時出現這種情況的話,上傳操作就會失敗.

-b        --coockie <name=data>

        通過coockie的方式把數據傳遞給 http 服務器。這個前提是之前從服務器接收的數據是以"set-cookie"的行方式傳遞的。提交的數據格式是這樣的:
                "name1=value1;name2=value2"等等。
        如果沒有發現=號的話,curl將會把這個串當作文件名稱來處理,從這個文件中讀取以前存儲的cookie值,如果讀取到的數據能夠匹配上本次應用的話,將會
        使用它,使用這個功能將會激活 curl 的“"cookie parser”模塊,用來解析並且存儲接收到的 cookie數據。如果你結合了 -L --location選項的話,這個用法
        將會更容易,從中讀取數據的文件應該是以 http頭 字符串方式存儲的,或者是以 Netscape/Mozilla 的cookie 方式存儲的。
       
        注意 -b --cookie 制定的文件,只用於讀取數據,而不是用於存儲cookie 數據,要存儲 cookie 数据的话,通過 -c --cookie-jar 選項來實現,或者也可以通過
        使用 -D --dump-header  來存儲 http 頭到一個文件中。

论坛徽章:
0
19 [报告]
发表于 2011-11-04 14:16 |只看该作者
第一期没经验,定的太多了,后面打算每期就一个源码,方便集中学习,而且感觉第一期的curl分析的还不是很完美(呵呵,楼主已经尽力了)。
因此,第二期主题是curl,希望更多的朋友能参与进来,把curl进行庖丁解牛式的分析,学习透!
架构版遵循一开始倡议时的原则,不在多,而在精,细。大家多多参与喔

论坛徽章:
0
20 [报告]
发表于 2011-11-08 17:10 |只看该作者
-B        --use-ascii
        當採用FTP或者LDAP協議時,允許使用ASCII方式傳輸數據。如果是FTP協議,可以通過在FTP的URL地址結尾添加"type=A"來強制使用ASCII傳輸。在win32平臺上,
        這個選項將會引起傳輸的數據以文本模式打印到標准輸出.

--basic
        告訴CURL使用http基本的認證方式。這個選項是默認的,因此這個選項單獨出現一般是沒有意義的。除非是你用他來覆蓋一個以前設置的認證方式。
        比如(--ntlm, --digest 或者 --negotiate)

-c        --cokie-jar <file name>
        指定一個文件,當curl進行完一個完整的操作後,它會把所有cokkie數據寫入這個文件。curl會把所有的從文件讀取的cookie值和從遠程服務器
        接收到的cookie值都寫入到這個文件中。如果沒有cookies,不會寫任何文件。寫入文件時,將會採用Netscape 的cookie方式存儲.如果你用單個的
        字符'-'作為文件名傳遞給curl,cookie的值將會被打印到標准輸出.
        這個選項將會激活curl 記錄和使用cookie的功能引擎。另外一個激活的方式就是使用選項 -b, --cookie.
        如果cookie jar 不能夠創建或者寫入,curl的操作不會失敗,甚至不會清晰的報告一個錯誤。使用 -v 選項能夠看到警告錯,這也就是打開cookie選項不能
        創建或者寫入文件時能看到的唯一反饋信息了。
        如果這個選項被使用了若干次,那麼,新的一次使用將會採用最近一次的文件名來存儲cookie信息.

-C        --continue-at <offset>
        繼續或者嘗試從以前的一個文件的指定便宜地址開始傳輸。給定的偏移就是傳輸時要精確的從源文件頭跳過的字節數。如果用來做上傳的話,
        FTP的SIZE命令將不會被 curl 使用。
        使用 "-C -"選項來通知curl 自動進行重傳查找和嘗試,然後才會根據指定的文件進行重傳查找和計算。
        如果該選項已經使用了好幾次,本次將會使用最近一次的值.

--compressed
        HTTP 使用libcurl支持的算法向服務器發送請求,要求響應數據是被壓縮的,報文文件是未壓縮的。        如果使用了這
        個選項,但是服務器返回了一個不支持的編碼,curl將會報告一個錯誤.

--connect-timeout <seconds>
        允許連接到服務器的過程中建立連接的最長時間。這個選項只限制連接建立過程,一旦curl已經連接上服務器,這個值就無效了。
        可以通過 -m --max-time來設置該選項
        如果這個選項被使用了好多次,那麼本次將會使用最近一次的值.

--create-dirs
        跟 -o 參數一起用時,curl 將會創建 -o 後面的參數中包含的一系列路徑。比如 -o /tmp/1/2/3/a.txt --create-dirs 目錄 /tmp/1/2/3 將會被創建。
        如果 -o 後面的参数中包含的路徑已經被創建,則不再創建。
        想要在使用 FTP 或者 SFTP 時創建遠程目錄的話,可以嘗試參數 --ftp-create-dirs
--crlf
        在上傳時把 LF 轉換成 CRLF。 對於 MVS(OS/390)是有用的。

man 的翻譯先到這裡,後面結合代碼的分析,補充上剩下的選項
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP