免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
123下一页
最近访问板块 发新帖
查看: 23179 | 回复: 29

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

论坛徽章:
0
发表于 2011-10-17 14:50 |显示全部楼层
本帖最后由 duanjigang 于 2011-11-04 14:09 编辑

源码阅读活动第一期的主贴在:http://bbs.chinaunix.net/thread-3600933-1-1.html

第一期主要针对三个下载工具:axel, wget, curl,目前duanjigang兄和wangzhen11aaa兄已经分别分析完了axel和wget


由于那个贴回复太多,影响对源码的分析理解和阅读贴子,特单开一贴,分析curl-7.22.0

小弟不才,对curl的分析才刚刚起了个头,抛砖引玉,请大家积极发挥!

评分

参与人数 3可用积分 +17 收起 理由
dreamice + 6 强力支持!
send_linux + 6 加油!
duanjigang + 5 支持!!

查看全部评分

论坛徽章:
0
发表于 2011-10-17 14:52 |显示全部楼层
CU源码阅读1: axel, wget, curl之编译调试环境


第一篇先记录环境工具准备,以及编译和调试步骤

首先,我个人比较喜欢Eclipse CDT这个IDE,因此很自然我需要把这三个项目拿到Eclipse中编译和调试

[axel]
  1. tar zxvf axel_2.4.orig.tar.gz
  2. cd axel-2.4/
  3. ./configure --debug=1 --strip=0
复制代码
到这里就OK了,第三步启用了调试,并禁止了strip。

打开Eclipse,选择File -> New -> Makefile Project with Existing Code
选择源码目录,语言选择C语言,选中Linux GCC Toolchain
Project -> Make Target -> Create
新建all, clean两个Make Target
Shift + F9调出Make Target,选择all并构建项目
OK,选择Run -> Debug As -> Local C/C++ Application,选择gdb/mi,确定就可以开始调试了
上面调试的是没有命令行参数的情况下运行axel,我们可以在Run -> Debug Configurations中增加命令行参数,把需要的选项,以及要下载的url添加进去就可以调试axel的下载功能了。

[curl]
  1. ./configure --enable-debug --disable-optimize --enable-curldebug
复制代码
Eclipse中的操作步骤类似,略之

[wget]
wget依赖于gnutls库,我用ubuntu的apt-get命令安装之:
  1. sudo apt-get install gnutls-bin libgnutls-dev
  2. export CFLAGS=-g
  3. ./configure --enable-debug
复制代码
上面修改了CFLAGS选项,增加-g以启用gcc的调试
Eclipse中的操作步骤类似,略之

论坛徽章:
0
发表于 2011-10-17 14:53 |显示全部楼层
CU源码阅读2: curl总体了解


虽然咱们要做的是源码阅读,但是我个人在阅读开源项目的代码时,一般不会直接就钻进代码深处去研究,而是按下面步骤来:
1. 首先看看相关的文档,对项目有个大致了解
2. 编译运行程序,输入各种参数来了解程序的使用
3. 梳理整个项目的目录、源码结构,并大致了解各个目录和文件的作用
4. 找到main函数,开始跟着程序的流程快速地走一遍
5. 深入地分析、理解代码,并对自己感兴趣,或者需要解决问题的地方重点关注

-------------------------------------------华丽的分隔线-------------------------------------------
老实说,我并没有用过curl这个工具,在我需要下载的时候,通常都是交给迅雷不及掩耳盗铃这个强大而又流氓的软件来完成,当然这是在windows下。当我在Linux下需要下载东西时,通常都是直接使用Chrome的下载功能,偶尔还用过wget这个东东,不过总体来说,速度是没法跟迅雷相提并论的。

libcurl库是curl工具的核心,而对于广泛使用的libcurl库,我也从来没有在自己的项目中使用过。究其原因,主要是我水平不行,没有写过什么大型的网络项目。

因此,我自身的开发水平,特别是网络编程方面,无论是开发功底还是开发经验,其实都不是很强的。要分析透curl和libcurl,恐怕不是一件容易的事情,很多方面也不可能分析得特别深入。不过我倒是希望这一路分析下来,也能够让我在网络编程方面能够进步一些。
-------------------------------------------华丽的分隔线-------------------------------------------

好吧,废话不多说,下面就让我们来看看从curl网站上的文档中能够获取到什么有用的信息

我们使用和分析的是curl 7.22.0稳定版本,curl的全名是"Client for URLs",也就是URL客户端。curl项目实际上由curl命令行工具、和libcurl库组成。

下面是网站上的官方解释:
[curl]
curl是一个命令行工具,用于传输URL数据。支持的协议包括DICT, FILE, FTP, FTPS, GOPHER, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, POP3, POP3S, RTMP, RTSP, SCP, SFTP, SMTP, SMTPS, TELNET, TFTP。

curl支持SSL证书、HTTP POST、HTTP PUT、FTP上传、HTTP基于form的上传、代理、cookies、用户+密码验证(基础、摘要、NTLM、Negotiate、kerberos等等)、文件重传、代理隧道,和其它很多有用的功能。

[libcurl]
libcurl是一个易于使用的客户端URL传输库。支持DICT, FILE, FTP, FTPS, GOPHER, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, POP3, POP3S, RTMP, RTSP, SCP, SFTP, SMTP, SMTPS, TELNET and TFTP等协议。

libcurl也支持SSL证书、HTTP POST、HTTP PUT、FTP上传、HTTP基于form的上传、代理、cookies、用户+密码验证(基础、摘要、NTLM、Negotiate、kerberos等等)、文件重传、代理隧道,和其它很多有用的功能

libcurl高度可移植,支持的平台包括:Solaris, NetBSD, FreeBSD, OpenBSD, Darwin, HPUX, IRIX, AIX, Tru64, Linux, UnixWare, HURD, Windows, Amiga, OS/2, BeOs, Mac OS X, Ultrix, QNX, OpenVMS, RISC OS, Novell NetWare, DOS, Symbian, OSF, Android, Minix, IBM TPF等等。

libcurl免费自由、线程安全、兼容IPv6、特性丰富、快速、社区支持


这里可以看到curl和其它下载工具的特性对比:
http://curl.haxx.se/docs/comparison-table.html
-------------------------------------------华丽的分隔线-------------------------------------------

下面我们来配置和编译curl 7.22.0

curl的配置项太多,一下子咱也看不过来,先不管那么多了,只把调试功能开启就拉倒了。

标准的三步曲:
  1. ./configure --enable-debug --disable-optimize --enable-curldebug
  2. make
  3. make install
复制代码
默认安装到/usr/local/bin/curl

curl命令的基本用法:
  1. curl [options] [URL...]
复制代码
选项和参数是可选的,但是至少要提供一个URL,当然也可以同时指定多个

为了感受一下curl的复杂度,你还是自己运行一下
  1. ./curl --help
复制代码
看看curl吓人的参数和选项

-------------------------------------------华丽的分隔线-------------------------------------------
好,下面我们来体验一下curl的使用
  1. 获得netscape web服务器的主页内容,直接打印输出
  2. curl http://www.netscape.com/

  3. 来个ftp文件,也是直接输出
  4. curl ftp://ftp.funet.fi/README

  5. 试试IPv6
  6. curl -g "http://[2001:1890:1112:1::20]/"

  7. 下载到文件
  8. curl -o thatpage.html http://www.netscape.com/
  9. curl -O www.haxx.se/index.html -O curl.haxx.se/download.html

  10. 使用用户和密码下载ftp
  11. curl ftp://name:passwd@machine.domain:port/full/path/to/file
  12. curl -u name:passwd ftp://machine.domain:port/full/path/to/file

  13. 代理
  14. curl -x my-proxy:888 ftp://ftp.leachsite.com/README

  15. HTTP字节范围
  16. curl -r 0-99 http://www.get.this/
  17. curl -r -500 http://www.get.this/

  18. 上传
  19. curl -T - ftp://ftp.upload.com/myfile
  20. curl -T uploadfile -u user:passwd ftp://ftp.upload.com/myfile
  21. curl -T uploadfile -u user:passwd ftp://ftp.upload.com/
  22. curl -T localfile -a ftp://ftp.upload.com/remotefile

  23. 详细输出/调试
  24. curl -v ftp://ftp.upload.com/
  25. curl --trace trace.txt www.haxx.se
复制代码
还有好多好多用法,今天就先到这里吧。等咱们分析到具体的功能时,再使用命令和参数来体验
-------------------------------------------华丽的分隔线-------------------------------------------

下一篇我们将查看curl项目的目录结构,以及主要目录和文件的作用。

论坛徽章:
0
发表于 2011-10-17 14:56 |显示全部楼层
CU源码阅读3: curl源码目录结构分析


说来惭愧,兄弟我以前写UNIX/Linux项目的时候,基本上是拿个Makefile模板,在上面改改改,完了就直接make。这里向大家推荐一个C/C++通用Makefile:http://www.javaeye.com/topic/774919 谁用谁知道!

因此我几乎没用过autoconf, automake, cmake等广泛使用的工具,对于大型项目如何使用这些工具来实现自动化构建和自动化测试,也不甚清楚。这一块将来还是得拿几个项目来实践实践,提升下使用相关工具的基本功。

但是优秀、成熟的开源项目,不可能采用这么原始的项目构建机制,curl也不例外。从curl的项目结构来看,主要应该是使用autoconf和automake来完成项目的构建,至少在基于UNIX的平台是这样。在Windows上,则使用了cmake来生成几个VC的项目文件(vc6curl.dsw、vc6curlsrc.dsw、vc6libcurl.dsw)。

至于是采用cmake来生成autoconf和automake的配置文件,还是开发者手工编写,我就不得而知了。总之咱们这一次的主要目的是阅读curl项目的源码,学习人家如何实现libcurl和curl,因此就不深究项目的构建和测试管理了。

下面是使用tree命令列出的curl-7.22.0主要目录:
  1. curl-7.22.0
  2. ├── CMake                                                 # CMake文件
  3. │   └── Platforms                                        # 平台相关的CMake文件
  4. ├── docs                                                  # 文档和示例代码
  5. │   ├── examples                                         # 主要是libcurl的使用示例代码
  6. │   └── libcurl                                          # libcurl的原始文档,用于生成可读格式的文档
  7. ├── include                                               # 头文件
  8. │   └── curl
  9. ├── lib                                                   # libcurl的源码
  10. ├── m4                                                    # autoconf需要的GNU M4文件
  11. ├── packages                                              # 各个平台打包二进制或源码包的相关文件
  12. │   ├── AIX                                              # 每个OS都有一个子目录
  13. │   │   └── RPM                                         # OS目录下则是当前OS支持打包的格式
  14. │   ├── DOS                                              # 如Linux下的RPM,Win32下的Cygwin exe等
  15. │   ├── EPM
  16. │   ├── Linux
  17. │   │   └── RPM
  18. │   ├── NetWare
  19. │   ├── OS400
  20. │   ├── Solaris
  21. │   ├── Symbian
  22. │   │   ├── bwins
  23. │   │   ├── eabi
  24. │   │   └── group
  25. │   ├── TPF
  26. │   ├── vms
  27. │   └── Win32
  28. │       └── cygwin
  29. ├── src                                                   # curl的源码
  30. │   └── macos
  31. │       └── src
  32. ├── tests                                                 # 测试相关的文件
  33. │   ├── certs                                            # 证书、认证相关的测试
  34. │   │   └── scripts
  35. │   ├── data                                             # 测试相关的数据
  36. │   ├── libtest                                          # 库测试
  37. │   ├── server                                           # 服务器测试
  38. │   └── unit                                             # 单元测试
  39. └── winbuild                                              # Windows构建
复制代码
上面简单地描述了curl源码目录的结构和用途,接下来就是对curl的源码进行分析了。
分析curl-7.22.0的源码,主要是从src/, lib/两个目录入手,而且尽量拣重要的文件来进行。


下一篇从src/main.c文件开始,这是curl的main函数入口,按代码的流程对项目进行大致的分析。

论坛徽章:
0
发表于 2011-10-17 15:31 |显示全部楼层
支持!!curl你来分析,我来顺着你的帖子看源码,嘿嘿

论坛徽章:
0
发表于 2011-10-17 16:02 |显示全部楼层
本帖最后由 雨过白鹭洲 于 2011-10-17 16:11 编辑

CU源码阅读4: curl之main函数初探


上面已经初步分析了curl-7.22.0项目的目录结构,我们重点关注src和lib两个子目录。

先来简单看看curl-7.22.0的main函数,定义在src/main.c文件中。

  1. int main(int argc, char *argv[])
  2. {
  3.   int res;
  4.   struct Configurable config; /* 从名字就可以看出来,程序需要使用的所有配置信息,相关数据都保存在这里 */

  5.   memset(&config, 0, sizeof(struct Configurable));

  6.   config.errors = stderr; /* 错误消息默认输出至stderr */

  7.   /* 确保文件描述符0, 1, 2(stdin, stdout, stderr)在程序工作之前被打开 */
  8.   /* 否则curl打开的前三个socket连接会成为stdin, stdout, stderr */
  9.   /* 因此被curl用做输入源、下载数据、错误日志 */
  10.   checkfds();

  11.   /* curl工具的核心函数,所有功能都是在这里边实现的 */
  12.   res = operate(&config, argc, argv);
  13.   
  14. #ifdef __SYMBIAN32__
  15.   if(config.showerror)
  16.     pressanykey(); /* 就是getchar() */
  17. #endif

  18.   free_config_fields(&config); /* 释放config */

  19. #ifdef __NOVELL_LIBC__
  20.   if(getenv("_IN_NETWARE_BASH_") == NULL)
  21.     pressanykey();
  22. #endif

  23. #ifdef __VMS
  24.   vms_special_exit(res, vms_show); // VMS比较怪异的程序退出exit()行为处理
  25. #else
  26.   return res;
  27. #endif
  28. }
复制代码
我没在vms上写过程序,不知道还有这个玩意!记在这里吧。。
  1. /*
  2. * VMS比较怪异的程序退出exit()行为处理
  3. * 简单说就是根据编译器版本、编译器选项、特性宏的设置,来调用相应的退出例程
  4. * 如果是Unix风格的shell,使用__posix_exit();如果是DCL shell,采用VMS编码并调用decc$exit()
  5. */
  6. void vms_special_exit(int code, int vms_show)
  7. {
  8.   int vms_code;

  9.   /* The Posix exit mode is only available after VMS 7.0 */
  10. #if __CRTL_VER >= 70000000
  11.   if(is_vms_shell() == 0) {
  12.     decc$__posix_exit(code);
  13.   }
  14. #endif

  15.   if(code > CURL_LAST) {   /* If CURL_LAST exceeded then */
  16.     vms_code = CURL_LAST;  /* curlmsg.h is out of sync.  */
  17.   }
  18.   else {
  19.     vms_code = vms_cond[code] | vms_show;
  20.   }
  21.   decc$exit(vms_code);
  22. }
复制代码
main函数的代码其实是非常少的,大部分的功能都由operate()函数实现,因此我们重点要分析这个家伙!

论坛徽章:
0
发表于 2011-10-17 21:31 |显示全部楼层
慢慢看 很顺畅

论坛徽章:
0
发表于 2011-10-18 11:30 |显示全部楼层
CU源码阅读5: curl之大块头operate函数(1)


前面我们已经看了curl-7.22.0的main函数,阅读以后表示无压力!

因为main()主要是调用operate()函数来完成curl的工作,你别看operate函数名字起得不咋滴,人家可是真正的核心函数,整整1280行代码,这下分析起来可吃力了。现在我也不可能把operate()的代码往这一拷,随便加几行注释,这可没法交差!

先从整体上把握一下operate()函数的流程吧,按执行顺序来,只捡重要的说:
  1. src/main.c -> static int operate(struct Configurable *config, int argc, argv_item_t argv[])
复制代码
1. 初始化全局libcurl库,实际上main_init()函数内部调用了curl_global_init(CURL_GLOBAL_DEFAULT)函数
  1.   if(main_init() != CURLE_OK) {
  2.     helpf(config->errors, "error initializing curl library\n");
  3.     return CURLE_FAILED_INIT;
  4.   }
复制代码
2. 获得一个curl handle,用于接下来的所有curl传输,并保存在全局的config中
  1.   curl = curl_easy_init();
  2.   if(!curl) {
  3.     clean_getout(config);
  4.     return CURLE_FAILED_INIT;
  5.   }
  6.   config->easy = curl;
复制代码
3. 获取libcurl的运行时版本及相关信息,curlinfo是个全局结构体指针,operate()函数并没有用到curlinfo,但是其它很多函数需要使用
  1.   // static curl_version_info_data *curlinfo;
  2.   curlinfo = curl_version_info(CURLVERSION_NOW);
复制代码
4. 初始化config的其它域,关于Configurable结构体,我们后面再详细介绍(TODO)
  1.   config->postfieldsize = -1;
  2.   config->showerror=TRUE;
  3.   config->use_httpget=FALSE;
  4.   config->create_dirs=FALSE;
  5.   config->maxredirs = DEFAULT_MAXREDIRS;
  6.   config->proto = CURLPROTO_ALL; /* FIXME: better to read from library */
  7.   config->proto_present = FALSE;
  8.   config->proto_redir =
  9.     CURLPROTO_ALL & ~(CURLPROTO_FILE|CURLPROTO_SCP); /* not FILE or SCP */
  10.   config->proto_redir_present = FALSE;
复制代码
5. 根据命令行参数,如果第一个参数不是"--",而是"-q",则忽略curl的配置文件,否则读取并使用curl的配置文件
  1.   if(argc>1 &&
  2.      (!curlx_strnequal("--", argv[1], 2) && (argv[1][0] == '-')) &&
  3.      strchr(argv[1], 'q')) {
  4.     ;
  5.   }
  6.   else {
  7.     parseconfig(NULL, config); /* NULL表示使用默认配置文件$HOME/.curlrc */
  8.   }
复制代码
curl配置文件的信息,以及配置文件如何影响curl的功能,我们后面视情况再分析(TODO)

6. 如果参数小于2,并且config->url_list为空,则报错退出
  1.   if((argc < 2)  && !config->url_list) {
  2.     helpf(config->errors, NULL);
  3.     return CURLE_FAILED_INIT;
  4.   }
复制代码
7. 接下来就是解析命令行参数,由于curl的参数实在太多,这里就不列出参数解析的详细代码,大致如下:
  1.   for(i = 1; i < argc; i++) {
  2.     if(stillflags && ('-' == argv[i][0])) {
  3.       if(curlx_strequal("--", argv[i]))
  4.         stillflags=FALSE;
  5.       else {
  6.         nextarg= (i < argc - 1)? argv[i+1]: NULL;
  7.         res = getparameter(flag, nextarg, &passarg, config);
  8.       }
  9.     }
  10.     else {
  11.       res = getparameter((char *)"--url", argv[i], &used, config);
  12.     }
  13.   }
复制代码
这个循环遍历了所有的参数和选项,并且调用getparameter()对单个参数进行解析
  1. static ParameterError getparameter(char *flag, /* f or -long-flag */
  2.                                    char *nextarg, /* NULL if unset */
  3.                                    bool *usedarg, /* set to TRUE if the arg
  4.                                                      has been used */
  5.                                    struct Configurable *config)
复制代码
getparameter做的事情无非就是根据命令行参数的设置,修改config数据结构,以此来调整和影响curl的功能

8. 如果config->url_list为空或者config->url_list->url为空,表示没有指定url;并且也没有指定--engine list,则报错退出
  1.   if((!config->url_list || !config->url_list->url) && !config->list_engines) {
  2.     clean_getout(config);
  3.     helpf(config->errors, "no URL specified!\n");
  4.     return CURLE_FAILED_INIT;
  5.   }
复制代码
9. 运行curl时,可以使用以下两个选项来设置发送给HTTP服务器的User-Agent字符串
-A, --user-agent <agent string>
-H, --header
如果命令行参数没有指定User-Agent,则下面代码将设置一个curl的默认值。
  1.   if(NULL == config->useragent) {
  2.     /* set non-zero default values: */
  3.     snprintf(useragent, sizeof(useragent),
  4.              CURL_NAME "/" CURL_VERSION " (" OS ") " "%s", curl_version());
  5.     config->useragent= useragent;
  6.   }
  7.   else
  8.     allocuseragent = TRUE; // 表示config->useragent是在getparameter()解析参数时动态分配的,因此需要释放

  9.   if(allocuseragent)
  10.     free(config->useragent);
复制代码
10. config->cacert和config->capath处理SSL的certificate文件和目录
可以使用命令行选项来指定
--cacert <CA certificate>
--capath <CA certificate directory>
也可以使用CURL_CA_BUNDLE、SSL_CERT_DIR、SSL_CERT_FILE环境变量来指定
Windows下面只能使用环境变量,如果未指定环境变量,则使用windows API函数SearchPath()来找到curl app的目录
  1.   if(!config->cacert &&
  2.      !config->capath &&
  3.      !config->insecure_ok) {
  4.     env = curlx_getenv("CURL_CA_BUNDLE");
  5.     if(env)
  6.       GetStr(&config->cacert, env);
  7.     else {
  8.       env = curlx_getenv("SSL_CERT_DIR");
  9.       if(env)
  10.         GetStr(&config->capath, env);
  11.       else {
  12.         env = curlx_getenv("SSL_CERT_FILE");
  13.         if(env)
  14.           GetStr(&config->cacert, env);
  15.       }
  16.     }

  17.     if(env)
  18.       curl_free(env);
  19. #ifdef WIN32
  20.     else
  21.       FindWin32CACert(config, "curl-ca-bundle.crt");
  22. #endif
  23.   }
复制代码
11. 如果指定了-G(--get)选项,所有-d, --data, --data-binary的数据都将使用HTTP GET请求,而不是POST请求。数据会被添加到url?后面。否则返回PARAM_BAD_USE
  1.   // getparameter(), 2907行
  2.     case 'G': /* HTTP GET */
  3.       config->use_httpget = TRUE;
  4.       break;

  5. // operate() 4848行
  6.   if(config->postfields) {
  7.     if(config->use_httpget) { // 指定了-G参数
  8.       /* Use the postfields data for a http get */
  9.       httpgetfields = strdup(config->postfields);
  10.       free(config->postfields);
  11.       config->postfields = NULL;
  12.       if(SetHTTPrequest(config,
  13.                         (config->no_body?HTTPREQ_HEAD:HTTPREQ_GET),
  14.                         &config->httpreq)) {
  15.         free(httpgetfields);
  16.         return PARAM_BAD_USE;
  17.       }
  18.     }
  19.     else {
  20.       if(SetHTTPrequest(config, HTTPREQ_SIMPLEPOST, &config->httpreq))
  21.         return PARAM_BAD_USE;
  22.     }
  23.   }
复制代码
12. struct curl_slist是libcurl定义的链表数据结构。这里easycode大体的作用是日志,所有libcurl调用和选项配置都会被写入easycode,然后dumpeasycode可以把所有信息记录在文件中。
  1.   // static struct curl_slist *easycode;

  2.   easycode = curl_slist_append(easycode, "CURL *hnd = curl_easy_init();");
  3.   if(!easycode) {
  4.     clean_getout(config);
  5.     res = CURLE_OUT_OF_MEMORY;
  6.     goto quit_curl;
  7.   }

  8. static void dumpeasycode(struct Configurable *config)
  9. {
  10.   // dump
  11. }
复制代码
13. 如果参数指定了--engine list,就打印出curl构建时指定支持的OpenSSL crypto engine列表,注意可能不是所有engine都在运行时可用,然后程序退出。
  1.   if(config->list_engines) {
  2.     struct curl_slist *engines = NULL;

  3.     curl_easy_getinfo(curl, CURLINFO_SSL_ENGINES, &engines);
  4.     list_engines(engines);
  5.     curl_slist_free_all(engines);
  6.     res = CURLE_OK;
  7.     goto quit_curl;
  8.   }
复制代码
----------------------------------------------------------------------------------------------------------------------------
分析代码截止src/main.c文件的4885行,到目前为止还没进入正题呢。。
----------------------------------------------------------------------------------------------------------------------------

下面curl就开始对指定的所有url进行处理,太累了,休息一下,下一篇继续!

论坛徽章:
0
发表于 2011-10-18 11:59 |显示全部楼层
见缝插针

沿袭axel分析的过程,先讲下工具的用法,然后再结合代码进行分析。
这样,后面每走到一个关键点,源码都能跟前面的用法结合起来,理解更深刻。

先看看curl的官网
http://curl.haxx.se/docs/manpage.html
的man手册(就是翻译了下):

用法:
        curl [选项] URL地址...


描述:
        curl 是一个网络工具,能够从服务器下载数据,或者向服务器上传数据。支持以下网络协议:
        DICT, FILE, FTP, FTPS, GOPHER, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, POP3, POP3S, RTMP, RTSP, SCP, SFTP, SMTP, SMTPS, TELNET 和

TFTP。
        curl 本身没有界面,是一个纯粹的命令行工具.       
        curl 提供了很多的特性支持,比如 代理,用户认证,FTP上传,HTTP post,ssl连接 cookies,文件重传等。下面列举的所有特性肯定会让你
        头晕的。呵呵。
        另外,curl之所以如此强大是因为有libcurl对它提供了丰富的网络传输相关的特性支持。

URL:
        看看curl是如何通过灵活的方式支持url参数的。

        首先,可以通过集合方式表示一组URL,比如:

  1.          http://site.{one,two,three,four}.com
复制代码
也可以通过[]来表示一个范围,来标识一组URL,比如

  1. ftp://ftp.number.com/file[1-100].txt
  2.         ftp://ftp.number.com/file[001-100].txt
  3.         ftp://ftp.number.com/file[a-z].txt
复制代码
嵌套的序列是不支持的。
       
        更灵活点,你可以告知一个范围,同时指定步进尺寸,这样来生成URL,比如:
  1.        
  2. http://www.number.com/file[1-100:10].txt 步进尺寸为 10
  3.         http://www.letters.com/file[a-z:2].txt   步进尺寸为 2
复制代码
如果你在输入URL时没有通过前缀来指明协议(比如 http://, ftp://等),curl将会猜测你可能使用什么协议。
        curl默认会使用http协议,当然它会根据主机名提供的信息来猜测你可能使用的协议。如果
        你键入

  1. ftp.test.com/1.rar, curl
复制代码
就会认为这是使用 ftp 协议进行传输。
        总的来说,curl会尽可能多的利用你输入的URL串来分析更多的信息。
        在一次curl的命令执行当中,curl会重用连接去实现多个文件的下载,这样能减少到服务器的连接,节省了
        多次连接的连接建立过程和握手过程,从而提供传输速度(在不同的curl命令中,连接重用这个特性没有)。

论坛徽章:
0
发表于 2011-10-19 15:50 |显示全部楼层
CU源码阅读6: curl之大块头operate函数(2)


分析到这里,我已经感觉有些吃力了,因为我对网络的应用层协议知之甚少,而且网络编程方面的经验也存在不定的不足。不过既然开了头,终不好虎头蛇尾。我尽力而为,请大家多多指教!

接上篇src/main.c文件的4885行,下面curl就开始对指定的所有url进行处理


1. 在看代码之前,我们先了解一下curl的URL和文件列表的glob支持
curl命令的url参数非常灵活,可以使用{}来表示一个序列,[]来表示范围,如下示例:
  1. http://site.{one,two,three,four}.com
  2. ftp://ftp.number.com/file[1-100].txt
  3. ftp://ftp.number.com/file[001-100].txt
  4. ftp://ftp.number.com/file[a-z].txt
  5. http://www.number.com/file[1-100:10].txt 步进尺寸为 10
  6. http://www.letters.com/file[a-z:2].txt   步进尺寸为 2
复制代码
这个特性是通过glob来实现的,等下我们看curl对URL的处理就可以看到

除了URL可以使用glob,当传输本地文件到远程URL时,要上传的文件也可以使用上面的形式,如:
  1. curl -T "{file1,file2}" http://www.uploadtothissite.com
  2. curl -T "img[1-1000].png" ftp://ftp.picturemania.com/upload/
复制代码
-T, --upload-file 选项向远程URL上传文件,顺便说一下,curl使用"-"表示直接从输入流获取数据,并上传至远程URL

如果指定了 -g, --globoff 选项,则curl会禁止glob序列"{}"以及范围"[]",此时config->globoff=FALSE
这时候URL和输入文件列表都不再进行glob展开。


2. 我们先简单看一下curl处理glob的相关接口,代码在urlglob.h/urlglob.c文件中
  1. typedef struct {
  2.   char* literal[10];
  3.   URLPattern pattern[9];
  4.   size_t size;
  5.   size_t urllen;
  6.   char *glob_buffer;
  7.   char beenhere;
  8.   char errormsg[80]; /* 错误消息缓冲区 */
  9. } URLGlob; // 这个结构体实际上对于我们来说,可以当做是透明的

  10. /*
  11. * 解析url字符串,得到URLGlob
  12. * glob:输出,后面几个接口需要使用
  13. * url:输入,字符串
  14. * urlnum:输出,解析到的URL数量
  15. * error:输入,消息记录文件录
  16. */
  17. int glob_url(URLGlob** glob, char* url, int *urlnum, FILE *error)

  18. /*
  19. * 获得glob中的下一个URL
  20. * URLGlob.beenhere成员记录了当前URL的索引,从0开始
  21. */
  22. char *glob_next_url(URLGlob *glob)

  23. /* 从glob以及输入的filename得到最终的输出文件 */
  24. char *glob_match_url(char *filename, URLGlob *glob)

  25. /* 释放glob */
  26. void glob_cleanup(URLGlob* glob)
复制代码
这部分的代码这里就不详细分析了,我们只管知道这几个接口有什么功能就好了


3. operate()使用了一个while循环来处理多个url,其中config->url_list是一个struct getout类型的指针,指向一串getout的第一个元素:
  1. /* 每个结点都包含了一个URL,用于get或put URL的内容 */
  2. struct getout {
  3.   struct getout *next; /* 下一个结点 */
  4.   char *url;     /* 要处理的URL */
  5.   char *outfile; /* 输出存储位置 */
  6.   char *infile;  /* 要上传的文件,如果GETOUT_UPLOAD设置了的话 */
  7.   int flags;     /* 选项 */
  8. };

  9. /* 用urlnode来遍历整个config->url_list链表 */
  10. struct getout *urlnode;
  11. urlnode = config->url_list;

  12.   /* 循环遍历所有URL列表 */
  13.   while(urlnode) {
  14.     /* 获得完整的URL(可能是NULL) */
  15.     dourl=urlnode->url;
  16.     url = dourl;

  17.      /* 这个结点没有URL,跳过它并继续下一个 */
  18.     if(NULL == url) {
  19.       if(urlnode->outfile)
  20.         free(urlnode->outfile);

  21.       /* 移动到下一个URL */
  22.       nextnode=urlnode->next;
  23.       free(urlnode); /* 释放当前 */
  24.       urlnode = nextnode; /* 赋给urlnode,以便下一次循环使用 */
  25.       continue;
  26.     }
  27.    
  28.     /***************************************/
  29.     /*********** 中间是处理URL的代码 ***********/
  30.     /***************************************/

  31.     /* 处理完成,释放结点 */
  32.     if(urlnode->url)
  33.       free(urlnode->url);
  34.     if(urlnode->outfile)
  35.       free(urlnode->outfile);
  36.     if(urlnode->infile)
  37.       free(urlnode->infile);

  38.     /* 同样移动到下一个结点 */
  39.     nextnode=urlnode->next;
  40.     free(urlnode);
  41.     urlnode = nextnode;
  42. }
复制代码
上面是operate()循环处理config->url_list的大体代码,不过中间对URL进行处理的代码才是核心。


4. 设置默认输出流为stdout,如果我们在命令行参数里未指定输出文件,就会使用默认输出
  1.     outs.stream = stdout;
  2.     outs.config = config;
  3.     outs.bytes = 0;
  4.     outs.filename = NULL;
复制代码
5. 对命令行指定的输入文件,即infiles进行glob展开
  1.     /* 如果glob启用,并且infiles不为空 */
  2.     if(!config->globoff && infiles) {
  3.       res = glob_url(&inglob, infiles, &infilenum,
  4.                      config->showerror?config->errors:NULL);
  5.       if(res != CURLE_OK) {
  6.         clean_getout(config);
  7.         if(outfiles)
  8.           free(outfiles);
  9.         break;
  10.       }
  11.     }
复制代码
inglob就是生成的输入文件glob

接下来循环处理inglob中指定的每个输入文件,如果inglob中没有输入文件,则循环仍然会执行一次,从而至少完成一次URL的处理

未完待续!
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

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

清除 Cookies - ChinaUnix - Archiver - WAP - TOP