Android網路程式設計:基礎理論彙總

蘇策發表於2017-12-05

關於作者

郭孝星,程式設計師,吉他手,主要從事Android平臺基礎架構方面的工作,歡迎交流技術方面的問題,可以去我的Github提issue或者發郵件至guoxiaoxingse@163.com與我交流。

文章目錄

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

在Android的網路開發過程中,我們通常會使用像Okhttp、Retrofit這種高度封裝的網路庫,它們完全遮蔽了相關技術細節。但是掌握其中的原理對我們來 說是很重要的,要知其然,也要知其所以然,只要掌握了這些原理,你才能更好的理解Okhttp等網路庫的原始碼實現。

網路程式設計通常會涉及以下幾個角色:

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

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

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

注:文章中部分圖片來源於網路,這次就偷個懶,有些流程圖就不畫了。?

一 TCP/IP

TCP(傳輸控制協議)是一種面向連線的、可靠的、基於位元組流的傳輸層通訊協議,

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

1.1 IP資料包與TCP報文

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

IP資料包

Android網路程式設計:基礎理論彙總
  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報文

Android網路程式設計:基礎理論彙總
  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)過程建立一個連線,使用四次分手 關閉一個連線。

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

Android網路程式設計:基礎理論彙總

三次握手

  • 第一次握手:建立連線。客戶端傳送連線請求報文段,將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。
Android網路程式設計:基礎理論彙總

請求報文

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 個部分組成,他們之間使用空格隔開。
  • 響應首部由關鍵字/值對組成,每行一對,關鍵字和值用英文冒號“:”分隔。請求首部部通知客戶端有關於服務端響應的資訊。
  • 響應實體是伺服器返回給客戶端的文字資訊。
Android網路程式設計:基礎理論彙總
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開發的主要目的,是提供對網站伺服器的身份 認證,保護交換資料的隱私與完整性。

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

Android網路程式設計:基礎理論彙總

注: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來實現,這樣比較佔用資源且非常被動。

Android網路程式設計:基礎理論彙總

一個典型的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();
      }
    };
  }
}
複製程式碼

相關文章