UDP
面向報(bào)文
UDP 是一個(gè)面向報(bào)文(報(bào)文可以理解為一段段的數(shù)據(jù))的協(xié)議。意思就是 UDP 只是報(bào)文的搬運(yùn)工,不會(huì)對(duì)報(bào)文進(jìn)行任何拆分和拼接操作。
具體來(lái)說(shuō)
- 在發(fā)送端,應(yīng)用層將數(shù)據(jù)傳遞給傳輸層的 UDP 協(xié)議,UDP 只會(huì)給數(shù)據(jù)增加一個(gè) UDP 頭標(biāo)識(shí)下是 UDP 協(xié)議,然后就傳遞給網(wǎng)絡(luò)層了
- 在接收端,網(wǎng)絡(luò)層將數(shù)據(jù)傳遞給傳輸層,UDP 只去除 IP 報(bào)文頭就傳遞給應(yīng)用層,不會(huì)任何拼接操作
不可靠性
- UDP 是無(wú)連接的,也就是說(shuō)通信不需要建立和斷開連接。
- UDP 也是不可靠的。協(xié)議收到什么數(shù)據(jù)就傳遞什么數(shù)據(jù),并且也不會(huì)備份數(shù)據(jù),對(duì)方能不能收到是不關(guān)心的
- UDP 沒(méi)有擁塞控制,一直會(huì)以恒定的速度發(fā)送數(shù)據(jù)。即使網(wǎng)絡(luò)條件不好,也不會(huì)對(duì)發(fā)送速率進(jìn)行調(diào)整。這樣實(shí)現(xiàn)的弊端就是在網(wǎng)絡(luò)條件不好的情況下可能會(huì)導(dǎo)致丟包,但是優(yōu)點(diǎn)也很明顯,在某些實(shí)時(shí)性要求高的場(chǎng)景(比如電話會(huì)議)就需要使用 UDP 而不是 TCP。
高效
因?yàn)?UDP 沒(méi)有 TCP 那么復(fù)雜,需要保證數(shù)據(jù)不丟失且有序到達(dá)。所以 UDP 的頭部開銷小,只有八字節(jié),相比 TCP 的至少二十字節(jié)要少得多,在傳輸數(shù)據(jù)報(bào)文時(shí)是很高效的。

頭部包含了以下幾個(gè)數(shù)據(jù)
- 兩個(gè)十六位的端口號(hào),分別為源端口(可選字段)和目標(biāo)端口
- 整個(gè)數(shù)據(jù)報(bào)文的長(zhǎng)度
- 整個(gè)數(shù)據(jù)報(bào)文的檢驗(yàn)和(IPv4 可選 字段),該字段用于發(fā)現(xiàn)頭部信息和數(shù)據(jù)中的錯(cuò)誤
傳輸方式
UDP 不止支持一對(duì)一的傳輸方式,同樣支持一對(duì)多,多對(duì)多,多對(duì)一的方式,也就是說(shuō) UDP 提供了單播,多播,廣播的功能。
TCP
頭部
TCP 頭部比 UDP 頭部復(fù)雜的多

對(duì)于 TCP 頭部來(lái)說(shuō),以下幾個(gè)字段是很重要的
- Sequence number,這個(gè)序號(hào)保證了 TCP 傳輸?shù)膱?bào)文都是有序的,對(duì)端可以通過(guò)序號(hào)順序的拼接報(bào)文
- Acknowledgement Number,這個(gè)序號(hào)表示數(shù)據(jù)接收端期望接收的下一個(gè)字節(jié)的編號(hào)是多少,同時(shí)也表示上一個(gè)序號(hào)的數(shù)據(jù)已經(jīng)收到
- Window Size,窗口大小,表示還能接收多少字節(jié)的數(shù)據(jù),用于流量控制
- 標(biāo)識(shí)符
- URG=1:該字段為一表示本數(shù)據(jù)報(bào)的數(shù)據(jù)部分包含緊急信息,是一個(gè)高優(yōu)先級(jí)數(shù)據(jù)報(bào)文,此時(shí)緊急指針有效。緊急數(shù)據(jù)一定位于當(dāng)前數(shù)據(jù)包數(shù)據(jù)部分的最前面,緊急指針標(biāo)明了緊急數(shù)據(jù)的尾部。
- ACK=1:該字段為一表示確認(rèn)號(hào)字段有效。此外,TCP 還規(guī)定在連接建立后傳送的所有報(bào)文段都必須把 ACK 置為一。
- PSH=1:該字段為一表示接收端應(yīng)該立即將數(shù)據(jù) push 給應(yīng)用層,而不是等到緩沖區(qū)滿后再提交。
- RST=1:該字段為一表示當(dāng)前 TCP 連接出現(xiàn)嚴(yán)重問(wèn)題,可能需要重新建立 TCP 連接,也可以用于拒絕非法的報(bào)文段和拒絕連接請(qǐng)求。
- SYN=1:當(dāng)SYN=1,ACK=0時(shí),表示當(dāng)前報(bào)文段是一個(gè)連接請(qǐng)求報(bào)文。當(dāng)SYN=1,ACK=1時(shí),表示當(dāng)前報(bào)文段是一個(gè)同意建立連接的應(yīng)答報(bào)文。
- FIN=1:該字段為一表示此報(bào)文段是一個(gè)釋放連接的請(qǐng)求報(bào)文。
狀態(tài)機(jī)
HTTP 是無(wú)連接的,所以作為下層的 TCP 協(xié)議也是無(wú)連接的,雖然看似 TCP 將兩端連接了起來(lái),但是其實(shí)只是兩端共同維護(hù)了一個(gè)狀態(tài)

TCP 的狀態(tài)機(jī)是很復(fù)雜的,并且與建立斷開連接時(shí)的握手息息相關(guān),接下來(lái)就來(lái)詳細(xì)描述下兩種握手。
在這之前需要了解一個(gè)重要的性能指標(biāo) RTT。該指標(biāo)表示發(fā)送端發(fā)送數(shù)據(jù)到接收到對(duì)端數(shù)據(jù)所需的往返時(shí)間。
建立連接三次握手

在 TCP 協(xié)議中,主動(dòng)發(fā)起請(qǐng)求的一端為客戶端,被動(dòng)連接的一端稱為服務(wù)端。不管是客戶端還是服務(wù)端,TCP 連接建立完后都能發(fā)送和接收數(shù)據(jù),所以 TCP 也是一個(gè)全雙工的協(xié)議。
起初,兩端都為 CLOSED 狀態(tài)。在通信開始前,雙方都會(huì)創(chuàng)建 TCB。 服務(wù)器創(chuàng)建完 TCB 后遍進(jìn)入 LISTEN 狀態(tài),此時(shí)開始等待客戶端發(fā)送數(shù)據(jù)。
第一次握手
客戶端向服務(wù)端發(fā)送連接請(qǐng)求報(bào)文段。該報(bào)文段中包含自身的數(shù)據(jù)通訊初始序號(hào)。請(qǐng)求發(fā)送后,客戶端便進(jìn)入 SYN-SENT 狀態(tài),x 表示客戶端的數(shù)據(jù)通信初始序號(hào)。
第二次握手
服務(wù)端收到連接請(qǐng)求報(bào)文段后,如果同意連接,則會(huì)發(fā)送一個(gè)應(yīng)答,該應(yīng)答中也會(huì)包含自身的數(shù)據(jù)通訊初始序號(hào),發(fā)送完成后便進(jìn)入 SYN-RECEIVED 狀態(tài)。
第三次握手
當(dāng)客戶端收到連接同意的應(yīng)答后,還要向服務(wù)端發(fā)送一個(gè)確認(rèn)報(bào)文??蛻舳税l(fā)完這個(gè)報(bào)文段后便進(jìn)入ESTABLISHED 狀態(tài),服務(wù)端收到這個(gè)應(yīng)答后也進(jìn)入 ESTABLISHED 狀態(tài),此時(shí)連接建立成功。
PS:第三次握手可以包含數(shù)據(jù),通過(guò) TCP 快速打開(TFO)技術(shù)。其實(shí)只要涉及到握手的協(xié)議,都可以使用類似 TFO 的方式,客戶端和服務(wù)端存儲(chǔ)相同 cookie,下次握手時(shí)發(fā)出 cookie 達(dá)到減少 RTT 的目的。
你是否有疑惑明明兩次握手就可以建立起連接,為什么還需要第三次應(yīng)答?
因?yàn)檫@是為了防止失效的連接請(qǐng)求報(bào)文段被服務(wù)端接收,從而產(chǎn)生錯(cuò)誤。
可以想象如下場(chǎng)景??蛻舳税l(fā)送了一個(gè)連接請(qǐng)求 A,但是因?yàn)榫W(wǎng)絡(luò)原因造成了超時(shí),這時(shí) TCP 會(huì)啟動(dòng)超時(shí)重傳的機(jī)制再次發(fā)送一個(gè)連接請(qǐng)求 B。此時(shí)請(qǐng)求順利到達(dá)服務(wù)端,服務(wù)端應(yīng)答完就建立了請(qǐng)求。如果連接請(qǐng)求 A 在兩端關(guān)閉后終于抵達(dá)了服務(wù)端,那么這時(shí)服務(wù)端會(huì)認(rèn)為客戶端又需要建立 TCP 連接,從而應(yīng)答了該請(qǐng)求并進(jìn)入 ESTABLISHED 狀態(tài)。此時(shí)客戶端其實(shí)是 CLOSED 狀態(tài),那么就會(huì)導(dǎo)致服務(wù)端一直等待,造成資源的浪費(fèi)。
PS:在建立連接中,任意一端掉線,TCP 都會(huì)重發(fā) SYN 包,一般會(huì)重試五次,在建立連接中可能會(huì)遇到 SYN FLOOD 攻擊。遇到這種情況你可以選擇調(diào)低重試次數(shù)或者干脆在不能處理的情況下拒絕請(qǐng)求。
斷開鏈接四次握手

TCP 是全雙工的,在斷開連接時(shí)兩端都需要發(fā)送 FIN 和 ACK。
第一次握手
若客戶端 A 認(rèn)為數(shù)據(jù)發(fā)送完成,則它需要向服務(wù)端 B 發(fā)送連接釋放請(qǐng)求。
第二次握手
B 收到連接釋放請(qǐng)求后,會(huì)告訴應(yīng)用層要釋放 TCP 鏈接。然后會(huì)發(fā)送 ACK 包,并進(jìn)入 CLOSE_WAIT 狀態(tài),表示 A 到 B 的連接已經(jīng)釋放,不接收 A 發(fā)的數(shù)據(jù)了。但是因?yàn)?TCP 連接時(shí)雙向的,所以 B 仍舊可以發(fā)送數(shù)據(jù)給 A。
第三次握手
B 如果此時(shí)還有沒(méi)發(fā)完的數(shù)據(jù)會(huì)繼續(xù)發(fā)送,完畢后會(huì)向 A 發(fā)送連接釋放請(qǐng)求,然后 B 便進(jìn)入 LAST-ACK 狀態(tài)。
PS:通過(guò)延遲確認(rèn)的技術(shù)(通常有時(shí)間限制,否則對(duì)方會(huì)誤認(rèn)為需要重傳),可以將第二次和第三次握手合并,延遲 ACK 包的發(fā)送。
第四次握手
A 收到釋放請(qǐng)求后,向 B 發(fā)送確認(rèn)應(yīng)答,此時(shí) A 進(jìn)入 TIME-WAIT 狀態(tài)。該狀態(tài)會(huì)持續(xù) 2MSL(最大段生存期,指報(bào)文段在網(wǎng)絡(luò)中生存的時(shí)間,超時(shí)會(huì)被拋棄) 時(shí)間,若該時(shí)間段內(nèi)沒(méi)有 B 的重發(fā)請(qǐng)求的話,就進(jìn)入 CLOSED 狀態(tài)。當(dāng) B 收到確認(rèn)應(yīng)答后,也便進(jìn)入 CLOSED 狀態(tài)。
為什么 A 要進(jìn)入 TIME-WAIT 狀態(tài),等待 2MSL 時(shí)間后才進(jìn)入 CLOSED 狀態(tài)?
為了保證 B 能收到 A 的確認(rèn)應(yīng)答。若 A 發(fā)完確認(rèn)應(yīng)答后直接進(jìn)入 CLOSED 狀態(tài),如果確認(rèn)應(yīng)答因?yàn)榫W(wǎng)絡(luò)問(wèn)題一直沒(méi)有到達(dá),那么會(huì)造成 B 不能正常關(guān)閉。
ARQ 協(xié)議
ARQ 協(xié)議也就是超時(shí)重傳機(jī)制。通過(guò)確認(rèn)和超時(shí)機(jī)制保證了數(shù)據(jù)的正確送達(dá),ARQ 協(xié)議包含停止等待 ARQ 和連續(xù) ARQ
停止等待 ARQ
正常傳輸過(guò)程
只要 A 向 B 發(fā)送一段報(bào)文,都要停止發(fā)送并啟動(dòng)一個(gè)定時(shí)器,等待對(duì)端回應(yīng),在定時(shí)器時(shí)間內(nèi)接收到對(duì)端應(yīng)答就取消定時(shí)器并發(fā)送下一段報(bào)文。
報(bào)文丟失或出錯(cuò)
在報(bào)文傳輸?shù)倪^(guò)程中可能會(huì)出現(xiàn)丟包。這時(shí)候超過(guò)定時(shí)器設(shè)定的時(shí)間就會(huì)再次發(fā)送丟包的數(shù)據(jù)直到對(duì)端響應(yīng),所以需要每次都備份發(fā)送的數(shù)據(jù)。
即使報(bào)文正常的傳輸?shù)綄?duì)端,也可能出現(xiàn)在傳輸過(guò)程中報(bào)文出錯(cuò)的問(wèn)題。這時(shí)候?qū)Χ藭?huì)拋棄該報(bào)文并等待 A 端重傳。
PS:一般定時(shí)器設(shè)定的時(shí)間都會(huì)大于一個(gè) RTT 的平均時(shí)間。
ACK 超時(shí)或丟失
對(duì)端傳輸?shù)膽?yīng)答也可能出現(xiàn)丟失或超時(shí)的情況。那么超過(guò)定時(shí)器時(shí)間 A 端照樣會(huì)重傳報(bào)文。這時(shí)候 B 端收到相同序號(hào)的報(bào)文會(huì)丟棄該報(bào)文并重傳應(yīng)答,直到 A 端發(fā)送下一個(gè)序號(hào)的報(bào)文。
在超時(shí)的情況下也可能出現(xiàn)應(yīng)答很遲到達(dá),這時(shí) A 端會(huì)判斷該序號(hào)是否已經(jīng)接收過(guò),如果接收過(guò)只需要丟棄應(yīng)答即可。
這個(gè)協(xié)議的缺點(diǎn)就是傳輸效率低,在良好的網(wǎng)絡(luò)環(huán)境下每次發(fā)送報(bào)文都得等待對(duì)端的 ACK 。
連續(xù) ARQ
在連續(xù) ARQ 中,發(fā)送端擁有一個(gè)發(fā)送窗口,可以在沒(méi)有收到應(yīng)答的情況下持續(xù)發(fā)送窗口內(nèi)的數(shù)據(jù),這樣相比停止等待 ARQ 協(xié)議來(lái)說(shuō)減少了等待時(shí)間,提高了效率。
累計(jì)確認(rèn)
連續(xù) ARQ 中,接收端會(huì)持續(xù)不斷收到報(bào)文。如果和停止等待 ARQ 中接收一個(gè)報(bào)文就發(fā)送一個(gè)應(yīng)答一樣,就太浪費(fèi)資源了。通過(guò)累計(jì)確認(rèn),可以在收到多個(gè)報(bào)文以后統(tǒng)一回復(fù)一個(gè)應(yīng)答報(bào)文。報(bào)文中的 ACK 可以用來(lái)告訴發(fā)送端這個(gè)序號(hào)之前的數(shù)據(jù)已經(jīng)全部接收到了,下次請(qǐng)發(fā)送這個(gè)序號(hào) + 1的數(shù)據(jù)。
但是累計(jì)確認(rèn)也有一個(gè)弊端。在連續(xù)接收?qǐng)?bào)文時(shí),可能會(huì)遇到接收到序號(hào) 5 的報(bào)文后,并未接到序號(hào) 6 的報(bào)文,然而序號(hào) 7 以后的報(bào)文已經(jīng)接收。遇到這種情況時(shí),ACK 只能回復(fù) 6,這樣會(huì)造成發(fā)送端重復(fù)發(fā)送數(shù)據(jù),這種情況下可以通過(guò) Sack 來(lái)解決,這個(gè)會(huì)在下文說(shuō)到。
滑動(dòng)窗口
在上面小節(jié)中講到了發(fā)送窗口。在 TCP 中,兩端都維護(hù)著窗口:分別為發(fā)送端窗口和接收端窗口。
發(fā)送端窗口包含已發(fā)送但未收到應(yīng)答的數(shù)據(jù)和可以發(fā)送但是未發(fā)送的數(shù)據(jù)。

發(fā)送端窗口是由接收窗口剩余大小決定的。接收方會(huì)把當(dāng)前接收窗口的剩余大小寫入應(yīng)答報(bào)文,發(fā)送端收到應(yīng)答后根據(jù)該值和當(dāng)前網(wǎng)絡(luò)擁塞情況設(shè)置發(fā)送窗口的大小,所以發(fā)送窗口的大小是不斷變化的。
當(dāng)發(fā)送端接收到應(yīng)答報(bào)文后,會(huì)隨之將窗口進(jìn)行滑動(dòng)

滑動(dòng)窗口實(shí)現(xiàn)了流量控制。接收方通過(guò)報(bào)文告知發(fā)送方還可以發(fā)送多少數(shù)據(jù),從而保證接收方能夠來(lái)得及接收數(shù)據(jù)。
Zero 窗口
在發(fā)送報(bào)文的過(guò)程中,可能會(huì)遇到對(duì)端出現(xiàn)零窗口的情況。在該情況下,發(fā)送端會(huì)停止發(fā)送數(shù)據(jù),并啟動(dòng) persistent timer 。該定時(shí)器會(huì)定時(shí)發(fā)送請(qǐng)求給對(duì)端,讓對(duì)端告知窗口大小。在重試次數(shù)超過(guò)一定次數(shù)后,可能會(huì)中斷 TCP 鏈接。
擁塞處理
擁塞處理和流量控制不同,后者是作用于接收方,保證接收方來(lái)得及接受數(shù)據(jù)。而前者是作用于網(wǎng)絡(luò),防止過(guò)多的數(shù)據(jù)擁塞網(wǎng)絡(luò),避免出現(xiàn)網(wǎng)絡(luò)負(fù)載過(guò)大的情況。
擁塞處理包括了四個(gè)算法,分別為:慢開始,擁塞避免,快速重傳,快速恢復(fù)。
慢開始算法
慢開始算法,顧名思義,就是在傳輸開始時(shí)將發(fā)送窗口慢慢指數(shù)級(jí)擴(kuò)大,從而避免一開始就傳輸大量數(shù)據(jù)導(dǎo)致網(wǎng)絡(luò)擁塞。
慢開始算法步驟具體如下
- 連接初始設(shè)置擁塞窗口(Congestion Window) 為 1 MSS(一個(gè)分段的最大數(shù)據(jù)量)
- 每過(guò)一個(gè) RTT 就將窗口大小乘二
- 指數(shù)級(jí)增長(zhǎng)肯定不能沒(méi)有限制的,所以有一個(gè)閾值限制,當(dāng)窗口大小大于閾值時(shí)就會(huì)啟動(dòng)擁塞避免算法。
擁塞避免算法
擁塞避免算法相比簡(jiǎn)單點(diǎn),每過(guò)一個(gè) RTT 窗口大小只加一,這樣能夠避免指數(shù)級(jí)增長(zhǎng)導(dǎo)致網(wǎng)絡(luò)擁塞,慢慢將大小調(diào)整到最佳值。
在傳輸過(guò)程中可能定時(shí)器超時(shí)的情況,這時(shí)候 TCP 會(huì)認(rèn)為網(wǎng)絡(luò)擁塞了,會(huì)馬上進(jìn)行以下步驟:
- 將閾值設(shè)為當(dāng)前擁塞窗口的一半
- 將擁塞窗口設(shè)為 1 MSS
- 啟動(dòng)擁塞避免算法
快速重傳
快速重傳一般和快恢復(fù)一起出現(xiàn)。一旦接收端收到的報(bào)文出現(xiàn)失序的情況,接收端只會(huì)回復(fù)最后一個(gè)順序正確的報(bào)文序號(hào)(沒(méi)有 Sack 的情況下)。如果收到三個(gè)重復(fù)的 ACK,無(wú)需等待定時(shí)器超時(shí)再重發(fā)而是啟動(dòng)快速重傳。具體算法分為兩種:
TCP Taho 實(shí)現(xiàn)如下
- 將閾值設(shè)為當(dāng)前擁塞窗口的一半
- 將擁塞窗口設(shè)為 1 MSS
- 重新開始慢開始算法
TCP Reno 實(shí)現(xiàn)如下
- 擁塞窗口減半
- 將閾值設(shè)為當(dāng)前擁塞窗口
- 進(jìn)入快恢復(fù)階段(重發(fā)對(duì)端需要的包,一旦收到一個(gè)新的 ACK 答復(fù)就退出該階段)
- 使用擁塞避免算法
TCP New Ren 改進(jìn)后的快恢復(fù)
TCP New Reno 算法改進(jìn)了之前 TCP Reno 算法的缺陷。在之前,快恢復(fù)中只要收到一個(gè)新的 ACK 包,就會(huì)退出快恢復(fù)。
在 TCP New Reno 中,TCP 發(fā)送方先記下三個(gè)重復(fù) ACK 的分段的最大序號(hào)。
假如我有一個(gè)分段數(shù)據(jù)是 1 ~ 10 這十個(gè)序號(hào)的報(bào)文,其中丟失了序號(hào)為 3 和 7 的報(bào)文,那么該分段的最大序號(hào)就是 10。發(fā)送端只會(huì)收到 ACK 序號(hào)為 3 的應(yīng)答。這時(shí)候重發(fā)序號(hào)為 3 的報(bào)文,接收方順利接收并會(huì)發(fā)送 ACK 序號(hào)為 7 的應(yīng)答。這時(shí)候 TCP 知道對(duì)端是有多個(gè)包未收到,會(huì)繼續(xù)發(fā)送序號(hào)為 7 的報(bào)文,接收方順利接收并會(huì)發(fā)送 ACK 序號(hào)為 11 的應(yīng)答,這時(shí)發(fā)送端認(rèn)為這個(gè)分段接收端已經(jīng)順利接收,接下來(lái)會(huì)退出快恢復(fù)階段。
HTTP
HTTP 協(xié)議是個(gè)無(wú)狀態(tài)協(xié)議,不會(huì)保存狀態(tài)。
Post 和 Get 的區(qū)別
先引入副作用和冪等的概念。
副作用指對(duì)服務(wù)器上的資源做改變,搜索是無(wú)副作用的,注冊(cè)是副作用的。
冪等指發(fā)送 M 和 N 次請(qǐng)求(兩者不相同且都大于 1),服務(wù)器上資源的狀態(tài)一致,比如注冊(cè) 10 個(gè)和 11 個(gè)帳號(hào)是不冪等的,對(duì)文章進(jìn)行更改 10 次和 11 次是冪等的。
在規(guī)范的應(yīng)用場(chǎng)景上說(shuō),Get 多用于無(wú)副作用,冪等的場(chǎng)景,例如搜索關(guān)鍵字。Post 多用于副作用,不冪等的場(chǎng)景,例如注冊(cè)。
在技術(shù)上說(shuō):
- Get 請(qǐng)求能緩存,Post 不能
- Post 相對(duì) Get 安全一點(diǎn)點(diǎn),因?yàn)镚et 請(qǐng)求都包含在 URL 里,且會(huì)被瀏覽器保存歷史紀(jì)錄,Post 不會(huì),但是在抓包的情況下都是一樣的。
- Post 可以通過(guò) request body來(lái)傳輸比 Get 更多的數(shù)據(jù),Get 沒(méi)有這個(gè)技術(shù)
- URL有長(zhǎng)度限制,會(huì)影響 Get 請(qǐng)求,但是這個(gè)長(zhǎng)度限制是瀏覽器規(guī)定的,不是 RFC 規(guī)定的
- Post 支持更多的編碼類型且不對(duì)數(shù)據(jù)類型限制
常見狀態(tài)碼
2XX 成功
- 200 OK,表示從客戶端發(fā)來(lái)的請(qǐng)求在服務(wù)器端被正確處理
- 204 No content,表示請(qǐng)求成功,但響應(yīng)報(bào)文不含實(shí)體的主體部分
- 205 Reset Content,表示請(qǐng)求成功,但響應(yīng)報(bào)文不含實(shí)體的主體部分,但是與 204 響應(yīng)不同在于要求請(qǐng)求方重置內(nèi)容
- 206 Partial Content,進(jìn)行范圍請(qǐng)求
3XX 重定向
- 301 moved permanently,永久性重定向,表示資源已被分配了新的 URL
- 302 found,臨時(shí)性重定向,表示資源臨時(shí)被分配了新的 URL
- 303 see other,表示資源存在著另一個(gè) URL,應(yīng)使用 GET 方法獲取資源
- 304 not modified,表示服務(wù)器允許訪問(wèn)資源,但因發(fā)生請(qǐng)求未滿足條件的情況
- 307 temporary redirect,臨時(shí)重定向,和302含義類似,但是期望客戶端保持請(qǐng)求方法不變向新的地址發(fā)出請(qǐng)求
4XX 客戶端錯(cuò)誤
- 400 bad request,請(qǐng)求報(bào)文存在語(yǔ)法錯(cuò)誤
- 401 unauthorized,表示發(fā)送的請(qǐng)求需要有通過(guò) HTTP 認(rèn)證的認(rèn)證信息
- 403 forbidden,表示對(duì)請(qǐng)求資源的訪問(wèn)被服務(wù)器拒絕
- 404 not found,表示在服務(wù)器上沒(méi)有找到請(qǐng)求的資源
5XX 服務(wù)器錯(cuò)誤
- 500 internal sever error,表示服務(wù)器端在執(zhí)行請(qǐng)求時(shí)發(fā)生了錯(cuò)誤
- 501 Not Implemented,表示服務(wù)器不支持當(dāng)前請(qǐng)求所需要的某個(gè)功能
- 503 service unavailable,表明服務(wù)器暫時(shí)處于超負(fù)載或正在停機(jī)維護(hù),無(wú)法處理請(qǐng)求
HTTP 首部
| 通用字段 | 作用 |
|---|---|
| Cache-Control | 控制緩存的行為 |
| Connection | 瀏覽器想要優(yōu)先使用的連接類型,比如 keep-alive
|
| Date | 創(chuàng)建報(bào)文時(shí)間 |
| Pragma | 報(bào)文指令 |
| Via | 代理服務(wù)器相關(guān)信息 |
| Transfer-Encoding | 傳輸編碼方式 |
| Upgrade | 要求客戶端升級(jí)協(xié)議 |
| Warning | 在內(nèi)容中可能存在錯(cuò)誤 |
| 請(qǐng)求字段 | 作用 |
|---|---|
| Accept | 能正確接收的媒體類型 |
| Accept-Charset | 能正確接收的字符集 |
| Accept-Encoding | 能正確接收的編碼格式列表 |
| Accept-Language | 能正確接收的語(yǔ)言列表 |
| Expect | 期待服務(wù)端的指定行為 |
| From | 請(qǐng)求方郵箱地址 |
| Host | 服務(wù)器的域名 |
| If-Match | 兩端資源標(biāo)記比較 |
| If-Modified-Since | 本地資源未修改返回 304(比較時(shí)間) |
| If-None-Match | 本地資源未修改返回 304(比較標(biāo)記) |
| User-Agent | 客戶端信息 |
| Max-Forwards | 限制可被代理及網(wǎng)關(guān)轉(zhuǎn)發(fā)的次數(shù) |
| Proxy-Authorization | 向代理服務(wù)器發(fā)送驗(yàn)證信息 |
| Range | 請(qǐng)求某個(gè)內(nèi)容的一部分 |
| Referer | 表示瀏覽器所訪問(wèn)的前一個(gè)頁(yè)面 |
| TE | 傳輸編碼方式 |
| 響應(yīng)字段 | 作用 |
|---|---|
| Accept-Ranges | 是否支持某些種類的范圍 |
| Age | 資源在代理緩存中存在的時(shí)間 |
| ETag | 資源標(biāo)識(shí) |
| Location | 客戶端重定向到某個(gè) URL |
| Proxy-Authenticate | 向代理服務(wù)器發(fā)送驗(yàn)證信息 |
| Server | 服務(wù)器名字 |
| WWW-Authenticate | 獲取資源需要的驗(yàn)證信息 |
| 實(shí)體字段 | 作用 |
|---|---|
| Allow | 資源的正確請(qǐng)求方式 |
| Content-Encoding | 內(nèi)容的編碼格式 |
| Content-Language | 內(nèi)容使用的語(yǔ)言 |
| Content-Length | request body 長(zhǎng)度 |
| Content-Location | 返回?cái)?shù)據(jù)的備用地址 |
| Content-MD5 | Base64加密格式的內(nèi)容 MD5檢驗(yàn)值 |
| Content-Range | 內(nèi)容的位置范圍 |
| Content-Type | 內(nèi)容的媒體類型 |
| Expires | 內(nèi)容的過(guò)期時(shí)間 |
| Last_modified | 內(nèi)容的最后修改時(shí)間 |
PS:緩存相關(guān)已在別的模塊中寫完,你可以 閱讀該小節(jié)
HTTPS
HTTPS 還是通過(guò)了 HTTP 來(lái)傳輸信息,但是信息通過(guò) TLS 協(xié)議進(jìn)行了加密。
TLS
TLS 協(xié)議位于傳輸層之上,應(yīng)用層之下。首次進(jìn)行 TLS 協(xié)議傳輸需要兩個(gè) RTT ,接下來(lái)可以通過(guò) Session Resumption 減少到一個(gè) RTT。
在 TLS 中使用了兩種加密技術(shù),分別為:對(duì)稱加密和非對(duì)稱加密。
對(duì)稱加密:
對(duì)稱加密就是兩邊擁有相同的秘鑰,兩邊都知道如何將密文加密解密。
非對(duì)稱加密:
有公鑰私鑰之分,公鑰所有人都可以知道,可以將數(shù)據(jù)用公鑰加密,但是將數(shù)據(jù)解密必須使用私鑰解密,私鑰只有分發(fā)公鑰的一方才知道。
TLS 握手過(guò)程如下圖:

- 客戶端發(fā)送一個(gè)隨機(jī)值,需要的協(xié)議和加密方式
- 服務(wù)端收到客戶端的隨機(jī)值,自己也產(chǎn)生一個(gè)隨機(jī)值,并根據(jù)客戶端需求的協(xié)議和加密方式來(lái)使用對(duì)應(yīng)的方式,發(fā)送自己的證書(如果需要驗(yàn)證客戶端證書需要說(shuō)明)
- 客戶端收到服務(wù)端的證書并驗(yàn)證是否有效,驗(yàn)證通過(guò)會(huì)再生成一個(gè)隨機(jī)值,通過(guò)服務(wù)端證書的公鑰去加密這個(gè)隨機(jī)值并發(fā)送給服務(wù)端,如果服務(wù)端需要驗(yàn)證客戶端證書的話會(huì)附帶證書
- 服務(wù)端收到加密過(guò)的隨機(jī)值并使用私鑰解密獲得第三個(gè)隨機(jī)值,這時(shí)候兩端都擁有了三個(gè)隨機(jī)值,可以通過(guò)這三個(gè)隨機(jī)值按照之前約定的加密方式生成密鑰,接下來(lái)的通信就可以通過(guò)該密鑰來(lái)加密解密
通過(guò)以上步驟可知,在 TLS 握手階段,兩端使用非對(duì)稱加密的方式來(lái)通信,但是因?yàn)榉菍?duì)稱加密損耗的性能比對(duì)稱加密大,所以在正式傳輸數(shù)據(jù)時(shí),兩端使用對(duì)稱加密的方式通信。
PS:以上說(shuō)明的都是 TLS 1.2 協(xié)議的握手情況,在 1.3 協(xié)議中,首次建立連接只需要一個(gè) RTT,后面恢復(fù)連接不需要 RTT 了。
HTTP 2.0
HTTP 2.0 相比于 HTTP 1.X,可以說(shuō)是大幅度提高了 web 的性能。
在 HTTP 1.X 中,為了性能考慮,我們會(huì)引入雪碧圖、將小圖內(nèi)聯(lián)、使用多個(gè)域名等等的方式。這一切都是因?yàn)闉g覽器限制了同一個(gè)域名下的請(qǐng)求數(shù)量,當(dāng)頁(yè)面中需要請(qǐng)求很多資源的時(shí)候,隊(duì)頭阻塞(Head of line blocking)會(huì)導(dǎo)致在達(dá)到最大請(qǐng)求數(shù)量時(shí),剩余的資源需要等待其他資源請(qǐng)求完成后才能發(fā)起請(qǐng)求。
你可以通過(guò) 該鏈接 感受下 HTTP 2.0 比 HTTP 1.X 到底快了多少。

在 HTTP 1.X 中,因?yàn)殛?duì)頭阻塞的原因,你會(huì)發(fā)現(xiàn)請(qǐng)求是這樣的

在 HTTP 2.0 中,因?yàn)橐肓硕嗦窂?fù)用,你會(huì)發(fā)現(xiàn)請(qǐng)求是這樣的

二進(jìn)制傳輸
HTTP 2.0 中所有加強(qiáng)性能的核心點(diǎn)在于此。在之前的 HTTP 版本中,我們是通過(guò)文本的方式傳輸數(shù)據(jù)。在 HTTP 2.0 中引入了新的編碼機(jī)制,所有傳輸?shù)臄?shù)據(jù)都會(huì)被分割,并采用二進(jìn)制格式編碼。

多路復(fù)用
在 HTTP 2.0 中,有兩個(gè)非常重要的概念,分別是幀(frame)和流(stream)。
幀代表著最小的數(shù)據(jù)單位,每個(gè)幀會(huì)標(biāo)識(shí)出該幀屬于哪個(gè)流,流也就是多個(gè)幀組成的數(shù)據(jù)流。
多路復(fù)用,就是在一個(gè) TCP 連接中可以存在多條流。換句話說(shuō),也就是可以發(fā)送多個(gè)請(qǐng)求,對(duì)端可以通過(guò)幀中的標(biāo)識(shí)知道屬于哪個(gè)請(qǐng)求。通過(guò)這個(gè)技術(shù),可以避免 HTTP 舊版本中的隊(duì)頭阻塞問(wèn)題,極大的提高傳輸性能。

Header 壓縮
在 HTTP 1.X 中,我們使用文本的形式傳輸 header,在 header 攜帶 cookie 的情況下,可能每次都需要重復(fù)傳輸幾百到幾千的字節(jié)。
在 HTTP 2.0 中,使用了 HPACK 壓縮格式對(duì)傳輸?shù)?header 進(jìn)行編碼,減少了 header 的大小。并在兩端維護(hù)了索引表,用于記錄出現(xiàn)過(guò)的 header ,后面在傳輸過(guò)程中就可以傳輸已經(jīng)記錄過(guò)的 header 的鍵名,對(duì)端收到數(shù)據(jù)后就可以通過(guò)鍵名找到對(duì)應(yīng)的值。
服務(wù)端 Push
在 HTTP 2.0 中,服務(wù)端可以在客戶端某個(gè)請(qǐng)求后,主動(dòng)推送其他資源。
可以想象以下情況,某些資源客戶端是一定會(huì)請(qǐng)求的,這時(shí)就可以采取服務(wù)端 push 的技術(shù),提前給客戶端推送必要的資源,這樣就可以相對(duì)減少一點(diǎn)延遲時(shí)間。當(dāng)然在瀏覽器兼容的情況下你也可以使用 prefetch 。
QUIC
這是一個(gè)谷歌出品的基于 UDP 實(shí)現(xiàn)的同為傳輸層的協(xié)議,目標(biāo)很遠(yuǎn)大,希望替代 TCP 協(xié)議。
- 該協(xié)議支持多路復(fù)用,雖然 HTTP 2.0 也支持多路復(fù)用,但是下層仍是 TCP,因?yàn)?TCP 的重傳機(jī)制,只要一個(gè)包丟失就得判斷丟失包并且重傳,導(dǎo)致發(fā)生隊(duì)頭阻塞的問(wèn)題,但是 UDP 沒(méi)有這個(gè)機(jī)制
- 實(shí)現(xiàn)了自己的加密協(xié)議,通過(guò)類似 TCP 的 TFO 機(jī)制可以實(shí)現(xiàn) 0-RTT,當(dāng)然 TLS 1.3 已經(jīng)實(shí)現(xiàn)了 0-RTT 了
- 支持重傳和糾錯(cuò)機(jī)制(向前恢復(fù)),在只丟失一個(gè)包的情況下不需要重傳,使用糾錯(cuò)機(jī)制恢復(fù)丟失的包
- 糾錯(cuò)機(jī)制:通過(guò)異或的方式,算出發(fā)出去的數(shù)據(jù)的異或值并單獨(dú)發(fā)出一個(gè)包,服務(wù)端在發(fā)現(xiàn)有一個(gè)包丟失的情況下,通過(guò)其他數(shù)據(jù)包和異或值包算出丟失包
- 在丟失兩個(gè)包或以上的情況就使用重傳機(jī)制,因?yàn)樗悴怀鰜?lái)了
DNS
DNS 的作用就是通過(guò)域名查詢到具體的 IP。
因?yàn)?IP 存在數(shù)字和英文的組合(IPv6),很不利于人類記憶,所以就出現(xiàn)了域名。你可以把域名看成是某個(gè) IP 的別名,DNS 就是去查詢這個(gè)別名的真正名稱是什么。
在 TCP 握手之前就已經(jīng)進(jìn)行了 DNS 查詢,這個(gè)查詢是操作系統(tǒng)自己做的。當(dāng)你在瀏覽器中想訪問(wèn) www.google.com 時(shí),會(huì)進(jìn)行一下操作:
- 操作系統(tǒng)會(huì)首先在本地緩存中查詢
- 沒(méi)有的話會(huì)去系統(tǒng)配置的 DNS 服務(wù)器中查詢
- 如果這時(shí)候還沒(méi)得話,會(huì)直接去 DNS 根服務(wù)器查詢,這一步查詢會(huì)找出負(fù)責(zé)
com這個(gè)一級(jí)域名的服務(wù)器 - 然后去該服務(wù)器查詢
google這個(gè)二級(jí)域名 - 接下來(lái)三級(jí)域名的查詢其實(shí)是我們配置的,你可以給
www這個(gè)域名配置一個(gè) IP,然后還可以給別的三級(jí)域名配置一個(gè) IP
以上介紹的是 DNS 迭代查詢,還有種是遞歸查詢,區(qū)別就是前者是由客戶端去做請(qǐng)求,后者是由系統(tǒng)配置的 DNS 服務(wù)器做請(qǐng)求,得到結(jié)果后將數(shù)據(jù)返回給客戶端。
PS:DNS 是基于 UDP 做的查詢。
從輸入 URL 到頁(yè)面加載完成的過(guò)程
這是一個(gè)很經(jīng)典的面試題,在這題中可以將本文講得內(nèi)容都串聯(lián)起來(lái)。
- 首先做 DNS 查詢,如果這一步做了智能 DNS 解析的話,會(huì)提供訪問(wèn)速度最快的 IP 地址回來(lái)
- 接下來(lái)是 TCP 握手,應(yīng)用層會(huì)下發(fā)數(shù)據(jù)給傳輸層,這里 TCP 協(xié)議會(huì)指明兩端的端口號(hào),然后下發(fā)給網(wǎng)絡(luò)層。網(wǎng)絡(luò)層中的 IP 協(xié)議會(huì)確定 IP 地址,并且指示了數(shù)據(jù)傳輸中如何跳轉(zhuǎn)路由器。然后包會(huì)再被封裝到數(shù)據(jù)鏈路層的數(shù)據(jù)幀結(jié)構(gòu)中,最后就是物理層面的傳輸了
- TCP 握手結(jié)束后會(huì)進(jìn)行 TLS 握手,然后就開始正式的傳輸數(shù)據(jù)
- 數(shù)據(jù)在進(jìn)入服務(wù)端之前,可能還會(huì)先經(jīng)過(guò)負(fù)責(zé)負(fù)載均衡的服務(wù)器,它的作用就是將請(qǐng)求合理的分發(fā)到多臺(tái)服務(wù)器上,這時(shí)假設(shè)服務(wù)端會(huì)響應(yīng)一個(gè) HTML 文件
- 首先瀏覽器會(huì)判斷狀態(tài)碼是什么,如果是 200 那就繼續(xù)解析,如果 400 或 500 的話就會(huì)報(bào)錯(cuò),如果 300 的話會(huì)進(jìn)行重定向,這里會(huì)有個(gè)重定向計(jì)數(shù)器,避免過(guò)多次的重定向,超過(guò)次數(shù)也會(huì)報(bào)錯(cuò)
- 瀏覽器開始解析文件,如果是 gzip 格式的話會(huì)先解壓一下,然后通過(guò)文件的編碼格式知道該如何去解碼文件
- 文件解碼成功后會(huì)正式開始渲染流程,先會(huì)根據(jù) HTML 構(gòu)建 DOM 樹,有 CSS 的話會(huì)去構(gòu)建 CSSOM 樹。如果遇到
script標(biāo)簽的話,會(huì)判斷是否存在async或者defer,前者會(huì)并行進(jìn)行下載并執(zhí)行 JS,后者會(huì)先下載文件,然后等待 HTML 解析完成后順序執(zhí)行,如果以上都沒(méi)有,就會(huì)阻塞住渲染流程直到 JS 執(zhí)行完畢。遇到文件下載的會(huì)去下載文件,這里如果使用 HTTP 2.0 協(xié)議的話會(huì)極大的提高多圖的下載效率。 - 初始的 HTML 被完全加載和解析后會(huì)觸發(fā)
DOMContentLoaded事件 - CSSOM 樹和 DOM 樹構(gòu)建完成后會(huì)開始生成 Render 樹,這一步就是確定頁(yè)面元素的布局、樣式等等諸多方面的東西
- 在生成 Render 樹的過(guò)程中,瀏覽器就開始調(diào)用 GPU 繪制,合成圖層,將內(nèi)容顯示在屏幕上了
