Jack Jiang

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

          導(dǎo)航

          公告


            ① 即時(shí)通訊開(kāi)發(fā)社區(qū)
            地址: 52im.net
            專(zhuān)業(yè)的資料、社區(qū)

            ② 關(guān)注我的公眾號(hào):

            讓技術(shù)不再封閉

            ③ 我的Github
            地址: 點(diǎn)此進(jìn)入
            好代碼,與大家分享
          <2019年4月>
          31123456
          78910111213
          14151617181920
          21222324252627
          2829301234
          567891011

          常用鏈接

          留言簿(286)

          隨筆檔案

          文章檔案

          搜索

          •  

          最新評(píng)論

          閱讀排行榜

          評(píng)論排行榜

          60天內(nèi)閱讀排行

          1、引言

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

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

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

          本文稍長(zhǎng),請(qǐng)?jiān)诳幢疚臅r(shí)保持耐心。

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

          2、關(guān)于作者

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

          3、系列文章

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

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

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

          不為人知的網(wǎng)絡(luò)編程(三):關(guān)閉TCP連接時(shí)為什么會(huì)TIME_WAIT、CLOSE_WAIT

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

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

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

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

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

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

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

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

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

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

          網(wǎng)絡(luò)編程懶人入門(mén)(五):快速理解為什么說(shuō)UDP有時(shí)比TCP更有優(yōu)勢(shì)

          3)《腦殘式網(wǎng)絡(luò)編程入門(mén)》也適合入門(mén)學(xué)習(xí),本系列大綱如下:

          腦殘式網(wǎng)絡(luò)編程入門(mén)(一):跟著動(dòng)畫(huà)來(lái)學(xué)TCP三次握手和四次揮手

          腦殘式網(wǎng)絡(luò)編程入門(mén)(二):我們?cè)谧x寫(xiě)Socket時(shí),究竟在讀寫(xiě)什么?

          腦殘式網(wǎng)絡(luò)編程入門(mén)(三):HTTP協(xié)議必知必會(huì)的一些知識(shí)

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

          4)其它跟HTTP有關(guān)的文章:

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

          美圖App的移動(dòng)端DNS優(yōu)化實(shí)踐:HTTPS請(qǐng)求耗時(shí)減小近半

          一分鐘理解 HTTPS 到底解決了什么問(wèn)題

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

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

          IM開(kāi)發(fā)基礎(chǔ)知識(shí)補(bǔ)課:正確理解前置HTTP SSO單點(diǎn)登陸接口的原理

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

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

          Comet技術(shù)詳解:基于HTTP長(zhǎng)連接的Web端實(shí)時(shí)通信技術(shù)

          WebSocket詳解(四):刨根問(wèn)底HTTP與WebSocket的關(guān)系(上篇)

          WebSocket詳解(五):刨根問(wèn)底HTTP與WebSocket的關(guān)系(下篇)

          4、在傳輸層捕獲HTTP報(bào)文

          4.1 背景介紹

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

          我請(qǐng)求服務(wù)的“Fence2Area”接口,輸入圍欄(fence)頂點(diǎn)(lng, lat)坐標(biāo)、坐標(biāo)系類(lèi)型(coordtype),輸出的則是多邊形的面積(area).

          一次正常的請(qǐng)求示例url, 這個(gè)大家都不陌生(我用docker_ip代替真實(shí)的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}

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

          {

              "data": {

                  "area": 48764135597.842606

              },

              "errstr": ""

          }

          area字段表示面積,errstr表示出錯(cuò)信息,空說(shuō)明沒(méi)有出錯(cuò)。

          4.2 抓包

          在真正發(fā)送請(qǐng)求之前,需要進(jìn)行抓包前的設(shè)置。在本地mac,我用wireshark; 而在遠(yuǎn)程docker上,我用tcpdump工具。

          mac本地:設(shè)置wireshark包過(guò)濾器,監(jiān)控本地主機(jī)和遠(yuǎn)程docker之間的通信。

          ip.addr eq docker_ip

          點(diǎn)擊開(kāi)始捕獲。

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

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

          4.3 請(qǐng)求、捕獲、分析

          準(zhǔn)備工作做完,我選了一個(gè)神圣的時(shí)刻,在本地通過(guò)瀏覽器訪(fǎng)問(wèn)如下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和遠(yuǎn)程的tcpdump都能抓取到HTTP網(wǎng)絡(luò)數(shù)據(jù)包。

          【關(guān)閉服務(wù)進(jìn)程】:

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

          首先,關(guān)閉gcs服務(wù)進(jìn)程,請(qǐng)求直接返回RST報(bào)文。

          如上圖,我在請(qǐng)求的時(shí)候,訪(fǎng)問(wèn)服務(wù)端的另一個(gè)端口5010, 這個(gè)端口沒(méi)有服務(wù)監(jiān)聽(tīng),和關(guān)閉gcs服務(wù)進(jìn)程是同樣的效果。可以看到,客戶(hù)端發(fā)送SYN報(bào)文,但直接被遠(yuǎn)程docker RST掉了。因?yàn)榉?wù)端操作系統(tǒng)找不到監(jiān)聽(tīng)此端口的進(jìn)程。

          【關(guān)閉docker】:

          關(guān)閉docker, 由于發(fā)送的SYN報(bào)文段得不到響應(yīng),因此會(huì)進(jìn)行重試,mac下重試的次數(shù)為10次。

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

          【重啟docker】:

          先進(jìn)行一次正常的訪(fǎng)問(wèn),隨后重啟docker。并再次在本地訪(fǎng)問(wèn)以上url, 瀏覽器這時(shí)還是用的上一次的端口,訪(fǎng)問(wèn)到服務(wù)端后,因?yàn)樗呀?jīng)重啟了,所以服務(wù)端已經(jīng)沒(méi)有這個(gè)連接的消息了。因此會(huì)返回一個(gè)RST報(bào)文。

          【正常請(qǐng)求】:

          服務(wù)正常啟動(dòng),正常發(fā)送請(qǐng)求,這次請(qǐng)求成功,那是當(dāng)然的,嘿嘿!

          這是在mac上用wireshark捕獲的數(shù)據(jù)包,共7個(gè)包,前三個(gè)包為3次握手的包,第四個(gè)包為HTTP層發(fā)送的請(qǐng)求數(shù)據(jù),第五個(gè)包為服務(wù)端的TCP 確認(rèn)報(bào)文,第六個(gè)包為服務(wù)端在HTTP層發(fā)送的響應(yīng)數(shù)據(jù),第七個(gè)包為mac對(duì)第六個(gè)包的確認(rèn)報(bào)文。

          重點(diǎn)來(lái)關(guān)注后面幾個(gè)包,先看第四個(gè)包:

          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

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

          剩余的數(shù)據(jù)部分即為T(mén)CP協(xié)議相關(guān)的。

          TCP也是20B固定長(zhǎng)度+可變長(zhǎng)度部分:

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

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

          因?yàn)閔ttp是字符流,所以我們先看一下ascii字符集,執(zhí)行命令:

          man ascii

          可以得到ascii碼,我們直接看十六進(jìn)制的結(jié)果:

          把上表的最后一列連起來(lái),就是:

          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表示回車(chē),換行。

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

          再來(lái)看第5個(gè)包,字節(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ù)部分即為T(mén)CP協(xié)議相關(guān)的。

          TCP也是20B固定長(zhǎng)度+可變長(zhǎng)度部分:

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

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

          再來(lái)看第六個(gè)包,字節(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ù)部分即為T(mén)CP協(xié)議相關(guān)的。TCP也是20B固定長(zhǎng)度+可變長(zhǎng)度部分:

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

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

          把上表的最后一列連起來(lái),就是:

          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,最后一行的長(zhǎng)度即為48個(gè)字節(jié)。

          最后,第七個(gè)包,字節(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ù)部分即為T(mén)CP協(xié)議相關(guān)的。TCP也是20B固定長(zhǎng)度+可變長(zhǎng)度部分:

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

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

          5、在應(yīng)用層學(xué)習(xí)HTTP協(xié)議

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

          5.1 整體介紹

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

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

          HTTP在傳輸一段報(bào)文時(shí),會(huì)以流的形式將報(bào)文數(shù)據(jù)的內(nèi)容通過(guò)一條打開(kāi)的TCP連接按序傳輸。TCP接到上層應(yīng)用交給它的數(shù)據(jù)流之后,會(huì)按序?qū)?shù)據(jù)流打散成一個(gè)個(gè)的分段。再交到IP層,通過(guò)網(wǎng)絡(luò)進(jìn)行傳輸。另一端的接收方則相反,它們將接收到的分段按序組裝好,交給上層HTTP協(xié)議進(jìn)行處理。

          5.2 編碼

          我們?cè)賮?lái)回顧一下:

          在之前的報(bào)文拆解過(guò)程中,我們看到多了很多%22,其實(shí),0x22是單引號(hào)"的ascii值。

          一方面,URL描述的資源為了能通過(guò)其他各種協(xié)議傳送,但是有些協(xié)議在傳輸過(guò)程中會(huì)剝?nèi)ヒ恍┨囟ǖ淖址?/p>

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

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

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

          轉(zhuǎn)義的方法:百分號(hào)(%)后跟著兩個(gè)表示ASCII碼的十六進(jìn)制數(shù)。比如:

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

          在URL中,當(dāng)上面的保留字符用在保留用途之外的場(chǎng)合時(shí),需要對(duì)URL進(jìn)行編碼。

          5.3 MIME類(lèi)型

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

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

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

          MIME通過(guò)斜杠來(lái)標(biāo)識(shí)對(duì)象的主類(lèi)型和其中的特定的子類(lèi)型,下表展示了一些常見(jiàn)的類(lèi)型,其中的實(shí)體主體是指body部分:

          5.4 URI/URL/URN

          URI(Uniform Resource Identifier, 統(tǒng)一資源標(biāo)識(shí)符)表示服務(wù)器資源,URL(Uniform Resource Locator, 統(tǒng)一資源定位符)和URN(Uniform Resource Name, 統(tǒng)一資源名)是URI的具體實(shí)現(xiàn)。URI是一個(gè)通用的概念,由兩個(gè)主要的子集URL和URN構(gòu)成,URL通過(guò)位置、URN通過(guò)名字來(lái)標(biāo)識(shí)資源。

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

          URL通常的格式是:

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

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

          5.5 HTTP方法

          HTTP支持幾種不同的請(qǐng)求方法,每種方法對(duì)服務(wù)器要求的動(dòng)作不同,如下圖是幾種常見(jiàn)的方法:

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

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

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

          5.6 狀態(tài)碼

          每條HTTP的響應(yīng)報(bào)文都會(huì)帶上一個(gè)三位數(shù)字的狀態(tài)碼和一條解釋性的“原因短語(yǔ)”,通知客戶(hù)端本次請(qǐng)求的狀態(tài),幫助客戶(hù)端快速理解事務(wù)處理結(jié)果,最常見(jiàn)的是:

          200 OK 

          404 Not Found

          500 Internal Server Error

          我們平時(shí)使用瀏覽器的時(shí)候,很多的錯(cuò)誤碼其實(shí)是由瀏覽器處理的,我們感知不到。但是404 Not Found會(huì)穿透重重迷霧,來(lái)到我們面前,為何?那是因?yàn)樗麑?duì)我們愛(ài)的深沉啊!

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

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

          5.7 報(bào)文格式

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

          舉個(gè)簡(jiǎn)單的請(qǐng)求報(bào)文和響應(yīng)報(bào)文的格式的例子:

          實(shí)際上,請(qǐng)求報(bào)文也是可以有body(主體)部分的。請(qǐng)求報(bào)文是由請(qǐng)求行(request line)、請(qǐng)求頭部(header)、空行、請(qǐng)求數(shù)據(jù)四個(gè)部分組成。唯一要注意的一點(diǎn)就是,請(qǐng)求報(bào)文即使body部分是空的,請(qǐng)求頭部后的回車(chē)換行符也是必須要有的。

          響應(yīng)報(bào)文的格式和請(qǐng)求報(bào)文的格式類(lèi)似:

          請(qǐng)求報(bào)文、響應(yīng)報(bào)文的起始行和響應(yīng)頭部里的字段都是文本化、結(jié)構(gòu)化的。而請(qǐng)求body卻可以包含任意二進(jìn)制數(shù)據(jù)(如圖片、視頻、軟件等),當(dāng)然也可以包含文本。

          有些首部是通用的,有些則是請(qǐng)求或者響應(yīng)報(bào)文才會(huì)有的。

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

          6、HTTP協(xié)議進(jìn)階

          6.1 代理

          HTTP的代理服務(wù)器既是Web服務(wù)器,又是Web客戶(hù)端。

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

          現(xiàn)實(shí)中,請(qǐng)求通過(guò)以下幾種方式打到代理服務(wù)器上去:

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

          請(qǐng)求和響應(yīng)的報(bào)文傳輸路徑通常都是一致的,只不過(guò)方向是相反的。因此,響應(yīng)報(bào)文上的via字段表示的中間節(jié)點(diǎn)的順序是剛好相反的。

          6.2 緩存

          當(dāng)有很多請(qǐng)求訪(fǎng)問(wèn)同一個(gè)頁(yè)面時(shí),服務(wù)器會(huì)多次傳輸同一份數(shù)據(jù),這些數(shù)據(jù)重復(fù)地在網(wǎng)絡(luò)中傳輸著,消耗著大量帶寬。如果將這些數(shù)據(jù)緩存下來(lái),就可以提高響應(yīng)速度,節(jié)省網(wǎng)絡(luò)帶寬了。

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

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

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

          2)命中時(shí),服務(wù)器回復(fù)304 Not Modified。

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

          順帶提一句,若要在項(xiàng)目中使用緩存,就一定要關(guān)注緩存命中比例。若命中比例不高,就要重新考慮設(shè)置緩存的必要性了。

          緩存服務(wù)器返回響應(yīng)的時(shí)候,是基于已緩存的服務(wù)器響應(yīng)的首部,再對(duì)一些首部字段做一些微調(diào)。比如向其中插入新鮮度信息(如Age, Expires首部等),而且通常會(huì)包含一個(gè)via首部來(lái)說(shuō)明緩存是由一個(gè)緩存代理提供的。注意,這時(shí)不要修改Date字段,它表示原始服務(wù)器最初構(gòu)建這條響應(yīng)的日期。

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

          文檔過(guò)期通過(guò)如下首部字段來(lái)表示緩存的有效期:

          當(dāng)上面兩個(gè)字段暗示的過(guò)期時(shí)間已到,需要向服務(wù)器再次驗(yàn)證文檔的新鮮度。如果這時(shí)緩存仍和服務(wù)器上的原始文檔一致,緩存只需要更新頭部的相關(guān)字段。如上表中提到的Expires字段等。

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

          條件GET請(qǐng)求會(huì)用到如下的字段:

          6.3 cookie

          cookie是服務(wù)器“貼在”客戶(hù)端身上的標(biāo)簽,由客戶(hù)端維護(hù)的狀態(tài)片段,并且只會(huì)回送給合適的站點(diǎn)。

          有兩類(lèi)cookie: 

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

          2)而持久cookie則保存在硬盤(pán)中,計(jì)算機(jī)重啟后仍然存在。

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

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

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

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

          那么瀏覽器在訪(fǎng)問(wèn)任意以stefno.com結(jié)尾的站點(diǎn)都會(huì)發(fā)送:

          Cookie: user="mary"

          6.4 實(shí)體和編碼

          響應(yīng)報(bào)文中的body部分傳輸?shù)臄?shù)據(jù)本質(zhì)上都是二進(jìn)制。我們從上面的報(bào)文數(shù)據(jù)也可以看出來(lái),都是用十六進(jìn)制數(shù)來(lái)表示,關(guān)鍵是怎么解釋這塊內(nèi)容。

          如果Content-Type定義是text/plain, 那說(shuō)明body內(nèi)容就是文本,我們直接按文本編碼來(lái)解釋?zhuān)蝗绻鸆ontent-Type定義是image/png, 說(shuō)明body部分是一幅圖片,那我們就按圖片的格式去解釋數(shù)據(jù)。

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

          HTTP的早期版本采用關(guān)閉連接的方式來(lái)劃定報(bào)文的結(jié)束。這帶來(lái)的問(wèn)題是顯而易見(jiàn)的:客戶(hù)端并不能分清是因?yàn)榉?wù)器正常結(jié)束還是中途崩潰了。這里,如果是客戶(hù)端用關(guān)閉來(lái)表示請(qǐng)求報(bào)文主體部分的結(jié)束,是不可取的,因?yàn)殛P(guān)閉之后,就無(wú)法獲取服務(wù)器的響應(yīng)了。當(dāng)然,客戶(hù)端可以采用半關(guān)閉的方式,只關(guān)閉數(shù)據(jù)發(fā)送方向,但是很多服務(wù)器是不識(shí)別的,會(huì)把半關(guān)閉當(dāng)成客戶(hù)端要成服務(wù)器斷開(kāi)來(lái)處理。

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

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

          常見(jiàn)的編碼類(lèi)型如下:

          當(dāng)然,客戶(hù)端為了避免服務(wù)器返回自己不能解碼的數(shù)據(jù),請(qǐng)求的時(shí)候,會(huì)在Accept-Encoding首部里帶上自己支持的編碼方式。如果不傳輸?shù)脑?huà),默認(rèn)可以接受任何編碼方式。

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

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

          HTTP協(xié)議中通過(guò)如下兩個(gè)首部來(lái)描述和控制傳輸編碼:

          分塊編碼的報(bào)文形式是這樣的:

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

          內(nèi)容編碼和傳輸編碼是可以結(jié)合起來(lái)使用的。

          6.5 國(guó)際化支持

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

          比如:

          Accept-Language: fr, en;q=0.8

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

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

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

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

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

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

          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 這個(gè)就是搜索的詞了。

          6.6 重定向與負(fù)載均衡

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

          這樣,用戶(hù)的請(qǐng)求會(huì)根據(jù)負(fù)載均衡的原則,被重定向到它應(yīng)該去的地方。

          HTTP重定向:

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

          當(dāng)然,缺點(diǎn)也是顯而易見(jiàn)的,由于客戶(hù)端要發(fā)送兩次請(qǐng)求,因此會(huì)增加耗時(shí)。

          DNS重定向:

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

          DNS服務(wù)器總是會(huì)返回所有的IP地址,但是DNS客戶(hù)端一般只會(huì)使用第一個(gè)IP地址,而且會(huì)緩存下來(lái),之后會(huì)一直用這個(gè)地址。所以,DNS輪轉(zhuǎn)通常不會(huì)平衡單個(gè)客戶(hù)端的負(fù)載。但是,由于DNS服務(wù)器對(duì)于不同的請(qǐng)求,總是會(huì)返回輪轉(zhuǎn)后的IP地址列表,因此,會(huì)把負(fù)載分散到多個(gè)客戶(hù)端。

          6.7 HTTP連接

          HTTP連接是HTTP報(bào)文傳輸?shù)年P(guān)鍵通道。

          【并行連接】:

          對(duì)于一個(gè)頁(yè)面上同時(shí)出現(xiàn)多個(gè)對(duì)象的時(shí)候,如果瀏覽器并行地打開(kāi)多個(gè)連接,同時(shí)去獲取這些對(duì)象,多個(gè)連接的TCP握手時(shí)延可以進(jìn)行重疊,速度會(huì)快起來(lái)。

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

          如果采用串行方式,那么連接時(shí)延會(huì)進(jìn)行疊加:

          采用并行連接之后:

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

          另一方面,如果客戶(hù)端網(wǎng)絡(luò)帶寬較小,每個(gè)連接都會(huì)去爭(zhēng)搶有限的帶寬,每個(gè)連接都會(huì)獲取較小的速度,即每個(gè)對(duì)象都會(huì)以較小的速度去加載。這樣,并行連接帶來(lái)的速度提升就會(huì)比較小,甚至沒(méi)有提升。

          【持久連接】:

          持久連接即HTTP的keep-alive機(jī)制。

          我們知道HTTP請(qǐng)求是“請(qǐng)求-應(yīng)答”模式,每次請(qǐng)求-應(yīng)答都要新建一個(gè)連接,完成之后要斷開(kāi)連接。HTTP是無(wú)狀態(tài)的,連接之間沒(méi)有任何關(guān)系。

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

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

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

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

          【管道化連接】:

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

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

          7、本文小結(jié)

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

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

          8、參考資料

          [1]【http長(zhǎng)連接】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權(quán)威指南】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)分類(lèi)】http://www.runoob.com/http/http-status-codes.html

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

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

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

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

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

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

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

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

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

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

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

          計(jì)算機(jī)網(wǎng)絡(luò)通訊協(xié)議關(guān)系圖(中文珍藏版)

          UDP中一個(gè)包的大小最大能多大?

          P2P技術(shù)詳解(一):NAT詳解——詳細(xì)原理、P2P簡(jiǎn)介

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

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

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

          高性能網(wǎng)絡(luò)編程(一):?jiǎn)闻_(tái)服務(wù)器并發(fā)TCP連接數(shù)到底可以有多少

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

          全面了解移動(dòng)端DNS域名劫持等雜癥:技術(shù)原理、問(wèn)題根源、解決方案等

          美圖App的移動(dòng)端DNS優(yōu)化實(shí)踐:HTTPS請(qǐng)求耗時(shí)減小近半

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

          IM開(kāi)發(fā)者的零基礎(chǔ)通信技術(shù)入門(mén)(一):通信交換技術(shù)的百年發(fā)展史(上)

          IM開(kāi)發(fā)者的零基礎(chǔ)通信技術(shù)入門(mén)(二):通信交換技術(shù)的百年發(fā)展史(下)

          IM開(kāi)發(fā)者的零基礎(chǔ)通信技術(shù)入門(mén)(三):國(guó)人通信方式的百年變遷

          IM開(kāi)發(fā)者的零基礎(chǔ)通信技術(shù)入門(mén)(四):手機(jī)的演進(jìn),史上最全移動(dòng)終端發(fā)展史

          IM開(kāi)發(fā)者的零基礎(chǔ)通信技術(shù)入門(mén)(五):1G到5G,30年移動(dòng)通信技術(shù)演進(jìn)史

          IM開(kāi)發(fā)者的零基礎(chǔ)通信技術(shù)入門(mén)(六):移動(dòng)終端的接頭人——“基站”技術(shù)

          IM開(kāi)發(fā)者的零基礎(chǔ)通信技術(shù)入門(mén)(七):移動(dòng)終端的千里馬——“電磁波”

          IM開(kāi)發(fā)者的零基礎(chǔ)通信技術(shù)入門(mén)(八):零基礎(chǔ),史上最強(qiáng)“天線(xiàn)”原理掃盲

          IM開(kāi)發(fā)者的零基礎(chǔ)通信技術(shù)入門(mén)(九):無(wú)線(xiàn)通信網(wǎng)絡(luò)的中樞——“核心網(wǎng)”

          IM開(kāi)發(fā)者的零基礎(chǔ)通信技術(shù)入門(mén)(十):零基礎(chǔ),史上最強(qiáng)5G技術(shù)掃盲

          IM開(kāi)發(fā)者的零基礎(chǔ)通信技術(shù)入門(mén)(十一):為什么WiFi信號(hào)差?一文即懂!

          IM開(kāi)發(fā)者的零基礎(chǔ)通信技術(shù)入門(mén)(十二):上網(wǎng)卡頓?網(wǎng)絡(luò)掉線(xiàn)?一文即懂!

          IM開(kāi)發(fā)者的零基礎(chǔ)通信技術(shù)入門(mén)(十三):為什么手機(jī)信號(hào)差?一文即懂!

          IM開(kāi)發(fā)者的零基礎(chǔ)通信技術(shù)入門(mén)(十四):高鐵上無(wú)線(xiàn)上網(wǎng)有多難?一文即懂!

          IM開(kāi)發(fā)者的零基礎(chǔ)通信技術(shù)入門(mén)(十五):理解定位技術(shù),一篇就夠

          >> 更多同類(lèi)文章 ……

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



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


          只有注冊(cè)用戶(hù)登錄后才能發(fā)表評(píng)論。


          網(wǎng)站導(dǎo)航:
           
          Jack Jiang的 Mail: jb2011@163.com, 聯(lián)系QQ: 413980957, 微信: hellojackjiang
          主站蜘蛛池模板: 宁陕县| 海安县| 志丹县| 四会市| 寻乌县| 开平市| 绥德县| 仁布县| 盖州市| 南通市| 衡东县| 宿松县| 大田县| 双江| 富蕴县| 汝南县| 平泉县| 芒康县| 鹤岗市| 邵东县| 壤塘县| 吉安县| 福贡县| 丰镇市| 安庆市| 阳朔县| 尚义县| 莱州市| 黄梅县| 三亚市| 桃江县| 新兴县| 赤壁市| 林州市| 文登市| 咸丰县| 陵川县| 甘德县| 屏山县| 宜昌市| 乌鲁木齐县|