<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    Jack Jiang

    我的最新工程MobileIMSDK:http://git.oschina.net/jackjiang/MobileIMSDK
    posts - 494, comments - 13, trackbacks - 0, articles - 1

    1、引言

    在文章《理論聯(lián)系實際:Wireshark抓包分析TCP 3次握手、4次揮手過程》中,我們學會了用wireshark來分析TCP的“三次握手,四次揮手”,非常好用。這就是傳說中的錘子,拿著 錘子,看什么都像 釘子!在這本文中,我對將準 HTTP這顆釘子,狠狠地砸下去。。。

    為了對網(wǎng)絡數(shù)據(jù)包的“流轉”有更加深刻的理解,我在docker(遠程)上部署一個服務,支持http方式調用。從客戶端(本地)用http方式請求其中的一個接口,并得到響應數(shù)據(jù)。同時本地通過wireshark抓包,遠程用tcpdump抓包,然后分析過程中的所有通信細節(jié)(悲劇是把美好的東西撕碎給人看,而我則是把復雜的東西撕碎了給人看)。

    本文的主要內容是:先通過工具獲取HTTP通信的數(shù)據(jù)包,再來抽絲剝繭,深入傳輸層二進制的天地里,解密HTTP所有的通信細節(jié)。分析過程中,由點到面,將相關知識串接起來。市面上講HTTP協(xié)議的文章很多,但深入到傳輸層從2進制的角度來解析,則相當少見。保證全篇讀完之后,你對HTTP的理解會上升一個臺階!

    本文稍長,請在看本文時保持耐心。

    (本文同步發(fā)布于:http://www.52im.net/thread-2456-1-1.html

    2、關于作者

    饒全成:畢業(yè)于華中科技大學,中科院計算所碩士,滴滴出行后端研發(fā)工程師。微信公眾號: 碼農桃花源,個人博客:https://www.cnblogs.com/qcrao-2018/

    3、系列文章

    1)本文是系列文章中的第8篇,本系列文章的大綱如下:

    不為人知的網(wǎng)絡編程(一):淺析TCP協(xié)議中的疑難雜癥(上篇)

    不為人知的網(wǎng)絡編程(二):淺析TCP協(xié)議中的疑難雜癥(下篇)

    不為人知的網(wǎng)絡編程(三):關閉TCP連接時為什么會TIME_WAIT、CLOSE_WAIT

    不為人知的網(wǎng)絡編程(四):深入研究分析TCP的異常關閉

    不為人知的網(wǎng)絡編程(五):UDP的連接性和負載均衡

    不為人知的網(wǎng)絡編程(六):深入地理解UDP協(xié)議并用好它

    不為人知的網(wǎng)絡編程(七):如何讓不可靠的UDP變的可靠?

    不為人知的網(wǎng)絡編程(八):從數(shù)據(jù)傳輸層深度解密HTTP》(本文)

    2)如果您覺得本系列文章過于專業(yè),您可先閱讀《網(wǎng)絡編程懶人入門》系列文章,該系列目錄如下:

    網(wǎng)絡編程懶人入門(一):快速理解網(wǎng)絡通信協(xié)議(上篇)

    網(wǎng)絡編程懶人入門(二):快速理解網(wǎng)絡通信協(xié)議(下篇)

    網(wǎng)絡編程懶人入門(三):快速理解TCP協(xié)議一篇就夠

    網(wǎng)絡編程懶人入門(四):快速理解TCP和UDP的差異

    網(wǎng)絡編程懶人入門(五):快速理解為什么說UDP有時比TCP更有優(yōu)勢

    3)《腦殘式網(wǎng)絡編程入門》也適合入門學習,本系列大綱如下:

    腦殘式網(wǎng)絡編程入門(一):跟著動畫來學TCP三次握手和四次揮手

    腦殘式網(wǎng)絡編程入門(二):我們在讀寫Socket時,究竟在讀寫什么?

    腦殘式網(wǎng)絡編程入門(三):HTTP協(xié)議必知必會的一些知識

    腦殘式網(wǎng)絡編程入門(四):快速理解HTTP/2的服務器推送(Server Push)

    4)其它跟HTTP有關的文章:

    從HTTP/0.9到HTTP/2:一文讀懂HTTP協(xié)議的歷史演變和設計思路

    美圖App的移動端DNS優(yōu)化實踐:HTTPS請求耗時減小近半

    一分鐘理解 HTTPS 到底解決了什么問題

    一篇讀懂HTTPS:加密原理、安全邏輯、數(shù)字證書等

    小白必讀:閑話HTTP短連接中的Session和Token

    IM開發(fā)基礎知識補課:正確理解前置HTTP SSO單點登陸接口的原理

    從HTTP到MQTT:一個基于位置服務的APP數(shù)據(jù)通信實踐概述

    基于APNs最新HTTP/2接口實現(xiàn)iOS的高性能消息推送(服務端篇)

    Comet技術詳解:基于HTTP長連接的Web端實時通信技術

    WebSocket詳解(四):刨根問底HTTP與WebSocket的關系(上篇)

    WebSocket詳解(五):刨根問底HTTP與WebSocket的關系(下篇)

    4、在傳輸層捕獲HTTP報文

    4.1 背景介紹

    我手頭現(xiàn)在有一個地理幾何相關的服務,它提供一組接口對外使用。其中有一個接口是Fence2Area. 使用方傳入一個圍欄(由點的列表組成,點由<經度,緯度>表示)、點的坐標系類型(谷歌地圖用的是wgs84, 國內騰訊、高德用的是soso, 而百度用的是另一套自己的坐標系),接口輸出的則是圍欄的面積。

    我請求服務的“Fence2Area”接口,輸入圍欄(fence)頂點(lng, lat)坐標、坐標系類型(coordtype),輸出的則是多邊形的面積(area).

    一次正常的請求示例url, 這個大家都不陌生(我用docker_ip代替真實的ip):

    http://docker_ip:7080/data?cmd=Fence2Area&meta={"caller":"test","TraceId":"test"}&request={"fence":[{"lng":10.2,"lat":10.2}, {"lng":10.2,"lat":8.2}, {"lng":8.2,"lat":8.2}, {"lng":8.2,"lat":10.2}],"coordtype":2}

    請求發(fā)出后,服務器進行處理,之后,客戶端收到返回的數(shù)據(jù)如下:

    {

        "data": {

            "area": 48764135597.842606

        },

        "errstr": ""

    }

    area字段表示面積,errstr表示出錯信息,空說明沒有出錯。

    4.2 抓包

    在真正發(fā)送請求之前,需要進行抓包前的設置。在本地mac,我用wireshark; 而在遠程docker上,我用tcpdump工具。

    mac本地:設置wireshark包過濾器,監(jiān)控本地主機和遠程docker之間的通信。

    ip.addr eq docker_ip

    點擊開始捕獲。

    遠程docker:該服務通過7080端口對外提供,使用如下命令捕獲網(wǎng)絡包:

    tcpdump -w /tmp/testHttp.cap port 7080 -s0

    4.3 請求、捕獲、分析

    準備工作做完,我選了一個神圣的時刻,在本地通過瀏覽器訪問如下url:

    http://docker_ip:7080/data?cmd=Fence2Area&meta={"caller":"test","TraceId":"test"}&request={"fence":[{"lng":10.2,"lat":10.2}, {"lng":10.2,"lat":8.2}, {"lng":8.2,"lat":8.2}, {"lng":8.2,"lat":10.2}],"coordtype":2}

    這樣本地的wireshark和遠程的tcpdump都能抓取到HTTP網(wǎng)絡數(shù)據(jù)包。

    【關閉服務進程】:

    正式請求之前,我們先看一下幾種特殊的情形。

    首先,關閉gcs服務進程,請求直接返回RST報文。

    如上圖,我在請求的時候,訪問服務端的另一個端口5010, 這個端口沒有服務監(jiān)聽,和關閉gcs服務進程是同樣的效果。可以看到,客戶端發(fā)送SYN報文,但直接被遠程docker RST掉了。因為服務端操作系統(tǒng)找不到監(jiān)聽此端口的進程。

    【關閉docker】:

    關閉docker, 由于發(fā)送的SYN報文段得不到響應,因此會進行重試,mac下重試的次數(shù)為10次。

    先每隔1秒重試了5次,再用“指數(shù)退避”的時間間隔重試,2s, 4s, 8s, 16s, 32s. 最后結束。

    【重啟docker】:

    先進行一次正常的訪問,隨后重啟docker。并再次在本地訪問以上url, 瀏覽器這時還是用的上一次的端口,訪問到服務端后,因為它已經重啟了,所以服務端已經沒有這個連接的消息了。因此會返回一個RST報文。

    【正常請求】:

    服務正常啟動,正常發(fā)送請求,這次請求成功,那是當然的,嘿嘿!

    這是在mac上用wireshark捕獲的數(shù)據(jù)包,共7個包,前三個包為3次握手的包,第四個包為HTTP層發(fā)送的請求數(shù)據(jù),第五個包為服務端的TCP 確認報文,第六個包為服務端在HTTP層發(fā)送的響應數(shù)據(jù),第七個包為mac對第六個包的確認報文。

    重點來關注后面幾個包,先看第四個包:

    0x0000:  4500 0295 0000 4000 3606 623b ac17 ccdc

    0x0010:  0a60 5cd4 db9b 1ba8 a59a 46ce 6d03 e87d

    0x0020:  8018 1015 0ee7 0000 0101 080a 2e4c b2ef

    0x0030:  0f20 3acf 4745 5420 2f64 6174 613f 636d

    0x0040:  643d 4665 6e63 6532 4172 6561 266d 6574

    0x0050:  613d 7b25 3232 6361 6c6c 6572 2532 323a

    0x0060:  2532 3274 6573 7425 3232 2c25 3232 5472

    0x0070:  6163 6549 6425 3232 3a25 3232 7465 7374

    0x0080:  2532 327d 2672 6571 7565 7374 3d7b 2532

    0x0090:  3266 656e 6365 2532 323a 5b7b 2532 326c

    0x00a0:  6e67 2532 323a 3130 2e32 2c25 3232 6c61

    0x00b0:  7425 3232 3a31 302e 327d 2c25 3230 7b25

    0x00c0:  3232 6c6e 6725 3232 3a31 302e 322c 2532

    0x00d0:  326c 6174 2532 323a 382e 327d 2c25 3230

    0x00e0:  7b25 3232 6c6e 6725 3232 3a38 2e32 2c25

    0x00f0:  3232 6c61 7425 3232 3a38 2e32 7d2c 2532

    0x0100:  307b 2532 326c 6e67 2532 323a 382e 322c

    0x0110:  2532 326c 6174 2532 323a 3130 2e32 7d5d

    0x0120:  2c25 3232 636f 6f72 6474 7970 6525 3232

    0x0130:  3a32 7d20 4854 5450 2f31 2e31 0d0a 486f

    0x0140:  7374 3a20 3130 2e39 362e 3932 2e32 3132

    0x0150:  3a37 3038 300d 0a55 7067 7261 6465 2d49

    0x0160:  6e73 6563 7572 652d 5265 7175 6573 7473

    0x0170:  3a20 310d 0a41 6363 6570 743a 2074 6578

    0x0180:  742f 6874 6d6c 2c61 7070 6c69 6361 7469

    0x0190:  6f6e 2f78 6874 6d6c 2b78 6d6c 2c61 7070

    0x01a0:  6c69 6361 7469 6f6e 2f78 6d6c 3b71 3d30

    0x01b0:  2e39 2c2a 2f2a 3b71 3d30 2e38 0d0a 5573

    0x01c0:  6572 2d41 6765 6e74 3a20 4d6f 7a69 6c6c

    0x01d0:  612f 352e 3020 284d 6163 696e 746f 7368

    0x01e0:  3b20 496e 7465 6c20 4d61 6320 4f53 2058

    0x01f0:  2031 305f 3133 5f36 2920 4170 706c 6557

    0x0200:  6562 4b69 742f 3630 352e 312e 3135 2028

    0x0210:  4b48 544d 4c2c 206c 696b 6520 4765 636b

    0x0220:  6f29 2056 6572 7369 6f6e 2f31 322e 302e

    0x0230:  3220 5361 6661 7269 2f36 3035 2e31 2e31

    0x0240:  350d 0a41 6363 6570 742d 4c61 6e67 7561

    0x0250:  6765 3a20 7a68 2d63 6e0d 0a41 6363 6570

    0x0260:  742d 456e 636f 6469 6e67 3a20 677a 6970

    0x0270:  2c20 6465 666c 6174 650d 0a43 6f6e 6e65

    0x0280:  6374 696f 6e3a 206b 6565 702d 616c 6976

    0x0290:  650d 0a0d 0a

    我們來逐字節(jié)分析:

    剩余的數(shù)據(jù)部分即為TCP協(xié)議相關的。

    TCP也是20B固定長度+可變長度部分:

    可變長度部分,協(xié)議如下:

    剩下來的就是數(shù)據(jù)部分了。我們一行一行地看。

    因為http是字符流,所以我們先看一下ascii字符集,執(zhí)行命令:

    man ascii

    可以得到ascii碼,我們直接看十六進制的結果:

    把上表的最后一列連起來,就是:

    GET /data?cmd=Fence2Area&meta={%22caller%22:%22test%22,%22TraceId%22:%22test%22}&request={%22fence%22:[{%22lng%22:10.2,%22lat%22:10.2},%20{%22lng%22:10.2,%22lat%22:8.2},%20{%22lng%22:8.2,%22lat%22:8.2},%20{%22lng%22:8.2,%22lat%22:10.2}],%22coordtype%22:2} HTTP/1.1

    Host: 10.96.92.212:7080

    Upgrade-Insecure-Requests: 1

    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

    User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0.2 Safari/605.1.15

    Accept-Language: zh-cn

    Accept-Encoding: gzip, deflate

    Connection: keep-alive

    其中,cr nl表示回車,換行。

    docker收到數(shù)據(jù)后,會回復一個ack包。第四個包的總長度為661字節(jié),去掉IP頭部20字節(jié),TCP頭部固定部分20字節(jié),TCP頭部可選長度為12字節(jié),共52字節(jié),因此TCP數(shù)據(jù)部分總長度為661-52=609字節(jié)。另外,序列號為2778351310.

    再來看第5個包,字節(jié)流如下:

    0x0000:  4500 0034 d28b 4000 4006 8810 0a60 5cd4

    0x0010:  ac17 ccdc 1ba8 db9b 6d03 e87d a59a 492f

    0x0020:  8010 00ec e04e 0000 0101 080a 0f20 3af7

    0x0030:  2e4c b2ef

    剩余的數(shù)據(jù)部分即為TCP協(xié)議相關的。

    TCP也是20B固定長度+可變長度部分:

    可變長度部分,協(xié)議如下:

    數(shù)據(jù)部分為空,這個包僅為確認包。

    再來看第六個包,字節(jié)流如下:

    0x0000:  4500 00f9 d28c 4000 4006 874a 0a60 5cd4

    0x0010:  ac17 ccdc 1ba8 db9b 6d03 e87d a59a 492f

    0x0020:  8018 00ec e113 0000 0101 080a 0f20 3af8

    0x0030:  2e4c b2ef 4854 5450 2f31 2e31 2032 3030

    0x0040:  204f 4b0d 0a41 6363 6573 732d 436f 6e74

    0x0050:  726f 6c2d 416c 6c6f 772d 4f72 6967 696e

    0x0060:  3a20 2a0d 0a44 6174 653a 2054 6875 2c20

    0x0070:  3033 204a 616e 2032 3031 3920 3132 3a32

    0x0080:  333a 3437 2047 4d54 0d0a 436f 6e74 656e

    0x0090:  742d 4c65 6e67 7468 3a20 3438 0d0a 436f

    0x00a0:  6e74 656e 742d 5479 7065 3a20 7465 7874

    0x00b0:  2f70 6c61 696e 3b20 6368 6172 7365 743d

    0x00c0:  7574 662d 380d 0a0d 0a7b 2264 6174 6122

    0x00d0:  3a7b 2261 7265 6122 3a34 3837 3634 3133

    0x00e0:  3535 3937 2e38 3432 3630 367d 2c22 6572

    0x00f0:  7273 7472 223a 2222 7d

    剩余的數(shù)據(jù)部分即為TCP協(xié)議相關的。TCP也是20B固定長度+可變長度部分:

    可變長度部分,協(xié)議如下:

    剩下來的就是數(shù)據(jù)部分了。我們一行一行地看:

    把上表的最后一列連起來,就是:

    HTTP/1.1 200 OK

    Access-Control-Allow-Origin: *

    Date: Thu, 03 Jan 2019 12:23:47 GMT

    Content-Length: 48

    Content-Type: text/plain; charset=utf-8

    {"data":{"area":48764135597.842606},"errstr":""}

    Content-Length: 48,最后一行的長度即為48個字節(jié)。

    最后,第七個包,字節(jié)流如下:

    0x0000:  4500 0034 0000 4000 3606 649c ac17 ccdc

    0x0010:  0a60 5cd4 db9b 1ba8 a59a 492f 6d03 e942

    0x0020:  8010 100f 1eb9 0000 0101 080a 2e4c b314

    0x0030:  0f20 3af8

    剩余的數(shù)據(jù)部分即為TCP協(xié)議相關的。TCP也是20B固定長度+可變長度部分:

    可變長度部分,協(xié)議如下:

    至此,一次完整的http請求的報文就解析完了。感覺如何,是不是很親切?(PS: WTF?看的人都抓狂了,還親切?哈哈)

    5、在應用層學習HTTP協(xié)議

    上面我們把HTTP協(xié)議相關的數(shù)據(jù)從2進制層給解密了,下面我將對照上面的數(shù)據(jù)拆解結果,一步步帶你從應用層深入認識HTTP協(xié)議。

    5.1 整體介紹

    HTTP(Hypertext Transfer Protocol)超文本傳輸協(xié)議,是在互聯(lián)網(wǎng)上進行通信時使用的一種協(xié)議。說得更形象一點:HTTP是現(xiàn)代互聯(lián)網(wǎng)中使用的公共語言。它最著名的應用是用在瀏覽器的服務器間的通信。

    HTTP屬于應用層協(xié)議,底層是靠TCP進行可靠地信息傳輸。

    HTTP在傳輸一段報文時,會以流的形式將報文數(shù)據(jù)的內容通過一條打開的TCP連接按序傳輸。TCP接到上層應用交給它的數(shù)據(jù)流之后,會按序將數(shù)據(jù)流打散成一個個的分段。再交到IP層,通過網(wǎng)絡進行傳輸。另一端的接收方則相反,它們將接收到的分段按序組裝好,交給上層HTTP協(xié)議進行處理。

    5.2 編碼

    我們再來回顧一下:

    在之前的報文拆解過程中,我們看到多了很多%22,其實,0x22是單引號"的ascii值。

    一方面,URL描述的資源為了能通過其他各種協(xié)議傳送,但是有些協(xié)議在傳輸過程中會剝去一些特定的字符;

    另一方面,URL還是可讀的,所以那些不可打印的字符就不能在URL中使用了,比如空格;

    最后,URL還得是完整的,它需要支持所有語言的字符。

    總之,基于很多原因,URL設計者將US-ASCII碼和其轉義序列集成到URL中,通過轉義序列,就可以用US-ASCII字符集的有限子集對任意字符或數(shù)據(jù)進行編碼了。

    轉義的方法:百分號(%)后跟著兩個表示ASCII碼的十六進制數(shù)。比如:

    所以上面在瀏覽器發(fā)送給服務器的URL進行了非“安全字符”編碼,也就不奇怪了吧?

    在URL中,當上面的保留字符用在保留用途之外的場合時,需要對URL進行編碼。

    5.3 MIME類型

    響應數(shù)據(jù)中,我們注意到有一個首部:

    Content-Type: text/plain; charset=utf-8

    互聯(lián)網(wǎng)上有數(shù)千種不同的數(shù)據(jù)類型,HTTP給每種對象都打上了MIME(Multipurpose Internet Media Extension, 多用途因特網(wǎng)郵件擴展)標簽,也就是響應數(shù)據(jù)中的Content-Type. MIME本來是用在郵件協(xié)議中的,后來被移植到了HTTP中。瀏覽器從服務器上取回了一個對象時,會去查看MIME類型,從而得知如何處理這種對象,是該展示圖片,還是調用聲卡播放聲音。

    MIME通過斜杠來標識對象的主類型和其中的特定的子類型,下表展示了一些常見的類型,其中的實體主體是指body部分:

    5.4 URI/URL/URN

    URI(Uniform Resource Identifier, 統(tǒng)一資源標識符)表示服務器資源,URL(Uniform Resource Locator, 統(tǒng)一資源定位符)和URN(Uniform Resource Name, 統(tǒng)一資源名)是URI的具體實現(xiàn)。URI是一個通用的概念,由兩個主要的子集URL和URN構成,URL通過位置、URN通過名字來標識資源。

    URL定義了資源的位置,表示資源的實際地址,在使用URL的過程中,如果URL背后的資源發(fā)生了位置移動,訪問者就找不到它了。這個時候就要用到URN了,它給定資源一個名字,無論它移動到哪里,都可以通過這個名字來訪問到它,簡直完美!

    URL通常的格式是:

    協(xié)議方案+服務器地址+具體的資源路徑

    協(xié)議方案(scheme),如 http, ftp,告知web客戶端怎樣訪問資源);服務器地址,如 www.oreilly.com; 具體的資源路徑,如 index.html.

    5.5 HTTP方法

    HTTP支持幾種不同的請求方法,每種方法對服務器要求的動作不同,如下圖是幾種常見的方法:

    HEAD方法只獲取頭部,不獲取數(shù)據(jù)部分。通過頭部可以獲取比如資源的類型(Content-Type)、資源的長度(Content-Length)這些信息。這樣,客戶端可以獲取即將請求資源的一些情況,可以做到心中有數(shù)。

    1)POST用于向服務器發(fā)送數(shù)據(jù),常見的是提交表單;

    2)PUT用于向服務器上的資源存儲數(shù)據(jù)。

    5.6 狀態(tài)碼

    每條HTTP的響應報文都會帶上一個三位數(shù)字的狀態(tài)碼和一條解釋性的“原因短語”,通知客戶端本次請求的狀態(tài),幫助客戶端快速理解事務處理結果,最常見的是:

    200 OK 

    404 Not Found

    500 Internal Server Error

    我們平時使用瀏覽器的時候,很多的錯誤碼其實是由瀏覽器處理的,我們感知不到。但是404 Not Found會穿透重重迷霧,來到我們面前,為何?那是因為他對我們愛的深沉啊!

    客戶端可以據(jù)此狀態(tài)碼,決定下一步的行動(如重定向等)。

    三位數(shù)字的第一位表示分類:

    5.7 報文格式

    HTTP報文實際上是由一行行的字符串組成的,每行字符串的末尾用\r\n分隔,人類可以很方便的閱讀。順便說一句,不是所有的協(xié)議都對人類這么友好的,像thrift協(xié)議,直接甩一堆字節(jié)給你,告訴你說0x0001表示調用方法,諸如此類的,你只能對著一個十六進制的數(shù)據(jù)塊一個個地去“解碼”。不可能像HTTP協(xié)議這樣,直接將字符編碼,人類可以直接讀懂。

    舉個簡單的請求報文和響應報文的格式的例子:

    實際上,請求報文也是可以有body(主體)部分的。請求報文是由請求行(request line)、請求頭部(header)、空行、請求數(shù)據(jù)四個部分組成。唯一要注意的一點就是,請求報文即使body部分是空的,請求頭部后的回車換行符也是必須要有的。

    響應報文的格式和請求報文的格式類似:

    請求報文、響應報文的起始行和響應頭部里的字段都是文本化、結構化的。而請求body卻可以包含任意二進制數(shù)據(jù)(如圖片、視頻、軟件等),當然也可以包含文本。

    有些首部是通用的,有些則是請求或者響應報文才會有的。

    順便提一下, 用telnet直連服務器的http端口,telnet命令會建立一條TCP通道,然后就可以通過這個通道直接發(fā)送HTTP請求數(shù)據(jù),獲取響應數(shù)據(jù)了。

    6、HTTP協(xié)議進階

    6.1 代理

    HTTP的代理服務器既是Web服務器,又是Web客戶端。

    使用代理可以“接觸”到所有流過的HTTP流量,代理可以對其進行監(jiān)視和修改。常見的就是對兒童過濾一些“成人”內容;網(wǎng)絡工程師會利用代理服務器來提高安全性,它可以限制哪些應用層的協(xié)議數(shù)據(jù)可以通過,過濾“病毒”等數(shù)據(jù);代理可以存儲緩存的文件,直接返回給訪問者,無需請求原始的服務器資源;對于訪問慢速網(wǎng)絡上的公共內容時,可以假扮服務器提供服務,從而提高訪問速度;這被稱為反向代理;可以作為內容路由器,如對付費用戶,則將請求導到緩存服務器,提高訪問速度;可以將頁面的語言轉換到與客戶端相匹配,這稱為內容轉碼器; 匿名代理會主動從HTTP報文中刪除身份相關的信息,如User-Agent, Cookie等字段。

    現(xiàn)實中,請求通過以下幾種方式打到代理服務器上去:

    報文每經過一個中間點(代理或網(wǎng)關),都需要在首部via字段的末尾插入一個可以代表本節(jié)點的獨特的字符串,包含實現(xiàn)的協(xié)議版本和主機地址。注意下圖中的via字段。

    請求和響應的報文傳輸路徑通常都是一致的,只不過方向是相反的。因此,響應報文上的via字段表示的中間節(jié)點的順序是剛好相反的。

    6.2 緩存

    當有很多請求訪問同一個頁面時,服務器會多次傳輸同一份數(shù)據(jù),這些數(shù)據(jù)重復地在網(wǎng)絡中傳輸著,消耗著大量帶寬。如果將這些數(shù)據(jù)緩存下來,就可以提高響應速度,節(jié)省網(wǎng)絡帶寬了。

    大部分緩存只有在客戶端發(fā)起請求,并且副本已經比較舊的情況下才會對副本的新鮮度進行檢測。最常用的請求首部是If-Modified-Since, 如果在xx時間(此時間即為If-Modified-Since的值)之后內容沒有變化,服務器會回應一個304 Not Modified. 否則,服務器會正常響應,并返回原始的文件數(shù)據(jù),而這個過程中被稱為再驗證命中。

    再驗證可能出現(xiàn)命中或未命中的情況:

    1)未命中時,服務器回復200 OK,并且返回完整的數(shù)據(jù);

    2)命中時,服務器回復304 Not Modified。

    還有一種情況,緩存被刪除了,那么根據(jù)響應狀態(tài)碼,緩存服務器也會刪除自己緩存的副本。

    順帶提一句,若要在項目中使用緩存,就一定要關注緩存命中比例。若命中比例不高,就要重新考慮設置緩存的必要性了。

    緩存服務器返回響應的時候,是基于已緩存的服務器響應的首部,再對一些首部字段做一些微調。比如向其中插入新鮮度信息(如Age, Expires首部等),而且通常會包含一個via首部來說明緩存是由一個緩存代理提供的。注意,這時不要修改Date字段,它表示原始服務器最初構建這條響應的日期。

    HTTP通過文檔過期機制和服務器再驗證機制保持已緩存數(shù)據(jù)和服務器間的數(shù)據(jù)充分一致。

    文檔過期通過如下首部字段來表示緩存的有效期:

    當上面兩個字段暗示的過期時間已到,需要向服務器再次驗證文檔的新鮮度。如果這時緩存仍和服務器上的原始文檔一致,緩存只需要更新頭部的相關字段。如上表中提到的Expires字段等。

    為了更好的節(jié)省網(wǎng)絡流量,緩存服務器可以通過相關首部向原始服務器發(fā)送一個條件GET請求, 這樣只有在緩存真正過期的情況下,才會返回原始的文檔,否則只會返回相關的首部。

    條件GET請求會用到如下的字段:

    6.3 cookie

    cookie是服務器“貼在”客戶端身上的標簽,由客戶端維護的狀態(tài)片段,并且只會回送給合適的站點。

    有兩類cookie: 

    1)會話cookie、持久cookie. 會話cookie在退出瀏覽器后就被刪除了;

    2)而持久cookie則保存在硬盤中,計算機重啟后仍然存在。

    服務器在給客戶端的響應字段首部加上Set-cookie或Set-cookie2, 值為名字=值的列表,即可以包含多個字段。當下次瀏覽器再次訪問到相同的網(wǎng)站時,會將這些字段通過Cookie帶上。cookie中保留的內容是服務器給此客戶端打的標簽,方便服務進行追蹤的識別碼。瀏覽器會將cookie以特定的格式存儲在特定的文件中。

    瀏覽器只會向產生這條cookie的站點發(fā)生cookie. Set-cookie字段的值會包含domain這個字段,告知瀏覽器可以把這條cookie發(fā)送給給相關的匹配的站點。path字段也是相似的功能。

    如i瀏覽器收到如下的cookie:

    Set-cookie: user="mary"; domain="stefno.com"

    那么瀏覽器在訪問任意以stefno.com結尾的站點都會發(fā)送:

    Cookie: user="mary"

    6.4 實體和編碼

    響應報文中的body部分傳輸?shù)臄?shù)據(jù)本質上都是二進制。我們從上面的報文數(shù)據(jù)也可以看出來,都是用十六進制數(shù)來表示,關鍵是怎么解釋這塊內容。

    如果Content-Type定義是text/plain, 那說明body內容就是文本,我們直接按文本編碼來解釋;如果Content-Type定義是image/png, 說明body部分是一幅圖片,那我們就按圖片的格式去解釋數(shù)據(jù)。

    Content-Length標示報文主體部分的數(shù)據(jù)長度大小,如果內容是壓縮的,那它表示的就是壓縮后的大小。另外,Content-Length在長連接的情況下,可以對多個報文進行正確地分段。所以,如果沒有采用分塊編碼,響應數(shù)據(jù)中必須帶上Content-Length字段。分塊編碼的情形中,數(shù)據(jù)被拆分成很多小塊,每塊都有大小說明。因此,任何帶有主體部分的報文(請求或是響應)都應帶上正確的Content-Length首部。

    HTTP的早期版本采用關閉連接的方式來劃定報文的結束。這帶來的問題是顯而易見的:客戶端并不能分清是因為服務器正常結束還是中途崩潰了。這里,如果是客戶端用關閉來表示請求報文主體部分的結束,是不可取的,因為關閉之后,就無法獲取服務器的響應了。當然,客戶端可以采用半關閉的方式,只關閉數(shù)據(jù)發(fā)送方向,但是很多服務器是不識別的,會把半關閉當成客戶端要成服務器斷開來處理。

    HTTP報文在傳輸?shù)倪^程中可能會遭到代理或是其他通信實體的無意修改,為了讓接收方知道這種情況,服務器會對body部分作一個md5, 并把值放到Content-MD5這個字段中。但是,如果中間的代理即修改了報文主體,又修改了md5, 就不好檢測了。因此規(guī)定代理是不能修改Content-MD5首部的。這樣,客戶端在收到數(shù)據(jù)后,先進行解碼,再算出md5, 并與Content-MD5首部進行比較。這主要是防止代理對報文進行了無意的改動。

    HTTP在發(fā)送內容之前需要對其進行編碼,它是對報文主體進行的可逆變換。比如將報文用gzip格式進行壓縮,減少傳輸時間。

    常見的編碼類型如下:

    當然,客戶端為了避免服務器返回自己不能解碼的數(shù)據(jù),請求的時候,會在Accept-Encoding首部里帶上自己支持的編碼方式。如果不傳輸?shù)脑挘J可以接受任何編碼方式。

    上面提到的編碼是內容編碼,它只是在響應報文的主體報文將原始數(shù)據(jù)進行編碼,改變的是內容的格式。還有另一種編碼:傳輸編碼。它與內容無關,它是為了改變報文數(shù)據(jù)在網(wǎng)絡上傳輸?shù)姆绞健鬏斁幋a是在HTTP 1.1中引入的一個新特性。

    通常,服務器需要先生成數(shù)據(jù),再進行傳輸,這時,可以計算數(shù)據(jù)的長度,并將其編碼到Content-Length中。但是,有時,內容是動態(tài)生成的,服務器希望在數(shù)據(jù)生成之前就開始傳輸,這時,是沒有辦法知道數(shù)據(jù)大小的。這種情況下,就要用到傳輸編碼來標注數(shù)據(jù)的結束的。

    HTTP協(xié)議中通過如下兩個首部來描述和控制傳輸編碼:

    分塊編碼的報文形式是這樣的:

    每個分塊包含一個長度值(十六進制,字節(jié)數(shù))和該分塊的數(shù)據(jù)。用于區(qū)隔長度值和數(shù)據(jù)。長度值不包含分塊中的任何序列。最后一個分塊,用長度值0來表示結束。注意報文首部包含一個Trailer: Content-MD5, 所以在緊跟著最后一個報文結束之后,就是一個拖掛。其他如,Content-Length, Trailer, Transfer-Encoding也可以作為拖掛。

    內容編碼和傳輸編碼是可以結合起來使用的。

    6.5 國際化支持

    HTTP為了支持國際化的內容,客戶端要告知服務器自己能理解的何種語言,以及瀏覽器上安裝了何種字母表編碼算法。這通過Accept-Charset和Accept-Language首部實現(xiàn)。

    比如:

    Accept-Language: fr, en;q=0.8

    Accept-Charset: iso-8859-1, utf-8

    表示:客戶端接受法語(fr, 優(yōu)先級默認為1.0)、英語(en, 優(yōu)先級為0.8),支持iso-8859-1, utf-8兩種字符集編碼。服務器則會在Content-Type首部里放上charset.

    本質上,HTTP報文的body部分存放的就是一串二進制碼,我們先把二進制碼轉換成字符代碼(如ascii是一個字節(jié)表示一個字符,而utf-8則表示一個字符的字節(jié)數(shù)不定,每個字符1~6個字節(jié)),之后,用字符代碼去字符集中找到對應的元素。

    比較常見的字符集是US-ASCII: 這個字符集是所有字符集的始祖,早在1968年就發(fā)布了標準。ASCII碼的代碼值從0到127, 只需要7個bit位就可以覆蓋代碼空間。HTTP報文的首部、URL使用的字符集就是ASCII碼。可以再看下上文報文分析部分的acsii碼集。

    US-ASCII是把每個字符編碼成固定的7位二進制值。UTF-8則是無固定的編碼方案。第一個字節(jié)的高位用來表示編碼后的字符所用的字節(jié)數(shù)(如果所用的字節(jié)數(shù)是5,則第一個字節(jié)前5bit都是1,第6bit是0),所需的后續(xù)的字節(jié)都含有6位的代碼值,前兩個bit位是用10標識。

    舉個例子,漢字“嚴”的Unicode編碼為4E25(100111000100101), 共有15位,落在上表中的第三行,因此“嚴”的編碼就需要三個字節(jié)。將100111000100101填入上表中的c位即可。因此,嚴的UTF-8編碼是11100100 10111000 10100101,轉換成十六進制就是E4B8A5. 比如我在谷歌搜索框里搜索“嚴”字,google發(fā)出的請求如下:

    https://www.google.com.hk/search?q=%E4%B8%A5&oq=%E4%B8%A5&aqs=chrome..69i57j0l5.3802j0j4&sourceid=chrome&ie=UTF-8&gws_rd=cr

    q=%E4%B8%A5 這個就是搜索的詞了。

    6.6 重定向與負載均衡

    Web內容通常分散地分布在很多地方,這可以防止“單點故障”,萬一某個地方發(fā)生地震了,機房被毀了,那還有其他地方的機房可以提供服務。一般都會有所謂的“雙活”,“多活”,所謂狡兔三窟嘛。

    這樣,用戶的請求會根據(jù)負載均衡的原則,被重定向到它應該去的地方。

    HTTP重定向:

    服務器收到客戶端請求后,向客戶端返回一條帶有狀態(tài)碼302重定向的報文,告訴他們應該去其他的地方試試。web站點將重定向看成一種簡單的負載均衡策略來使用,重定向服務器找到可用的負載最小的機器,由于服務器知道客戶端的地址,理論上來說,可以做到最優(yōu)的重定向選擇。

    當然,缺點也是顯而易見的,由于客戶端要發(fā)送兩次請求,因此會增加耗時。

    DNS重定向:

    DNS將幾個IP地址關聯(lián)到一個域上,采用算法決定返回的IP地址。可以是簡單的輪轉;也可以是更高級的算法,如返回負載最輕的服務器的IP地址,稱為負載均衡算法;如果考慮地理位置,返回給客戶端最近位置的地址,稱為鄰接路由算法;還有一種是繞過出現(xiàn)故障的地址,稱為故障屏蔽算法。

    DNS服務器總是會返回所有的IP地址,但是DNS客戶端一般只會使用第一個IP地址,而且會緩存下來,之后會一直用這個地址。所以,DNS輪轉通常不會平衡單個客戶端的負載。但是,由于DNS服務器對于不同的請求,總是會返回輪轉后的IP地址列表,因此,會把負載分散到多個客戶端。

    6.7 HTTP連接

    HTTP連接是HTTP報文傳輸?shù)年P鍵通道。

    【并行連接】:

    對于一個頁面上同時出現(xiàn)多個對象的時候,如果瀏覽器并行地打開多個連接,同時去獲取這些對象,多個連接的TCP握手時延可以進行重疊,速度會快起來。

    如一個包含3張圖片的頁面,瀏覽器要發(fā)送4次HTTP請求來獲取頁面。1個用于頂層的HTML頁面,3個用于圖片。

    如果采用串行方式,那么連接時延會進行疊加:

    采用并行連接之后:

    但是并行連接也不絕對提升速度,如果一個頁面有數(shù)百個內嵌對象,那要啟動數(shù)百個連接,對服務器的性能也是非常大的挑戰(zhàn)。所以,通常瀏覽器會限制并行連接的總數(shù)據(jù)在一個較小的值,通常是4個,而且服務端可以隨意關閉客戶端超量的連接。

    另一方面,如果客戶端網(wǎng)絡帶寬較小,每個連接都會去爭搶有限的帶寬,每個連接都會獲取較小的速度,即每個對象都會以較小的速度去加載。這樣,并行連接帶來的速度提升就會比較小,甚至沒有提升。

    【持久連接】:

    持久連接即HTTP的keep-alive機制。

    我們知道HTTP請求是“請求-應答”模式,每次請求-應答都要新建一個連接,完成之后要斷開連接。HTTP是無狀態(tài)的,連接之間沒有任何關系。

    HTTP是應用層協(xié)議,TCP是傳輸層協(xié)議。HTTP底層仍然采用TCP進行傳輸數(shù)據(jù)。TCP為HTTP提供了一層可靠的比特傳輸通道。HTTP一般交換的數(shù)據(jù)都不大,而每次連接都要進行TCP三次握手,很大一部分時間都消耗在這上面,有時候甚至能達到50%。如果能復用連接,就可以減少由于TCP三次握手所帶來的時延。

    HTTP 1.1默認開啟keep-alive機制,從上面抓到的包也可以看到。這樣,數(shù)據(jù)傳輸完成之后保持TCP連接不斷開,之后同域名下復用連接,繼續(xù)用這個通道傳輸數(shù)據(jù)。服務器在響應一個請求后,可以保持這個連接keep-alive timeout的時間,在這個時間內沒有請求,則關閉此連接;否則,重新開始倒計時keep-alive timeout時間。

    HTTP有keep-alive機制,目的是可以在一個TCP連接上傳輸多個HTTP事務,以此提高通信效率。底層的TCP其實也有keep-alive機制,它是為了探測TCP連接的活躍性。TCP層的keepalive可以在任何一方設置,可以是一端設置、兩端同時設置或者兩端都沒有設置。新建socket的時候需要設置,從而使得協(xié)議棧調用相關函數(shù)tcp_set_keepalive,來激活連接的keep-alive屬性。

    當網(wǎng)絡兩端建立了TCP連接之后,閑置(雙方沒有任何數(shù)據(jù)流發(fā)送往來)時間超過tcp_keepalive_time后,服務器內核就會嘗試向客戶端發(fā)送偵測包,來判斷TCP連接狀況(有可能客戶端崩潰、強制關閉了應用、主機不可達等等)。如果沒有收到對方的回答(ack包),則會在 tcp_keepalive_intvl后再次嘗試發(fā)送偵測包,直到收到對方的ack,如果一直沒有收到對方的ack,一共會嘗試 tcp_keepalive_probes次,每次的間隔時間在這里分別是15s, 30s, 45s, 60s, 75s。如果嘗試tcp_keepalive_probes次后,依然沒有收到對方的ack包,則會丟棄該TCP連接。TCP連接默認閑置時間是2小時,一般設置為30分鐘足夠了。

    【管道化連接】:

    在keep-alive的基礎上,我們可以做地更進一步,在響應到達之前,我們將多條請求按序放入請求隊列,服務端在收到請求后,必須按照順序對應請求的響應。但由于網(wǎng)絡環(huán)境非常復雜,因此即使請求是按順序發(fā)送的,也不一定是按順序到達服務端的。而且就算是服務端按序處理的,也不一定是按序返回給客戶端,所以最好是在響應中附帶一些可以標識請求的參數(shù)。

    為了安全起見,管道化的連接只適合“冪等”的請求,一般我們認為:GET/HEAD/PUT/DELETE/TRACE/OPTIONS等方法都是冪等的。

    7、本文小結

    以上,就是所有HTTP的通信細節(jié)了,足夠在日常開發(fā) 作中使用了。更多沒有涉及的細節(jié)可以在用到的時候再去仔細研究。

    文章看完了,不知道你對HTTP的理解有沒有更上一層樓?歡迎一起交流探討。

    8、參考資料

    [1]【http長連接】https://www.cnblogs.com/cswuyg/p/3653263.html

    [2]【http/tcp keep alive】https://segmentfault.com/a/1190000012894416

    [3]【http/tcp keep alive】http://www.nowamagic.net/academy/detail/23350305

    [4]【http/tcp keep alive】https://laravel-china.org/articl ... n-the-http-protocol

    [5]【tcp keep alive】http://blog.51cto.com/zxtong/1788252

    [6]【http權威指南】https://book.douban.com/subject/10746113/

    [7]【HTTP狀態(tài)碼】https://www.cnblogs.com/starof/p/5035119.html

    [8]【HTTP協(xié)議】https://www.cnblogs.com/ranyonsue/p/5984001.html

    [9]【HTTP狀態(tài)分類】http://www.runoob.com/http/http-status-codes.html

    [10]【url編碼】http://www.ruanyifeng.com/blog/2010/02/url_encoding.html

    附錄:更多網(wǎng)絡編程文章

    TCP/IP詳解 - 第11章·UDP:用戶數(shù)據(jù)報協(xié)議

    TCP/IP詳解 - 第17章·TCP:傳輸控制協(xié)議

    TCP/IP詳解 - 第18章·TCP連接的建立與終止

    TCP/IP詳解 - 第21章·TCP的超時與重傳

    技術往事:改變世界的TCP/IP協(xié)議(珍貴多圖、手機慎點)

    通俗易懂-深入理解TCP協(xié)議(上):理論基礎

    通俗易懂-深入理解TCP協(xié)議(下):RTT、滑動窗口、擁塞處理

    理論經典:TCP協(xié)議的3次握手與4次揮手過程詳解

    理論聯(lián)系實際:Wireshark抓包分析TCP 3次握手、4次揮手過程

    計算機網(wǎng)絡通訊協(xié)議關系圖(中文珍藏版)

    UDP中一個包的大小最大能多大?

    P2P技術詳解(一):NAT詳解——詳細原理、P2P簡介

    P2P技術詳解(二):P2P中的NAT穿越(打洞)方案詳解

    P2P技術詳解(三):P2P技術之STUN、TURN、ICE詳解

    通俗易懂:快速理解P2P技術中的NAT穿透原理

    高性能網(wǎng)絡編程(一):單臺服務器并發(fā)TCP連接數(shù)到底可以有多少

    高性能網(wǎng)絡編程(二):上一個10年,著名的C10K并發(fā)連接問題

    高性能網(wǎng)絡編程(三):下一個10年,是時候考慮C10M并發(fā)問題了

    高性能網(wǎng)絡編程(四):從C10K到C10M高性能網(wǎng)絡應用的理論探索

    高性能網(wǎng)絡編程(五):一文讀懂高性能網(wǎng)絡編程中的I/O模型

    高性能網(wǎng)絡編程(六):一文讀懂高性能網(wǎng)絡編程中的線程模型

    技術掃盲:新一代基于UDP的低延時網(wǎng)絡傳輸層協(xié)議——QUIC詳解

    讓互聯(lián)網(wǎng)更快:新一代QUIC協(xié)議在騰訊的技術實踐分享

    現(xiàn)代移動端網(wǎng)絡短連接的優(yōu)化手段總結:請求速度、弱網(wǎng)適應、安全保障

    聊聊iOS中網(wǎng)絡編程長連接的那些事

    移動端IM開發(fā)者必讀(一):通俗易懂,理解移動網(wǎng)絡的“弱”和“慢”

    移動端IM開發(fā)者必讀(二):史上最全移動弱網(wǎng)絡優(yōu)化方法總結

    IPv6技術詳解:基本概念、應用現(xiàn)狀、技術實踐(上篇)

    IPv6技術詳解:基本概念、應用現(xiàn)狀、技術實踐(下篇)

    從HTTP/0.9到HTTP/2:一文讀懂HTTP協(xié)議的歷史演變和設計思路

    以網(wǎng)游服務端的網(wǎng)絡接入層設計為例,理解實時通信的技術挑戰(zhàn)

    邁向高階:優(yōu)秀Android程序員必知必會的網(wǎng)絡基礎

    全面了解移動端DNS域名劫持等雜癥:技術原理、問題根源、解決方案等

    美圖App的移動端DNS優(yōu)化實踐:HTTPS請求耗時減小近半

    Android程序員必知必會的網(wǎng)絡通信傳輸層協(xié)議——UDP和TCP

    IM開發(fā)者的零基礎通信技術入門(一):通信交換技術的百年發(fā)展史(上)

    IM開發(fā)者的零基礎通信技術入門(二):通信交換技術的百年發(fā)展史(下)

    IM開發(fā)者的零基礎通信技術入門(三):國人通信方式的百年變遷

    IM開發(fā)者的零基礎通信技術入門(四):手機的演進,史上最全移動終端發(fā)展史

    IM開發(fā)者的零基礎通信技術入門(五):1G到5G,30年移動通信技術演進史

    IM開發(fā)者的零基礎通信技術入門(六):移動終端的接頭人——“基站”技術

    IM開發(fā)者的零基礎通信技術入門(七):移動終端的千里馬——“電磁波”

    IM開發(fā)者的零基礎通信技術入門(八):零基礎,史上最強“天線”原理掃盲

    IM開發(fā)者的零基礎通信技術入門(九):無線通信網(wǎng)絡的中樞——“核心網(wǎng)”

    IM開發(fā)者的零基礎通信技術入門(十):零基礎,史上最強5G技術掃盲

    IM開發(fā)者的零基礎通信技術入門(十一):為什么WiFi信號差?一文即懂!

    IM開發(fā)者的零基礎通信技術入門(十二):上網(wǎng)卡頓?網(wǎng)絡掉線?一文即懂!

    IM開發(fā)者的零基礎通信技術入門(十三):為什么手機信號差?一文即懂!

    IM開發(fā)者的零基礎通信技術入門(十四):高鐵上無線上網(wǎng)有多難?一文即懂!

    IM開發(fā)者的零基礎通信技術入門(十五):理解定位技術,一篇就夠

    >> 更多同類文章 ……

    (本文同步發(fā)布于:http://www.52im.net/thread-2456-1-1.html



    作者:Jack Jiang (點擊作者姓名進入Github)
    出處:http://www.52im.net/space-uid-1.html
    交流:歡迎加入即時通訊開發(fā)交流群 215891622
    討論:http://www.52im.net/
    Jack Jiang同時是【原創(chuàng)Java Swing外觀工程BeautyEye】【輕量級移動端即時通訊框架MobileIMSDK】的作者,可前往下載交流。
    本博文 歡迎轉載,轉載請注明出處(也可前往 我的52im.net 找到我)。


    只有注冊用戶登錄后才能發(fā)表評論。


    網(wǎng)站導航:
     
    Jack Jiang的 Mail: jb2011@163.com, 聯(lián)系QQ: 413980957, 微信: hellojackjiang
    主站蜘蛛池模板: 91久久青青草原线免费| 久章草在线精品视频免费观看| 曰批免费视频播放在线看片二| yy一级毛片免费视频| 三上悠亚电影全集免费| 久久不见久久见免费视频7| 一色屋成人免费精品网站| 国产又大又长又粗又硬的免费视频| 亚洲 自拍 另类小说综合图区| 亚洲综合另类小说色区| 91亚洲国产在人线播放午夜| 亚洲中文字幕无码中文| 人人公开免费超级碰碰碰视频| 秋霞人成在线观看免费视频| 亚洲第一成年免费网站| 亚洲国产精品激情在线观看| 亚洲AV电影院在线观看| 亚洲精品无码你懂的| 三年片免费高清版 | 亚洲AV日韩AV永久无码下载| 亚洲大香人伊一本线| 粉色视频免费入口| 久久精品国产这里是免费| 妞干网手机免费视频| 亚洲精品无码久久久久| 亚洲国产成人精品激情| 国产高清视频免费在线观看| a拍拍男女免费看全片| 国产成人免费全部网站| 久久99国产亚洲精品观看| 亚洲狠狠色丁香婷婷综合| a级毛片在线免费看| 免费羞羞视频网站| 久久亚洲国产伦理| 日日摸日日碰夜夜爽亚洲| 久久精品毛片免费观看| 亚洲福利精品电影在线观看| 亚洲欧洲春色校园另类小说| 一进一出60分钟免费视频| 一二三四在线播放免费观看中文版视频 | 国产99久久久国产精免费|