關於做者javascript
郭孝星,程序員,吉他手,主要從事Android平臺基礎架構方面的工做,歡迎交流技術方面的問題,能夠去個人Github提issue或者發郵件至guoxiaoxingse@163.com與我交流。html
文章目錄java
第一次閱覽本系列文章,請參見導讀,更多文章請參見文章目錄。android
在Android的網絡開發過程當中,咱們一般會使用像Okhttp、Retrofit這種高度封裝的網絡庫,它們徹底屏蔽了相關技術細節。可是掌握其中的原理對咱們來 說是很重要的,要知其然,也要知其因此然,只要掌握了這些原理,你才能更好的理解Okhttp等網絡庫的源碼實現。git
網絡編程一般會涉及如下幾個角色:程序員
怎麼去理解它們的關係呢?🤔github
例如咱們是雙十一從馬老闆家買了部手機,這個時候咱們就是客戶端,馬老闆就是服務端。手機要經過快遞公司的汽車運送到咱們手中。TCP/IP就至關於汽車,可是光有汽車是不夠的,還要對汽車 進行分類,否則都是同樣的汽車就亂套了,而完成分類的就是HTTP/HTTPS了,HTTP/HTTPS會告訴這些汽車,你是負責送貨的(GET),你是負責退貨的(POST)等等。web
注:文章中部分圖片來源於網絡,此次就偷個懶,有些流程圖就不畫了。😁算法
TCP(傳輸控制協議)是一種面向鏈接的、可靠的、基於字節流的傳輸層通訊協議,編程
TCP協議是HTTP/HTTPS、WebSocket等協議的基礎,咱們首先來看看它們的報文格式。
關於IP數據報與TCP報文你只須要理解它的結構就行,不用去記它,等到使用的時候不記得了,查一下就行了。
IP數據報
TCP報文
TCP用三次握手(three-way handshake)過程建立一個鏈接,使用四次分手 關閉一個鏈接。
三次握手與四次分手的流程以下所示:
三次握手
四次分手
三次握手與四次分手也是個老生常談的概念,舉個簡單的例子說明一下。
三次握手
例如你小時候出去玩,常常玩忘了回家吃飯。你媽媽也常常過來喊你。若是你沒有走遠,在門口的小土堆上玩泥巴,你媽媽會喊:"小新,回家吃飯了"。你聽到後會迴應:"知道了,一會就回去"。媽媽聽 到你的迴應後又說:"快點回來,飯要涼了"。這樣你媽媽和你就完成了三次握手的過程。😁說到這裏你也能夠理解三次握手的必要性,少了其中一個環節,另外一方就會陷入等待之中。
三次握手的目的是爲了防止已失效的鏈接請求報文段忽然又傳送到了服務端,於是產生錯誤.
四次分手
例如偶像言情劇乾淨利落的分手,女主對男主說:咱們分手吧🙄,男主說:分就分吧😰。女主說:你果真是不愛我了,你只知道讓我多喝熱水🙄。男主說:事到現在也沒什麼好說的了,祝你幸福🙃。四次分手完成。說到這裏你能夠理解 了四次分手的必要性,第一次是女方(客戶端)提出分手,第二次是男主(服務端)贊成女主分手,第三次是女主肯定男主再也不愛她,也贊成男主分手。第四次兩人完全拜拜(斷開鏈接)。
由於TCP是全雙工模式,因此四次分手的目的就是爲了可靠地關閉鏈接。
HTTP(HyperText Transfer Protocol)是一種用於分佈式、協做式和超媒體信息系統的應用層協議[1]。HTTP是萬維網的數據通訊的基礎。
HTTP是最多見的應用層協議,咱們平常開發中基本上接觸到的都是這個協議。
HTTP應用程序是經過相互發送報文工做的,報文是HTTP應用程序之間發送的數據塊,報文一般分爲請求報文和響應報文兩種,請求報文向服務器請求一個動做,響應報文將請求結果返回給客戶端。
HTTP請求報文分爲三部分:請求行、請求首部、請求實體.
請求報文
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/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/POST。
GET與POST在本質上都是TCP鏈接,只是GET直接把參數寫在請求行中,而POST把參數放在請求體中。關於這兩個方法,要注意如下兩點:
狀態碼
更多關於狀態碼的細節能夠參見HTTP狀態碼。
首部
首部一般和方法配合工做,共同決定了客戶端和服務器能作什麼事情。
常見的通用首部
常見的請求首部
常見的響應首部
常見的實體首部
更多關於首部的細節能夠參見HTTP首部。
HTTPS是一種經過計算機網絡進行安全通訊的傳輸協議。HTTPS經由HTTP進行通訊,但利用SSL/TLS來加密數據包。HTTPS開發的主要目的,是提供對網站服務器的身份 認證,保護交換數據的隱私與完整性。
以下圖所示,能夠很明顯的看出兩個的區別:
注:TLS是SSL的升級替代版,具體發展歷史能夠參考傳輸層安全性協議。
HTTP與HTTPS在寫法上的區別也是前綴的不一樣,客戶端處理的方式也不一樣,具體說來:
因此你能夠看到,HTTPS比HTTP多了一層與SSL的鏈接,這也就是客戶端與服務端SSL握手的過程,整個過程主要完成如下工做:
SSL握手是一個相對比較複雜的過程,更多關於SSL握手的過程細節能夠參考TLS/SSL握手過程
SSL/TSL的常見開源實現是OpenSSL,OpenSSL是一個開放源代碼的軟件庫包,應用程序可使用這個包來進行安全通訊,避免竊聽,同時確認另外一端鏈接者的身份。這個包普遍被應用在互聯網的網頁服務器上。 更多源於OpenSSL的技術細節能夠參考OpenSSL。
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 ... |
+---------------------------------------------------------------+
複製代碼
已定義的幀類型:
WebSocket協議的控制幀有3種:
前面咱們講完了幾種經常使用的協議,最後咱們再看看和編碼相關的知識,這在平常的業務開發中也常常用到。
前面咱們已經提到過與內容編碼相關的實體首部:
對於咱們來講,比較常見到的也須要重點關注的是Content-Type、Content-Encoding這兩個。
Content-Type描述的是當前內容的MIME類型,關於MIME類型:
MIME:表示一種主要的對象類型和一個特定的子類型。
它主要有如下幾種類型:
Content-Encoding描述的是編碼類型,它的意義在於告訴服務端當前客戶端支持的編碼方式,這樣服務端就會根據該編碼方式來編碼數據。若是沒有該首部,則默認認爲 客戶端支持全部的編碼方式。
Accept-Encoding 可以接受的編碼方式列表。 Accept-Encoding: gzip:q=1.0, deflate:q=0.5, *:q=0
複製代碼
另外Accept-Encoding還能夠用q來表示編碼優先級,1.0表示最但願使用的編碼,0表示不想接受該編碼。
常見的編碼類型有:
固然咱們經常使用的就是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();
}
};
}
}
複製代碼