嵌入式 wget 非递归下载

来源:互联网 发布:伴读软件 编辑:程序博客网 时间:2024/05/18 12:35


1整体概括

上篇分析的是wget参数解析,本篇主要是分析wget非递归下载html或者文件。wget实际上就是通过sock 向web服务器发送http数据包(GET or POST),web服务器收到请求后,发回回复包给wget,当然了http 传输层是tcp协议,简单来说wget 发tcp包,发送内容符合http协议,web服务器解析(such as nginx、apache)请求包,针对请求回复回复包。so easy.

整个wget可以用下面的流程图概括


其中config_analysis已经在前一篇已经分析过了,本篇就是分析wget后面的实现。

我们仅仅是不加任何参数的非递归下载,也就是执行如下命令:

wget www.baidu.com/index.html

2 代码详细解析

之前分析的是参数解析篇,参数解析完之后,会对参数进行校验。此段代码略过

nurl=argc –optind //参数解析完后,获取用户下载url个数,因为执行的命令是

wget www.baidu.com/index.html所以 nurl==1

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //code1:  
  2. url = alloca_array (char *, nurl + 1);  
  3. for (i = 0; i < nurl; i++, optind++)  
  4. {  
  5. char *rewritten = rewrite_shorthand_url (argv[optind]);//为url增加http://  
  6. if (rewritten)  
  7. url[i] = rewritten;  
  8. else  
  9. url[i] = xstrdup (argv[optind]);  
  10. }  

url[i] = NULL;//设置url最后元素为空,作为标记变量

上面那块代码主要是为url分配内存,分配nurl+1枚个元素的char*数组。

函数rewrite_shorthand_url (argv[optind]) 主要是为url添加http://字段,支持用户不输入协议,wget支持http、https、ftp协议,如果用户没输入协议,默认http://。并且url最后一个元素置为NULL,作为标志位。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //code2部分代码:  
  2. for (t = url; *t; t++)  
  3. {  
  4. char *filename = NULL, *redirected_URL = NULL;  
  5. int dt, url_err;  
  6. /* Need to do a new struct iri every time, because 
  7. * retrieve_url may modify it in some circumstances, 
  8. * currently. */  
  9. struct iri *iri = iri_new ();  
  10. struct url *url_parsed;  
  11.   
  12. set_uri_encoding (iri, opt.locale, true);  
  13. /*对url进行解析*/  
  14. url_parsed = url_parse (*t, &url_err, iri, true);  
  15.    
  16. if (!url_parsed)  
  17. {  
  18. char *error = url_error (*t, url_err);  
  19. logprintf (LOG_NOTQUIET, "%s: %s.\n",*t, error);  
  20. xfree (error);  
  21. inform_exit_status (URLERROR);  
  22. }  
  23. else  
  24. {  
  25.     /*如果是递归or需要页面css js之类的,并且不是ftp协议*/  
  26. if ((opt.recursive || opt.page_requisites)  
  27. && (url_scheme (*t) != SCHEME_FTP || url_uses_proxy (url_parsed)))  
  28. /*此case为递归url下载*/  
  29. {  
  30. int old_follow_ftp = opt.follow_ftp;  
  31. /* Turn opt.follow_ftp on in case of recursive FTP retrieval */  
  32. if (url_scheme (*t) == SCHEME_FTP)  
  33. opt.follow_ftp = 1;  
  34.   
  35. retrieve_tree (url_parsed, NULL);  
  36. opt.follow_ftp = old_follow_ftp;                                                                                                                      
  37. }  
  38. else  
  39. {  
  40.     /*此处为非递归url下载*/  
  41. retrieve_url (url_parsed, *t, &filename, &redirected_URL, NULL,  
  42. &dt, opt.recursive, iri, true);  
  43. }  


[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. <span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">  
  2. </span>  
[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. <span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">代码遍历url,对用户的每一个url都进行下载。</span>  

函数set_uri_encoding主要是对url进行解析一个正常的url是如下格式:

scheme://host[:port][/path][;params][?query][#fragment],此函数就是对url解析出来每一个结构。如下:


[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. url :                   http://www.baidu.com/index.html  
  2. scheme:             SCHEME_HTTP  
  3. host:               www.baidu.com   
  4. port:               80  
  5. path:               index.html  
  6. params:             NULL  
  7. query:              NULL  
  8. fragment:               NULL  
  9. file:                   index.html  
  10. user:               NULL  
  11. passwd:             NULL  

同时会对url进行utf-8编码。

会根据用户参数来决定是递归下载or非递归下载

递归下载条件:(用户输入-r or –p) && (not ftp协议 or use_proxy)

因为我们是直接下载,所以会跳到

retrieve_url (url_parsed, *t,&filename, &redirected_URL, NULL,

&dt, opt.recursive, iri, true);

2.1 retrieve_url

//如果使用proxy会设置一些属性,因为没有用proxy所以跳过了。

2.2 httploop

result = http_loop (u, orig_parsed,&mynewloc, &local_file, refurl, dt, proxy_url, iri);

参数说明:

u和orig_parsed是属性是相同值

mynewloc 指向NULL。

local_file 指向NULL。

refurl指向NULL。

dt 为 -1。

proxy_url 指向NULL。

iri为上层分析的那个iri,包括编码方式。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //code  
  2. hstat.referer = referer;//设置referer,此时的referer为NULL  
  3. //保存文件名称 首先是通过 --output-document 如果没有就获取url后缀名称  
  4. if (opt.output_document)  
  5. {  
  6. hstat.local_file = xstrdup (opt.output_document);  
  7. got_name = true;  
  8. }  
  9. else if (!opt.content_disposition)  
  10. {  
  11. hstat.local_file = url_file_name (opt.trustservernames ? u : original_url, NULL);  
  12. /*此函数主要是如果u->file如果存在,会生成一个新的文件名file_1…如果是设置 
  13. 了clobber就会覆盖*/  
  14. got_name = true;  
  15. }    

2.3 gethttp

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. req = request_new ();//构造一个req头  
  2. static struct request *  
  3. request_new (void)  
  4. {  
  5. struct request *req = xnew0 (struct request);//分配request结构  
  6. req->hcapacity = 8;//初始化http头部数组为8个  
  7. req->headers = xnew_array (struct request_header, req->hcapacity);//分配                                                                                                
  8. return req;  
  9.  }  

下面是请求结构

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. struct request {                                                                                                                                                    
  2. const char *method;//请求方法  
  3. char *arg;       //请求内容  
  4. /*此结构保存http header的key和value,比如content-length:xxxx 
  5.     Key为content-length 
  6.     Value为xxx 
  7. */  
  8. struct request_header {  
  9. char *name, *value;  
  10. enum rp release_policy;  
  11. } *headers;  
  12. int hcount;  
  13. int hcapacity;  //此头部容量  
  14. };<p>  
  15. </p>  

设置http方法

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. request_set_method(req, meth, meth_arg)  
  2. {  
  3. req->method = meth;  
  4. req->arg = arg;  
  5. }  

设置http header

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. static void  
  2. request_set_header (struct request *req, char *name, char *value,  
  3.                      enum rp release_policy)  
  4. {  
  5. struct request_header *hdr;  
  6. int i;  
  7.   
  8. if (!value)                                                                                                                                                       
  9. {  
  10. /* A NULL value is a no-op; if freeing the name is requested, 
  11. free it now to avoid leaks.  */  
  12. if (release_policy == rel_name || release_policy == rel_both)  
  13. xfree (name);  
  14. return;  
  15. }  
  16.   
  17. //首先是遍历所有头部,如果说找到的话,就释放设置成新的头  
  18. for (i = 0; i < req->hcount; i++)  
  19. {  
  20. hdr = &req->headers[i];  
  21. if (0 == strcasecmp (name, hdr->name))  
  22. {  
  23. /* Replace existing header. */  
  24. release_header (hdr);  
  25. hdr->name = name;  
  26. hdr->value = value;  
  27. hdr->release_policy = release_policy;  
  28. return;  
  29. }  
  30. }  
  31.   
  32. //如果用户设置的头很多,超过了8个就重新分配 2的幂增长  
  33. if (req->hcount >= req->hcapacity)  
  34.     {  
  35. req->hcapacity <<= 1;  
  36. req->headers = xrealloc (req->headers, req->hcapacity * sizeof (*hdr));  
  37. }  
  38. hdr = &req->headers[req->hcount++];  
  39. hdr->name = name;  
  40. hdr->value = value;  
  41. hdr->release_policy = release_policy;  
  42. }  

后面设置头都调用request_set_header这个函数

连接服务器:

Sock = connect_to_host (conn->host,conn->port)

如果是host为ip地址,那么就直接连接,如果不是,首先查找dns cache

Dns_cachehash table(如果给出的是host,就得获得host的ip)

此hash 是通过算法把host字符串算成一个int 的key,然后再求余算索引,然后hash处理冲突才用开放定址法。

创建key算法(key为host,算出来的结果为hash的key

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. static unsigned long  
  2. hash_string_nocase (const void *key)                                                                                                                                 
  3. {  
  4.     const char *p = key;  
  5. unsigned int h = c_tolower (*p);  
  6.   
  7. if (h)  
  8. for (p += 1; *p != '\0'; p++)  
  9. h = (h << 5) - h + c_tolower (*p);  
  10.   
  11. return h;  
  12.  }  

查找hash key 算法是开放定址法,这里就不说了。

Hash表的value为structaddress_list *al

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. struct address_list {  
  2. int count;                    /* number of adrresses */  
  3. ip_address *addresses;        /* pointer to the string of addresses */                                                                                             
  4.   
  5. int faulty;                   /* number of addresses known not to work. */  
  6. bool connected;               /* whether we were able to connect to 
  7.                                 one of the addresses in the list, 
  8.                                  at least once. */  
  9.   
  10. int refcount;                 /* reference count; when it drops to 
  11.          
  12.                            0, the entry is freed. */  
  13. };  
  14. typedef struct {  
  15. /* Address family, one of AF_INET or AF_INET6. */  
  16. int family;  
  17.   
  18. /* The actual data, in the form of struct in_addr or in6_addr: */  
  19. union {  
  20. struct in_addr d4;      /* IPv4 address */  
  21. #ifdef ENABLE_IPV6  
  22. struct in6_addr d6;     /* IPv6 address */  
  23. #endif  
  24. } data;  
  25.   
  26. /* Under IPv6 getaddrinfo also returns scope_id.  Since it's 
  27. Pv6-specific it strictly belongs in the above union, but we put 
  28. it here for simplicity.  */  
  29. #if defined ENABLE_IPV6 && defined HAVE_SOCKADDR_IN6_SCOPE_ID  
  30. int ipv6_scope;  
  31. #endif  
  32. } ip_address;  

以下是hash表提供的接口:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. cache_query(host)       //search  
  2. cache_remove(host)  //delete  
  3. cache_store(host,val)   //insert  

如果在dns hash table中找不到,就调用gethostbyname api来获取host的ip,对每一个ip port进行connect,直到连接成功为止。连接成功host后,把req组包发送出去(request_send)

 

读取回复头:

Head = read_http_response_head(sock)

此时使用了select做为事件超时和MSG_PEEK预先读取内核socket read buffer数据,但是数据不删除,直到找到\r\n\r\n(fd_peek),然后进行实际读取(fd_read)

New 回复数据包:

resp = resp_new (head);

解析数据包

读取body部分:

hs->res = fd_read_body (sock, fp,contlen != -1 ? contlen : 0,

hs->restval, &hs->rd_size, &hs->len,&hs->dltime,flags);


0 0
原创粉丝点击