@[TOC]
原文連結:blog.csdn.net/jieqiang3/a…
這篇主要梳理一下web相關的原理知識。由於個人水平有限,文章中可能會出現錯誤,如果你覺得哪一部分有錯誤,或者發現了錯別字等內容,歡迎在評論區告訴我。
TCP
TCP(傳輸控制協議),負責在不可靠的傳輸通道之上提供可靠的抽象層。 TCP的存在價值主要專注於可靠兩個字,也就是它能夠確保傳送的所有位元組流都能夠完整的被接收到,而且到達客戶端的順序也是一樣。但凡事都有兩面性,如果精確性得到了保證的話,就會犧牲一定的速度和效率。 先來看TCP的連線和斷開連線,也就是我們都熟悉的三次握手四次揮手:
三次握手
- 首先客戶端會生成一個隨機序列號x,併傳送一個SYN分組,其中可能還包括其他TCP標誌和選項。
- 服務端收到分組以後給x+1,並生成一個隨機序列號y,追加自己的標誌和選項,然後返回給客戶端。
- 客戶端收到服務端訊息以後給x和y都+1,然後傳送一個ACK分組。
經過這個三次握手以後客戶端和服務端就可以互相傳輸資料了。這裡客戶端可以在傳送ACK分組之後立即傳送資料,服務端必須在接收到第三部的ACK分組以後才能傳輸資料。
那麼接下來又有一個問題了,為什麼是三次握手呢?為什麼不是兩次,或者四次五次握手呢? TCP是一個雙向通訊協議,為了資料傳輸的可靠性,通訊雙方會維護一個序列號,以標識發出去的資料包裡有哪些資料是被對方收到的。如果只有兩次握手的話,服務端就無法確認他的序列號是否得到了確認,只有客戶端的序列號得到了確認。而四次,五次握手的話則會造成資源的浪費。可以肯定的是,三次握手是在確保可靠性的基礎上,效能上的最優解。
四次揮手
重點:TCP關閉連線的時候每個方向都必須要單獨進行關閉。當一方傳送一個FIN訊息的時候,就意味申請終止這一個方向上的資料連線,當收到一個FIN就意味著這一方向上沒有資料訊息再傳過來了,也就不會收到資料了。 TCP客戶端服務端雙方都可以發起close,這裡我們以客戶端發起close為圖例,來看下過程:- 首先客戶端傳送一個FIN,這就意味著客戶端不會再傳送任何資料訊息到服務端了,然後進入FIN-WAIT1狀態。
- 服務端收到FIN訊息以後呢,傳送i 個ACK給客戶端,確認序號為收到的的序號u+1,然後進入CLOSE_WAIT狀態。
- 服務端傳送一個FIN,這就意味著服務端不再傳送任何資料訊息到客戶端,然後進入LAST_ACK狀態。
- 客戶端收到FIN訊息以後,進入TIME_WAIT狀態,然後傳送一個ACK給服務端,確認序號為收到的序號w+1,然後進入TIME_WAIT狀態。服務端收到ACK報文段以後就關閉連線了。。。客戶端等待2MSL以後沒有收到任何回覆,就說明服務端已經正常關閉,然後自己關閉。
那麼為什麼要四次揮手呢??? TCP的一端發出FIN報文段之後,僅僅表示這一端沒有資料要傳送給另一端了,但是他仍然能接受來自另一端的資料。所以如上面所示客戶端先傳送訊息給服務端說我不發資料訊息給你了,然後服務端發給客戶端確認,然後服務端再發訊息給客戶端跟客戶端說我也不發資料訊息給你了,然後客戶端回覆確認,然後服務端關閉,之後客戶端等待了以後也關閉。所以這就是需要四次揮手才能來關閉連線的原因。
在實際的網路傳輸過程中,很容易出現擁塞的現象,TCP對於擁塞控制主要有以下四個演算法:
- 慢啟動
- 擁塞預防
- 擁塞發生時,快速重傳
- 快速恢復
慢啟動
要理解這四個演算法的話,首先先要理解擁塞視窗(cwnd)的概念:傳送端對從客戶端接受確認(ACK)之前可以傳送資料的限制。 TCP的擁塞控制主要依賴於這個擁塞視窗來控制,首先三次握手的時候,客戶端和服務端會通過ACK分組告知對方自己的接收視窗(rwnd)的大小,然後三次握手ACK訊息成功傳遞以後雙方就可以互相發訊息了,但是如果在一開始連線剛建立就向網路中傳送大量的資料包的話,就很容易導致網路中路由器的快取空間耗盡,發聲擁塞,所以即客戶端與伺服器之間最大可以傳輸(未經 ACK 確認的)資料量取 rwnd 和 cwnd 變數中的最小值。而這個cwnd的值會根據演算法慢慢指數級增加,同時也設定了一個ssthresh門限值,如果cwnd達到這個值以後會讓cwnd增長變得平滑,也就是慢啟動。
擁塞預防
擁塞預防的原理就是加入ssthresh門限值來限制 cwnd的增長,當cwnd超過該值以後,慢啟動過程結束,進入擁塞避免階段。擁塞避免的主要思想是加法增大,也就是cwnd的值不再指數級往上升,開始加法增加。此時當視窗中所有的報文段都被確認時,cwnd的大小加1,cwnd的值就隨著RTT開始線性增加,這樣就可以避免增長過快導致網路擁塞,慢慢的增加調整到網路的最佳值。
快速重傳
TCP的每一個報文段都有一個定時器,叫做重傳定時器,如果重傳定時器超時並且沒有得到資料確認的話,TCP就會對該報文段進行重傳處理,這裡TCP也就把網路定義為擁塞的狀態,會進行如下操作:
- 把ssthresh降低為cwnd值的一半
- 把cwnd重新設定為1
- 重新進入慢啟動過程
快速恢復
快速恢復是當收到3個重複ACK的時候才會觸發,進入快速恢復階段:
- 當收到3個重複ACK時,把ssthresh設定為cwnd的一半,把cwnd設定為ssthresh的值加3,然後重傳丟失的報文段,加3的原因是因為收到3個重複的ACK,表明有3個“老”的資料包離開了網路。
- 再收到重複的ACK時,擁塞視窗增加1。
- 當收到新的資料包的ACK時,把cwnd設定為第一步中的ssthresh的值。原因是因為該ACK確認了新的資料,說明從重複ACK時的資料都已收到,該恢復過程已經結束,可以回到恢復之前的狀態了,也即再次進入擁塞避免狀態。
UDP
上面介紹TCP的時候我們已經看出來,TCP是基於連線的協議,建立了可靠的連線以後雙方才能互相傳遞資料。而UDP正好跟TCP相反,UDP是面向非連線的協議,不與對方建立連線,直接把資料包發過去就好了的一種協議,UDP是盡最大努力交付的。 所以UDP的傳輸速度快,因為不需要什麼三次握手這種操作,而且UDP 沒有擁塞控制。UDP只適合用於一些只需要傳遞少量資料,對可靠性要求不是很高的應用場景,比如一些多媒體通訊的要求。
TLS
首先這裡我們先來弄清楚一個概念,也就是TLS和SSL的區別,這也是我在看《web效能權威指南》時候一臉懵逼的。 TLS:傳輸層安全協議,用於兩個應用程式之間提供保密性和資料完整性。該協議由兩層組成:TLS記錄協議和TLS握手協議。 SSL:安全套接字層,位於可靠的面向連線的網路層協議和應用層協議之間的一種協議層。SSL通過互相認證、使用數字簽名確保完整性、使用加密確保私密性,以確保客戶端和服務端之間的安全通訊。由兩層組成:SSL記錄協議和SSL握手協議。 兩者有什麼聯絡呢? TLS是以SSL 3.0為基礎於1999年作為SSL的新版本推出的,也就是說SSL是TLS的前世,提供更強大的支援。 現在應該弄清楚兩者之間的區別以後,我們就可以來看TLS協議的原理了。
TLS握手
先來看流程圖:
- 首先伺服器生成自己的公鑰和私鑰,將公鑰傳送給CA機構
- CA機構生成自己的公鑰和私鑰,並用私鑰對伺服器傳送給自己的公鑰簽名生CA證照
- CA機構把CA證照傳送給服務端
- 瀏覽器內建了CA根證照的CA公鑰
- 以上都為準備階段,接下來開始正式連線,客戶端向服務端發起三次握手
- 服務端將CA證照發給客戶端,裡面包含了服務端生成的公鑰,有效期等
- 客戶端根據自己的CA公鑰驗證CA證照的有效性
- 客戶端生成隨機對稱金鑰
- 將這個隨機對稱金鑰用服務端給的公鑰加密發給服務端
- 這裡如果是銀行U盾這種的話,伺服器會有一個驗證客戶端的合法性的步驟,其他基本用不到
- 然後雙方就可以通過這個隨機金鑰來進行通訊了
注意這裡的隨機對稱金鑰是對稱加密,所以運算速度非常快,而伺服器公鑰只用於加密"對話金鑰"本身,這樣就減少了加密運算的消耗時間。另外為了優化效能,為了彌補完整的TLS握手所帶來的額外的延遲和計算量,TLS提供了會話恢復功能,即在多個連線間共享這個協商後的安全金鑰,也就是上面第9部中生成的用公鑰加密的隨機對稱金鑰: 在內部伺服器會給每個客戶端儲存一個會話ID和協商後的會話引數就是金鑰,對應客戶端也會有儲存,這樣下次連線的時候就可以告訴對方自己有儲存上次連線的資料,這樣就可以迅速連線,節省一次往返,相當於加了一層緩衝層。
TLS的握手過程基本確保了傳輸的資訊加密過;有身份認證機制,A想發訊息給B,A能確保傳送到的是B而不是中間被人攔截接受;有校驗機制,能保證資訊不會被篡改,這三個機制就是TLS出現的意義所在。
HTTP 1.x
HTTP 1.1出現的一個重要目標就是為了提升HTTP 1.0的效能。 接下來列舉幾個比較重要的效能優化點:
- 持久化連線以支援連線複用;
- 分塊傳輸編碼以支援流失響應;
- 引入HTTP 管道概念做到可以並行處理;
- 引入更多的快取控制策略等。
持久化連線
我們知道一個TCP連線建立的時候要經過三次握手才能進行資料傳遞,而且還可能存在慢啟動延遲,所以如果每次去調一個請求如果都需要建立一個TCP連線的話那麼顯然是很不友好的。所以HTTP 1.1.的時候加入了持久化連線,也就是支援場連線和請求的流水線處理,在一個TCP連線上可以傳送多個HTTP請求和響應,HTTP1.1中是預設開啟keep-alive的。這個持久化連線不會被一直保留,在空閒一段時間後被關閉,節約資源。 但是如果僅僅通過一個持久化連線,然後不斷在這個連線上傳送一個又一個的請求的話,經過實踐效率並不理想,所以HTTP 1.1又加入了向同一個IP地址重複建立多個TCP連線的機制。大家可以用wireshark抓資料包試試,你會發現在幾秒鐘裡面會建立好幾個目的埠為80的TCP連線。現在大多數現代瀏覽器,包括桌面和移動瀏覽器,都支援每個主機開啟 6 個連線。
HTTP管道化
HTTP管道化在我的理解其實就是,客戶端傳送多個http以FIFO佇列的形式請求到服務端以後,服務端返回這些請求的響應按照同樣的FIFO佇列的形式輸出到客戶端。這裡,服務端會嚴格按照FIFO佇列輸出,如果服務端併發處理且優先順序較高的請求處理時間比較長的話,那麼優先順序較低且先處理完的請求會被放入伺服器的緩衝區,等待優先順序較高的請求先回復以後再回復較低的,以FIFO佇列形式。 在出現管道化之前,http請求都是順序請求的,也就是說下一個請求只有在當前請求的響應被完全接受以後才能傳送。所以這就有一個問題,http兩個請求之間會有很大的延遲。 一般來說,只有冪等的請求才會進行管道化操作,冪等的請求其實就是多次操作都不會改變結果的請求,比如GET和HEAD。所以一般PUT和POST都不會被管道化,
HTTP 2.0
2012年Google提出了SPDY方案,優化了HTTP 1.X的請求延遲、安全性等問題,而HTTP 2.0可以說是在SPDY的基礎上設計出來的升級版plus。 HTTP 2.0最大的特點就是在不該動HTTP 語義,HTTP方法、狀態碼、URI及首部欄位等等核心的東西的基礎上,突破了HTTP 1.X的效能限制,最大的改變就是HTTP 2.0新加了一個二進位制分幀層。
從上面的圖中我們可以看到,二進位制分幀層的位置是在應用層和傳輸層之間,把HTTP 1.1中的首部資訊封裝到Headers幀中,把request body封裝到了DATA 幀中。HTTP 2.0會把所有的傳輸資訊分割為更小的訊息和幀,並對他們採用二進位制格式的編碼形式。 先來看幾個在HTTP 2.0中的新概念:- 流 已建立的連線上的雙向位元組流
- 訊息 與邏輯訊息對應的完整的一系列資料幀
- 幀 HTTP 2.0通訊的最小單位
HTTP 2.0簡而言之,就是在一個TCP連線上,建立任意數量的雙向位元組流,在每條位元組流上以訊息的形式來傳遞訊息,而訊息的是由一個或者多個幀組成的。 這裡這個幀是可以亂序傳遞的,每個幀都有一個幀首部,幀首部上會有一個流識別符號,最後會根據這個流識別符號來重新組裝順序。 另外為了進一步增加效率,HTTP 2.0有一個首部壓縮的機制,HTTP 2.0在客戶端和服務端都會用一個“首部表”來跟蹤和儲存之前傳送的鍵值對,對於相同的資料,就不會再通過每次請求和響應來傳送了。 如果首部發生變化了,那麼只需要傳送變化了的資料放到Headers幀裡,新增或者修改的首部幀就會跟蹤到“首部表”,然後不斷的更新變化。
WebSocket
WebSocket 是HTML5中一種全新的web通訊技術,是一種最通用最靈活的一個傳輸機制,真正實現了瀏覽器與伺服器的全雙工實時通訊。 WebSocket 資源 URL 採用了自定義模式:ws 表示純文字通訊(如 ws://example. com/socket),wss 表示使用加密通道通訊(TCP+TLS)。WebSocket協議可以算是HTTP協議的一種補充,一種增強,以HTTP協議為基礎,跟HTTP協議最大的區別在我理解就是:HTTP協議中只有你傳送了一個request,你才會得到一個response,是輪詢機制的,也就是說response是被動的,不能主動發起。而WebSocket中伺服器則隨時可以向客戶端傳遞訊息,全雙工實時通訊。
這是Android中引用了org.java-websocket:Java-WebSocket:1.3.9後使用WebSocket一些api回撥,使用起來簡單明瞭很方便。
try {
webSocketClient = new WebSocketClient(new URI(webSocketUrl)) {
@Override
public void onOpen(ServerHandshake handshakedata) {
}
@Override
public void onMessage(final String message) {
}
@Override
public void onClose(int code, String reason, boolean remote) {
}
@Override
public void onError(Exception ex) {
}
@Override
public void onWebsocketPong(WebSocket conn, Framedata f) {
super.onWebsocketPong(conn, f);
}
};
複製程式碼
協議詳解
上面講區別的時候講到WebSocket是基於HTTP協議的,其實說的直白點,WebSocket是借用了HTTP的協議來完成了一部分握手,起到向下相容現有瀏覽器的作用。 WebSocket協議有兩部分組成:握手和資料傳輸。
握手
我們來看一個RFC6455文件中給出的一個客戶端握手訊息例項:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com
複製程式碼
這裡我們可以看到WebSocket使用HTTP來建立連線,但其中又定義了一系列心的header域:Upgrade: websocket Connection: Upgrade來告訴伺服器,我發起的是WebSocket協議,並通過Sec-WebSocket-Key、Sec-WebSocket-Protocol、Sec-WebSocket-Version三個值來校驗以及告訴伺服器Draft協議版本。 這之後伺服器會返回一個標準的HTTP的Response訊息來通知客戶端我已經接受請求切換為WebSocket協議了:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat
複製程式碼
資料傳輸
建立了 WebSocket 連線後,客戶端就可以隨時傳送或接收 UTF-8 或二進位制訊息。 WebSocket 提供的是一條雙向通訊的通道,也就是說,在同一個 TCP 連線上,可以雙向傳輸資料,不再需要Request訊息。WebSocket 通訊只涉及訊息,應用程式碼無需擔心緩衝、解析、重建接收到的資料。 另外,WebSocket的資料幀是有序的。
心跳機制
websocket為了保持客戶端、服務端的實時雙向通訊,需要確保客戶端、服務端之間的TCP通道保持連線沒有斷開,所以會有一個心跳包機制:
- 傳送方 -> 接受方:ping,對應opcode為ox9
- 接收方 -> 傳送方:pong,對應opcode為0xA
經典的效能優化最佳實踐
以下內容摘自《web效能權威指南》,我覺得很有參考價值:
無論什麼網路,也不管所用網路協議是什麼版本,所有應用都應該致力於消除或減 少不必要的網路延遲,將需要傳輸的資料壓縮至最少。這兩條標準是經典的效能優 化最佳實踐,是其他數十條效能準則的出發點。
- 減少DNS查詢 每一次主機名解析都需要一次網路往返,從而增加請求的延遲時間,同時還會阻 塞後續請求。
- 重用TCP連線 儘可能使用持久連線,以消除 TCP 握手和慢啟動延遲
- 減少HTTP重定向 HTTP 重定向極費時間,特別是不同域名之間的重定向,更加費時;這裡面既有 額外的 DNS 查詢、TCP 握手,還有其他延遲。最佳的重定向次數為零。
- 使用CDN(內容分發網路) 把資料放到離使用者地理位置更近的地方,可以顯著減少每次 TCP 連線的網路延 遲,增大吞吐量。這一條既適用於靜態內容,也適用於動態內容
- 去掉不必要的資源 任何請求都不如沒有請求快。
- 在客戶端快取資源 應該快取應用資源,從而避免每次請求都傳送相同的內容。
- 傳輸壓縮過的內容 傳輸前應該壓縮應用資源,把要傳輸的位元組減至最少:確保對每種要傳輸的資源 採用最好的壓縮手段。
- 消除不必要的請求開銷 減少請求的 HTTP 首部資料(比如 HTTP cookie),節省的時間相當於幾次往返 的延遲時間。
- 並行處理請求和響應 請求和響應的排隊都會導致延遲,無論是客戶端還是伺服器端。這一點經常被忽 視,但卻會無謂地導致很長延遲。
- 針對協議版本採取優化措施 HTTP 1.x 支援有限的並行機制,要求打包資源、跨域分散資源,等等。相對而 言,HTTP 2.0 只要建立一個連線就能實現最優效能,同時無需針對 HTTP 1.x 的 那些優化方法。
參考資料: