分析wget與curl發送web請求方式的區別

首先申明標題黨!

前幾天,有一張圖片訪問404(從第三方站點拉去),後來查到原因是下載超過5s(wget帶了超時參數--timeout=5),所以下載失敗。但是直接訪問原圖又是非常快,基本感覺不到延時。開始懷疑是服務器網絡原因,用host獲取該域名的ip地址,無法ping通。

初步結論是:網絡原因,無法ping通

服務器可能會設置禁ping,於是我就用wget不帶超時下載了一次,發現確實可以下載,只是耗時非常嚴重,如圖:

wget下載耗時

花了20s,纔將此圖完整下下來。換成curl來下載此圖,結果如圖:

結果就很明顯,curl僅用1s的時間下載此圖,而wget卻用了20s才做完相同的事兒。我這並不是說curl的性能優於wget啊,換成其他網站url,時間消耗就差不多。那到底是什麼原因才導致瞭如此巨大的差異了,勾起了我的好奇心。

 

用strace、tcpdump分別跟蹤了wget和curl,摘取了相對核心的地方來做分析。

wget的strace結果

由於strace的內容比較多比較雜,爲了方便分析摘出了比較核心的幾處

  1. 11:52:37.877356 execve("/usr/bin/wget", ["wget""http://newpic.jxnews.com.cn/0/11"], [/* 30 vars */]) = 0 
  2.  
  3. 11:52:37.899984 socket(PF_INET, SOCK_DGRAM, IPPROTO_IP) = 3 
  4. 11:52:37.900043 connect(3, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("10.55.21.254")}, 28) = 0 
  5. 11:52:37.900114 fcntl(3, F_GETFL)       = 0x2 (flags O_RDWR) 
  6. 11:52:37.900165 fcntl(3, F_SETFL, O_RDWR|O_NONBLOCK) = 0 
  7. 11:52:37.900215 poll([{fd=3, events=POLLOUT}], 1, 0) = 1 ([{fd=3, revents=POLLOUT}]) 
  8. 11:52:37.900284 sendto(3, "\353J\1\0\0\1\0\0\0\0\0\0\6newpic\6jxnews\3com\2c"..., 38, MSG_NOSIGNAL, NULL, 0) = 38 
  9. 11:52:37.900773 poll([{fd=3, events=POLLIN}], 1, 5000) = 0 (Timeout) 
  10. 11:52:47.900462 poll([{fd=3, events=POLLOUT}], 1, 0) = 1 ([{fd=3, revents=POLLOUT}]) 
  11. 11:52:47.900529 sendto(3, "\353J\1\0\0\1\0\0\0\0\0\0\6newpic\6jxnews\3com\2c"..., 38, MSG_NOSIGNAL, NULL, 0) = 38 
  12. 11:52:47.900606 poll([{fd=3, events=POLLIN}], 1, 5000) = 0 (Timeout) 
  13. 11:52:57.900996 close(3)                = 0 
  14.  
  15.  
  16. 11:52:57.901134 socket(PF_INET, SOCK_DGRAM, IPPROTO_IP) = 3 
  17. 11:52:57.901192 connect(3, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("10.55.21.254")}, 28) = 0 
  18. 11:52:57.901270 fcntl(3, F_GETFL)       = 0x2 (flags O_RDWR) 
  19. 11:52:57.901322 fcntl(3, F_SETFL, O_RDWR|O_NONBLOCK) = 0 
  20. 11:52:57.901374 poll([{fd=3, events=POLLOUT}], 1, 0) = 1 ([{fd=3, revents=POLLOUT}]) 
  21. 11:52:57.901436 sendto(3, "c\237\1\0\0\1\0\0\0\0\0\0\6newpic\6jxnews\3com\2c"..., 50, MSG_NOSIGNAL, NULL, 0) = 50 
  22. 11:52:57.901646 poll([{fd=3, events=POLLIN}], 1, 5000) = 1 ([{fd=3, revents=POLLIN}]) 
  23. 11:52:57.902350 ioctl(3, FIONREAD, [125]) = 0 
  24. 11:52:57.902421 recvfrom(3, "c\237\201\203\0\1\0\0\0\1\0\0\6newpic\6jxnews\3com\2c"..., 1024, 0, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("10.55.21.254")}, [16]) = 125 
  25. 11:52:57.902516 close(3)                = 0 
  26.  
  27.  
  28. 11:52:57.902592 socket(PF_INET, SOCK_DGRAM, IPPROTO_IP) = 3 
  29. 11:52:57.902647 connect(3, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("10.55.21.254")}, 28) = 0 
  30. 11:52:57.902707 fcntl(3, F_GETFL)       = 0x2 (flags O_RDWR) 
  31. 11:52:57.902757 fcntl(3, F_SETFL, O_RDWR|O_NONBLOCK) = 0 
  32. 11:52:57.902808 poll([{fd=3, events=POLLOUT}], 1, 0) = 1 ([{fd=3, revents=POLLOUT}]) 
  33. 11:52:57.902881 sendto(3, "\343\25\1\0\0\1\0\0\0\0\0\0\6newpic\6jxnews\3com\2c"..., 38, MSG_NOSIGNAL, NULL, 0) = 38 
  34. 11:52:57.903027 poll([{fd=3, events=POLLIN}], 1, 5000) = 1 ([{fd=3, revents=POLLIN}]) 
  35. 11:52:57.983323 ioctl(3, FIONREAD, [92]) = 0 
  36. 11:52:57.983394 recvfrom(3, "\343\25\201\200\0\1\0\1\0\2\0\0\6newpic\6jxnews\3com\2c"..., 1024, 0, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("10.55.21.254")}, [16]) = 92 
  37. 11:52:57.983482 close(3)                = 0 
  38.  
  39.  
  40.  
  41. 11:52:57.983584 write(2, "59.52.28.153", 12) = 12 
  42. 11:52:57.983707 write(2, "\n", 1)       = 1 
  43. 11:52:57.983825 open("/usr/share/locale/zh.GBK/LC_MESSAGES/wget.mo", O_RDONLY) = -1 ENOENT (No such file or directory) 
  44. 11:52:57.983905 open("/usr/share/locale/zh/LC_MESSAGES/wget.mo", O_RDONLY) = -1 ENOENT (No such file or directory) 
  45. 11:52:57.983979 write(2, "Connecting to newpic.jxnews.com."..., 55) = 55 
  46. 11:52:57.984073 socket(PF_INET, SOCK_STREAM, IPPROTO_IP) = 3 
  47. 11:52:57.984139 connect(3, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("59.52.28.153")}, 16) = 0 
  48. 11:52:58.045641 write(2, "\322\321\301\254\275\323\241\243\n", 9) = 9 
  49. 11:52:58.045760 select(4, NULL, [3], NULL, {900, 0}) = 1 (out [3], left {900, 0}) 
  50. 11:52:58.045832 write(3, "GET /0/11/84/34/11843462_991618."..., 155) = 155 

在11:52:37這一刻,執行wget命令。第一個較爲重要的動作是做dns解析,向域名服務器10.55.21.254發起了兩次數據包的長度是38字節查詢請求,得到兩次timeout,在11:52:57正式關閉了請求。

系統嘗試向dns服務器10.55.21.254發送長度爲50字節的數據包的查詢請求,並獲得返回結果(這裏無法知道獲知返回結果)。

系統在獲得返回結果後,第四次向10.55.21.254發送請求,獲得返回結果後,得到url的ip地址,發起了http請求。

 

通過已經分析,可以得到幾個結論:

1、對域名解析方式上的差異導致wget和curl的耗時差別巨大

2、第三次改變數據包的查詢,雖然獲取得了返回,但並沒有獲取域名的ip

 

wget的tcpdump結果

tcpdump的結果圖:

相對於strace的日誌,tcpdump的就比較乾淨,爲了方便分析,還是摘除了關鍵部分

  1. 11:52:37.900351 IP wapcms-169.54429 > dns.domain:  60234+ AAAA? newpic.jxnews.com.cn. (38) 
  2. 11:52:47.900568 IP wapcms-169.54429 > dns.domain:  60234+ AAAA? newpic.jxnews.com.cn. (38) 
  3.  
  4. 11:52:57.901476 IP wapcms-169.46106 > dns.domain:  25503+ AAAA? newpic.jxnews.com.cn.localdomain. (50) 
  5. 11:52:57.902233 IP dns.domain > wapcms-169.46106:  25503 NXDomain 0/1/0 (125) 
  6.  
  7. 11:52:57.902924 IP wapcms-169.58634 > dns.domain:  58133+ A? newpic.jxnews.com.cn. (38) 
  8. 11:52:57.983135 IP dns.domain > wapcms-169.58634:  58133 1/2/0 A 59.52.28.153 (92) 

很清晰的與strace的結果對應上了,兩次38字節的數據包timeout,一次50字節的數據包,獲得返回

NXDomain 0/1/0 (125),一次38字節的請求獲得域名的ip地址:59.52.28.153

 

curl的strace結果

 

  1. execve("/usr/bin/curl", ["curl""http://newpic.jxnews.com.cn/0/11""-o""1.jpg"], [/* 31 vars */]) = 0 
  2.         = 0 
  3. socket(PF_INET, SOCK_DGRAM, IPPROTO_IP) = 3 
  4. connect(3, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("10.55.21.254")}, 28) = 0 
  5. fcntl(3, F_GETFL)                       = 0x2 (flags O_RDWR) 
  6. fcntl(3, F_SETFL, O_RDWR|O_NONBLOCK)    = 0 
  7. poll([{fd=3, events=POLLOUT}], 1, 0)    = 1 ([{fd=3, revents=POLLOUT}]) 
  8. sendto(3, "\370T\1\0\0\1\0\0\0\0\0\0\6newpic\6jxnews\3com\2c"..., 38, MSG_NOSIGNAL, NULL, 0) = 38 
  9. poll([{fd=3, events=POLLIN}], 1, 5000)  = 1 ([{fd=3, revents=POLLIN}]) 
  10. ioctl(3, FIONREAD, [92])                = 0 
  11. recvfrom(3, "\370T\201\200\0\1\0\1\0\2\0\0\6newpic\6jxnews\3com\2c"..., 1024, 0, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("10.55.21.254")}, [16]) = 92 
  12. close(3)   
  13.                               = 0 
  14. socket(PF_INET, SOCK_STREAM, IPPROTO_TCP) = 3 
  15. fcntl(3, F_GETFL)                       = 0x2 (flags O_RDWR) 
  16. fcntl(3, F_SETFL, O_RDWR|O_NONBLOCK)    = 0 
  17. connect(3, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("120.203.216.133")}, 16) = -1 EINPROGRESS (Operation now in progress) 
  18. poll([{fd=3, events=POLLOUT}], 1, 300000) = 1 ([{fd=3, revents=POLLOUT}]) 
  19. getsockopt(3, SOL_SOCKET, SO_ERROR, [0], [4]) = 0 
  20. sendto(3, "GET /0/11/84/34/11843462_991618."..., 194, MSG_NOSIGNAL, NULL, 0) = 194 

很明顯的發現,curl通過一次dns查詢就獲取了url的ip地址,然後就發起http請求。

 

curl的tcpdump結果

 

  1. 15:19:39.744520 IP wapcms-169.48990 > dns.domain:  63572+ A? newpic.jxnews.com.cn. (38) 
  2. 15:19:39.744905 IP wapcms-169.38502 > dns.domain:  13836+ PTR? 254.21.55.10.in-addr.arpa. (43) 
  3. 15:19:39.745626 IP dns.domain > wapcms-169.38502:  13836* 1/1/0 (86) 
  4. 15:19:39.793797 IP dns.domain > wapcms-169.48990:  63572 1/2/0 A 120.203.216.133 (92) 
  5. 15:19:39.793890 IP wapcms-169.31975 > dns.domain:  54867+ PTR? 133.216.203.120.in-addr.arpa. (46) 
  6. 15:19:39.848460 IP dns.domain > wapcms-169.31975:  54867 NXDomain 0/1/0 (102) 

 

 

從上面基本上可以得到結論,wget與curl在域名解析上有很大的不同。

 

通過查閱dns協議和tcpdump的輸出數據格式得到結論是:

wget兼容IPv6,而curl不兼容,該url的dns解析20s的時間發生在解析IPv6上

引申:
host命令兼容IPv6 dig不兼容 (均爲不帶任何附加參數)

 追加證明:

wget強制使用IPv4進行dns的結果如圖:

 

 

注:

dns協議見附件,相關附帶知識如下:

tcpdupm中dns數據包格式

域名服務請求的格式是

src>dst:idop?flagsqtypeqclassname(len)

h2opolo.1538>helios.domain:3+A?ucbvax.berkeley.edu.(37)

主機h2opolo訪問helios上的域名服務,詢問和ucbvax.berkeley.edu.關聯的地址記錄(qtype=A).查詢號是 `3'.` +'表明設置了遞歸請求標誌.查詢長度是37字節,不包括UDP和IP頭.查詢操作是普通的Query操作,因此op域可以忽略.如果op設置成其他什麼 東西,它應該顯示在`3'和`+'之間.類似的,qclass是普通的C_IN類型,也被忽略了.其他類型的qclass應該在`A'後面顯示.
Tcpdump 會檢查一些不規則情況,相應的結果作爲補充域放在方括號內:如果某個查詢包含回答,名字服務或管理機構部分,就把ancount,nscount,或 arcount顯示成`[na]',`[nn]'或`[nau]',這裏的n代表相應的數量.如果在第二和第三字節中,任何一個回答位(AA,RA或 rcode)或任何一個`必須爲零'的位被置位,就顯示`[b2&3=x]',這裏的x是報頭第二和第三字節的16進制數.

UDP名字服務回答

名字服務回答的格式是

src>dst:idoprcodeflagsa/n/autypeclassdata(len)

helios.domain>h2opolo.1538:33/3/7A128.32.137.3(273)
helios.domain>h2opolo.1537:2NXDomain*0/1/0(97)


第一個例子裏,helios回答了h2opolo發出的標識爲3的詢問,一共是3個回答記錄,3個名字服務記錄和7個管理結構記錄.第一個回答紀錄的類型是 A(地址),數據是internet地址128.32.137.3.回答的全長爲273字節,不包括UDP和IP報頭.作爲A記錄的class (C_IN)可以忽略op(詢問)和rcode(NoError).
在第二個例子裏,helios對標識爲2的詢問作出域名不存在(NXDomain)的回答,沒有回答記錄,一個名字服務記錄,而且沒有管理結構.
`*'表明設置了權威回答(authoritativeanswer).由於沒有回答記錄,這裏就不顯示type,class和data.

其他標誌字符可以顯示爲`-'(沒有設置遞歸有效(RA))和`|'(設置消息截短(TC)).如果`問題'部分沒有有效的內容,就顯示`[nq]'.

注意名字服務的詢問和回答一般說來比較大,68字節的snaplen可能無法捕捉到足夠的報文內容.如果你的確在研究名字服務的情況,可以使用-s選項增大捕捉緩衝區.`-s128'應該效果不錯了.

 

IPv6的正向解析與反向解析

a. 正向解析  

    IPv4的地址正向解析的資源記錄是“A”記錄。IPv6地址的正向解析目前有兩種資源記錄,即,“AAAA”和“A6”記錄。其中, “AAAA”較早提出<4>,它是對“A”記錄的簡單擴展,由於IP地址由32位擴展到128位,擴大了4倍,所以資源記錄由“A”擴大成4 個“A”。“AAAA”用來表示域名和IPv6地址的對應關係,並不支持地址的層次性。

   “A6”在RFC2874<5>中提出,它是把一個IPv6地址與多個“A6”記錄建立聯繫,每個“A6”記錄都只包含了IPv6地址的一部分,結合後拼裝成一個完整的IPv6地址。“A6”記錄支持一些“AAAA”所不具備的新特性,如地址聚合,地址更改(Renumber)等。

   首先,“A6”記錄方式根據TLA、NLA和SLA的分配層次把128位的IPv6的地址分解成爲若干級的地址前綴和地址後綴,構成了一個地址鏈。每個地址前綴和地址後綴都是地址鏈上的一環,一個完整的地址鏈就組成一個IPv6地址。這種思想符合IPv6地址的層次結構,從而支持地址聚合。

   其次,用戶在改變ISP時,要隨ISP改變而改變其擁有的IPv6地址。如果手工修改用戶子網中所有在DNS中註冊的地址,是一件非常繁瑣的事情。而在用“A6”記錄表示的地址鏈中,只要改變地址前綴對應的ISP名字即可,可以大大減少DNS中資源記錄的修改。並且在地址分配層次中越靠近底層,所需要改動的越少。  

b. 反向解析  

     IPv6反向解析的記錄和IPv4一樣,是“PTR”,但地址表示形式有兩種。一種是用 “.”分隔的半字節16進制數字格式(Nibble Format),低位地址在前,高位地址在後,域後綴是“IP6.INT.”。另一種是二進制串(Bit-string)格式,以“\<”開頭, 16進制地址(無分隔符,高位在前,低位在後)居中,地址後加“>”,域後綴是“IP6.ARPA.”。半字節16進制數字格式與“AAAA”對應,是對IPv4的簡單擴展。二進制串格式與“A6”記錄對應,地址也象“A6”一樣,可以分成多級地址鏈表示,每一級的授權用“DNAME”記錄。和 “A6”一樣,二進制串格式也支持地址層次特性。

     總之,以地址鍊形式表示的IPv6地址體現了地址的層次性,支持地址聚合和地址更改。但是,由於一次完整的地址解析分成多個步驟進行,需要按照地址的分配層次關係到不同的DNS服務器進行查詢。所有的查詢都成功才能得到完整的解析結果。這勢必會延長解析時間,出錯的機會也增加。因此,需要進一步改進DNS地址鏈功能,提高域名解析的速度才能爲用戶提供理想的服務。

 

 



 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章