国产成人精品无码青草_亚洲国产美女精品久久久久∴_欧美人与鲁交大毛片免费_国产果冻豆传媒麻婆精东

15158846557 在線咨詢 在線咨詢
15158846557 在線咨詢
所在位置: 首頁 > 營銷資訊 > 網(wǎng)站運(yùn)營 > 關(guān)于安卓開發(fā)的一些你必須要掌握的網(wǎng)絡(luò)知識(一):網(wǎng)絡(luò)基礎(chǔ)與網(wǎng)絡(luò)框架Ok

關(guān)于安卓開發(fā)的一些你必須要掌握的網(wǎng)絡(luò)知識(一):網(wǎng)絡(luò)基礎(chǔ)與網(wǎng)絡(luò)框架Ok

時(shí)間:2023-05-28 09:30:01 | 來源:網(wǎng)站運(yùn)營

時(shí)間:2023-05-28 09:30:01 來源:網(wǎng)站運(yùn)營

關(guān)于安卓開發(fā)的一些你必須要掌握的網(wǎng)絡(luò)知識(一):網(wǎng)絡(luò)基礎(chǔ)與網(wǎng)絡(luò)框架OkHttp: 為了能把OkHttp以及作為安卓開發(fā)必須掌握的關(guān)于網(wǎng)絡(luò)知識能寫清楚,本文從網(wǎng)絡(luò)知識講起,然后會手動(dòng)仿照OkHttp的源碼結(jié)構(gòu)來寫一個(gè)它的架構(gòu),然后結(jié)合OkHttp的源碼來為大家講解。相信大家看完本系列后,除了能搞透OkHttp之外,還會對網(wǎng)絡(luò)框架有一個(gè)更深的認(rèn)識。

一、HTTP協(xié)議

說到HTTP協(xié)議,其實(shí)有很多安卓開發(fā)者對這個(gè)HTTP協(xié)議也是一知半解,所以在做網(wǎng)絡(luò)請求這塊時(shí),解決問題時(shí)都是憑著網(wǎng)上搜的答案加上自己的感覺來操作,就算問題最后解決了,也只是剛好運(yùn)氣好讓你撞對了。因此很有必要講一下這個(gè)HTTP協(xié)議。

其實(shí)不外乎就是兩點(diǎn):1)瀏覽器上地址欄輸入網(wǎng)址,然后就打開網(wǎng)站 2)開發(fā)時(shí)候你使用網(wǎng)絡(luò)框架去請求后端,然后后端返回?cái)?shù)據(jù)給你

以上這兩個(gè)地方其實(shí)就是使用了HTTP協(xié)議。

HTTP全稱超文本傳輸協(xié)議,也就是說可以指向其他文本的鏈接,那么我們輸入的網(wǎng)址以及我們對服務(wù)器發(fā)送的請求鏈接,其實(shí)就是超文本鏈接了,那么整個(gè)傳輸過程就是進(jìn)行HTTP協(xié)議的過程,而文本你可以理解就是一段html文本,用來展示頁面。而在安卓開發(fā)中,服務(wù)器返回的一般是json字符串,或者xml文件等。

1.工作方式

如圖所示,當(dāng)你輸入了網(wǎng)址之后,點(diǎn)擊回車鍵,瀏覽器會把這串URL字符串會被封裝成HTTP報(bào)文,傳到服務(wù)器上去。我們來分析一下URL字符串:

可以看到,假設(shè)我們要訪問的網(wǎng)址是http://www.baidu.com/user?username=James,URL則分為三個(gè)部分,而這三部分被封裝成報(bào)文,它的結(jié)構(gòu)是這樣的:

雖然說GET方法也有Body,但一般請求時(shí),都不會加的,而POST才會有Body。而這些請求行、Header和Body又是什么意思,接下來會一一為大家講解。而現(xiàn)在繼續(xù)看回下一個(gè)知識點(diǎn):服務(wù)器收到請求之后會發(fā)送響應(yīng)報(bào)文給客戶端,響應(yīng)報(bào)文的結(jié)構(gòu)如下圖所示:

跟請求報(bào)文是有區(qū)別的,狀態(tài)行是協(xié)議版本、狀態(tài)碼和狀態(tài)信息,響應(yīng)頭部Header也有,而Body里就是響應(yīng)數(shù)據(jù),這些Header里的數(shù)據(jù)等概念同意會在接下來的段落里講清楚。

2.報(bào)文結(jié)構(gòu)

2.1請求方法

在請求報(bào)文中的請求行里,首先是一個(gè)請求方法,也就是我們平時(shí)看到的GET或者POST,它其實(shí)是有四種:

1)GET:獲取資源,沒有Body,直接通過URL字符串請求發(fā)送給服務(wù)器

2)POST:增加或修改資源,有Body。Body里存放的就是要修改或者增加的數(shù)據(jù)是什么

可以看到,上面這個(gè)請求報(bào)文,從請求行中可以看到它的請求方法是POST,然后就是Headers,再到最后的Body里的數(shù)據(jù)是一個(gè)類似key-value形式連起來的字符串?dāng)?shù)據(jù)就是表示要增加或修改資源的參數(shù)。

3)PUT:修改資源,有Body。它跟POST很像,區(qū)別在于PUT冪等,也就是請求多次,它都是按回你最終那次為準(zhǔn),而POST則不冪等,每發(fā)送一次,就會修改一次,而GET跟PUT也是一樣,每次發(fā)送同樣的請求,也都是以最后那次為最終結(jié)果。

4)DELETE:刪除資源,沒有Body。很好理解,就是請求服務(wù)器刪除該資源,也是冪等。

5)HEAD:跟GET很像,不過唯一區(qū)別就是HEAD發(fā)送請求后,服務(wù)器返回的響應(yīng)報(bào)文中是沒有Body,一般HEAD請求都是用來作試探性請求,提前先探測來獲取到一些前期準(zhǔn)備工作相關(guān)的信息。

2.2狀態(tài)碼

這是響應(yīng)報(bào)文中的狀態(tài)行里出現(xiàn)的,對結(jié)果作出的一個(gè)描述,比如200表示獲取成功,而它其實(shí)可以按照開頭數(shù)字進(jìn)行劃分:

1)2XX:一般表示請求以及響應(yīng)成功

2)4XX:客戶端錯(cuò)誤,可能發(fā)送的請求或者請求參數(shù)有錯(cuò)等

3)5XX:服務(wù)器錯(cuò)誤,可能服務(wù)器那邊在處理請求時(shí)出錯(cuò)等

4)3XX:重定向。比如301(當(dāng)用戶以URL1進(jìn)行請求時(shí),服務(wù)器會返回301,然后客戶端接收到之后會自動(dòng)以URL2來二次請求)

5)1XX:臨時(shí)性消息。比如當(dāng)客戶端要使用HTTP2協(xié)議來請求時(shí),會先在Headers頭里加一個(gè)數(shù)據(jù)用來詢問服務(wù)器是否支持,然后服務(wù)器收到這個(gè)請求報(bào)文后,如果支持的話,就會在響應(yīng)報(bào)文里發(fā)送的狀態(tài)碼變?yōu)?01發(fā)給客戶端。還有一種情況,當(dāng)客戶端要進(jìn)行分段請求,那么也會在Header里加入這個(gè)數(shù)據(jù)表示分段請求,然后發(fā)給服務(wù)器后,服務(wù)器如果表示可以,就會返回狀態(tài)碼100,客戶端接收到之后會繼續(xù)進(jìn)行請求。

所以這些狀態(tài)碼都能幫助客戶端和服務(wù)端了解網(wǎng)絡(luò)情況以及方便調(diào)試網(wǎng)絡(luò)請求。

2.3Header

HTTP消息里的元數(shù)據(jù),好像很抽象,其實(shí)就是HTTP消息里摻雜另外一些消息,你可以理解為進(jìn)一步向服務(wù)器(或客戶端)說明一些情況。比如上文中我們介紹狀態(tài)碼時(shí)的100和101,客戶端當(dāng)要對服務(wù)器說明自己要發(fā)送的請求是使用HTTP2或者分段請求時(shí),就會在Header里添加對應(yīng)的數(shù)據(jù),這個(gè)Header就是元數(shù)據(jù)了。

常見的Header

1)Host:服務(wù)器主機(jī)地址。其實(shí)服務(wù)器尋址在發(fā)送請求那刻起(DNS--IP層做的事)的時(shí)候已經(jīng)尋到了要給哪個(gè)服務(wù)器發(fā)送請求,但還是需要發(fā)送這個(gè)Header里的Host給服務(wù)器是因?yàn)橐粋€(gè)IP地址可能會映射好幾個(gè)服務(wù)器主機(jī)地址(因?yàn)樘摂M主機(jī)地址的存在),因此還是要添加這個(gè)說明來讓雙方進(jìn)一步參考。

2)Content-Length:內(nèi)容的長度(字節(jié)),可以讓服務(wù)器在長長的數(shù)據(jù)流里知道真正的請求數(shù)據(jù)從哪里開始,到哪里結(jié)束

3)Content-Type:內(nèi)容的類型。這個(gè)header很重要,因?yàn)樗硎景l(fā)送(或返回)的內(nèi)容是什么類型,這個(gè)內(nèi)容的類型非常重要:

--text/html:html文本,發(fā)送(或返回)的數(shù)據(jù)是瀏覽器頁面

--application/x-www-form-urlencoded:普通表單,encoded URL格式。就是Post的時(shí)候body里的要發(fā)送的參數(shù),它是key1=value1&key2=value2的形式

--multipart/form-data:多部分形式,一般用于傳輸包含二進(jìn)制內(nèi)容(圖片、文件等)的多項(xiàng)內(nèi)容

該請求報(bào)文的格式從上到下,請求行,然后到Header,Header里的Content-Type指定了multipart/form-data,表示要發(fā)送的形式是多部分的,而這里設(shè)置了一個(gè)boundary屬性,它起到一個(gè)邊界線作用,用來分割要發(fā)送的各個(gè)部分的數(shù)據(jù),而我們這里要發(fā)送的數(shù)據(jù),首先有普通表單數(shù)據(jù)的參數(shù)name,它的值這里是James,而接下來要發(fā)送的數(shù)據(jù)則是一個(gè)頭像圖片,名字叫James.jpg,所以FEA45V....這一連串的二進(jìn)制數(shù)據(jù)就是這張頭像圖片,每部分要發(fā)送的數(shù)據(jù)都用boundary來限定好,這樣看起來簡潔清晰,而且能被服務(wù)器識別與分界。當(dāng)要傳文件或者圖片的同時(shí),也要把普通的表單參數(shù)發(fā)送給服務(wù)器,就可以使用這張形式。

--application/json:json形式,用于Web API的響應(yīng)或者POST/PUT請求

很好理解,請求的時(shí)候,傳的參數(shù)是json字符串。

--image/jpeg/application/zip...:單文件,用于Web API響應(yīng)或POST/PUT請求。它是用來傳輸文件等最高效的方式,不過需要服務(wù)端學(xué)習(xí)與了解此種方式。

非常簡潔,Content-Type里設(shè)置image/jpeg,然后body里傳的是圖片數(shù)據(jù)。

4)Transfer-Encoding:chunked,服務(wù)器分塊響應(yīng),有時(shí)服務(wù)器可能不能一下子就把一段數(shù)據(jù)完整傳輸給客戶端,因此把已經(jīng)算好的數(shù)據(jù)先分塊傳輸給客戶端,剩下的數(shù)據(jù)段繼續(xù)分塊發(fā)送給客戶端。此時(shí)Body長度不能確定,Content-Length不能使用。

0表示內(nèi)容結(jié)束,已經(jīng)傳完。

5)Location:重定向的目標(biāo)URL,在重定向的時(shí)候它的值就是那個(gè)最終要跳轉(zhuǎn)的URL值

6)User-Agent:用戶代理,不同的代理可能會導(dǎo)致解析的內(nèi)容渲染都不一樣。常見的代理有:

7)Range/Accept-Range:指定Body的內(nèi)容范圍。當(dāng)服務(wù)器支持客戶端分段接收數(shù)據(jù)時(shí),就可以設(shè)置這個(gè)header里的Accept-Range的值來指定客戶端要接收的body數(shù)據(jù)要接收多少。Accept-Range的值是bytes字節(jié),設(shè)置Accept-Range:bytes=1024,這樣客戶端發(fā)送給服務(wù)器之后,服務(wù)器就會返回1024字節(jié)的數(shù)據(jù)給客戶端。

8)Cookie/Set-Cookie:發(fā)送Cookie/設(shè)置Cookie(下面會講解)

9)Authorization:授權(quán)信息(下面會講解)

10)Accept:客戶端能接收的數(shù)據(jù)類型,比如text/html

11)Accept-Charset:客戶端接收數(shù)據(jù)的字符集,比如utf-8

12)Accept-Encoding:客戶端接收數(shù)據(jù)的壓縮編碼類型,比如gzip

以上便是一些常見的以及重要的header,還有一些header如果感興趣的可自行搜索,這里就不作講解了。

2.4Cache

cache是緩存,第一次請求該接口獲取到數(shù)據(jù)后,將這些數(shù)據(jù)緩存起來,等到下次如果再需要該接口的數(shù)據(jù)時(shí),可以直接使用緩存起來的數(shù)據(jù),而不用再耗費(fèi)性能重復(fù)請求了,而緩存的數(shù)據(jù)根據(jù)緩存時(shí)的時(shí)間的新鮮程度來分等級,優(yōu)先級高的緩存數(shù)據(jù)則可以不被清除,而時(shí)間比較舊的緩存數(shù)據(jù)則要清除,再次請求接口,然后更新為新鮮數(shù)據(jù)。

那設(shè)置緩存的方法就是設(shè)置Header,有以下幾個(gè):

--Cache-Control:

1)設(shè)置no-cache:表示要緩存數(shù)據(jù),然后下次需要使用該接口的數(shù)據(jù)時(shí),需要請求詢問服務(wù)器這些緩存數(shù)據(jù)是否失效

2)設(shè)置no-store:不需緩存數(shù)據(jù)

3)設(shè)置max-age:當(dāng)要再次使用該接口數(shù)據(jù)時(shí),此時(shí)的時(shí)間如果是在max-age這個(gè)時(shí)間值之前,緩存的數(shù)據(jù)無需詢問服務(wù)器是否失效,直接使用便可。當(dāng)超過了這個(gè)時(shí)間后,緩存的數(shù)據(jù)就表示失效。

--Last-Modified if-Modified-Since :表示服務(wù)器發(fā)送給客戶端響應(yīng)時(shí)告訴客戶端下次如果需要使用這個(gè)接口數(shù)據(jù)時(shí)要詢問緩存數(shù)據(jù)在這個(gè)時(shí)間段前是否改過這個(gè)數(shù)據(jù)的內(nèi)容,沒有改過,則繼續(xù)使用緩存數(shù)據(jù)。

--Etag if-None-Match:相當(dāng)于一個(gè)服務(wù)器針對這個(gè)數(shù)據(jù)而創(chuàng)建的一個(gè)對應(yīng)指紋數(shù)據(jù),發(fā)給客戶端,而客戶端下次需要使用這個(gè)接口數(shù)據(jù)時(shí),拿這個(gè)指紋數(shù)據(jù)詢問服務(wù)器這個(gè)數(shù)據(jù)對應(yīng)的這個(gè)etag判斷是否相等,不相等則證明緩存數(shù)據(jù)失效,重新請求接口;相等則表示緩存數(shù)據(jù)沒失效,可繼續(xù)使用緩存數(shù)據(jù)。

--Cache-Control:private/public:表示請求發(fā)送給服務(wù)器這個(gè)過程的時(shí)候,一些中間節(jié)點(diǎn)是否可訪問和操作這些數(shù)據(jù),private則不能,public則可以。

二、加密

加密與解密也是網(wǎng)絡(luò)請求中涉及到的知識點(diǎn),非常重要。加密的兩個(gè)因素:算法和密鑰。算法即要怎么去算(比如使用加減乘除),密鑰即用什么來算(比如用一串?dāng)?shù)字564846),這樣也就是對564846進(jìn)行加減乘除后變成另一個(gè)密文字符串。

1.對稱加密

使用密鑰和加密算法對數(shù)據(jù)進(jìn)行轉(zhuǎn)換,得到的無意義數(shù)據(jù)則為密文,使用密鑰和解密算法對密文進(jìn)行逆向轉(zhuǎn)換(解密),可以得到原數(shù)據(jù)。

A對數(shù)據(jù)進(jìn)行加密后,然后把密文發(fā)給了B,B拿到密文后,用同樣的密鑰使用解密算法進(jìn)行解密,就得到原數(shù)據(jù)了。而對稱加密的經(jīng)典算法有DES和AES等。

2.非對稱加密

使用公鑰(對外公開的)對數(shù)據(jù)進(jìn)行加密得到密文,使用私鑰(私有的,只有自己擁有)對數(shù)據(jù)進(jìn)行解密得到原數(shù)據(jù)。

是不是覺得很神奇,為什么加密和解密用的都是同一種算法,但是經(jīng)過兩種密鑰之后,又能還原,其實(shí)非對稱加密其中一個(gè)原理是利用了計(jì)算機(jī)的進(jìn)制計(jì)算會溢出,然后取有效的位數(shù),從而能達(dá)到還原,如果感興趣的可以自行去搜搜,這是算法,不是本文的重點(diǎn)。

這樣的話,非對稱加密要比對稱加密安全得多,因?yàn)榧词沟谌浇厝〉桨l(fā)送方和接收方的各自的公鑰也沒用,因?yàn)榻饷鼙仨毷亲约旱乃借€才能解密。

3.數(shù)字簽名

如果用私鑰加密數(shù)據(jù),然后用公鑰解密,可以把密文還原為原數(shù)據(jù)嗎?

如圖所示,假設(shè)我們把中間紅色定為原數(shù)據(jù),那么此時(shí)只看右邊的話,就是用私鑰進(jìn)行加密原數(shù)據(jù)的過程,而右邊的數(shù)據(jù)為密文,那么如果想解密,根據(jù)上文講的非對稱加密與解密原理,是不是就應(yīng)該用該密文使用公鑰(因?yàn)橐獌砂巡灰粯拥拿荑€)配合(相同的)加密算法去解密,那這個(gè)過程不就是左邊的數(shù)據(jù)到紅色數(shù)據(jù)的過程嗎,因此用私鑰加密的密文,是可以用公鑰還原為原數(shù)據(jù)的。不過正常情況下,我們應(yīng)該是使用公鑰加密,私鑰還原,這樣才是正確的非對稱加密做法。

不過用私鑰加密,公鑰還原的這一特性,被用來應(yīng)用在數(shù)字簽名上。數(shù)字簽名是用來驗(yàn)證該數(shù)據(jù)是否是發(fā)送方發(fā)送的,而不是其他第三者發(fā)送的。

因?yàn)槟苡霉€還原到的這個(gè)數(shù)據(jù)肯定是這個(gè)發(fā)送方發(fā)送的,因?yàn)檫@個(gè)簽名數(shù)據(jù)是發(fā)送方用它自己的私鑰加密而得的,私鑰只有這個(gè)發(fā)送方才有的,任何第三者都沒有的。

當(dāng)然,你怎么知道你用公鑰還原的這個(gè)數(shù)據(jù)就是發(fā)送方發(fā)的呢,即使也是看起來是很正常的數(shù)據(jù),也保不齊是第三方用它的私鑰加密而得的,因此可以用加密+簽名的方式:對原數(shù)據(jù)先使用加密算法,然后用對方公鑰加密成密文,然后再用自己私鑰加密原數(shù)據(jù)為數(shù)字簽名,這樣對方要得到原數(shù)據(jù)就要先使用自己的私鑰來解密,而要驗(yàn)證這個(gè)數(shù)據(jù)是否是發(fā)送方發(fā)送的,那就使用發(fā)送方的公鑰進(jìn)行驗(yàn)證

這個(gè)數(shù)字簽名能讓第三方不能偽造成發(fā)送方發(fā)送數(shù)據(jù)了,因?yàn)槎嗔蓑?yàn)證數(shù)字簽名這一步,簽名只能是用私鑰來加密的,而私鑰只有發(fā)送方才有的,第三方用它自己的私鑰加密一個(gè)簽名數(shù)據(jù)發(fā)給接收方,而接收方用發(fā)送方的公鑰進(jìn)行驗(yàn)證時(shí),得到的數(shù)據(jù)就跟第三方偽造成發(fā)送方時(shí)發(fā)的原數(shù)據(jù)不一樣,也就能識別出來。

非對稱加密的經(jīng)典算法有RSA和DSA。

補(bǔ)充:可能會有人將密鑰和登錄密碼兩者搞混,其實(shí)很好理解,密鑰(key)相當(dāng)于一把鑰匙,有它才能開門(加密或者解密),而登錄密碼只是一個(gè)用來證明“你是你”的通過碼(password)。

4.Base64

它是一種編碼,編碼,就是按照某種算法把一個(gè)字符換算為另一個(gè)字符。而Base64就是將二進(jìn)制數(shù)據(jù)轉(zhuǎn)換成由64個(gè)字符組成的字符串的編碼算法。它有個(gè)碼表:

總共64個(gè)二進(jìn)制數(shù)字,分別對應(yīng)A-Z,a-z以及一些其他標(biāo)點(diǎn)字符等64個(gè),然后根據(jù)這個(gè)碼表來算:

以6位為一個(gè)字符來進(jìn)行分割,因此原先三個(gè)二進(jìn)制,就變成四個(gè)6位的二進(jìn)制數(shù)字,也就把原來只有三個(gè)字符變成如今的四個(gè)字符,由此可見,轉(zhuǎn)成Base64的數(shù)據(jù)會變長。Base64主要用于一定要上傳字符串的場景,Base64其實(shí)不高效也不安全。人人都知道怎么計(jì)算,而且會將原數(shù)據(jù)變長。

5.URL encoding

也是其中一種編碼,將URL中的一些字符使用百分號“%”等特殊字符進(jìn)行編碼,為的是消除歧義和避免解析錯(cuò)誤。

可以看到,地址欄輸入的是一個(gè)中文,然后實(shí)則發(fā)送過去時(shí)會將中文字符編碼為一串帶著百分號的字符串。

6.壓縮與解壓縮

壓縮是把數(shù)據(jù)換一種方式來存儲,以減少存儲空間,而解壓縮則是把壓縮后的數(shù)據(jù)還原為原先的數(shù)據(jù)的形式再以使用。常見的壓縮算法有DEFLATE、JPEG和MP3等。

壓縮屬于編碼,因?yàn)榫幋a的本質(zhì)是將A數(shù)據(jù)轉(zhuǎn)換成B數(shù)據(jù),然后B數(shù)據(jù)通過逆運(yùn)算后能還原回A數(shù)據(jù),而壓縮符合這個(gè)特點(diǎn)。其中運(yùn)用編碼最多就是媒體數(shù)據(jù),比如音頻和視頻等。

圖片、音頻和視頻經(jīng)常會用到編碼來轉(zhuǎn)換成另一種數(shù)據(jù),然后再對該數(shù)據(jù)進(jìn)行解碼來還原回原數(shù)據(jù)。比如把圖像數(shù)據(jù)(argb)寫出為JPG、PNG等編碼格式。

而經(jīng)典的視頻數(shù)據(jù)原始數(shù)據(jù)是yum數(shù)據(jù),然后經(jīng)過編碼后變成h264碼流,然后再進(jìn)行封裝編碼成mp4格式數(shù)據(jù)。其中,h264變成mp4這個(gè)過程其實(shí)是有壓縮的,因?yàn)橐欢汛a流,肯定是有重復(fù)性的字符的,因此可以使用壓縮算法來進(jìn)行壓縮,這樣能減少冗余重復(fù)的字符,從而達(dá)到減少存儲空間的目的。而要把編碼后的數(shù)據(jù)還原為原數(shù)據(jù),則進(jìn)行逆運(yùn)算,也就是視頻解碼。

7.序列化與反序列化

序列化與反序列化也是編碼,序列化是指把數(shù)據(jù)對象(一般內(nèi)存中,比如JVM中的對象)轉(zhuǎn)換成字節(jié)序列(方便網(wǎng)絡(luò)傳輸)的過程,而反序列化就是把字節(jié)序列重新轉(zhuǎn)換回內(nèi)存中的對象

8.Hash

把任意數(shù)據(jù)轉(zhuǎn)換成指定大小范圍(通常很?。┑臄?shù)據(jù)。用于摘要、數(shù)字指紋等。經(jīng)典算法有MD5、SHA1、SHA256等。

Hash算法要求算出的值碰撞率要低,也就是要具有唯一性。當(dāng)要重寫某個(gè)類的equals方法時(shí),也要重寫hashCode方法,因?yàn)閔ashCode相當(dāng)于一個(gè)對象的內(nèi)存地址,唯一標(biāo)識,一定要碰撞率小,這樣才能保證不會出現(xiàn)明明equals方法是不相等,而由于碰撞率高兩個(gè)對象的hashCode都一樣,這樣就出問題了,所以用到hashMap時(shí)會出錯(cuò),hashMap就是用到了hashCode的。

當(dāng)然也可以用hash讓數(shù)據(jù)具有隱秘性,比如讓“James”去作MD5算一下,這樣就會變成“sdads...”類似的無意義字符串。不過它也是可以被人“撞庫”,意思就是現(xiàn)在很多人會專門記錄一些詞的MD5之后的字符串是多少,記錄起來,形成一張字典,然后拿著這張字典,一個(gè)個(gè)地去試錯(cuò),這樣有可能被它找到正確的數(shù)據(jù)。

因此有種措施,就是“加鹽”,也就是定義好另一個(gè)字符串,比如這個(gè)鹽是“jjj”,那么在對“James”進(jìn)行MD5計(jì)算時(shí),可以把“James”加上“jjj”后再進(jìn)行MD5計(jì)算,這樣安全性會高很多,而這個(gè)鹽“jjj”只有服務(wù)器自己存著,不能公開出去。到了客戶端把數(shù)據(jù)加鹽然后再M(fèi)D5之后,傳給服務(wù)器時(shí),服務(wù)器就可以從自己本地把鹽拿出來跟“James”進(jìn)行MD5后得到的值跟客戶端這個(gè)值做對比,如果一樣,則證明的確是客戶端傳過來的,而不是第三方偽造的。

Hash不是編碼,它是抽取原數(shù)據(jù)的一些特征經(jīng)過計(jì)算而得出的一個(gè)很小的數(shù)據(jù),它不能逆運(yùn)算后轉(zhuǎn)換回原數(shù)據(jù)的,因此它不是編碼。嚴(yán)格上來說,它也不是加密,因?yàn)榧用芤彩强赡孓D(zhuǎn)的,有加密就會有解密。

Hash可以用到非對稱加密當(dāng)中去,原非對稱加密中數(shù)字簽名是跟原數(shù)據(jù)一樣大,這樣就會造成數(shù)據(jù)容量很大,而為了減少數(shù)據(jù)量,可以把數(shù)字簽名用原數(shù)據(jù)進(jìn)行hash,變成摘要,這樣數(shù)字簽名的數(shù)據(jù)大小就瞬間減少很多:

以上過程是在原數(shù)據(jù)沒有進(jìn)行非對稱加密的情況下進(jìn)行簽名驗(yàn)證,如果原數(shù)據(jù)是非對稱加密了的話,則用私鑰對密文進(jìn)行解密,得到原數(shù)據(jù)之后進(jìn)行Hash,得到摘要數(shù)據(jù),然后再用對方公鑰對簽名后的摘要進(jìn)行解密,得到待驗(yàn)證的原摘要,最后就拿著它跟摘要進(jìn)行對比,兩者如果相同,那就驗(yàn)證成功。

三、登錄授權(quán)

登錄其實(shí)也是授權(quán),把屬于你自己的權(quán)限授予你自己,然后就可以獲取某些信息,可以操作某些事情,而授權(quán)就是把權(quán)限授予給你,讓你能獲取某些信息,可以操作某些事情。

登錄與授權(quán)的使用方式:Cookie與Authorization

1.Cookie

Cookie的起源是來源于早期電商網(wǎng)站的購物車功能,因?yàn)橘徫镘囀菍儆谟脩羰詹氐墓δ?,并未真正購買,因此電商平臺認(rèn)為購物車的數(shù)據(jù)不應(yīng)該存在他們自己本地上管理,而是交由瀏覽器開發(fā)者保存與管理。而這一功能就叫Cookie。

首先當(dāng)客戶端往購物車存一本書,則往服務(wù)端發(fā)送請求,服務(wù)器收到請求后,就在返回包的header里也就是Set-Cookie:shop="book=1"(shop="book=1"只是一個(gè)例子,可根據(jù)實(shí)際開發(fā)自己定值)設(shè)置好后給客戶端:

就這樣,客戶端的Cookie就會存下shop="book=1",下次再訪問該網(wǎng)站http://shop.com時(shí),客戶端(瀏覽器)根據(jù)這個(gè)shop="book=1"的Cookie設(shè)置更新購物車,顯示已經(jīng)收藏了一本書,而接著如果想繼續(xù)發(fā)送一個(gè)請求是在購物車?yán)锾砑右徊坑?jì)算機(jī),除了在body把計(jì)算機(jī)post過去之外,還在header里設(shè)置Cookie:shop="book=1"發(fā)送給服務(wù)器:

然后服務(wù)器接收到請求后,再次去設(shè)置header的Set-Cookie:shop="book=1&computer=1"返回給客戶端:

客戶端接收到后就把Cookie更新為shop="book=1&computer=1"。后續(xù)要對賬購物車?yán)锏臅陀?jì)算機(jī)做操作,就可以繼續(xù)把Cookie傳給服務(wù)器,讓服務(wù)器作出相關(guān)響應(yīng)。

Cookie除了可以做購物車之外,還可以用它來管理登錄狀態(tài)

當(dāng)客戶端發(fā)送登錄請求給服務(wù)端后,服務(wù)器便記錄該用戶以及設(shè)置一個(gè)會話id給Set-Cookie,Set-Cookie:sessionId=James&123456(這里的值也是根據(jù)實(shí)際開發(fā)要求自己定義),表示跟客戶端此時(shí)的用戶建立里一個(gè)會話,返回給客戶端:

客戶端把這個(gè)Cookie記錄為sessionId=James&123456,然后客戶端再對服務(wù)器進(jìn)行其他請求時(shí),也把這個(gè)header里的Cookie值也發(fā)送給服務(wù)器:

服務(wù)器拿到這個(gè)請求后,便會對比自己記錄的關(guān)于該用戶的Cookie值,匹配一致后就搜尋該Cookie對應(yīng)的用戶名,就可以根據(jù)該用戶名去返回對應(yīng)的數(shù)據(jù)

而此時(shí)客戶端如果想繼續(xù)發(fā)送購物車請求時(shí),可以再添加Cookie值,兩個(gè)Cookie值可以疊加,互不干擾:

這樣之后每次請求,客戶端的header里的Cookie值就可以有多個(gè),然后服務(wù)器就根據(jù)這些Cookie值來作出相應(yīng)的返回。

Cookie還可以用來管理用戶偏好以及分析用戶行為等,原理跟上面的登錄會話一樣

服務(wù)器設(shè)置了一個(gè)Cookie值,里面包含一個(gè)clientId和一個(gè)鏈接,該鏈接其實(shí)是一個(gè)第三方網(wǎng)站(專門用來記錄用戶的瀏覽網(wǎng)址信息),然后服務(wù)器把Cookie返回給客戶端,這樣客戶端的Cookie就記錄這些數(shù)據(jù),等下次再訪問別的網(wǎng)站時(shí),因?yàn)榭蛻舳说腃ookie里是有那個(gè)網(wǎng)址的,因此會跳轉(zhuǎn)到那個(gè)網(wǎng)址(但這里因?yàn)槲覀冊O(shè)置的是一張圖片網(wǎng)址,因此對于用戶來說只會彈出一張圖片),然后觸發(fā)到圖片的網(wǎng)址也就是第三方服務(wù)器記錄是從哪個(gè)網(wǎng)站觸發(fā)圖片的(因?yàn)镃ookie記錄的信息),這樣也就知道你上一個(gè)瀏覽的網(wǎng)址是什么,進(jìn)而逐漸形成了一條用戶行為鏈,進(jìn)而可以精準(zhǔn)推薦相關(guān)廣告給用戶。

Cookie的安全問題:

XSS(跨站腳本攻擊),拿到客戶端的Cookie,從而獲取你的用戶信息。在cookie后加上HttpOnly可防止。

XSRF(跨站請求偽造),偽造成真實(shí)訪問網(wǎng)站。也就是讓用戶跳轉(zhuǎn)到別的網(wǎng)站(比如你前些天登錄過的銀行網(wǎng)站),去進(jìn)行取錢的操作請求,因?yàn)槟阌衏ookie,會記錄銀行取錢時(shí)這一過程,因?yàn)橥ㄟ^這個(gè)cookie值就可以讓用戶進(jìn)行這個(gè)操作。防止這個(gè)漏洞的方法可以在請求網(wǎng)址時(shí)在cookie后加上Referer,它的值是你要跳轉(zhuǎn)的網(wǎng)址,這就可以讓瀏覽器知道你是從哪個(gè)網(wǎng)站跳轉(zhuǎn)來的,進(jìn)而判斷是否信任。

2.Authorization

它比Cookie要安全,有兩種方式:

(1)Basic方式, 直接設(shè)置header里的Authorization值, Authorization:Basic<<username:password(Base64ed)>> ,意思就是把用戶名和密碼加在一起然后進(jìn)行Base64轉(zhuǎn)換,變成一串無意義的字符串,然后設(shè)置為Authorization:Basic sbMidasd......可以了。然后服務(wù)器接收到后匹配成功就表示授權(quán)成功。

(2)Bearer方式, 其實(shí)就是持有Token令牌的人 。 Authorization:Bearer<< bearer token>> 這個(gè)token就是授權(quán)方給客戶端的。

Bearer方式還有OAuth2,比如第三方請求對方授權(quán):

然后http://csdn.com返回個(gè)授權(quán)碼給第三方網(wǎng)站,然后第三方網(wǎng)站把這個(gè)授權(quán)碼即Authorization code傳給這個(gè)第三方網(wǎng)站的服務(wù)器:

服務(wù)器會把Authorization code以及當(dāng)初http://csdn.com很早那次請求就給第三方的client_secret(證明我是我)一并發(fā)給csdn.com:

然后http://csdn.com確定后沒問題就返回token給第三方的服務(wù)器:

就這樣,OAuth2流程到這里就結(jié)束了,接下來如果第三方要進(jìn)行一些后續(xù)操作,就通過它的服務(wù)器去請求csdn.com,把令牌設(shè)置在header的Authorization:Bearer里,然后進(jìn)行請求。

http://csdn.com就會返回相應(yīng)的信息給第三方的服務(wù)器。

微信登錄屬于使用第三方登錄,它也是OAuth2的流程,比如我現(xiàn)在在我的軟件上點(diǎn)擊使用微信登錄,那么微信就會跳出授權(quán)頁面,點(diǎn)擊確認(rèn),微信就會返回Authorization code給我的軟件,然后接下去的步驟就跟上面一樣的步驟,這里就不再重復(fù)說了。

所以其實(shí)按照嚴(yán)格來說,客戶端不應(yīng)該持有token的,持有token的事應(yīng)該是客戶端的服務(wù)器持有的,如果由客戶端持有token,那就浪費(fèi)了OAuth2的流程,token由客戶端持有也不安全。

如果你不使用OAuth2,那就是自己軟件訪問自家服務(wù)器的時(shí)候可以不用按照OAuth2的流程,服務(wù)器直接返回token給客戶端,客戶端持有token,然后以bearer token方式去請求服務(wù)器后續(xù)操作和數(shù)據(jù)。

四、TCP/IP協(xié)議族

一系列協(xié)議組成的一個(gè)網(wǎng)絡(luò)分層模型。當(dāng)我們把一個(gè)數(shù)據(jù)從網(wǎng)絡(luò)中發(fā)送給接收方時(shí),因?yàn)榫W(wǎng)絡(luò)是不穩(wěn)定的,不能保證傳輸一定成功,所以當(dāng)傳輸過程中數(shù)據(jù)丟包,則就要重新發(fā)送,但這樣的話,如果傳輸?shù)臄?shù)據(jù)很大,因?yàn)橹貍鞯脑?,?dǎo)致最終傳輸?shù)臅r(shí)間就變長,那這樣網(wǎng)速就變差了,一系列問題也會產(chǎn)生。

因此,把傳輸?shù)臄?shù)據(jù)分塊來傳輸

假設(shè)第二塊B傳輸失敗,那就重新傳第二塊,其他數(shù)據(jù)塊成功傳輸,這樣速度上也就是只多傳了一次B,而且數(shù)據(jù)量還是1/4,比起原來的一整塊傳輸失敗后要重新傳就快很多了。

不過,還可以優(yōu)化,那就是分層,因?yàn)榫W(wǎng)絡(luò)傳輸是一個(gè)很復(fù)雜的過程,需要做很多事,比如設(shè)置配置,確定各種地址和選擇那條傳輸路徑等,如果所有事情都交給一層來做,那也是很耗費(fèi)時(shí)間以及很混亂,因此,可以分層,每一層負(fù)責(zé)做其對應(yīng)的事情,然后把數(shù)據(jù)塊一層一層往下傳,直到所有的準(zhǔn)備工作都做好了,數(shù)據(jù)塊也就封裝好了,就交給最后的線路去傳給接收方,然后接收方把原始數(shù)據(jù)一層一層往上解封裝,最后傳到服務(wù)器或者接收方上使用。這樣一個(gè)網(wǎng)絡(luò)傳輸過程就變得簡單和高效。

分層模型:也就是常見的OSI模型,總共有7層

1)應(yīng)用層:應(yīng)用層是進(jìn)行傳輸工作,針對軟件層面來傳輸?shù)模热鐬g覽器進(jìn)行HTTP

2)表示層:對傳輸數(shù)據(jù)進(jìn)一步封裝,比如**加密**,壓縮等

3)會話層:機(jī)器與機(jī)器之間通過端口建立會話

4)傳輸層:TCP、UDP協(xié)議使得兩臺機(jī)器通信

5)網(wǎng)絡(luò)層:由上面各層傳下來的數(shù)據(jù)打包成數(shù)據(jù)包,里面包含IP地址等信息,然后通過路由器尋找最優(yōu)路徑進(jìn)行傳輸

6)數(shù)據(jù)鏈路層:進(jìn)一步封裝數(shù)據(jù)包,里面包含MAC物理地址,通過交換機(jī)進(jìn)行傳輸

7)物理層:數(shù)據(jù)包轉(zhuǎn)換成比特流數(shù)據(jù),通過網(wǎng)線或光纖進(jìn)行傳輸

而基于以上模型又演化出TCP/IP模型:應(yīng)用層、傳輸層、網(wǎng)絡(luò)層和網(wǎng)絡(luò)接口層

它把原先OSI模型中的應(yīng)用層、表示層和會話層合并為應(yīng)用層,而數(shù)據(jù)鏈路層和物理層則合并為網(wǎng)絡(luò)接口層,其他層不變,而各層負(fù)責(zé)的職責(zé)仍然不變,合并的層則融合了之前的功能。

1.TCP連接

TCP連接是傳輸層協(xié)議,實(shí)現(xiàn)雙方進(jìn)行聯(lián)通。

TCP連接的建立是一個(gè)稱為“三次握手”的過程,客戶端先將標(biāo)志位SYN設(shè)為1,隨機(jī)產(chǎn)生一個(gè)值Seq=X(自定義值),然后將該數(shù)據(jù)包發(fā)送給服務(wù)器,等待服務(wù)器響應(yīng);服務(wù)器接收到數(shù)據(jù)后將標(biāo)志位SYN設(shè)為1,ACK=X+1,然后隨機(jī)產(chǎn)生一個(gè)值Seq=Y(自定義值),并將該數(shù)據(jù)包發(fā)送給客戶端確認(rèn)連接請求;客戶端收到服務(wù)器的確認(rèn)后,檢查數(shù)據(jù)以及序號,如果正確則將標(biāo)志位ACK設(shè)為Y+1,Seq=Z(自定義值),發(fā)送給服務(wù)器,服務(wù)器檢查各項(xiàng)值,如果都正確則連接建立成功,客戶端與服務(wù)端之間就可以開始傳輸數(shù)據(jù)了。

每一次的箭頭表示一次握手過程。

TCP連接的關(guān)閉(TCP連接很耗費(fèi)資源,既然不用需要再通信的話,就要關(guān)閉連接):四次揮手,關(guān)閉連接的時(shí)候,客戶端此時(shí)會發(fā)送一個(gè)斷開連接的請求給服務(wù)端,服務(wù)端接收該請求后,則表示同意斷開,然后就返回一個(gè)消息給客戶端,客戶端拿到確認(rèn)消息后,則就再一次發(fā)送真的斷開消息給服務(wù)端,表示斷開,而服務(wù)器拿到該消息后則就返回消息給客戶端,表示關(guān)閉成功,具體過程還需要雙方設(shè)置各種值以及確認(rèn)這些值是否正確,如圖所示:

也是跟三次握手一樣,一個(gè)箭頭表示一次揮手過程。

當(dāng)使用的是HTTP1.1協(xié)議時(shí),客戶端跟服務(wù)端連接成功后,往后的網(wǎng)絡(luò)請求中,都不用再重新進(jìn)行三次握手,直接進(jìn)行會話便可,而等到不再連接的時(shí)候,則會四次揮手?jǐn)嚅_連接。這樣就不用像1.0協(xié)議時(shí)那樣,每次都要重新進(jìn)行三次握手。

長連接就是強(qiáng)制這個(gè)連接不被關(guān)閉,由于服務(wù)器在一段時(shí)間如果發(fā)現(xiàn)某個(gè)端口沒有消息傳送了,就認(rèn)為不再連接,因此會關(guān)閉它,因此長連接需要心跳方式來實(shí)現(xiàn),每過一段時(shí)間會發(fā)送心跳包消息給服務(wù)端,這樣就能一直保持連接。

2.HTTPS

就是HTTP加SSL,SSL就是一個(gè)安全層的意思,就是在HTTP之下增加的一個(gè)安全層,用于保障HTTP的加密傳輸。它的本質(zhì)是在客戶端和服務(wù)器之間協(xié)商出一個(gè)對稱密鑰,每次發(fā)送信息之前內(nèi)容加密,收到之后解密,達(dá)到內(nèi)容的加密傳輸。這里不用非對稱加密是因?yàn)樗鼪]對稱加密快。

HTTPS連接過程:

1)客戶端請求建立TLS連接(屬于TCP連接)

2)服務(wù)器發(fā)回證書

3)客戶端驗(yàn)證服務(wù)器證書

4)客戶端信任服務(wù)器后,和服務(wù)器協(xié)商對稱密鑰

5)使用對稱密鑰開始通信

這里的服務(wù)器證書是包含服務(wù)器的公鑰以及服務(wù)器地址等相關(guān)服務(wù)器信息,里面有一個(gè)很重要的數(shù)據(jù),就是證書的簽名,結(jié)合我們前面講到的知識可以知道,這里的證書簽名就是我們客戶端用來驗(yàn)證這個(gè)服務(wù)器證書的真實(shí)性,也就是證明服務(wù)器是服務(wù)器的證據(jù)。那客戶端怎么驗(yàn)證這個(gè)證書簽名,那就要用這個(gè)證書里另一個(gè)數(shù)據(jù),也就是證書機(jī)構(gòu)公鑰來驗(yàn)證,用公鑰去計(jì)算這個(gè)證書,得到一個(gè)原數(shù)據(jù),然后客戶端本身也有該證書的,因此也用那個(gè)公鑰去計(jì)算客戶端的這個(gè)證書,得到的數(shù)據(jù)跟服務(wù)器這個(gè)數(shù)據(jù)進(jìn)行對比,如果兩者是一樣,則證明這個(gè)服務(wù)器證書的真實(shí)性。當(dāng)然,這樣也是有局限性,因?yàn)檫@也只能證明是簽名是來自于這個(gè)機(jī)構(gòu),但如果這個(gè)機(jī)構(gòu)也是第三方偽造的呢,那這個(gè)證書機(jī)構(gòu)也需要提供它的簽發(fā)方的公鑰和其他相關(guān)信息來驗(yàn)證,因?yàn)楹灠l(fā)方證書是屬于根證書,根證書是比較安全和權(quán)威的,它是操作系統(tǒng)被創(chuàng)建時(shí)一并存有的,屬于微軟等權(quán)威機(jī)構(gòu)的。

客戶端信任服務(wù)器后,就會發(fā)送Pre-master Secret給服務(wù)器,然后再通過Pre-master Secret和隨機(jī)數(shù)生成Master Secret,然后再通過Master Secret計(jì)算出客戶端加密密鑰和服務(wù)器加密密鑰以及客戶端和服務(wù)端的Mac Secret,這四個(gè)數(shù)據(jù)都是讓數(shù)據(jù)真實(shí)性變強(qiáng),防止被偽造。

最后要注意的是,在Android中,有時(shí)不能使用https是因?yàn)樽C書信息不全或者系統(tǒng)沒有及時(shí)更新根證書等,也有可能是因?yàn)橛玫氖亲院灻C書,因此可以自寫證書驗(yàn)證過程去解決。

五、OkHttp

OkHttp是對于Socket(基于TCP通信,對TCP協(xié)議的封裝)的封裝的網(wǎng)絡(luò)框架。所以,使用方便,效率也好。

補(bǔ)充:作為面試時(shí)被問得最多的就是,為什么要使用OkHttp時(shí),可以這樣作答:Xutil能支持網(wǎng)絡(luò)請求,又可以加載圖片,也可以操作數(shù)據(jù)庫,但就我個(gè)人而言,我覺得一個(gè)網(wǎng)絡(luò)框架應(yīng)該就只專注于網(wǎng)絡(luò)請求就好了。Retrofit的確是使用方便,但底層是對OkHttp的封裝,所以我覺得既然如此,我也就沒去用了。Volley對于圖片的支持不是很友好,所以就沒去了解。OkHttp是對Socket的封裝,更貼近底層,所以使用它也意味著我可以更好去了解整個(gè)網(wǎng)絡(luò)底層,這樣也能提高我解決網(wǎng)絡(luò)問題的能力。

1.源碼分析

OkHttpClient和Request對象分別構(gòu)建好之后,便交由Call實(shí)現(xiàn)類RealCall的enqueue方法去請求并得到返回?cái)?shù)據(jù)。所以重點(diǎn)跟蹤RealCall的enqueue方法:

然后調(diào)用了dispatcher的enqueue方法:

可以看到,首先進(jìn)行判斷,如果當(dāng)前運(yùn)行隊(duì)列(存儲的是要執(zhí)行的請求)中的任務(wù)數(shù)小于64(maxRequests),并且同時(shí)訪問同一臺服務(wù)器的請求小于5(maxRequestsPerHost),就將call請求放入運(yùn)行隊(duì)列,否則就放入準(zhǔn)備隊(duì)列(存儲的是待執(zhí)行的請求),加入到運(yùn)行隊(duì)列(runningAsyncCalls)中后,則通過線程池執(zhí)行任務(wù),即AsyncCall的execute方法:

重點(diǎn)是Response response = getResponseWithInterceptorChain()。就是這句,獲取請求返回的數(shù)據(jù)。在講getResponseWithInterceptorChain()前,有些知識點(diǎn)應(yīng)該要先了解清楚。

2.構(gòu)建者模式

什么是構(gòu)建者模式,它是典型的設(shè)計(jì)模式之一,使用流式的方法去構(gòu)建對象,在此過程中可以讓用戶自己選擇構(gòu)建過程中對屬性設(shè)置默認(rèn)值,當(dāng)然還有另一個(gè)優(yōu)點(diǎn),就是沒有設(shè)置默認(rèn)值的屬性其實(shí)在底層里可以由架構(gòu)師去給它設(shè)置好,這樣用戶即便沒有設(shè)置,也有默認(rèn)值,這也是為什么構(gòu)建者模式在創(chuàng)建架構(gòu)的時(shí)候會被大家經(jīng)常使用。

現(xiàn)在有個(gè)例子,那就是房主人要建造一個(gè)房子,找建筑師建,而建筑師則找建筑工人去建,建筑師會先在圖紙上畫好一些默認(rèn)值,然后給房主人看,房主人則可以根據(jù)自己的意見來修改這些默認(rèn)值,而不修改的話,則可以用回默認(rèn)值,這樣確認(rèn)好之后,建筑師則可以拿圖紙給建筑工人去建房子了。最開始當(dāng)我(作為房主人)要在構(gòu)建對象的時(shí)候,設(shè)置屬性的話,就要頻繁寫些冗余的代碼了

當(dāng)構(gòu)建好對象時(shí),第一次設(shè)置好了值,但又想改動(dòng),則又要set多一次,所以,需要進(jìn)一步進(jìn)行改善,在設(shè)置的方法里返回this,即該對象本身:

這樣在調(diào)用的時(shí)候就可以流式調(diào)用:

比起之前要一個(gè)一個(gè)去set,現(xiàn)在則可以一路set下去,要再次改的話,直接流式接下去繼續(xù)set。

接下來我們要理清房子跟圖紙的關(guān)系,因?yàn)樵趎ew HttpClient.Builder().build()這句代碼中點(diǎn)進(jìn)去看源碼可知,HttpClient的內(nèi)部類Builder里的屬性跟HttpClient的全局變量屬性是一樣的:

public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory { ... final Dispatcher dispatcher; final @Nullable Proxy proxy; final List<Protocol> protocols; final List<ConnectionSpec> connectionSpecs; final List<Interceptor> interceptors; final List<Interceptor> networkInterceptors; final EventListener.Factory eventListenerFactory; final ProxySelector proxySelector; final CookieJar cookieJar; final @Nullable Cache cache; final @Nullable InternalCache internalCache; final SocketFactory socketFactory; final @Nullable SSLSocketFactory sslSocketFactory; final @Nullable CertificateChainCleaner certificateChainCleaner; final HostnameVerifier hostnameVerifier; final CertificatePinner certificatePinner; final Authenticator proxyAuthenticator; final Authenticator authenticator; final ConnectionPool connectionPool; ... public OkHttpClient() { this(new Builder()); } OkHttpClient(Builder builder) { this.dispatcher = builder.dispatcher; this.proxy = builder.proxy; this.protocols = builder.protocols; this.connectionSpecs = builder.connectionSpecs; this.interceptors = Util.immutableList(builder.interceptors); this.networkInterceptors = Util.immutableList(builder.networkInterceptors); this.eventListenerFactory = builder.eventListenerFactory; this.proxySelector = builder.proxySelector; this.cookieJar = builder.cookieJar; this.cache = builder.cache; this.internalCache = builder.internalCache; this.socketFactory = builder.socketFactory; ... public static final class Builder { Dispatcher dispatcher; @Nullable Proxy proxy; List<Protocol> protocols; List<ConnectionSpec> connectionSpecs; final List<Interceptor> interceptors = new ArrayList<>(); final List<Interceptor> networkInterceptors = new ArrayList<>(); EventListener.Factory eventListenerFactory; ProxySelector proxySelector; CookieJar cookieJar; @Nullable Cache cache; @Nullable InternalCache internalCache; SocketFactory socketFactory; @Nullable SSLSocketFactory sslSocketFactory; @Nullable CertificateChainCleaner certificateChainCleaner; HostnameVerifier hostnameVerifier; CertificatePinner certificatePinner; Authenticator proxyAuthenticator; Authenticator authenticator; ConnectionPool connectionPool; ... public OkHttpClient build() { return new OkHttpClient(this); } 如代碼所示,OkHttpClient的內(nèi)部類Builder里的屬性跟OkHttpClient的全局變量屬性是一樣的,在new OkHttpClient.Builder().build()調(diào)用后,先調(diào)用Builder的構(gòu)造方法構(gòu)建Builder對象,這時(shí)其實(shí)在給Builder對象里的屬性賦值了,然后就是調(diào)用OkHttpClient的構(gòu)造方法,把Builder里的屬性賦值給OkHttpClient的屬性,這樣就等于把圖紙的屬性就能賦值給房子的屬性了,所以Builder類其實(shí)就是圖紙,而HttpClient則是房子。而在構(gòu)造方法里最終返回的就是對象this,所以我們在new的時(shí)候不僅僅可以以流式去設(shè)置這些屬性值,還能不設(shè)置,因?yàn)椴辉O(shè)置的話就可以使用默認(rèn)值。最后設(shè)置好屬性值之后,就調(diào)用builder的build()方法把Builder里的屬性賦值給OkHttpClient的屬性,然后返回房子對象,即OkHttpClient對象。

總結(jié)一下,構(gòu)建者模式就是一步一步地去設(shè)置,去添加,最終變成一個(gè)復(fù)雜的對象,這樣內(nèi)部數(shù)據(jù)過于復(fù)雜的時(shí)候,用戶去創(chuàng)建該對象的時(shí)候,就顯得簡潔方便,而且拿OkHttpClient來說,它里面有各種復(fù)雜的對象和屬性,涉及到網(wǎng)絡(luò)知識的,所以用戶在創(chuàng)建的時(shí)候并不會對所有屬性都了解,從而也就不知道該設(shè)什么值好,而這樣,就可以通過構(gòu)建者模式很友好地給用戶提前在底層里設(shè)置好默認(rèn)值,這樣用戶創(chuàng)建OkHttpClient時(shí)就會很簡單方便??梢詡髯约鹤远x的屬性,也可以不設(shè)置而選擇用默認(rèn)值。當(dāng)然,構(gòu)建者模式如果說有缺點(diǎn),那就是在屬性這里,會顯得冗余重復(fù),因?yàn)榉孔佣x一套屬性,圖紙上也定義了同意一套屬性。不過相對于它的優(yōu)點(diǎn)來說,還是可以忽略的。

3.責(zé)任鏈模式

現(xiàn)在我們再看回getResponseWithInterceptorChain()方法,點(diǎn)進(jìn)去看它的詳情:

代碼不算很多,最明顯可以看到有一個(gè)Interceptor的集合添加多個(gè)不同類型的Interceptor,最后返回Interceptor.Chain chain的proceed()方法。

其實(shí)這個(gè)Interceptor是攔截器,依次添加的攔截器是retryAndFollowUpInterceptor(請求重試和請求重定向);橋攔截器bridgeInterceptor(進(jìn)行添加header,比如加密和壓縮等);緩存攔截器(設(shè)置緩存策略的,假若滿足條件,則使用緩存,然后就不走下一步攔截器邏輯,直接返回緩存回去);鏈接攔截器ConnectInterceptor(管理Socket的,去建立連接);最后還有一個(gè)攔截器就是callServerInterceptor(讀取和封裝返回的數(shù)據(jù)等)。

責(zé)任鏈的好處就是每個(gè)攔截器負(fù)責(zé)對應(yīng)的工作,而一旦某個(gè)攔截器處理結(jié)果不成功的話,則可以把結(jié)果直接往前面一個(gè)一個(gè)返回,而后面攔截器不用繼續(xù)工作。

這里的添加的攔截器的方式其實(shí)是用到了責(zé)任鏈模式,下面重點(diǎn)來說一下責(zé)任鏈模式。先定義一個(gè)父節(jié)點(diǎn):

在里面定義了isIntercept變量,當(dāng)isIntercept為true則自己處理,為false則讓子節(jié)點(diǎn)處理。而addInterceptor()方法就是添加下一個(gè)節(jié)點(diǎn)作為自己的nextInterceptor節(jié)點(diǎn),代碼很好理解。然后依次創(chuàng)建子節(jié)點(diǎn):

而此時(shí)依次調(diào)用它們:

可以看到現(xiàn)在是比較麻煩的,因?yàn)楣?jié)點(diǎn)1要添加節(jié)點(diǎn)2,節(jié)點(diǎn)2要添加節(jié)點(diǎn)3,就要依次添加,然后從節(jié)點(diǎn)1開始執(zhí)行。為了進(jìn)一步優(yōu)化,接下來定義一個(gè)父節(jié)點(diǎn)接口類:

兩個(gè)參數(shù)的作用已經(jīng)解釋得很清楚,然后仿照OkHttp那樣,也創(chuàng)建一個(gè)管理者類去管理節(jié)點(diǎn)并且實(shí)現(xiàn)IBaseInterceptor接口:

如上圖所示,邏輯很清晰,用集合iBaseInterceptors添加一個(gè)個(gè)節(jié)點(diǎn),然后doProcess()方法則通過每次index的自增,來獲取到下一個(gè)節(jié)點(diǎn),從而執(zhí)行它的doProcess()方法,同時(shí),把父節(jié)點(diǎn)也傳過去。然后接著定義節(jié)點(diǎn):

當(dāng)子節(jié)點(diǎn)要處理則表示它要攔截處理,不攔截則調(diào)用傳進(jìn)來的父節(jié)點(diǎn)的doProcess()方法,這樣就可以再調(diào)用父節(jié)點(diǎn)的doAction方法,此時(shí)因?yàn)閕ndex已經(jīng)+1,導(dǎo)致調(diào)用的是下一個(gè)子節(jié)點(diǎn)的doProcess(),依次類推下去。

如圖所示,直接調(diào)用chainManager(作為父節(jié)點(diǎn))的doProcess()方法,第二個(gè)參數(shù)傳的是chainManager,這樣就可以一直遍歷節(jié)點(diǎn)集合,取出下一個(gè)節(jié)點(diǎn),執(zhí)行它的doAction()方法,以此類推。

以上便是一個(gè)責(zé)任鏈模式,搞懂之后就可以分析OkHttp的攔截器了。

1)重試/重定向攔截器:要自定義的其實(shí)并不多,主要還是用于重定向和重連這塊。

2)橋攔截器:是設(shè)置header屬性等。

3)緩存攔截器:就是設(shè)置緩存策略,服務(wù)器會在某個(gè)時(shí)間段前才會去更新數(shù)據(jù),那么這就意味著當(dāng)客戶端第二次再去請求服務(wù)器這段數(shù)據(jù)時(shí),那服務(wù)器可以只返回一個(gè)時(shí)間戳給客戶端,客戶端可通過這個(gè)時(shí)間戳或者字段來跟自己本地緩存的作判斷,如果符合,則可以繼續(xù)使用本地緩存數(shù)據(jù),不符合的才去請求服務(wù)器返回更新過的數(shù)據(jù),然后又再一次緩存該數(shù)據(jù)和時(shí)間戳字段,進(jìn)行下一輪的緩存策略。(這個(gè)就是上文中說到Cache時(shí)的緩存機(jī)制,忘記的可以回到上文去)

4)連接攔截器:初始化和準(zhǔn)備好要Socket(就是進(jìn)行TCP連接的框架,屬于Java),供CallServerInterceptor使用。

5)CallServerInterceptor攔截器:跟服務(wù)器進(jìn)行真正的交互邏輯,讀取和封裝數(shù)據(jù)在這里面進(jìn)行。

getResponseWithInterceptorChain方法里最后調(diào)用了chain.proceed(),調(diào)用了它的實(shí)現(xiàn)類RealInterceptorChain的proceed方法:

可以看到,通過索引不斷自增,然后獲取下一個(gè)攔截器,調(diào)用它的intercept()方法,因?yàn)槭歉割愐弥赶蜃宇悓ο?,因此我們看看RetryAndFollowUpInterceptor的intercept()方法:

把父類chain賦值給realChain,所以中間調(diào)用的realChain的proceed方法其實(shí)就是上文講解的那樣它調(diào)用了父管理類節(jié)點(diǎn)的proceed()方法,然后索引值+1,調(diào)用下一個(gè)攔截器的攔截方法來做它對應(yīng)的操作,然后把最終的response返回出去,中途如果某個(gè)攔截器不處理或者處理失敗,則中途返回response,這就是責(zé)任鏈模式了。

4.線程池

現(xiàn)在可以知道,通過建造者模式去設(shè)置請求,然后通過責(zé)任鏈模式去發(fā)送請求,那么我再次回到enqueue方法,可以看到最終是ExecutorService線程池執(zhí)行每個(gè)請求任務(wù)的:

可以看到SynchronousQueue隊(duì)列是ExecutorService線程池它的內(nèi)部隊(duì)列,作用是當(dāng)線程池的任務(wù)數(shù)已經(jīng)滿了,那么此時(shí)如果再有任務(wù)數(shù)進(jìn)來,那就將它放入該內(nèi)部隊(duì)列里進(jìn)行等待。該隊(duì)列的特點(diǎn)是沒有容量,要么0要么1,就是當(dāng)把一個(gè)任務(wù)放入它里面時(shí),別的任務(wù)就不能再添加了。要先把里面的任務(wù)移除掉,才能再添加。而這樣就可以保證添加進(jìn)來的任務(wù)可以馬上執(zhí)行,而不用再等待別的任務(wù)。而真正用來添加需要等待的任務(wù)則已經(jīng)定義好了一個(gè)專門用來管理等待任務(wù)的等待隊(duì)列,也就是上文提到的readyAsyncCalls。

繼續(xù)看回execute()方法:

最后一句代碼client.dispatcher().finished(),點(diǎn)進(jìn)去看詳情:

可以看到,它將已經(jīng)處理完的call從運(yùn)行隊(duì)列中remove掉,然后執(zhí)行promoteCalls()方法:

可以看到,它是在遍歷等待隊(duì)列readyAsyncCalls,然后取出任務(wù),調(diào)用runningAsyncCalls.add()方法,將該任務(wù)添加到運(yùn)行隊(duì)列里,然后執(zhí)行任務(wù),即executorService().execute(call),然后就是這樣循環(huán)下去,直到所有任務(wù)都執(zhí)行完。這就是一個(gè)請求與得到響應(yīng)的循環(huán)過程。

5.跟服務(wù)器交互過程

而真正與服務(wù)器進(jìn)行交互是在ConnectInterceptor攔截器里,Socket的初始化在里面進(jìn)行的。首先看它的攔截方法:

點(diǎn)擊查看streamAllocation的newStream()方法:

可以看到一行關(guān)鍵代碼findHealthyConnection方法返回一個(gè)RealConnection對象,我們看看這個(gè)findHealthyConnection()方法:

可以看到這里有一個(gè)類似于線程池的類對象connectionPool,再結(jié)合官方文檔可以知道OkHttp在跟服務(wù)器進(jìn)行連接交互的時(shí)候,其實(shí)也是啟動(dòng)了一個(gè)連接池,那么我們就去看看是不是就是這個(gè)connectionPool:

public final class ConnectionPool { /** * Background threads are used to cleanup expired connections. There will be at most a single * thread running per connection pool. The thread pool executor permits the pool itself to be * garbage collected. */ private static final Executor executor = new ThreadPoolExecutor(0 /* corePoolSize */, Integer.MAX_VALUE /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp ConnectionPool", true)); /** The maximum number of idle connections for each address. */ private final int maxIdleConnections; private final long keepAliveDurationNs; private final Runnable cleanupRunnable = new Runnable() { @Override public void run() { while (true) { long waitNanos = cleanup(System.nanoTime()); if (waitNanos == -1) return; if (waitNanos > 0) { long waitMillis = waitNanos / 1000000L; waitNanos -= (waitMillis * 1000000L); synchronized (ConnectionPool.this) { try { ConnectionPool.this.wait(waitMillis, (int) waitNanos); } catch (InterruptedException ignored) { } } } } } }; private final Deque<RealConnection> connections = new ArrayDeque<>(); final RouteDatabase routeDatabase = new RouteDatabase(); boolean cleanupRunning; .... 可以看到,該類里有線程池executor,以及隊(duì)列connections,里面裝的是RealConnection,那我們再看看RealConnection類:

終于看到Socket了,該類果然封裝了Socket。那么接下來就是看看connectPool是什么時(shí)候初始化的。通過查看它的構(gòu)造方法可以查到,是在構(gòu)造Builder類時(shí)初始化的:

所以當(dāng)你想自定義該連接池時(shí),可以在此步驟中進(jìn)行。

那么到目前為止,可以總結(jié)兩個(gè)部分:第一部分,就是OkHttp對于請求它是用線程池去執(zhí)行。而第二部分,則是用Socket去跟服務(wù)器進(jìn)行連接,也是用了一個(gè)線程池去處理。從而就達(dá)成兩個(gè)輪詢的閉環(huán)。

總算是講完了,下一篇會繼續(xù)連載關(guān)于網(wǎng)絡(luò)優(yōu)化以及抓包、統(tǒng)計(jì)網(wǎng)絡(luò)流量等一些網(wǎng)絡(luò)知識。







掃一掃 關(guān)注我的公眾號

這里有你感興趣的技術(shù)文與深度文

歡迎大家來投稿,分享你的文章!



關(guān)鍵詞:網(wǎng)絡(luò),掌握,知識,基礎(chǔ)

74
73
25
news

版權(quán)所有? 億企邦 1997-2025 保留一切法律許可權(quán)利。

為了最佳展示效果,本站不支持IE9及以下版本的瀏覽器,建議您使用谷歌Chrome瀏覽器。 點(diǎn)擊下載Chrome瀏覽器
關(guān)閉