前言
關(guān)于網(wǎng)絡(luò)編程這一塊的內(nèi)容主穗,其實(shí)很早就想寫(xiě)一塊的內(nèi)容温算。畢竟網(wǎng)絡(luò)編程這一塊的內(nèi)容是Android開(kāi)發(fā)中扣草,除了ui和framework以外胆绊,最常接觸的模塊氨鹏。這個(gè)部分的知識(shí)是橫跨所有的編程的知識(shí)棧。因此压状,我們必須深入的掌握這部分的內(nèi)容仆抵。
本系列本來(lái)想放在Android重學(xué)系列的,畢竟后面的篇章會(huì)進(jìn)入Android的Linux內(nèi)核中解析socket的源碼种冬,不過(guò)考慮到這是共有的知識(shí)棧镣丑,也就獨(dú)立出一個(gè)專題來(lái)總結(jié)。
特別是做后端的開(kāi)發(fā)們娱两,對(duì)這一部分的內(nèi)容應(yīng)該是了然于心莺匠,甚至對(duì)socket的底層源碼都十分熟悉。而我們作為Android開(kāi)發(fā),socket作為經(jīng)常接觸Android的核心模塊一部分,有什么理由不去探索一二呢瘪贱?
本系列將會(huì)以O(shè)kHttp為核心颤介,從Http協(xié)議一路到底層看看網(wǎng)絡(luò)通信的過(guò)程中Linux內(nèi)核都做了什么?在來(lái)看看騰訊的開(kāi)源庫(kù)mars中究竟都做了什么優(yōu)化跟匆,能做到跨平臺(tái)高并發(fā)的網(wǎng)絡(luò)吞吐量壓力异袄?
其實(shí)在去年,我已經(jīng)對(duì)這一部分進(jìn)行了鋪墊玛臂,寫(xiě)了http://www.reibang.com/p/5061860545ef關(guān)于OKio的源碼解析烤蜕,有興趣可以去看看。
這里也算是對(duì)去年迹冤,專門(mén)學(xué)習(xí)研究socket和網(wǎng)絡(luò)協(xié)議一次系列總結(jié)讽营。
如果遇到什么問(wèn)題歡迎來(lái)到本文探討http://www.reibang.com/p/ba60ff3c56e6
正文
我們先來(lái)對(duì)整個(gè)網(wǎng)絡(luò)通信協(xié)議有一個(gè)大體的了解,才好繼續(xù)下去泡徙。一聊到網(wǎng)絡(luò)通信協(xié)議橱鹏,就不得不提及網(wǎng)絡(luò)通信的七層協(xié)議,以及TCP/IP模型的五層協(xié)議
下面是一幅圖:
這個(gè)基礎(chǔ)模型就是網(wǎng)絡(luò)通信協(xié)議的根本。如果不熟悉這個(gè)基礎(chǔ)模型莉兰,去聊底層的代碼那是不現(xiàn)實(shí)的挑围。
我們先解釋OSI七層協(xié)議的每一個(gè)意義:
物理層
物理層就是物理硬件層面上的接口,還記得在大學(xué)網(wǎng)絡(luò)工程課程中做過(guò)的1-3糖荒,2-6交叉法做的水晶頭接線杉辙。物理層就是這個(gè)物理層面上的鏈接。也就是在TCP/IP協(xié)議中標(biāo)注的ethernet
端口
數(shù)據(jù)鏈路層
當(dāng)只是兩個(gè)電腦進(jìn)行通信還好說(shuō)捶朵,兩個(gè)電腦通過(guò)接線頭進(jìn)行鏈接即可蜘矢。但是一旦是3個(gè)以上的電腦,就需要如路由器综看,交換機(jī)品腹,,集線器寓搬。但是這樣就出現(xiàn)了一個(gè)很大的問(wèn)題,三臺(tái)電腦需要發(fā)送到正確的電腦句喷,就需要解決如下幾個(gè)問(wèn)題:
- 1.數(shù)據(jù)包發(fā)給誰(shuí)镣典,怎么發(fā)送?
- 2.如果一起發(fā)唾琼,怎么保證這些數(shù)據(jù)不混亂
- 3.發(fā)送出錯(cuò)時(shí)候怎么辦兄春?
而這里就需要數(shù)據(jù)鏈路層,也就是常說(shuō)的MAC(Medium Access Control
也就是多媒體控制訪問(wèn)
)層解決的問(wèn)題锡溯。名字叫做多媒體控制訪問(wèn)赶舆,MAC層控制多媒體發(fā)送數(shù)據(jù)的時(shí)候,規(guī)定了什么數(shù)據(jù)先發(fā)祭饭,什么數(shù)據(jù)后發(fā)芜茵,防止出現(xiàn)了混亂,也就是解決了第二個(gè)問(wèn)題九串,這個(gè)方式也叫多路訪問(wèn)寺鸥。
多路訪問(wèn)分為三種方式:
- 1.信道劃分 不同的數(shù)據(jù)包走不同的通道
- 2.輪作協(xié)議 不同的數(shù)據(jù)包傳輸通道按照一定的規(guī)律,輪流協(xié)作發(fā)送數(shù)據(jù)包
- 3.隨機(jī)接入?yún)f(xié)議 嘗試先發(fā)送篡腌,但是發(fā)現(xiàn)網(wǎng)絡(luò)環(huán)境擁堵,則先阻塞勾效,等到通暢后在發(fā)送
聊到mac層嘹悼,就肯定會(huì)聊到mac層的數(shù)據(jù)封包,而mac的數(shù)據(jù)封包就是為了解決第一個(gè)問(wèn)題和第三個(gè)問(wèn)題层宫,數(shù)據(jù)發(fā)給誰(shuí)怎么發(fā)杨伙,發(fā)給誰(shuí),怎么考驗(yàn)是否錯(cuò)誤萌腿。
在每一臺(tái)機(jī)器的網(wǎng)卡中都存在一個(gè)物理地址限匣,這個(gè)物理地址可以說(shuō)在以太網(wǎng)中唯一的網(wǎng)卡機(jī)器標(biāo)識(shí)(絕大部分情況),能通過(guò)這個(gè)標(biāo)識(shí)找到需要通信的目標(biāo)毁菱。
- 源mac地址就是指 發(fā)送網(wǎng)絡(luò)請(qǐng)求的mac地址
- 目標(biāo)mac地址就是指需要通信的mac地址米死。
- 類型 則是指當(dāng)前數(shù)據(jù)中,保存的數(shù)據(jù)類型是什么贮庞?是IP報(bào)文還是ARP報(bào)文
- CRC冗余校驗(yàn) 通過(guò)XOR算法峦筒,來(lái)確定發(fā)送的過(guò)程中是否發(fā)生了錯(cuò)誤。
有沒(méi)有想過(guò)如果此時(shí)窗慎,電腦是第一次接入到網(wǎng)絡(luò)勘天,它并不知道目標(biāo)的mac地址怎么辦?
ARP協(xié)議
此時(shí)就需要一個(gè)叫做ARP協(xié)議
了捉邢。但是ARP協(xié)議
也不是單獨(dú)可以運(yùn)行,還需要知道后面數(shù)據(jù)中的IP地址商膊,兩個(gè)聯(lián)動(dòng)起來(lái)才能正確的找到需要通信起來(lái)伏伐。
換句話說(shuō)就是已知IP地址,求目標(biāo)的mac地址晕拆。
整個(gè)流程如下圖:
只有經(jīng)歷了這個(gè)過(guò)程藐翎,第一次接入網(wǎng)絡(luò)材蹬,才能正確的找到需要通信的ip地址對(duì)應(yīng)的mac地址。
VLAN 協(xié)議
當(dāng)出現(xiàn)更大規(guī)模的網(wǎng)絡(luò)接入吝镣,如一個(gè)辦公大樓接入同一個(gè)網(wǎng)絡(luò)之后堤器。如果都是接入同一個(gè)交換機(jī),太多的消息都經(jīng)過(guò)同一個(gè)交換機(jī)的局域網(wǎng)很容易被抓包末贾,那么就沒(méi)有什么安全性闸溃。
解決這個(gè)的方案有兩種:
- 1.物理隔離 多弄一臺(tái)交換機(jī),分成兩個(gè)局域網(wǎng)拱撵,但是不好控制容易浪費(fèi)
- 2.虛擬隔離 也就是VLAN協(xié)議辉川,構(gòu)建多個(gè)虛擬局域網(wǎng)
整個(gè)協(xié)議如圖
在原來(lái)的二層上新增一個(gè)TAG,這個(gè)TAG里面有一個(gè)VLAN ID
,這個(gè)ID 有12位拴测,也就是有4096
個(gè)VLAN乓旗。如果交換機(jī)是支持VLAN
,就會(huì)把這個(gè)TAG取出來(lái)獲取其中的VLAN ID
并識(shí)別。只有相同的VLAN ID
包才能互相轉(zhuǎn)發(fā)集索,不同的VLAN
包是不能互相看到的屿愚。
交換機(jī)之間有一個(gè)Trunk
口,互相鏈接交換機(jī)务荆。
網(wǎng)絡(luò)層
IP地址
有了mac地址妆距,可能不少人就覺(jué)得,因?yàn)樗俏ㄒ坏乃钥梢赃M(jìn)行全局的通信了蛹含,就以尋找mac地址為基準(zhǔn)毅厚。
然而,這是不可能實(shí)現(xiàn)的浦箱。mac地址就像一個(gè)人的身份證一樣吸耿,但是我們想要找到一個(gè)人除了知道找誰(shuí)之外,還需要知道這個(gè)人住哪里才行酷窥。這就需要網(wǎng)絡(luò)成的IP地址了咽安。
有了IP地址才知道需要往哪里通信,通過(guò)IP地址找到對(duì)應(yīng)的地方后蓬推,就需要通過(guò)mac地址找到具體的人在哪里
IP地址的組成往往分為5種類型:
我們常用的A妆棒,B,C三類網(wǎng)絡(luò)地址沸伏。前一部分位網(wǎng)絡(luò)號(hào)糕珊,后一部分為主機(jī)號(hào)。相當(dāng)于住在4單元406號(hào)房一個(gè)意思毅糟。主機(jī)號(hào)可以看成一個(gè)個(gè)主機(jī)接入的序列红选。如果是按照這種設(shè)計(jì),很容出現(xiàn)過(guò)于浪費(fèi)的情況姆另。
特殊的D類網(wǎng)絡(luò)地址喇肋,屬于組播地址坟乾,一般是用于如郵件往某個(gè)郵件組發(fā)送是的這個(gè)郵件接受組都能接收到。
比如B類地址蝶防,就有65534個(gè)主機(jī)號(hào)甚侣,也就是能接入這么多電腦,不就浪費(fèi)了這么多的位置嗎间学?
所以誕生了一個(gè)無(wú)類型域間選路(CIDR)
的概念殷费。
一般的,一個(gè)CIDR的IP地址會(huì)寫(xiě)成如下格式:
192.168.0.1/24
24代表32位IP地址中前24位為網(wǎng)絡(luò)號(hào)菱鸥,最后9位代表主機(jī)號(hào)宗兼。伴隨著CIDR,存在一個(gè)所有人都能監(jiān)聽(tīng)到的廣播地址氮采,與一個(gè)子網(wǎng)掩碼殷绍。
比如一個(gè)網(wǎng)絡(luò)號(hào)10.100.122
那么發(fā)送一個(gè)數(shù)據(jù)包往10.100.122.255
所有主機(jī)都能監(jiān)聽(tīng)到。
子網(wǎng)掩碼是用來(lái)計(jì)算一個(gè)CIDR 中IP地址的網(wǎng)絡(luò)號(hào)鹊漠。比如子網(wǎng)掩碼為255.255.255.0
,代表頭24位就是網(wǎng)絡(luò)號(hào)主到。
IP報(bào)文結(jié)構(gòu)
有了IP地址還不夠,還需要更多的信息才能正確的定位躯概。當(dāng)我們發(fā)送一個(gè)郵件登钥,需要什么呢?發(fā)件人娶靡,發(fā)件人地址牧牢,收件人以及收件人地址,以及郵件內(nèi)容姿锭。
轉(zhuǎn)化過(guò)來(lái)就是需要源IP地址塔鳍,目標(biāo)IP地址,以及傳輸過(guò)來(lái)的內(nèi)容呻此,由于網(wǎng)絡(luò)環(huán)境很復(fù)雜還需要一些版本號(hào)轮纫,校驗(yàn)碼等數(shù)據(jù)。就有了下面這幅著名的IP報(bào)文結(jié)構(gòu)圖:
所謂IP報(bào)文實(shí)際上就是在MAC層的封裝基礎(chǔ)上填寫(xiě)更多的內(nèi)容焚鲜。
有了IP報(bào)文之后掌唾,我們就可以往更廣大的網(wǎng)絡(luò)世界進(jìn)行通信了。
IP 路由協(xié)議
路由協(xié)議忿磅,實(shí)際上可以和Android 開(kāi)發(fā)中的Router的概念做對(duì)比糯彬,一個(gè)頁(yè)面對(duì)應(yīng)一個(gè)url路由。而這里則是不同局域網(wǎng)不同的路由葱她。
當(dāng)我們想要訪問(wèn)一個(gè)服務(wù)器網(wǎng)卡時(shí)候情连,會(huì)經(jīng)過(guò)網(wǎng)關(guān),就會(huì)嘗試的判斷是否在網(wǎng)關(guān)同一個(gè)網(wǎng)段(也就是網(wǎng)絡(luò)號(hào))览效。
- 在同一個(gè)網(wǎng)段却舀,則使用
ARP
協(xié)議,嘗試查找這個(gè)局域網(wǎng)內(nèi)對(duì)應(yīng)的主機(jī) - 不是同一個(gè)網(wǎng)段锤灿,就會(huì)先發(fā)往默認(rèn)的網(wǎng)關(guān)挽拔,這個(gè)默認(rèn)網(wǎng)關(guān)一定和發(fā)送者處于同一個(gè)網(wǎng)絡(luò)號(hào)。過(guò)程是先通過(guò)
ARP協(xié)議
找到網(wǎng)關(guān)但校,接著把MAC地址和網(wǎng)關(guān)的MAC地址封裝起來(lái)螃诅,然后網(wǎng)關(guān)發(fā)送出去。
路由協(xié)議分為兩大類:
- 靜態(tài)路由
- 動(dòng)態(tài)路由
靜態(tài)路由
實(shí)際上就是在路由器上記錄一條條規(guī)則状囱,比如想要訪問(wèn)A术裸,需要從哪個(gè)端口出去,下一個(gè)IP地址是誰(shuí)亭枷。
在這個(gè)過(guò)程中袭艺,會(huì)遇到兩種網(wǎng)關(guān):
- 轉(zhuǎn)發(fā)網(wǎng)關(guān) 不會(huì)改變IP地址
- NAT網(wǎng)關(guān) 會(huì)改變IP地址
因此就會(huì)出現(xiàn)兩種情況:
1.網(wǎng)關(guān)之間IP地址是可見(jiàn)的:
每一次經(jīng)過(guò)一個(gè)網(wǎng)關(guān),都更換目標(biāo)MAC地址以及源MAC地址叨粘,而保留IP地址來(lái)記錄原本的發(fā)送方和接收方是誰(shuí)猾编。2.網(wǎng)關(guān)之間IP地址是不可見(jiàn)的:
首先每一個(gè)服務(wù)器在因特網(wǎng)內(nèi)有一個(gè)ip地址,當(dāng)然這個(gè)服務(wù)器在自己的局域網(wǎng)內(nèi)有另一個(gè)面向局域網(wǎng)的ip地址升敲。
當(dāng)服務(wù)器A想要訪問(wèn)服務(wù)器B的時(shí)候答倡,經(jīng)歷兩種網(wǎng)關(guān),一個(gè)是轉(zhuǎn)發(fā)網(wǎng)關(guān)路由器A驴党,一個(gè)是NAT網(wǎng)關(guān)路由器B:
核心就是不斷的切換MAC地址瘪撇,抵達(dá)下一個(gè)網(wǎng)關(guān)。而在靜態(tài)路由表中記錄了國(guó)際IP地址和本地局域網(wǎng)IP地址的映射港庄,在進(jìn)出NAT網(wǎng)關(guān)的時(shí)候進(jìn)行一次切換倔既,可以直接找到。
核心是根據(jù)目的 IP 地址來(lái)配置路由攘轩。
靜態(tài)路由表叉存,NAT網(wǎng)關(guān)映射一般都是配置在IPTable中,進(jìn)行查詢的度帮。
$ ip route list table main
60.190.27.189/30 dev eth3 proto kernel scope link src 60.190.27.190
183.134.188.1 dev eth2 proto kernel scope link src 183.134.189.34
192.168.1.0/24 dev eth1 proto kernel scope link src 192.168.1.1
127.0.0.0/8 dev lo scope link
default via 183.134.188.1 dev eth2
比如說(shuō)運(yùn)營(yíng)商靜態(tài)的寫(xiě)入給這個(gè)路由器寫(xiě)下了如上規(guī)則:
給網(wǎng)卡eth2 分配了國(guó)際IP地址為183.134.189.34
,對(duì)應(yīng)的網(wǎng)關(guān)是183.134.188.1
因此到了不同的網(wǎng)關(guān)歼捏,就對(duì)應(yīng)了不同的全局IP地址。注意一個(gè)路由器能配置多個(gè)網(wǎng)關(guān)
動(dòng)態(tài)路由
然而這種寫(xiě)死的路由表策略往往不足以應(yīng)付現(xiàn)在的網(wǎng)絡(luò)的復(fù)雜情況笨篷。
動(dòng)態(tài)路由實(shí)際上是指動(dòng)態(tài)的在整個(gè)網(wǎng)絡(luò)中構(gòu)建整個(gè)通信結(jié)構(gòu)瞳秽,而不是寫(xiě)在命令ip route中。
動(dòng)態(tài)構(gòu)建ip 網(wǎng)絡(luò)關(guān)系圖有兩種算法:
1.距離矢量路由算法
每一個(gè)路由器中都包含了全局的路由表率翅。每隔一段時(shí)間就同步一下局域網(wǎng)內(nèi)路由表的鏈接狀態(tài)练俐。有了這個(gè)表就能知道如何通過(guò)最短的路程找到目標(biāo)地址。這種的問(wèn)題是冕臭,好消息傳輸快腺晾,壞消息傳輸慢燕锥。這么一個(gè)場(chǎng)景甘邀,比如說(shuō)有某個(gè)主機(jī)失聯(lián)了隧膘,此時(shí)需要訪問(wèn)所有路由器之后糟秘,拿到全局的路由表消痛,才知道這個(gè)主機(jī)原來(lái)斷開(kāi)鏈接了荧关。
一般應(yīng)用于 外網(wǎng)之間的路由省骂,又稱為外網(wǎng)路由協(xié)議(簡(jiǎn)稱 BGP)
举户。為什么外部變化大的反而使用這種收斂壞消息速度慢的協(xié)議呢尤仍?
你可以想想在一個(gè)國(guó)家中有數(shù)量有限的大型自治系統(tǒng)成為AS
蕉世。
而AS
分為3種:
1.
Stub AS
這種AS系統(tǒng)對(duì)外鏈接只有一個(gè)蔼紧,不會(huì)傳輸其他AS
的包。如個(gè)人和小公司網(wǎng)絡(luò)2.
Multihomed AS
這種AS系統(tǒng)可能鏈接多個(gè)AS系統(tǒng)狠轻,但是拒絕幫助他AS傳輸包3.
Transit AS
這種AS系統(tǒng)相當(dāng)于高速公路奸例,會(huì)鏈接多個(gè)AS系統(tǒng),也會(huì)幫助其他AS傳輸包
每一個(gè)AS 自治系統(tǒng)都有自己的邊界路由器哈误,使用這種路由器和外界交互哩至。
說(shuō)回來(lái),現(xiàn)在運(yùn)用的BGP
分為兩種eBGP
和iBGP
蜜自。自治系統(tǒng)之間使用eBGP
進(jìn)行交互菩貌,而自治系統(tǒng)內(nèi)部使用iBGP
使得內(nèi)部路由找到抵達(dá)外網(wǎng)目的地最好的邊界路由器。
正因?yàn)檫@些大型的自治系統(tǒng)不容易變更重荠,所以反而使用這種方式會(huì)更好箭阶。
2.鏈路狀態(tài)路由算法
當(dāng)前路由器把自己和鄰居路由表的關(guān)系發(fā)送出去,路由器會(huì)接受每一個(gè)路由器鄰居的關(guān)系構(gòu)建出一個(gè)路由表戈鲁。
常用的對(duì)應(yīng)算法是OSPF(Open Shortest Path First仇参,開(kāi)放式最短路徑優(yōu)先).一般用于數(shù)據(jù)中心內(nèi)部進(jìn)行決策,因此也叫內(nèi)部網(wǎng)關(guān)協(xié)議
傳輸層
從這里開(kāi)始就是日常開(kāi)發(fā)耳熟能詳?shù)膮f(xié)議都在這里了婆殿,比如TCP,UDP,用于ping的ICMP等诈乒。當(dāng)然還有更加快速基于UDP開(kāi)發(fā)的QUIC,已經(jīng)經(jīng)常用于流媒體的協(xié)議RTMP都在這一層婆芦。
這里我們先只討論TCP和UDP怕磨,關(guān)于RTMP的我會(huì)放到后面音視頻模塊中講解。
TCP和UDP之間有什么區(qū)別消约?
TCP會(huì)建立三次握手的鏈接肠鲫,而UCP不會(huì)。所以就有說(shuō)法說(shuō)或粮,TCP面向鏈接导饲,UDP面向無(wú)鏈接。
所謂的鏈接,就是為了維護(hù)客戶端和服務(wù)端之間的鏈接而建立的數(shù)據(jù)結(jié)構(gòu)來(lái)維護(hù)雙方的狀態(tài)渣锦,用這樣的數(shù)據(jù)結(jié)構(gòu)來(lái)保證面向鏈接的特性硝岗。
更加仔細(xì)的區(qū)別大致有如下幾點(diǎn):
1.TCP提供可靠交付。通過(guò)TCP鏈接傳輸?shù)臄?shù)據(jù)袋毙,無(wú)差錯(cuò)辈讶,不丟失,不重復(fù)娄猫,按順序達(dá)到;UDP繼承了IP包的特性生闲,不保證不丟失媳溺,不保證順序。
2.TCP面向字節(jié)流碍讯,發(fā)送的是一個(gè)流沒(méi)頭沒(méi)尾悬蔽,這是因?yàn)門(mén)CP在維護(hù)自己的狀態(tài)。而UDP則是基于IP報(bào)文捉兴,一個(gè)個(gè)發(fā)送蝎困,一個(gè)個(gè)接受。
3.TCP有阻塞擁塞控制倍啥,通過(guò)阻塞窗口來(lái)進(jìn)行限制發(fā)送流量禾乘。而UDP都沒(méi)有,要發(fā)送就發(fā)送
4.TCP是一個(gè)有狀態(tài)的服務(wù)虽缕,能夠精確的記錄那些包發(fā)送了始藕,接受了。而UDP則是無(wú)狀態(tài)服務(wù)氮趋。
UDP
UDP的結(jié)構(gòu)如上伍派。
UDP的使用場(chǎng)景
一般來(lái)說(shuō)UDP經(jīng)常用于如下場(chǎng)景:
1.需要資源少,內(nèi)網(wǎng)情況很好的剩胁,對(duì)于數(shù)據(jù)丟包不敏感诉植。比如一些硬件通過(guò)udp聯(lián)檢到app中,發(fā)送一些對(duì)時(shí)效不太重要的數(shù)據(jù)
2.不需要一對(duì)一溝通昵观,建立鏈接晾腔,而是進(jìn)行廣播
3.需要處理速度快,時(shí)延低索昂,可以容忍少數(shù)丟包建车,就算是網(wǎng)絡(luò)擁塞也直接發(fā)送。
QUIC
正因?yàn)橛羞@種特殊特性椒惨,所以誕生了QUIC協(xié)議缤至。我記得這個(gè)協(xié)議還是2年前一個(gè)微信哥們和我提到過(guò),說(shuō)他們那邊研究過(guò)這個(gè)覺(jué)得很不錯(cuò)。這是Google基于UDP協(xié)議上進(jìn)一步開(kāi)發(fā)的领斥,目的是為了減少延時(shí)嫉到。
為什么QUIC能辦到呢?其實(shí)原理很簡(jiǎn)單月洛。這也是分場(chǎng)景的何恶。因?yàn)橐苿?dòng)App現(xiàn)在都是基于Http協(xié)議,而Http協(xié)議又是基于TCP的嚼黔。那么就需要阻塞擁塞窗口進(jìn)行流量控制细层。
那么問(wèn)題就來(lái)了,因?yàn)槭且苿?dòng)設(shè)備唬涧,和主機(jī)設(shè)備不一樣疫赎。主機(jī)設(shè)備如果發(fā)現(xiàn)擁塞窗口阻塞的厲害那么可以認(rèn)為當(dāng)前的通信到服務(wù)器的網(wǎng)絡(luò)環(huán)境比較擁擠,需要讓一點(diǎn)資源讓服務(wù)器那邊反應(yīng)過(guò)來(lái)碎节。
但是移動(dòng)設(shè)備往往是移動(dòng)的捧搞,可能動(dòng)著動(dòng)著出現(xiàn)網(wǎng)絡(luò)環(huán)境不好的情況,那么每一次斷開(kāi)TCP的握手狮荔,又重新握手就會(huì)出現(xiàn)延時(shí)十分厲害胎撇。
因此誕生了QUIC,QUIC在UDP快速的基礎(chǔ)上殖氏,再加上一些校驗(yàn)的邏輯晚树。這部分內(nèi)容看看后面有沒(méi)有想法,可以和大家聊聊底層設(shè)計(jì)受葛。
RTMP
因?yàn)檫@種機(jī)制题涨,在流媒體中也迅速普及UDP的使用,如RTMP協(xié)議总滩。因?yàn)槔系靡暺翈瑪?shù)丟了也就丟了纲堵,在直播這些流媒體領(lǐng)域如何追求畫(huà)面的實(shí)時(shí)同步才是更加重要的考察點(diǎn)。
當(dāng)然還有游戲中傳送包等情況闰渔。
TCP
先來(lái)看看TCP的數(shù)據(jù)結(jié)構(gòu):
一聊到TCP就一定會(huì)聊到3次握手和4次揮手席函。在我們大學(xué)的網(wǎng)絡(luò)編程的課本中經(jīng)常出現(xiàn)下面2幅圖:
TCP三次握手
三次握手中發(fā)生了幾次狀態(tài)的變化。其實(shí)也是保證了包的順序以及應(yīng)答之間的狀態(tài)冈涧。
總的來(lái)說(shuō)就是三個(gè)步驟:
- 請(qǐng)求
- 應(yīng)答
- 應(yīng)答之應(yīng)答
實(shí)際上就是為了應(yīng)付復(fù)雜的網(wǎng)絡(luò)環(huán)境茂附,而出現(xiàn)的一種保證機(jī)制。當(dāng)然有人會(huì)穩(wěn)為什么不是4次督弓,5次呢营曼?實(shí)際上確實(shí)可以這樣下去,甚至40次都可以愚隧。但是只需要保證客戶端A確保鏈接上了服務(wù)器B蒂阱,B就會(huì)立即發(fā)送數(shù)據(jù)的流程即可。
圖中seq就是代表了當(dāng)前發(fā)送包的序列,每一個(gè)包的序列默認(rèn)來(lái)說(shuō)都是每次遞增1.所以可以通過(guò)這個(gè)規(guī)律知道包那些的發(fā)送漏了录煤,從而在底層進(jìn)行排序鳄厌,排序好后在發(fā)送到上層。
整個(gè)流程如下:
A發(fā)起
SYN
后妈踊,就進(jìn)入了SYN_SEND
狀態(tài)B收到
SYN
數(shù)據(jù)包后了嚎,返回SYN(屬于自己的序列y)
,以及ack(客戶端上一個(gè)包的序列+1)
廊营,然后處于SYN_RCVD
狀態(tài)歪泳。ack
記錄了客戶端接受到的包應(yīng)該消費(fèi)的序列號(hào)A收到B的應(yīng)答也就是服務(wù)端的
SYN
和ACK
后,返回AC
K給服務(wù)端露筒。這個(gè)過(guò)程seq
是從B收到的ack
的序列 以及ack
中記錄服務(wù)器上一個(gè)包的seq序列
加1夹囚。
這里ack
記錄了服務(wù)器收到客戶端消息后應(yīng)該對(duì)應(yīng)的序列號(hào)
換句話說(shuō),就是通過(guò)拿到對(duì)方的ack來(lái)校驗(yàn)本次seq的序列是否正確邀窃。
TCP四次揮手
在4次揮手整個(gè)流程如下:
1.客戶端A傳送完數(shù)據(jù)后,決定關(guān)閉TCP的通信假哎。則發(fā)出一個(gè)
FIN
和seq(序列號(hào))為q
給服務(wù)器B瞬捕,進(jìn)入FIN_WAIT_1
的狀態(tài)。2.B收到了A的
FIN
信息后舵抹,進(jìn)入到CLOSED_WAIT
肪虎。接著發(fā)送ACK
ack為p+13.當(dāng)A收到了
ACK
之后,就進(jìn)入到了FIN_WAIT_2
狀態(tài)惧蛹,等待服務(wù)器發(fā)送下一個(gè)狀態(tài)扇救。這一次接受ACK實(shí)際上就是拿到剛才的A發(fā)出的應(yīng)答,說(shuō)明服務(wù)B接收到這個(gè)關(guān)閉消息香嗓,但是此時(shí)服務(wù)器B可能還需要處理點(diǎn)事情才能正式關(guān)閉這條鏈路迅腔,所以還需要等一次。如果B服務(wù)器不發(fā)送后續(xù)的自己關(guān)閉的狀態(tài)靠娱,則會(huì)一直停在這個(gè)狀態(tài)中沧烈,但是在Linux中會(huì)有一個(gè)超時(shí)時(shí)間進(jìn)行設(shè)置。4.如果B沒(méi)有結(jié)束像云,則發(fā)送一個(gè)
FIN
和ACK
以及seq為q
以及ack=p+1
.進(jìn)入到了LAST_ACK
狀態(tài)5.A客戶端之后知道B也發(fā)送了B即將關(guān)閉的信息后锌雀,A接收到到之后則進(jìn)入到
TIME_WAIT
狀態(tài)。等待時(shí)間為2MSL(2個(gè)單位的最大報(bào)文生存時(shí)間迅诬,協(xié)議規(guī)定 MSL 為 2 分鐘腋逆,實(shí)際應(yīng)用中常用的是 30 秒,1 分鐘和 2 分鐘等
)侈贷。發(fā)送一個(gè)ACK
ack為q+1惩歉。經(jīng)過(guò)2MSL后,進(jìn)入到CLOSED
狀態(tài)
這個(gè)過(guò)程的行為原因有2點(diǎn):
1.A如果此時(shí)直接走人了,此時(shí)TCP鏈接需要A等待一個(gè)
TIME_WAIT
的時(shí)間柬泽,這個(gè)時(shí)間需要足夠長(zhǎng)慎菲。如果B沒(méi)有收到A發(fā)送的ACK消息,則B會(huì)重新發(fā)送FIN
和ACK
也就是第4部的過(guò)程锨并。等到B發(fā)送的包都死掉了露该,再關(guān)閉2.A直接走人,那么A的端口就空出來(lái)了第煮。B不知道還是繼續(xù)發(fā)送到這個(gè)端口解幼,就會(huì)出現(xiàn)發(fā)送錯(cuò)應(yīng)用的問(wèn)題
如果這個(gè)過(guò)程中,B超過(guò)了2MSL的時(shí)間包警,都沒(méi)有收到A發(fā)送的FIN的ACK
包撵摆,就不等了,直接設(shè)置為RST
關(guān)閉這個(gè)口
- 6.當(dāng)服務(wù)器B收到了A發(fā)送的
ACK
則進(jìn)入到CLOSED害晦。
這個(gè)過(guò)程seq的轉(zhuǎn)化特铝,首先是A往B通信,所以第一次為p壹瘟,第二次B回應(yīng)了一次ACK
之后就是p+1.
其次鲫剿,是B往A通信,所以seq重新設(shè)置為q稻轨,ack代表應(yīng)答的是p+1灵莲。此時(shí)A收到后不需要B處理所以seq不需要設(shè)置,設(shè)置為p+1告訴B這個(gè)p+1對(duì)應(yīng)序列號(hào)的消息已經(jīng)應(yīng)答了殴俱。
擁塞窗口
.一開(kāi)始窗口只有一個(gè)mss
大小叫做慢啟動(dòng)政冻。接著翻倍的增長(zhǎng)窗口大小,直到ssthresh
臨界值线欲,之后就變成線性增長(zhǎng)明场。這個(gè)過(guò)程我們成為擁塞避免
。當(dāng)出現(xiàn)丟包的時(shí)候李丰,就說(shuō)明網(wǎng)絡(luò)環(huán)境開(kāi)始變得緊張榕堰,就會(huì)開(kāi)始調(diào)整窗口大小。
擁塞窗口有兩種調(diào)整窗口大小的邏輯:
- 將窗口大小重新調(diào)整為1個(gè)
mss
嫌套,重新經(jīng)歷翻倍和線性增長(zhǎng)逆屡。
- 將窗口大小重新調(diào)整為1個(gè)
- 2.將當(dāng)前的窗口大小調(diào)整為當(dāng)前的一半,重新以線性進(jìn)行增長(zhǎng)踱讨。
通過(guò)這種方式調(diào)整客戶端的發(fā)送包的速度魏蔗。
滑動(dòng)窗口
在TCP中,控制包的發(fā)送主要是通過(guò)擁塞窗口分為如下幾個(gè)部分進(jìn)行管理痹筛,從而得知緩沖隊(duì)列哪些包發(fā)送且確認(rèn)回收了莺治,哪些包發(fā)送了在等待服務(wù)器回收廓鞠,哪些包準(zhǔn)備發(fā)送,哪些包不能發(fā)送谣旁。
在這里面有一個(gè)滑動(dòng)窗口的概念床佳,控制哪些發(fā)送的包行為。具體在后文會(huì)聊到榄审。
Socket
而Socket 套字節(jié)就是面向開(kāi)發(fā)者最常用的api砌们,而這個(gè)api實(shí)際上是四層協(xié)議也就是傳輸層的api封裝。我們可以在socket中選擇對(duì)應(yīng)的協(xié)議去執(zhí)行不同的傳輸層TCP還是UDP的協(xié)議搁进。不過(guò)更多的還是關(guān)注TCP和UDP相關(guān)的內(nèi)容浪感。更加詳細(xì)的內(nèi)容會(huì)在之后解析源碼中放出。
應(yīng)用層
Http協(xié)議與Https協(xié)議
對(duì)于Http協(xié)議還是Https協(xié)議饼问,都是我們應(yīng)用開(kāi)發(fā)接觸頻率最高的影兽。簡(jiǎn)單的來(lái)說(shuō)Https協(xié)議就是在Http協(xié)議的基礎(chǔ)上進(jìn)行了加密安全保護(hù)。
無(wú)論是哪種協(xié)議莱革,一旦聊起來(lái)辅肾,我們必定會(huì)聊到的下面這兩幅Http協(xié)議結(jié)構(gòu)圖:
Http請(qǐng)求的結(jié)構(gòu):
都是很熟悉的內(nèi)容:
大致分為三個(gè)部分:
請(qǐng)求行 里面保存當(dāng)前的請(qǐng)求方式(get,post,put,delete)等厕氨;URL資源路徑挺举;Http協(xié)議版本
首部 里面就是我們經(jīng)常用請(qǐng)求頭酬蹋。里面包含了用于設(shè)置客戶端可接受的字符集
Accept-Charset
;正文格式Content-Type
(如Json左冬,xml等);用于控制緩存的Cache-control
(當(dāng)客戶端存在max-age
則比較資源緩存的時(shí)間和max-age
的大小纸型,資源緩存的小(沒(méi)有超出緩存失效)則客戶端可以接受緩存資源拇砰,如果為0則直接交給服務(wù)器應(yīng)用獲取最新資源);If-Modified-Since
也是關(guān)于資源緩存狰腌,如果資源更新了則下載最新資源除破,沒(méi)有更新則返回304讓客戶端處理。實(shí)體 里面包含了請(qǐng)求的數(shù)據(jù)琼腔,如Post就會(huì)在里面設(shè)置數(shù)據(jù)字節(jié)內(nèi)容
Http響應(yīng)的結(jié)構(gòu):
整個(gè)結(jié)構(gòu)和Http的請(qǐng)求結(jié)構(gòu)很像瑰枫。實(shí)際上變化的是從請(qǐng)求行變成了狀態(tài)行,其他都是類似的丹莲,不過(guò)是從客戶端設(shè)置的內(nèi)容變成服務(wù)端設(shè)置的內(nèi)容光坝。
一般來(lái)的,我們開(kāi)發(fā)最重點(diǎn)關(guān)注的還是狀態(tài)碼甥材,這里列一下簡(jiǎn)單的例子:
- 200 代表請(qǐng)求成功返回
- 304 代表結(jié)果沒(méi)變盯另,請(qǐng)從緩存讀取
- 404 代表資源找不到,一般是url輸出錯(cuò)誤
- 500 代表服務(wù)異常
當(dāng)然還有其他的洲赵,如重定向等鸳惯。這部分內(nèi)容放在OkHttp的解析商蕴,來(lái)看看這個(gè)庫(kù)是怎么處理的。
Http 1.1
Http 1.1是基于Http 1.0的基礎(chǔ)上發(fā)展過(guò)來(lái)的芝发。
HTTP 1.0規(guī)定瀏覽器與服務(wù)器只保持短暫的連接绪商,瀏覽器的每次請(qǐng)求都需要與服務(wù)器建立一個(gè)TCP連接,服務(wù)器完成請(qǐng)求處理后立即斷開(kāi)TCP連接辅鲸,服務(wù)器不跟蹤每個(gè)客戶也不記錄過(guò)去的請(qǐng)求格郁。
HTTP 1.1的持續(xù)連接,也需要增加新的請(qǐng)求頭來(lái)幫助實(shí)現(xiàn)瓢湃,例如理张,Connection請(qǐng)求頭的值為Keep-Alive時(shí),客戶端通知服務(wù)器返回本次請(qǐng)求結(jié)果后保持連接绵患;Connection請(qǐng)求頭的值為close時(shí)雾叭,客戶端通知服務(wù)器返回本次請(qǐng)求結(jié)果后關(guān)閉連接。HTTP 1.1還提供了與身份認(rèn)證落蝙、狀態(tài)管理和Cache緩存等機(jī)制相關(guān)的請(qǐng)求頭和響應(yīng)頭织狐。
Http 2.0
Http 2.0是基于Http 1.1的基礎(chǔ)上發(fā)展過(guò)來(lái)的。
Http 1.1是以純文本的形式進(jìn)行傳輸筏勒,每一次都會(huì)帶上完整的Http頭部不考慮pipeline模式移迫,每一次都是完整的一來(lái)一回不斷的發(fā)出了重復(fù)的部分,這樣對(duì)實(shí)時(shí)性上存在不少問(wèn)題管行。
因此Http 2.0在1.1之上做了如下改進(jìn):
1.Http 2.0 會(huì)對(duì)Http的頭部進(jìn)行壓縮厨埋,原來(lái)的頭部(首部)持有了大量的key和value,在2.0中會(huì)建立一個(gè)索引表捐顷,對(duì)相同的頭部只會(huì)發(fā)送這個(gè)索引表
2.Http 2.0 會(huì)將一個(gè)TCP鏈接中切分為多個(gè)流荡陷,每個(gè)流有自己的ID,這個(gè)流可以從客戶端發(fā)送給服務(wù)器迅涮,也能從服務(wù)器發(fā)送給客戶端(也就是流的復(fù)用)
3.Http 2.0 將傳輸信息分為更小的消息和幀废赞,并對(duì)他們采用二進(jìn)制格式編碼。常用的幀為Header幀叮姑,用于傳輸Header的內(nèi)容并開(kāi)啟新的流唉地。接著就是Data幀用于傳輸正文實(shí)體內(nèi)容(多個(gè)Data同屬一個(gè)流)
通過(guò)上面三點(diǎn),Http 2.0把多個(gè)請(qǐng)求劃分在不同的流中传透,把內(nèi)容拆分成為幀進(jìn)行二進(jìn)制傳輸耘沼。這些幀可以打亂順序傳輸,最后根據(jù)幀的首部流標(biāo)識(shí)符朱盐。
下面是一個(gè)示意圖:
在這個(gè)過(guò)程中服務(wù)器和客戶端不需要再對(duì)請(qǐng)求一一對(duì)應(yīng)耕拷,可以同時(shí)處理多個(gè)請(qǐng)求和應(yīng)答。
實(shí)際上是在把三次串行的請(qǐng)求轉(zhuǎn)化成三個(gè)流托享,將數(shù)據(jù)分成幀亂序發(fā)送骚烧。
每一個(gè)數(shù)據(jù)幀的數(shù)據(jù)格式為:
注意Type代表當(dāng)前的數(shù)據(jù)幀是什么類型浸赫,F(xiàn)lag代表當(dāng)前數(shù)據(jù)幀是什么狀態(tài),StreamID當(dāng)前數(shù)據(jù)幀是屬于哪一個(gè)復(fù)用流的赃绊,F(xiàn)rame PayLoad就是當(dāng)前數(shù)據(jù)幀所荷載的內(nèi)容既峡。
所有的這些都能先通過(guò)長(zhǎng)度獲取到后整個(gè)數(shù)據(jù)幀的內(nèi)存范圍后再逐步解析。
Https 協(xié)議
再聊Https協(xié)議之前需要明白下面兩個(gè)知識(shí)點(diǎn):
加密
對(duì)稱加密 就是指客戶端和服務(wù)端公用一個(gè)密鑰碧查,通過(guò)這個(gè)密鑰加密和解密獲得網(wǎng)絡(luò)請(qǐng)求傳輸內(nèi)容运敢。對(duì)稱加密保密性不好容易被破解,對(duì)稱加密如果是通過(guò)網(wǎng)絡(luò)請(qǐng)求獲取的忠售,就容易被攔截獲取到传惠。
非對(duì)稱加密 分成一對(duì)密鑰,有公鑰和私鑰稻扬。一般是通過(guò)公鑰加密卦方,私鑰解密。這樣就能將公鑰往外傳泰佳,自己保護(hù)好私鑰就不容易被破解盼砍。
數(shù)字證書(shū)
不對(duì)稱加密如何把公鑰發(fā)送出去呢?這又牽扯到另一個(gè)概念逝她,數(shù)字證書(shū)CA浇坐。
公鑰發(fā)送出去要么就是放在公網(wǎng)的某個(gè)地址讓人下載,要么就是請(qǐng)求的時(shí)候下載黔宛。
現(xiàn)在公認(rèn)一套流程是借助一個(gè)權(quán)威網(wǎng)站近刘,從這個(gè)權(quán)威網(wǎng)站中下載公鑰。而這個(gè)權(quán)威網(wǎng)站下發(fā)的東西就是我們常說(shuō)的證書(shū)
臀晃。證書(shū)
中包含了證書(shū)所有者觉渴,發(fā)布機(jī)構(gòu)以及日期。
而發(fā)布證書(shū)的權(quán)威網(wǎng)站就是我們常說(shuō)的CA
( Certificate Authority)积仗。
證書(shū)請(qǐng)求可以通過(guò)這個(gè)命令生成:
openssl req -key cliu8siteprivate.key -new -out cliu8sitecertificate.req
將這個(gè)請(qǐng)求發(fā)給權(quán)威機(jī)構(gòu),權(quán)威機(jī)構(gòu)會(huì)給它蓋一個(gè)章也就是使用簽名算法生成一個(gè)簽名:
openssl x509 -req -in cliu8sitecertificate.req -CA cacertificate.pem -CAkey caprivate.key -out cliu8sitecertificate.pem
而 cliu8sitecertificate.pem 就是簽過(guò)名的證書(shū).
里面有個(gè) Issuer(誰(shuí)頒發(fā)的這個(gè)證書(shū));Subject(證書(shū)發(fā)給誰(shuí)的)蜕猫;Validity(證書(shū)有效期限)寂曹;Public-Key(公鑰內(nèi)容);Signature Algorithm 是簽名算法回右。
怎么保證這個(gè)權(quán)威機(jī)構(gòu)是沒(méi)問(wèn)題的呢隆圆?那么就需要更加上層的權(quán)威機(jī)構(gòu)給這個(gè)權(quán)威機(jī)構(gòu)添加一個(gè)CA證書(shū)。在這一層層的嵌套上翔烁,就有一個(gè)Root CA作為最全球最為權(quán)威的機(jī)構(gòu)渺氧。通過(guò)這種層層授權(quán),才讓非對(duì)稱模式在互聯(lián)網(wǎng)上流行蹬屹。
所以說(shuō)侣背,看見(jiàn)有的網(wǎng)站需要你添加Root CA到自己的電腦時(shí)候請(qǐng)注意了白华,一旦根CA出現(xiàn)了問(wèn)題,之后Https也會(huì)變得不安全贩耐。
Https通信模型
DNS
當(dāng)網(wǎng)絡(luò)的七層協(xié)議都了解之后弧腥,可以聊聊其他常用的概念,如DNS潮太。
一般來(lái)說(shuō)我們平時(shí)訪問(wèn)網(wǎng)站的時(shí)候不是通過(guò)ip地址訪問(wèn)管搪,而是通過(guò)一個(gè)url域名訪問(wèn)的。而映射ip地址和url的地址薄是通過(guò)一個(gè)名為DNS的轉(zhuǎn)化的铡买。所以DNS很重要通常設(shè)置為高并發(fā)更鲁,高可用,分布式的奇钞。
DNS分為三種:
- 根DNS 返回頂級(jí)域的DNS服務(wù)器 IP地址
- 頂級(jí)域DNS 返回權(quán)威DNS服務(wù)器IP地址 (如.com ,.net,.cn)
- 權(quán)威DNS 返回相應(yīng)主機(jī)的ip地址
解析流程:
1.客戶端訪問(wèn)一個(gè)www.163.com,就會(huì)發(fā)出DNS請(qǐng)求給本地域名服務(wù)器 (本地 DNS)澡为,請(qǐng)求這個(gè)網(wǎng)站對(duì)應(yīng)的IP地址什么?本地 DNS是指 如果是通過(guò)DHCP配置那么本地DNS就是由移動(dòng)運(yùn)營(yíng)商配置的某個(gè)機(jī)房中一個(gè)主機(jī)
2.本地 DNS 收到來(lái)自客戶端的請(qǐng)求.就會(huì)從本地緩存表中查找蛇券。如果沒(méi)有就去根DNS中詢問(wèn)缀壤。根DNS不會(huì)告訴你具體的ip而是指明一個(gè)道路。
3.根DNS接受到本地DNS的請(qǐng)求后纠亚,查詢后綴塘慕,把請(qǐng)求交給頂級(jí)域DNS中處理
4.本地 DNS 轉(zhuǎn)向問(wèn)頂級(jí)域名服務(wù)器,而頂級(jí)進(jìn)一步的解析這個(gè)url地址就會(huì)進(jìn)一步的確定到權(quán)威DNS服務(wù)器是哪個(gè)蒂胞?
5.最后本地DNS訪問(wèn)權(quán)威DNS服務(wù)機(jī)图呢,最終拿到www.163.com的ip地址返回
HttpDNS
傳統(tǒng)的DNS訪問(wèn)會(huì)遇到如下幾個(gè)問(wèn)題:
1.域名緩存問(wèn)題 因?yàn)橥獠康膗rl可能和緩存的ip地址映射對(duì)不上,可能會(huì)返回過(guò)時(shí)的IP地址骗随,導(dǎo)致訪問(wèn)錯(cuò)地方蛤织。運(yùn)營(yíng)商會(huì)把一些靜態(tài)頁(yè)面,緩存到本運(yùn)營(yíng)商的服務(wù)器內(nèi)鸿染,此時(shí)也會(huì)因?yàn)榫彺嬷赶蛄死系捻?yè)面指蚜。
2.域名轉(zhuǎn)發(fā)問(wèn)題 如果是 A 運(yùn)營(yíng)商的客戶,訪問(wèn)自己運(yùn)營(yíng)商的 DNS 服務(wù)器涨椒,如果 A 運(yùn)營(yíng)商去權(quán)威 DNS 服務(wù)器查詢的話摊鸡,權(quán)威 DNS 服務(wù)器知道你是 A 運(yùn)營(yíng)商的,就返回給一個(gè)部署在 A 運(yùn)營(yíng)商的網(wǎng)站地址蚕冬,這樣針對(duì)相同運(yùn)營(yíng)商的訪問(wèn)免猾,速度就會(huì)快很多。但是A運(yùn)營(yíng)商偷懶直接把請(qǐng)求交給B運(yùn)營(yíng)商處理囤热,那么權(quán)威服務(wù)器就會(huì)以為是A運(yùn)營(yíng)商管理的路由猎提,就會(huì)一直訪問(wèn)A運(yùn)營(yíng)商導(dǎo)致慢
3.域名更新 本地DNS是不同運(yùn)營(yíng)商部署的,解析的邏輯上也有區(qū)別旁蔼。有的會(huì)忽略掉TTL的超時(shí)限制锨苏,導(dǎo)致權(quán)威DNS解析變更時(shí)候過(guò)慢疙教。
4.解析延遲問(wèn)題 可能會(huì)遇到多層DNS服務(wù)器,由于是遞歸導(dǎo)致耗時(shí)嚴(yán)重
為了解決這個(gè)問(wèn)題蚓炬,就誕生了HttpDns松逊。HttpDns本質(zhì)上就是在自己客戶端搭建一個(gè)基于Http協(xié)議本地的DNS服務(wù)器集群,自己做映射緩存肯夏。一般都是手機(jī)端自己使用经宏,手機(jī)端想要使用就需要手機(jī)添加HttpDNS的SDK。
工作流程也很簡(jiǎn)單驯击,如果訪問(wèn)一個(gè)地址烁兰,則先從自己的緩存訪問(wèn)是否有緩存,有則返回徊都。至于什么時(shí)候過(guò)時(shí)沪斟,那就是自己進(jìn)行設(shè)置。如果本地沒(méi)有暇矫,則需要訪問(wèn)HttpDNS的服務(wù)器主之,在本地HttpDNS服務(wù)器中IP列表中,選擇一個(gè)發(fā)出Http請(qǐng)求李根,訪問(wèn)UP地址槽奕。
手機(jī)客戶端自然知道手機(jī)在哪個(gè)運(yùn)營(yíng)商、哪個(gè)地址房轿。由于是直接的 HTTP 通信粤攒,HttpDNS 服務(wù)器能夠準(zhǔn)確知道這些信息,因而可以做精準(zhǔn)的全局負(fù)載均衡囱持。
總結(jié)起來(lái)就是解決亮點(diǎn)問(wèn)題:
- 1.解析速度和更新速度上的問(wèn)題
- 2.智能調(diào)度上的問(wèn)題
Okhttp的設(shè)計(jì)
Okhttp可以分為如下七層協(xié)議:
- 1.retryAndFollowUpInterceptor 重試攔截器
- 2.BridgeInterceptor 建立網(wǎng)絡(luò)橋梁的攔截器夯接,主要是為了給網(wǎng)絡(luò)請(qǐng)求時(shí)候,添加各種各種必要參數(shù)纷妆。如Cookie盔几,Content-type
- 3.CacheInterceptor 緩存攔截器,主要是為了在網(wǎng)絡(luò)請(qǐng)求時(shí)候掩幢,根據(jù)返回碼處理緩存逊拍。
- 4.ConnectInterceptor 鏈接攔截器,主要是為了從鏈接池子中查找可以復(fù)用的socket鏈接粒蜈。
- 5.CallServerInterceptor 真正執(zhí)行網(wǎng)絡(luò)請(qǐng)求的邏輯顺献。
- 6.Interceptor 用戶定義的攔截器旗国,在重試攔截器之前執(zhí)行
- 7.networkInterceptors 用戶定義的網(wǎng)絡(luò)攔截器枯怖,在CallServerInterceptor(執(zhí)行網(wǎng)絡(luò)請(qǐng)求攔截器)之前運(yùn)行。
頭三層協(xié)議專門(mén)用于處理狀態(tài)碼重試緩存幾種情況:
Http響應(yīng)狀態(tài)碼大致上可以分為如下幾種情況:
2XX 代表請(qǐng)求成功
200能曾,203度硝,204 代表請(qǐng)求成功肿轨,可以對(duì)響應(yīng)數(shù)據(jù)進(jìn)行緩存
30X 代表資源發(fā)生變動(dòng)或者沒(méi)有變動(dòng)
300 是指有多種選擇。請(qǐng)求的資源包含多個(gè)位置蕊程,此時(shí)請(qǐng)求也可以看作成功椒袍,此時(shí)也會(huì)進(jìn)行緩存起來(lái)。此時(shí)也會(huì)記錄下需要跳轉(zhuǎn)Header中的Location藻茂,并重新設(shè)置為全新的跳轉(zhuǎn)url驹暑。記住這個(gè)過(guò)程是先執(zhí)行了緩存攔截器后,再執(zhí)行跳轉(zhuǎn)攔截器辨赐。
301 請(qǐng)求的資源已經(jīng)永久移動(dòng)了 會(huì)自動(dòng)重定向优俘。此時(shí)還是一樣會(huì)緩存當(dāng)前的結(jié)果后,嘗試獲取Location的url 進(jìn)行重定向(Http 1.0內(nèi)容)掀序,不允許重定向時(shí)候改變請(qǐng)求方式(如get轉(zhuǎn)化成post)
302 代表臨時(shí)移動(dòng)的資源帆焕,所以沒(méi)有特殊處理并不會(huì)緩存結(jié)果,因?yàn)檫@個(gè)響應(yīng)數(shù)據(jù)很可能時(shí)效性很短不恭;但是如果設(shè)置了
Cache-Control
叶雹,Expires
這些緩存時(shí)效頭部就會(huì)進(jìn)行緩存,接著會(huì)獲取Location的url 進(jìn)行重定向(Http 1.0內(nèi)容)换吧,不允許重定向時(shí)候改變請(qǐng)求方式(如get轉(zhuǎn)化成post)303 代表查看其他資源折晦,而這個(gè)過(guò)程可以不視作一個(gè)正常的響應(yīng)結(jié)果,也因?yàn)樵试S改變請(qǐng)求方式式散;因此也不會(huì)進(jìn)行緩存筋遭,接著會(huì)獲取Location的url 進(jìn)行重定向.
304 代表資源沒(méi)有發(fā)生變動(dòng),且緩存策略是允許刷新的暴拄。那么就說(shuō)明服務(wù)器這段時(shí)間內(nèi)對(duì)這個(gè)請(qǐng)求的應(yīng)答沒(méi)有變化漓滔,客戶端直接從緩存獲取即可。此時(shí)客戶端就會(huì)從緩存攔截器中的緩存對(duì)象獲取緩存好的響應(yīng)信息乖篷。
307 同302 也是一個(gè)臨時(shí)移動(dòng)資源的標(biāo)志位响驴,不同的是這是來(lái)自Http 1.1協(xié)議。為什么出現(xiàn)一個(gè)一樣的呢撕蔼?因?yàn)?02在很多瀏覽器的實(shí)現(xiàn)是允許改變請(qǐng)求方式豁鲤,因此307強(qiáng)制規(guī)定不允許改變
308 同301 是一個(gè)永久移動(dòng)的資源路徑,來(lái)自Http 1.1.原因也是因?yàn)閺?qiáng)制規(guī)范不允許改變請(qǐng)求方式鲸沮,但是允許進(jìn)行緩存琳骡。
4XX 客戶端異常或者客戶端需要特殊處理
401 請(qǐng)求要求用戶的身份認(rèn)證讼溺。這個(gè)過(guò)程就會(huì)獲取設(shè)置在Authenticator 中的賬號(hào)密碼楣号,添加到頭部中重試這個(gè)請(qǐng)求。
403 代表拒絕訪問(wèn),okhttp不會(huì)做任何處理直接返回
404 代表客戶端請(qǐng)求異常炫狱,說(shuō)明這個(gè)url的請(qǐng)求狀態(tài)有問(wèn)題藻懒,okhttp也會(huì)進(jìn)行緩存學(xué)習(xí),下一次再一次訪問(wèn)的時(shí)候就會(huì)直接返回異常视译。
405 代表當(dāng)前請(qǐng)求的方式出錯(cuò)了嬉荆,這個(gè)請(qǐng)求不支持這種請(qǐng)求方式
407 和401類似 不過(guò)在這里面代表的是使用代理的Authenticator. authenticate 進(jìn)行賬號(hào)密碼的校驗(yàn)
408 服務(wù)器等待客戶端發(fā)送請(qǐng)求超時(shí)處理 狀態(tài)碼408不常見(jiàn),但是在HAProxy中會(huì)比較常見(jiàn)酷含。這種情況說(shuō)明我們可以重復(fù)的進(jìn)行沒(méi)有修改過(guò)的請(qǐng)求(甚至是非冪等請(qǐng)求),從頭部中獲取對(duì)應(yīng)的key鄙早,從而決定是否立即重試
410 代表資源已經(jīng)不可用了,此時(shí)okhttp也會(huì)學(xué)習(xí)椅亚,緩存這個(gè)結(jié)果直到超過(guò)緩存時(shí)效蝶锋。
414 代表請(qǐng)求的URL長(zhǎng)度超出了服務(wù)器可以處理的長(zhǎng)度。很少見(jiàn)這種情況什往,這種也是數(shù)據(jù)一種異常扳缕,所以okhttp也會(huì)獲取摘要學(xué)習(xí)
421 代表客戶端所在的ip地址到服務(wù)器的連接數(shù)超過(guò)了服務(wù)器最大的連接數(shù)。此時(shí)還是有機(jī)會(huì)進(jìn)行重新請(qǐng)求别威,因?yàn)樵贖ttp 2.0協(xié)議中允許流的復(fù)用蔚万。
5XX 服務(wù)端異常
500 服務(wù)端出現(xiàn)了無(wú)法處理的錯(cuò)誤吁讨,直接報(bào)錯(cuò)了串稀。 這種情況不會(huì)做處理岖瑰,直接拋出錯(cuò)誤即可
501 服務(wù)端此時(shí)不支持請(qǐng)求所需要的功能,服務(wù)器無(wú)法識(shí)別請(qǐng)求的方法豺妓,并且無(wú)法支持對(duì)任何資源的請(qǐng)求惜互。 這種錯(cuò)誤okhhtp可以緩存學(xué)習(xí),因?yàn)槭欠?wù)器的web系統(tǒng)需要升級(jí)了琳拭。
503 服務(wù)器過(guò)載训堆,暫時(shí)不處理。一般會(huì)帶上
Retry-After
告訴客戶端延時(shí)多少時(shí)間之后再次請(qǐng)求白嘁。然而okhttp不會(huì)做延時(shí)處理坑鱼,而是交給開(kāi)發(fā)者處理,他只會(huì)處理Retry-After
為0的情況絮缅,也就是立即處理504 一般是指網(wǎng)關(guān)超時(shí)鲁沥,注意如果okhttp禁止了網(wǎng)絡(luò)請(qǐng)求和緩存也會(huì)返回504
retryAndFollowUpInterceptor
主要處理了如下幾個(gè)方向的問(wèn)題:
- 1.異常,或者協(xié)議重試(408客戶端超時(shí)耕魄,權(quán)限問(wèn)題画恰,503服務(wù)暫時(shí)不處理,retry-after為0)
- 2.重定向
- 3.重試的次數(shù)不能超過(guò)20次吸奴。
BridgeInterceptor
主要是把Cookie允扇,Content-type設(shè)置到頭部中马靠。很多時(shí)候,初學(xué)者會(huì)疑惑為什么自己加的頭部會(huì)失效蔼两,就是因?yàn)樵贏pplication攔截器中處理后,又被BridgeInterceptor 覆蓋了逞度。需要使用networkInterceptor
CacheInterceptor
主要是處理304等響應(yīng)體的緩存额划。通過(guò)DiskLruCache緩存起來(lái)。
到這里前三層屬于對(duì)Http協(xié)議處理的攔截器就完成了档泽,接下來(lái)幾層就是okhttp如何管理鏈接的俊戳。
ConnectInterceptor
ConnectInterceptor 鏈接攔截器在okhttp中做了如下幾件事情:
1.嘗試從ConnectionPool 中獲取可以進(jìn)行多路復(fù)用的socket鏈接(當(dāng)然需要http 2.0協(xié)議的請(qǐng)求)
-
2.從ProxySelector 中獲取直連或者代理的資源路徑,在這個(gè)過(guò)程中馆匿,會(huì)處理三種情況:
- 2.1 如果是
Proxy.Type.SOCKET
則通過(guò)InetSocketAddress.createUnresolved
解析獲取到InetSocketAddress
對(duì)象 - 2.2 如果是
Proxy.Type.DIRECT
或者Proxy.Type.HTTP
則通過(guò)Address的DNS方法調(diào)用lookup 查詢 host對(duì)應(yīng)對(duì)應(yīng)的ip地址抑胎,生成InetSocketAddress
對(duì)象
- 2.1 如果是
-
3.當(dāng)拿到了該資源路徑對(duì)應(yīng)所有的ip地址,(不是一對(duì)一渐北,是因?yàn)榭赡苁?08等情況存在了負(fù)載均衡實(shí)際關(guān)聯(lián)不同的服務(wù)器)阿逃,并開(kāi)始嘗試鏈接。
- 3.1 如果是簡(jiǎn)單的知道了當(dāng)前的代理模式是非Http代理模式赃蛛,那么就會(huì)直接調(diào)用socket.connect 進(jìn)行鏈接
- 3.2. 如果當(dāng)前的代理模式是Http代理模式恃锉,那么會(huì)先先構(gòu)造一個(gè)虛假的請(qǐng)求和應(yīng)答,交給本地的proxyAuthenticator 嘗試獲取該代理服務(wù)器的需要的賬號(hào)和密碼呕臂,這樣就不會(huì)出現(xiàn)407/408等權(quán)限異常再重新進(jìn)行一次請(qǐng)求破托。當(dāng)獲取好后就添加到頭部,并且調(diào)用socket.connect.
- 3.3 獲取socket的輸入輸出流歧蒋,保存在全局土砂。
-
- establishProtocol 嘗試這處理具體的協(xié)議,這個(gè)方法主要處理了Http 1.0和Http 2.0谜洽。
-
4.1. Http 1.0 存入了SSLSocketFactory對(duì)象萝映,說(shuō)明允許進(jìn)行TLS/SSL 的加密傳輸(一般都存在一個(gè)默認(rèn)的對(duì)象)。就會(huì)依次執(zhí)行握手過(guò)程阐虚,依次為:
- 4.1.1. 根據(jù)原來(lái)的socket對(duì)象锌俱,通過(guò)SSLSocketFactory 包裹生成一個(gè)新的sslSocket對(duì)象
- 4.1.2. sslSocket.startHandshake sslsocket對(duì)象開(kāi)始進(jìn)行握手
- 4.1.3. 獲取sslSocket.session 對(duì)象,調(diào)用他的handshake方法敌呈,開(kāi)始握手
- 4.1.4. 從Address的certificatePinner 中檢索握手成功的證書(shū)贸宏,保證合法信任并且是X509Certificate
-
4.2. 如果是Http 2.0 除了進(jìn)行connectTls的操作之外,還會(huì)開(kāi)始依次傳輸如下三種類型的http 數(shù)據(jù)幀:
- 4.2.1. TYPE_PING 代表當(dāng)前鏈接還活躍著磕洪,在RFC說(shuō)明協(xié)議中吭练,說(shuō)明這個(gè)協(xié)議優(yōu)先級(jí)是最高,必須先發(fā)送析显。
- 4.2.2 往服務(wù)端發(fā)送序言鲫咽,說(shuō)明客戶端的流開(kāi)始傳遞的了,會(huì)發(fā)送如下的數(shù)據(jù):
PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n
- 4.2.3. TYPE_SETTINGS 把當(dāng)前的okhttp設(shè)置的客戶端的設(shè)置,如一個(gè)數(shù)據(jù)幀最大的負(fù)載容量是多少(默認(rèn)為16kb)分尸,當(dāng)前的允許控制的最大(默認(rèn)是Int的最大值相當(dāng)于不限制)交給服務(wù)器锦聊,服務(wù)器會(huì)把客戶端的配置合并起來(lái),進(jìn)行對(duì)客戶端的適配
- 4.2.4 TYPE_WINDOW_UPDATE 嚴(yán)格來(lái)說(shuō)這個(gè)也是保存在settings中的箩绍,然而服務(wù)器只會(huì)讀取前6個(gè)數(shù)組孔庭,剛好就沒(méi)有讀取這個(gè)所有復(fù)用并發(fā)流窗口大小。而是專門(mén)通過(guò)TYPE_WINDOW_UPDATE 和初始的65535大小的并發(fā)流窗口大小進(jìn)行調(diào)節(jié)
到這里就完成了整個(gè)ConnectInterceptor 的工作材蛛。比起http 2.0的協(xié)議初始化圆到,我們更需要關(guān)注的是,這個(gè)過(guò)程中由如下兩個(gè)核心的方法:
- dns lookup 把資源地址轉(zhuǎn)化為ip地址
- socket.connect 通過(guò)socket把客戶端和服務(wù)端聯(lián)系起來(lái)
- socket.starthandshake
- socket.handshake
CallServerInterceptor
先來(lái)看看Okhttp的管理活躍鏈接
實(shí)際上是由一個(gè)RealConnectionPool 緩存所有的RealConnection卑吭。實(shí)際上對(duì)應(yīng)上層來(lái)說(shuō)每一個(gè)RealConnection就是代表每一個(gè)網(wǎng)絡(luò)鏈接的抽象門(mén)面芽淡。
而實(shí)際上真正工作的是其中的Socket對(duì)象。整個(gè)socket鏈接大致可以分為如下幾個(gè)步驟:
- dns lookup 把資源地址轉(zhuǎn)化為ip地址
- socket.connect 通過(guò)socket把客戶端和服務(wù)端聯(lián)系起來(lái)
- socket.starthandshake
- socket.handshake
這四個(gè)步驟都是在ConnectionInterceptor 攔截器中完成豆赏。
雖然都是RealConnection對(duì)象挣菲,但是分發(fā)到CallServerInterceptor之前會(huì)生成一個(gè)Exchange對(duì)象,其中這個(gè)對(duì)象就會(huì)根據(jù)Http1.0/1.1 或者Http2.0 協(xié)議 對(duì)應(yīng)生成不同的Http1ExchangeCodec 以及 Http2ExchangeCodec. 這兩個(gè)對(duì)象就是根據(jù)協(xié)議類型對(duì)數(shù)據(jù)流進(jìn)行解析掷邦。
無(wú)論這兩個(gè)協(xié)議做了什么己单,都可以抽象成如下幾個(gè)方法:
1.Exchange.writeRequestHeaders http1中就是把請(qǐng)求行和頭部寫(xiě)入了socket臨時(shí)緩沖區(qū);http2就是把代表Header的數(shù)據(jù)幀數(shù)寫(xiě)到okio臨時(shí)緩沖區(qū)耙饰。
2.Exchange.readResponseHeaders http1情況下如果沒(méi)有請(qǐng)求體纹笼,那么則是嘗試的讀取響應(yīng)體中的狀態(tài)行頭部等數(shù)據(jù);如果是http2則是等待讀取從服務(wù)端傳遞過(guò)來(lái)的頭部數(shù)據(jù)幀數(shù)據(jù)到緩存隊(duì)列中苟跪。
3.Exchange.createRequestBody http1則是獲取ChunkedSink一個(gè)寫(xiě)入流廷痘;http2則是獲取一個(gè)FrameSink寫(xiě)入流。
4.requestBody.writeTo 往createRequestBody創(chuàng)建的寫(xiě)入流寫(xiě)入數(shù)據(jù)件已。
5.Exchange.finishRequest 把請(qǐng)求體等數(shù)據(jù)一口氣上傳到服務(wù)端
6.Exchange.openResponseBody 獲取響應(yīng)體的讀取流保存到Response對(duì)象中笋额。當(dāng)需要獲取時(shí)候,就調(diào)用toString就會(huì)讀取讀取流的數(shù)據(jù)轉(zhuǎn)化為字符串篷扩。
后話
有了這些基礎(chǔ)后兄猩,我們?cè)購(gòu)腛kHttp開(kāi)始閱讀源碼,看看這個(gè)手機(jī)端最出名的網(wǎng)絡(luò)請(qǐng)求庫(kù)是怎么設(shè)計(jì)的鉴未。
本文不是最終版本枢冤,之后會(huì)陸續(xù)更新詳細(xì)。