Android網絡編程:基礎理論彙總

關於做者javascript

郭孝星,程序員,吉他手,主要從事Android平臺基礎架構方面的工做,歡迎交流技術方面的問題,能夠去個人Github提issue或者發郵件至guoxiaoxingse@163.com與我交流。html

文章目錄java

第一次閱覽本系列文章,請參見導讀,更多文章請參見文章目錄android

在Android的網絡開發過程當中,咱們一般會使用像Okhttp、Retrofit這種高度封裝的網絡庫,它們徹底屏蔽了相關技術細節。可是掌握其中的原理對咱們來 說是很重要的,要知其然,也要知其因此然,只要掌握了這些原理,你才能更好的理解Okhttp等網絡庫的源碼實現。git

網絡編程一般會涉及如下幾個角色:程序員

  • HTTP/HTTPS
  • TCP/IP
  • 客戶端/服務端

怎麼去理解它們的關係呢?🤔github

例如咱們是雙十一從馬老闆家買了部手機,這個時候咱們就是客戶端,馬老闆就是服務端。手機要經過快遞公司的汽車運送到咱們手中。TCP/IP就至關於汽車,可是光有汽車是不夠的,還要對汽車 進行分類,否則都是同樣的汽車就亂套了,而完成分類的就是HTTP/HTTPS了,HTTP/HTTPS會告訴這些汽車,你是負責送貨的(GET),你是負責退貨的(POST)等等。web

注:文章中部分圖片來源於網絡,此次就偷個懶,有些流程圖就不畫了。😁算法

一 TCP/IP

TCP(傳輸控制協議)是一種面向鏈接的、可靠的、基於字節流的傳輸層通訊協議,編程

TCP協議是HTTP/HTTPS、WebSocket等協議的基礎,咱們首先來看看它們的報文格式。

1.1 IP數據報與TCP報文

關於IP數據報與TCP報文你只須要理解它的結構就行,不用去記它,等到使用的時候不記得了,查一下就行了。

IP數據報

  1. 版本——佔 4 bit,指IP協議的版本. 目前的 IP 協議版本號爲 4 (即 IPv4)
  2. 首部長度——佔 4 bit,可表示的最大數值是 15 個單位(一個單位爲 4 字節)所以 IP 的首部長度的最大值是60字節。
  3. 總長度——佔 16 bit,指首部和數據之和的長度,單位爲字節,所以數據報的最大長度爲 65535 字節。總長度必須不超過最大傳送單元 MTU。
  4. 標識(identification) 佔 16 bit,它是一個計數器,用來產生數據報的標識。當數據報須要分片時,此標識表示同一個數據報的分片。
  5. 標誌(flag):3 bit,D0:MF,D1:DF,D2保留, DF位用來表示數據報是否容許分片,DF=1不分片;MF位表示是否有後續分片,MF=0表示是最後一片。
  6. 片偏移(13 bit)指出:較長的分組在分片後某分片在原分組中的相對位置。片偏移以 8 個字節爲偏移單位。
  7. 生存時間(8 bit)記爲 TTL (Time To Live)表示數據報在網絡中的壽命,其單位爲秒。在目前的實際應用中,常以「跳」爲單位。
  8. 協議(8 bit)字段指出此數據報攜帶的數據使用何種協議(如TCP/UDP等)以便目的主機的 IP 層將數據部分上交給哪一個處理過程
  9. 首部檢驗和(16 bit)字段只檢驗數據報的首部不包括數據部分。這裏不採用 CRC 檢驗碼而採用簡單的「反碼算術求和」計算方法。
  10. 源地址和目的地址都各佔 4 字節,32bit 的IP地址
  11. 可選字段的長度是 可變的,1~40 字節,用於增長IP數據報的控制功能。
  12. 填充字段保證IP首部長度是 4 字節的整倍數

TCP報文

  1. 源端口和目的端口字段——各佔 2 字節。端口是傳輸層與應用層的服務接口。傳輸層的複用和分用功能都要經過端口才能實現。
  2. 序號字段——佔 4 字節。TCP 鏈接中傳送的數據流中的每個字節都編上一個序號。序號字段的值則指的是本報文段所發送的數據的第一個字節的序號。
  3. 確認號字段——佔 4 字節,是指望收到對方的下一個報文段的數據的第一個字節的序號。
  4. 數據偏移——佔 4 bit,它指出 TCP 報文段的數據起始處距離 TCP 報文段的起始處有多遠。「數據偏移」的單位不是字節而是 32 bit 字(4 字節爲計算單位)。
  5. 保留字段——佔 6 bit,保留爲從此使用,但目前應置爲 0。
  6. 緊急比特 URG —— 當 URG=1 時,代表緊急指針字段有效。它告訴系統此報文段中有緊急數據,應儘快傳送(至關於高優先級的數據)。
  7. 確認比特 ACK —— 只有當 ACK=1 時確認號字段纔有效。當 ACK=0 時,確認號無效。
  8. 推送比特 PSH(Push)接收方 TCP 收到推送比特置1的報文段,就儘快地交付給接收應用進程,而再也不等到整個緩存都填滿了後再向上交付.
  9. 復位比特 RST (Reset) —— 當 RST=1 時,代表 TCP 鏈接中出現嚴重差錯(如因爲主機崩潰或其餘緣由),必須釋放鏈接,而後再從新創建運輸鏈接。
  10. 同步比特 SYN —— 同步比特 SYN 置爲 1,就表示這是一個鏈接請求或鏈接接受報文。
  11. 終止比特 FIN (FINal) —— 用來釋放一個鏈接。當FIN=1 時,代表此報文段的發送端的數據已發送完畢,並要求釋放運輸鏈接。
  12. 窗口字段 —— 佔 2 字節。窗口字段用來控制對方發送的數據量,單位爲字節。TCP 鏈接的一端根據設置的緩存空間大小肯定本身的接收窗口大小,而後通知對方以肯定對方的發送窗口的上限。
  13. 檢驗和 —— 佔 2 字節。檢驗和字段檢驗的範圍包括首部和數據這兩部分。在計算檢驗和時,要在 TCP 報文段的前面加上 12 字節的僞首部。
  14. 緊急指針字段 —— 佔 16 bit。緊急指針指出在本報文段中的緊急數據的最後一個字節的序號。
  15. 選項字段 —— 長度可變。TCP 首部能夠有多達40字節的可選信息,用於把附加信息傳遞給終點,或用來對齊其它選項。

1.2 三次握手與四次分手

TCP用三次握手(three-way handshake)過程建立一個鏈接,使用四次分手 關閉一個鏈接。

三次握手與四次分手的流程以下所示:

三次握手

  • 第一次握手:創建鏈接。客戶端發送鏈接請求報文段,將SYN位置爲1,Sequence Number爲x;而後,客戶端進入SYN_SEND狀態,等待服務器的確認;
  • 第二次握手:服務器收到SYN報文段。服務器收到客戶端的SYN報文段,須要對這個SYN報文段進行確認,設置Acknowledgment Number爲x+1(Sequence Number+1);同時,本身本身還要發送SYN請求信息,將SYN位置爲1,Sequence Number爲y;服務器端將上述全部信息放到一個報文段(即SYN+ACK報文段)中,一併發送給客戶端,此時服務器進入SYN_RECV狀態;
  • 第三次握手:客戶端收到服務器的SYN+ACK報文段。而後將Acknowledgment Number設置爲y+1,向服務器發送ACK報文段,這個報文段發送完畢之後,客戶端和服務器端都進入ESTABLISHED狀態,完成TCP三次握手。 完成了三次握手,客戶端和服務器端就能夠開始傳送數據。以上就是TCP三次握手的整體介紹。

四次分手

  • 第一次分手:主機1(可使客戶端,也能夠是服務器端),設置Sequence Number和Acknowledgment Number,向主機2發送一個FIN報文段;此時,主機1進入FIN_WAIT_1狀態;這表示主機1沒有數據要發送給主機2了;
  • 第二次分手:主機2收到了主機1發送的FIN報文段,向主機1回一個ACK報文段,Acknowledgment Number爲Sequence Number加1;主機1進入FIN_WAIT_2狀態;主機2告訴主機1,我「贊成」你的關閉請求;
  • 第三次分手:主機2向主機1發送FIN報文段,請求關閉鏈接,同時主機2進入LAST_ACK狀態;
  • 第四次分手:主機1收到主機2發送的FIN報文段,向主機2發送ACK報文段,而後主機1進入TIME_WAIT狀態;主機2收到主機1的ACK報文段之後,就關閉鏈接;此時,主機1等待2MSL後依然沒有收到回覆,則證實Server端已正常關閉,那好,主機1也能夠關閉鏈接了。

三次握手與四次分手也是個老生常談的概念,舉個簡單的例子說明一下。

三次握手

例如你小時候出去玩,常常玩忘了回家吃飯。你媽媽也常常過來喊你。若是你沒有走遠,在門口的小土堆上玩泥巴,你媽媽會喊:"小新,回家吃飯了"。你聽到後會迴應:"知道了,一會就回去"。媽媽聽 到你的迴應後又說:"快點回來,飯要涼了"。這樣你媽媽和你就完成了三次握手的過程。😁說到這裏你也能夠理解三次握手的必要性,少了其中一個環節,另外一方就會陷入等待之中。

三次握手的目的是爲了防止已失效的鏈接請求報文段忽然又傳送到了服務端,於是產生錯誤.

四次分手

例如偶像言情劇乾淨利落的分手,女主對男主說:咱們分手吧🙄,男主說:分就分吧😰。女主說:你果真是不愛我了,你只知道讓我多喝熱水🙄。男主說:事到現在也沒什麼好說的了,祝你幸福🙃。四次分手完成。說到這裏你能夠理解 了四次分手的必要性,第一次是女方(客戶端)提出分手,第二次是男主(服務端)贊成女主分手,第三次是女主肯定男主再也不愛她,也贊成男主分手。第四次兩人完全拜拜(斷開鏈接)。

由於TCP是全雙工模式,因此四次分手的目的就是爲了可靠地關閉鏈接。

二 HTTP/HTTPS

HTTP(HyperText Transfer Protocol)是一種用於分佈式、協做式和超媒體信息系統的應用層協議[1]。HTTP是萬維網的數據通訊的基礎。

HTTP是最多見的應用層協議,咱們平常開發中基本上接觸到的都是這個協議。

2.1 HTTP報文

HTTP應用程序是經過相互發送報文工做的,報文是HTTP應用程序之間發送的數據塊,報文一般分爲請求報文和響應報文兩種,請求報文向服務器請求一個動做,響應報文將請求結果返回給客戶端。

HTTP請求報文分爲三部分:請求行、請求首部、請求實體.

  • 請求行由方法字段、URL 字段 和HTTP 協議版本字段 3 個部分組成,他們之間使用空格隔開。
  • 請求頭部由關鍵字/值對組成,每行一對,關鍵字和值用英文冒號「:」分隔。請求頭部通知服務器有關於客戶端請求的信息。
  • 請求實體不在 GET 方法中使用,而是在POST 方法中使用。POST 方法適用於須要客戶填寫表單的場合。與請求包體相關的最常使用的是包體類型 Content-Type 和包體長度 Content-Length。

請求報文

GET /his?wd=&from=pc_web&rf=3&hisdata=&json=1&p=3&sid=20740_20742_1424_18280_20417_17001_15840_11910_20744_20705&csor=0&cb=jQuery110206488567241711853_1469936513370&_=1469936513371 HTTP/1.1
Host: www.baidu.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:47.0) Gecko/20100101 Firefox/47.0
Accept: text/javascript, application/javascript, application/ecmascript, application/x-ecmascript, *//*; q=0.01
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate, br
X-Requested-With: XMLHttpRequest
Referer: https://www.baidu.com/
Cookie: BAIDUID=DB24D5F4AB36694CF00C4877ADA56562:FG=1; BIDUPSID=DB24D5F4AB36694CF00C4877ADA56562; PSTM=1469936050; BDRCVFR[gltLrB7qNCt]=mk3SLVN4HKm; BD_CK_SAM=1; H_PS_PSSID=20740_20742_1424_18280_20417_17001_15840_11910_20744_20705; BD_UPN=133252; H_PS_645EC=96a0XJobAseSCdbn9%2FviULLD7KreCHN4V4HzQtcGacKF8tGu13Nzd6j9PoB2SPPVj1d5; BD_HOME=0; __bsi=11860814506529643127_00_0_I_R_25_0303_C02F_N_I_I_0
Connection: keep-alive
複製代碼

響應報文

HTTP響應報文分爲三部分:狀態行、響應首部、響應實體。

  • 狀態行由 HTTP 協議版本字段、狀態碼和狀態碼的描述文本 3 個部分組成,他們之間使用空格隔開。
  • 響應首部由關鍵字/值對組成,每行一對,關鍵字和值用英文冒號「:」分隔。請求首部部通知客戶端有關於服務端響應的信息。
  • 響應實體是服務器返回給客戶端的文本信息。
HTTP/1.1 200 OK
Server: bfe/1.0.8.14
Date: Sun, 31 Jul 2016 03:41:53 GMT
Content-Type: baiduApp/json; v6.27.2.14; charset=UTF-8
Content-Length: 95
Connection: keep-alive
Cache-Control: private
Expires: Sun, 31 Jul 2016 04:41:53 GMT
Set-Cookie: __bsi=12018325985460509248_00_0_I_R_4_0303_C02F_N_I_I_0; expires=Sun, 31-Jul-16 03:41:58 GMT; domain=www.baidu.com; path=/
複製代碼

報文一般由如下部分組成:

  • 方法(method):客戶端但願服務器對資源執行的動做。例如:GET
  • 請求URL(request url):客戶端要訪問的資源URL。例如:www.google.com
  • 版本(version):報文所使用的HTTP版本。例如:HTTP/1.1
  • 狀態碼(status code):描述請求過程當中發生的情況。例如:200
  • 緣由短語(reason phrase):狀態碼的解釋。例如:OK
  • 首部(header)向請求報文或者響應報文中添加一些附加信息。例如:Content-Type: text/html
  • 實體(body):包含一個由任意數據組成的數據塊。例如:

方法

方法(method):客戶端但願服務器對資源執行的動做。

  • GET 請求指定url的數據,請求體爲空(例如打開網頁)。
  • POST 請求指定url的數據,同時傳遞參數(在請求體中)。
  • HEAD 相似於get請求,只不過返回的響應體爲空,用於獲取響應頭。
  • PUT 從客戶端向服務器傳送的數據取代指定的文檔的內容。
  • DELETE 請求服務器刪除指定的頁面。
  • CONNECT HTTP/1.1協議中預留給可以將鏈接改成管道方式的代理服務器。
  • OPTIONS 容許客戶端查看服務器的性能。
  • TRACE 回顯服務器收到的請求,主要用於測試或診斷。

咱們主要討論咱們最經常使用的兩個GET/POST。

  • GET方法一般用於請求服務器發送某個資源。
  • POST方法一般用於向服務器提交數據。

GET與POST在本質上都是TCP鏈接,只是GET直接把參數寫在請求行中,而POST把參數放在請求體中。關於這兩個方法,要注意如下兩點:

  • HTTP協議自己並無規定GET請求行的長度顯示,可是瀏覽器和服務端有這個限制,瀏覽器支持的URL場地通常都2kb,服務器通常爲64kb(能夠設置)。
  • GET中若是包含中文,須要進行編碼URLEncoder.encode(params, "gbk")。

狀態碼

  • 1xx:信息提示
  • 2xx:成功
  • 3xx:重定向
  • 4xx:客戶端錯誤
  • 5xx:服務端錯誤

更多關於狀態碼的細節能夠參見HTTP狀態碼

首部

首部一般和方法配合工做,共同決定了客戶端和服務器能作什麼事情。

  • 通用首部:客戶端和服務端均可以使用的首部,提供一些通用的功能。例如:Date: Sat, 11 Jan 2003 02:44:04 GMT 提供了構建報文的時間。
  • 請求首部:請求報文特有,它們爲服務器提供一些額外的信息。例如:Accept: / 告知服務器會接收與其請求相符的任意媒體類型。
  • 響應首部:響應報文特有,它們爲客戶端提供一些額外的信息。例如:Server: GWS/2.0 告知客戶端與其通訊的服務端是GWS/2.0。
  • 實體首部:實體中特有,爲實體提供一些說明。例如:Content-Type: text/html 告知實體中的內容類型是text/html。
  • 擴展首部:非標準首部,能夠由開發者建立。

常見的通用首部

  • Date 發送該消息的日期和時間(按照 RFC 7231 中定義的"超文本傳輸協議日期"格式來發送) Date: Tue, 15 Nov 1994 08:12:31 GMT
  • Host 服務器的域名(用於虛擬主機 ),以及服務器所監聽的傳輸控制協議端口號。若是所請求的端口是對應的服務的標準端口,則端口號可被省略。

常見的請求首部

  • Accept 可以接受的迴應內容類型(Content-Types)。Accept: text/plain
  • Accept-Charset 可以接受的字符集 Accept-Charset: utf-8
  • Accept-Encoding 可以接受的編碼方式列表。 Accept-Encoding: gzip, deflate
  • Accept-Language 可以接受的迴應內容的天然語言列表。 Accept-Language: en-US
  • Accept-Datetime 可以接受的按照時間來表示的版本 Accept-Datetime: Thu, 31 May 2007 20:35:00 GMT
  • Authorization 用於超文本傳輸協議的認證的認證信息 Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
  • Cookie 以前由服務器經過 Set- Cookie (下文詳述)發送的一個 超文本傳輸協議Cookie Cookie: $Version=1; Skin=new;
  • User-Agent 瀏覽器的瀏覽器身份標識字符串 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:12.0) Gecko/20100101 Firefox/21.0
  • Upgrade 要求服務器升級到另外一個協議。 Upgrade: HTTP/2.0, SHTTP/1.3, IRC/6.9, RTA/x11

常見的響應首部

  • Server 服務器的名字 Server: Apache/2.4.1 (Unix)
  • Status 用來講明當前這個超文本傳輸協議迴應的 狀態。Status: 200 OK
  • Upgrade 要求客戶端升級到另外一個協議。 Upgrade: HTTP/2.0, SHTTP/1.3, IRC/6.9, RTA/x11
  • Age 這個對象在代理緩存中存在的時間,以秒爲單位 Age: 12
  • Cache-Control 向從服務器直到客戶端在內的全部緩存機制告知,它們是否能夠緩存這個對象。其單位爲秒 Cache-Control: max-age=3600 常設
  • Connection 針對該鏈接所預期的選項 Connection: close
  • Location 用來進行重定向,或者在建立了某個新資源時使用。 Location: http://www.w3.org/pub/WWW/People.html

常見的實體首部

  • Content-Type 當前內容的MIME類型 Content-Type: text/html; charset=utf-8
  • Content-Length 迴應消息體的長度,以 字節 (8位爲一字節)爲單位 Content-Length: 348
  • Content-Encoding 在數據上使用的編碼類型。 Content-Encoding: gzip
  • Content-Language 內容所使用的語言
  • Content-Language 內容所使用的語言
  • Content-Location 所返回的數據的一個候選位置 Content-Location: /index.htm
  • Content-MD5 迴應內容的二進制 MD5 散列,以 Base64 方式編碼 Content-MD5: Q2hlY2sgSW50ZWdyaXR5IQ==
  • Content-Range 這條部分消息是屬於某條完整消息的哪一個部分 Content-Range: bytes 21010-47021/47022

更多關於首部的細節能夠參見HTTP首部

2.2 HTTP與HTTPS

HTTPS是一種經過計算機網絡進行安全通訊的傳輸協議。HTTPS經由HTTP進行通訊,但利用SSL/TLS來加密數據包。HTTPS開發的主要目的,是提供對網站服務器的身份 認證,保護交換數據的隱私與完整性。

以下圖所示,能夠很明顯的看出兩個的區別:

注:TLS是SSL的升級替代版,具體發展歷史能夠參考傳輸層安全性協議

HTTP與HTTPS在寫法上的區別也是前綴的不一樣,客戶端處理的方式也不一樣,具體說來:

  • 若是URL的協議是HTTP,則客戶端會打開一條到服務端端口80(默認)的鏈接,並向其發送老的HTTP請求。
  • 若是URL的協議是HTTPS,則客戶端會打開一條到服務端端口443(默認)的鏈接,而後與服務器握手,以二進制格式與服務器交換一些SSL的安全參數,附上加密的 HTTP請求。

因此你能夠看到,HTTPS比HTTP多了一層與SSL的鏈接,這也就是客戶端與服務端SSL握手的過程,整個過程主要完成如下工做:

  • 交換協議版本號
  • 選擇一個兩端都瞭解的密碼
  • 對兩端的身份進行認證
  • 生成臨時的會話密鑰,以便加密信道。

SSL握手是一個相對比較複雜的過程,更多關於SSL握手的過程細節能夠參考TLS/SSL握手過程

SSL/TSL的常見開源實現是OpenSSL,OpenSSL是一個開放源代碼的軟件庫包,應用程序可使用這個包來進行安全通訊,避免竊聽,同時確認另外一端鏈接者的身份。這個包普遍被應用在互聯網的網頁服務器上。 更多源於OpenSSL的技術細節能夠參考OpenSSL

三 WebSocket

WebSocket是一種在單個TCP鏈接上進行全雙工通信的協議,它使得客戶端和服務器之間的數據交換變得更加簡單,容許服務端主動向客戶端推送數據。在WebSocket API中,瀏覽器和服務器只須要完成一次握 手,二者之間就直接能夠建立持久性的鏈接,並進行雙向數據傳輸。

爲何須要WebSocket,由於 HTTP 協議有一個缺陷:通訊只能由客戶端發起。而WebSocket能夠實現雙向通訊。通常來講WebSocket是用來實現雙工通訊的長鏈接的。HTTP想要達到 這種效果,通常會經過輪詢或者long poll來實現,這樣比較佔用資源且很是被動。

一個典型的WebSocket請求與響應

客戶端請求

GET / HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Host: example.com
Origin: http://example.com
Sec-WebSocket-Key: sN9cRrP/n9NdMgdcy2VJFQ==
Sec-WebSocket-Version: 13
複製代碼

服務器響應

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: fFBooB7FAkLlXgRSz0BT3v4hq5s=
Sec-WebSocket-Location: ws://example.com/
複製代碼

這裏會使用Upgrade: websocket Connection: Upgrade 提示當前發起的是WebSocket協議,注意升級協議。

注:Okhttp已支持WebSocket。

咱們一樣也來看看WebSocket的報文結構。

0                   1                   2                   3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len |    Extended payload length    |
|I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
|N|V|V|V|       |S|             |   (if payload len==126/127)   |
| |1|2|3|       |K|             |                               |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
|     Extended payload length continued, if payload len == 127  |
+ - - - - - - - - - - - - - - - +-------------------------------+
|                               |Masking-key, if MASK set to 1  |
+-------------------------------+-------------------------------+
| Masking-key (continued)       |          Payload Data         |
+-------------------------------- - - - - - - - - - - - - - - - +
:                     Payload Data continued ...                :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
|                     Payload Data continued ...                |
+---------------------------------------------------------------+
複製代碼
  • FIN:1位,標識消息是不是最後一幀。1個消息由1個或者多個數據幀組成,若消息由1幀構成,則起始幀就是結束幀。
  • RSV1,RSV2,RSV3:1位,預留位,用於自定義擴展。若是沒有擴展,則各位爲0;若是定義擴展,即各位非0,但擴展中沒有該值的定義,則關閉鏈接。
  • opcode:4位,標識幀類型,幀類型微分控制幀與非控制幀,若是接收到未知幀,接收端就必須關閉鏈接。
  • MASK:1位,掩碼位,標識幀裏的數據是否通過加密,若是幀通過掩碼加密處理,該位爲1,Masking-key幀的數據就是掩碼密鑰,用於解碼Payload。 WebSocket協議規定

已定義的幀類型:

  • %x0 denotes a continuation frame
  • %x1 denotes a text frame
  • %x2 denotes a binary frame
  • %x3-7 are reserved for further non-control frames
  • %x8 denotes a connection close
  • %x9 denotes a ping
  • %xA denotes a pong
  • %xB-F are reserved for further control frames

WebSocket協議的控制幀有3種:

  • 關閉幀:用於關閉鏈接,客戶端能夠發送關閉幀給服務端,服務端也能夠發送關閉幀給客戶端。
  • Ping/Pong幀:用於心跳檢測,服務端向客戶端發送Ping幀,客戶端回覆Pong幀,表示鏈接還存在,能夠繼續通訊。

前面咱們講完了幾種經常使用的協議,最後咱們再看看和編碼相關的知識,這在平常的業務開發中也常常用到。

四 實體與編碼

前面咱們已經提到過與內容編碼相關的實體首部:

  • Content-Type 當前內容的MIME類型 Content-Type: text/html; charset=utf-8
  • Content-Length 迴應消息體的長度,以字節 (8位爲一字節)爲單位 Content-Length: 348
  • Content-Encoding 在數據上使用的編碼類型。 Content-Encoding: gzip
  • Content-Language 內容所使用的語言
  • Content-Location 所返回的數據的一個候選位置 Content-Location: /index.htm
  • Content-MD5 迴應內容的二進制 MD5 散列,以 Base64 方式編碼 Content-MD5: Q2hlY2sgSW50ZWdyaXR5IQ==
  • Content-Range 這條部分消息是屬於某條完整消息的哪一個部分 Content-Range: bytes 21010-47021/47022

對於咱們來講,比較常見到的也須要重點關注的是Content-Type、Content-Encoding這兩個。

Content-Type描述的是當前內容的MIME類型,關於MIME類型:

MIME:表示一種主要的對象類型和一個特定的子類型。

它主要有如下幾種類型:

  • Text:用於標準化地表示的文本信息,文本消息能夠是多種字符集和或者多種格式的;
  • Multipart:用於鏈接消息體的多個部分構成一個消息,這些部分能夠是不一樣類型的數據;
  • Application:用於傳輸應用程序數據或者二進制數據;
  • Message:用於包裝一個E-mail消息;
  • Image:用於傳輸靜態圖片數據;
  • Audio:用於傳輸音頻或者音聲數據;
  • Video:用於傳輸動態影像數據,能夠是與音頻編輯在一塊兒的視頻數據格式。

Content-Encoding描述的是編碼類型,它的意義在於告訴服務端當前客戶端支持的編碼方式,這樣服務端就會根據該編碼方式來編碼數據。若是沒有該首部,則默認認爲 客戶端支持全部的編碼方式。

Accept-Encoding	可以接受的編碼方式列表。	Accept-Encoding: gzip:q=1.0, deflate:q=0.5, *:q=0	
複製代碼

另外Accept-Encoding還能夠用q來表示編碼優先級,1.0表示最但願使用的編碼,0表示不想接受該編碼。

常見的編碼類型有:

  • compress – UNIX的「compress」程序的方法(歷史性,不推薦大多數應用使用,應該使用gzip或deflate)
  • deflate – 基於deflate算法(定義於RFC 1951)的壓縮,使用zlib數據格式(RFC 1950)封裝
  • exi – W3C高效XML交換
  • gzip – GNU zip格式(定義於RFC 1952)。此方法截至2011年3月,是應用程序支持最普遍的方法。
  • identity – 不轉換內容。這是內容編碼的默認值。
  • pack200-gzip – 傳輸Java存檔文件的網絡傳輸格式

固然咱們經常使用的就是gzip,Okhttp裏面能夠利用Okio進行gzip壓縮,這裏咱們也貼下代碼。

/** This interceptor compresses the HTTP request body. Many webservers can't handle this! */
final class GzipRequestInterceptor implements Interceptor {
  @Override public Response intercept(Interceptor.Chain chain) throws IOException {
    Request originalRequest = chain.request();
    if (originalRequest.body() == null || originalRequest.header("Content-Encoding") != null) {
      return chain.proceed(originalRequest);
    }

    Request compressedRequest = originalRequest.newBuilder()
        .header("Content-Encoding", "gzip")
        .method(originalRequest.method(), gzip(originalRequest.body()))
        .build();
    return chain.proceed(compressedRequest);
  }

  private RequestBody gzip(final RequestBody body) {
    return new RequestBody() {
      @Override public MediaType contentType() {
        return body.contentType();
      }

      @Override public long contentLength() {
        return -1; // We don't know the compressed length in advance!
      }

      @Override public void writeTo(BufferedSink sink) throws IOException {
        BufferedSink gzipSink = Okio.buffer(new GzipSink(sink));
        body.writeTo(gzipSink);
        gzipSink.close();
      }
    };
  }
}
複製代碼
相關文章
相關標籤/搜索