本文原做者阮一峯,做者博客:ruanyifeng.com。javascript
HTTP 協議是最重要的互聯網基礎協議之一,它從最初的僅爲瀏覽網頁的目的進化到如今,已是短鏈接通訊的事實工業標準,最新版本 HTTP/2 更是讓它再次成爲技術熱點。php
做爲即時通信開發者來講,深入理解HTTP協議有助於在現今複雜移動網絡環境下的優化和最佳實踐的開展,本文將通俗易懂的地介紹 HTTP 協議的歷史演變和設計思路。css
學習交流:html
- 即時通信開發交流3羣:185926912[推薦]java
- 移動端IM開發入門文章:《新手入門一篇就夠:從零開發移動端IM》git
(本文同步發佈於:http://www.52im.net/thread-1709-1-1.html)github
《網絡編程懶人入門(七):深刻淺出,全面理解HTTP協議》編程
《現代移動端網絡短鏈接的優化手段總結:請求速度、弱網適應、安全保障》瀏覽器
《IM開發基礎知識補課(四):正確理解HTTP短鏈接中的Cookie、Session和Token》緩存
《IM開發基礎知識補課:正確理解前置HTTP SSO單點登錄接口的原理》
《從HTTP到MQTT:一個基於位置服務的APP數據通訊實踐概述》
HTTP 是基於 TCP/IP 協議的應用層協議(詳見《網絡編程懶人入門(一):快速理解網絡通訊協議(上篇)》、《網絡編程懶人入門(二):快速理解網絡通訊協議(下篇)》)。它不涉及數據包(packet)傳輸,主要規定了客戶端和服務器之間的通訊格式,默認使用80端口。
最先版本是1991年發佈的0.9版。該版本極其簡單,只有一個命令GET:
GET /index.html
上面命令表示,TCP 鏈接(connection)創建後,客戶端向服務器請求(request)網頁index.html。
協議規定,服務器只能迴應HTML格式的字符串,不能迴應別的格式:
Hello World
服務器發送完畢,就關閉TCP鏈接。
1996年5月,HTTP/1.0 版本發佈,內容大大增長(詳見 RFC1945)。
首先,任何格式的內容均可以發送。這使得互聯網不只能夠傳輸文字,還能傳輸圖像、視頻、二進制文件。這爲互聯網的大發展奠基了基礎。
其次,除了GET命令,還引入了POST命令和HEAD命令,豐富了瀏覽器與服務器的互動手段。
再次,HTTP請求和迴應的格式也變了。除了數據部分,每次通訊都必須包括頭信息(HTTP header),用來描述一些元數據。
其餘的新增功能還包括狀態碼(status code)、多字符集支持、多部分發送(multi-part type)、權限(authorization)、緩存(cache)、內容編碼(content encoding)等。
下面是一個1.0版的HTTP請求的例子:
GET / HTTP/1.0
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5)
Accept: */*
能夠看到,這個格式與0.9版有很大變化。
第一行是請求命令,必須在尾部添加協議版本(HTTP/1.0)。後面就是多行頭信息,描述客戶端的狀況。
服務器的迴應以下:
HTTP/1.0 200 OK
Content-Type: text/plain
Content-Length: 137582
Expires: Thu, 05 Dec 1997 16:00:00 GMT
Last-Modified: Wed, 5 August 1996 15:55:28 GMT
Server: Apache 0.84
Hello World
迴應的格式是"頭信息 + 一個空行(\r\n) + 數據"。其中,第一行是"協議版本 + 狀態碼(status code) + 狀態描述"。(詳見《網絡編程懶人入門(七):深刻淺出,全面理解HTTP協議》)
關於字符的編碼,1.0版規定,頭信息必須是 ASCII 碼,後面的數據能夠是任何格式。所以,服務器迴應的時候,必須告訴客戶端,數據是什麼格式,這就是Content-Type字段的做用。
下面是一些常見的Content-Type字段的值:
text/plain
text/html
text/css
image/jpeg
image/png
image/svg+xml
audio/mp4
video/mp4
application/javascript
application/pdf
application/zip
application/atom+xml
這些數據類型總稱爲MIME type,每一個值包括一級類型和二級類型,之間用斜槓分隔。
除了預約義的類型,廠商也能夠自定義類型,就像下面這樣:
application/vnd.debian.binary-package
上面的類型代表,發送的是Debian系統的二進制數據包。
MIME type還能夠在尾部使用分號,添加參數:
Content-Type: text/html; charset=utf-8
上面的類型代表,發送的是網頁,並且編碼是UTF-8。
客戶端請求的時候,可使用Accept字段聲明本身能夠接受哪些數據格式:
Accept: */*
上面代碼中,客戶端聲明本身能夠接受任何格式的數據。
MIME type不只用在HTTP協議,還能夠用在其餘地方,好比HTML網頁:
因爲發送的數據能夠是任何格式,所以能夠把數據壓縮後再發送。
Content-Encoding字段說明數據的壓縮方法:
Content-Encoding: gzip
Content-Encoding: compress
Content-Encoding: deflate
客戶端在請求時,用Accept-Encoding字段說明本身能夠接受哪些壓縮方法:
Accept-Encoding: gzip, deflate
HTTP/1.0 版的主要缺點是,每一個TCP鏈接只能發送一個請求。發送數據完畢,鏈接就關閉,若是還要請求其餘資源,就必須再新建一個鏈接。
TCP鏈接的新建成本很高,由於須要客戶端和服務器三次握手,而且開始時發送速率較慢(slow start)。因此,HTTP 1.0版本的性能比較差。隨着網頁加載的外部資源愈來愈多,這個問題就愈發突出了。
爲了解決這個問題,有些瀏覽器在請求時,用了一個非標準的Connection字段:
Connection: keep-alive
這個字段要求服務器不要關閉TCP鏈接,以便其餘請求複用。
服務器一樣迴應這個字段:
Connection: keep-alive
一個能夠複用的TCP鏈接就創建了,直到客戶端或服務器主動關閉鏈接。可是,這不是標準字段,不一樣實現的行爲可能不一致,所以不是根本的解決辦法。
1997年1月,HTTP/1.1 版本發佈,只比 1.0 版本晚了半年。HTTP/1.1進一步完善了 HTTP 協議,一直用到了20年後的今天,直到如今仍是最流行的版本。
1.1 版的最大變化,就是引入了持久鏈接(persistent connection),即TCP鏈接默認不關閉,能夠被多個請求複用,不用聲明Connection: keep-alive。
客戶端和服務器發現對方一段時間沒有活動,就能夠主動關閉鏈接。
不過,規範的作法是,客戶端在最後一個請求時,發送Connection: close,明確要求服務器關閉TCP鏈接:
Connection: close
目前,對於同一個域名,大多數瀏覽器容許同時創建6個持久鏈接。
1.1 版還引入了管道機制(pipelining),即在同一個TCP鏈接裏面,客戶端能夠同時發送多個請求。這樣就進一步改進了HTTP協議的效率。
舉例來講,客戶端須要請求兩個資源。之前的作法是,在同一個TCP鏈接裏面,先發送A請求,而後等待服務器作出迴應,收到後再發出B請求。管道機制則是容許瀏覽器同時發出A請求和B請求,可是服務器仍是按照順序,先回應A請求,完成後再回應B請求。
一個TCP鏈接如今能夠傳送多個迴應,勢必就要有一種機制,區分數據包是屬於哪個迴應的。
這就是Content-length字段的做用,聲明本次迴應的數據長度:
Content-Length: 3495
上面代碼告訴瀏覽器,本次迴應的長度是3495個字節,後面的字節就屬於下一個迴應了。
在1.0版中,Content-Length字段不是必需的,由於瀏覽器發現服務器關閉了TCP鏈接,就代表收到的數據包已經全了。
使用Content-Length字段的前提條件是,服務器發送迴應以前,必須知道迴應的數據長度。
對於一些很耗時的動態操做來講,這意味着,服務器要等到全部操做完成,才能發送數據,顯然這樣的效率不高。更好的處理方法是,產生一塊數據,就發送一塊,採用"流模式"(stream)取代"緩存模式"(buffer)。
所以,1.1版規定能夠不使用Content-Length字段,而使用"分塊傳輸編碼"(chunked transfer encoding)。
只要請求或迴應的頭信息有Transfer-Encoding字段,就代表迴應將由數量未定的數據塊組成:
Transfer-Encoding: chunked
每一個非空的數據塊以前,會有一個16進制的數值,表示這個塊的長度。最後是一個大小爲0的塊,就表示本次迴應的數據發送完了。
下面是一個例子:
HTTP/1.1 200 OK
Content-Type: text/plain
Transfer-Encoding: chunked
25
This is the data in the first chunk
1C
and this is the second one
3
con
8
sequence
0
1.1版還新增了許多動詞方法:PUT、PATCH、HEAD、 OPTIONS、DELETE。
另外,客戶端請求的頭信息新增了Host字段,用來指定服務器的域名:
Host: example.com
有了Host字段,就能夠將請求發往同一臺服務器上的不一樣網站,爲虛擬主機的興起打下了基礎。
雖然1.1版容許複用TCP鏈接,可是同一個TCP鏈接裏面,全部的數據通訊是按次序進行的。服務器只有處理完一個迴應,纔會進行下一個迴應。要是前面的迴應特別慢,後面就會有許多請求排隊等着。這稱爲"隊頭堵塞"(Head-of-line blocking)。
爲了不這個問題,只有兩種方法:一是減小請求數,二是同時多開持久鏈接。這致使了不少的網頁優化技巧,好比合並腳本和樣式表、將圖片嵌入CSS代碼、域名分片(domain sharding)等等。若是HTTP協議設計得更好一些,這些額外的工做是能夠避免的。
有關HTTP/1.1的詳細技術介紹,詳見《網絡編程懶人入門(七):深刻淺出,全面理解HTTP協議》。
2009年,谷歌公開了自行研發的 SPDY 協議,主要解決 HTTP/1.1 效率不高的問題。
這個協議在Chrome瀏覽器上證實可行之後,就被看成 HTTP/2 的基礎,主要特性都在 HTTP/2 之中獲得繼承。
2015年,HTTP/2 發佈。它不叫 HTTP/2.0,是由於標準委員會不打算再發布子版本了,下一個新版本將是 HTTP/3。
HTTP/1.1 版的頭信息確定是文本(ASCII編碼),數據體能夠是文本,也能夠是二進制。HTTP/2 則是一個完全的二進制協議,頭信息和數據體都是二進制,而且統稱爲"幀"(frame):頭信息幀和數據幀。
二進制協議的一個好處是,能夠定義額外的幀。HTTP/2 定義了近十種幀,爲未來的高級應用打好了基礎。若是使用文本實現這種功能,解析數據將會變得很是麻煩,二進制解析則方便得多。
HTTP/2 複用TCP鏈接,在一個鏈接裏,客戶端和瀏覽器均可以同時發送多個請求或迴應,並且不用按照順序一一對應,這樣就避免了"隊頭堵塞"。
舉例來講,在一個TCP鏈接裏面,服務器同時收到了A請求和B請求,因而先回應A請求,結果發現處理過程很是耗時,因而就發送A請求已經處理好的部分, 接着迴應B請求,完成後,再發送A請求剩下的部分。
這樣雙向的、實時的通訊,就叫作多工(Multiplexing)。
由於 HTTP/2 的數據包是不按順序發送的,同一個鏈接裏面連續的數據包,可能屬於不一樣的迴應。所以,必需要對數據包作標記,指出它屬於哪一個迴應。
HTTP/2 將每一個請求或迴應的全部數據包,稱爲一個數據流(stream)。每一個數據流都有一個獨一無二的編號。數據包發送的時候,都必須標記數據流ID,用來區分它屬於哪一個數據流。另外還規定,客戶端發出的數據流,ID一概爲奇數,服務器發出的,ID爲偶數。
數據流發送到一半的時候,客戶端和服務器均可以發送信號(RST_STREAM幀),取消這個數據流。1.1版取消數據流的惟一方法,就是關閉TCP鏈接。這就是說,HTTP/2 能夠取消某一次請求,同時保證TCP鏈接還打開着,能夠被其餘請求使用。
客戶端還能夠指定數據流的優先級。優先級越高,服務器就會越早迴應。
HTTP 協議不帶有狀態,每次請求都必須附上全部信息。因此,請求的不少字段都是重複的,好比Cookie和User Agent,如出一轍的內容,每次請求都必須附帶,這會浪費不少帶寬,也影響速度。
HTTP/2 對這一點作了優化,引入了頭信息壓縮機制(header compression)。一方面,頭信息使用gzip或compress壓縮後再發送;另外一方面,客戶端和服務器同時維護一張頭信息表,全部字段都會存入這個表,生成一個索引號,之後就不發送一樣字段了,只發送索引號,這樣就提升速度了。
HTTP/2 容許服務器未經請求,主動向客戶端發送資源,這叫作服務器推送(server push)。
常見場景是客戶端請求一個網頁,這個網頁裏面包含不少靜態資源。正常狀況下,客戶端必須收到網頁後,解析HTML源碼,發現有靜態資源,再發出靜態資源請求。其實,服務器能夠預期到客戶端請求網頁後,極可能會再請求靜態資源,因此就主動把這些靜態資源隨着網頁一塊兒發給客戶端了。
《技術往事:改變世界的TCP/IP協議(珍貴多圖、手機慎點)》
《通俗易懂-深刻理解TCP協議(下):RTT、滑動窗口、擁塞處理》
《理論聯繫實際:Wireshark抓包分析TCP 3次握手、4次揮手過程》
《P2P技術詳解(一):NAT詳解——詳細原理、P2P簡介》
《P2P技術詳解(二):P2P中的NAT穿越(打洞)方案詳解》
《P2P技術詳解(三):P2P技術之STUN、TURN、ICE詳解》
《高性能網絡編程(一):單臺服務器併發TCP鏈接數到底能夠有多少》
《高性能網絡編程(二):上一個10年,著名的C10K併發鏈接問題》
《高性能網絡編程(三):下一個10年,是時候考慮C10M併發問題了》
《高性能網絡編程(四):從C10K到C10M高性能網絡應用的理論探索》
《鮮爲人知的網絡編程(一):淺析TCP協議中的疑難雜症(上篇)》
《鮮爲人知的網絡編程(二):淺析TCP協議中的疑難雜症(下篇)》
《鮮爲人知的網絡編程(三):關閉TCP鏈接時爲何會TIME_WAIT、CLOSE_WAIT》
《鮮爲人知的網絡編程(七):如何讓不可靠的UDP變的可靠?》
《網絡編程懶人入門(五):快速理解爲何說UDP有時比TCP更有優點》
《網絡編程懶人入門(六):史上最通俗的集線器、交換機、路由器功能原理入門》
《技術掃盲:新一代基於UDP的低延時網絡傳輸層協議——QUIC詳解》
《現代移動端網絡短鏈接的優化手段總結:請求速度、弱網適應、安全保障》
《移動端IM開發者必讀(一):通俗易懂,理解移動網絡的「弱」和「慢」》
《移動端IM開發者必讀(二):史上最全移動弱網絡優化方法總結》
《從HTTP/0.9到HTTP/2:一文讀懂HTTP協議的歷史演變和設計思路》
>> 更多同類文章 ……
(本文同步發佈於:http://www.52im.net/thread-1709-1-1.html)