帶著問題學習(通常是面試考點)
- HTTP是如何使用TCP連接的
- TCP連接的時延、瓶頸及存在的障礙
- HTTP的優化,包括并行連接、keep-alive(持久連接)和管道化連接
- 管理連接時應該和不應該做的事
TCP連接
TCP的數據通過IP分組(或IP數據報)的小數據塊來發送。
協議順序:HTTP
(>> SSL
or TLS
) >> TCP
>> IP
數據傳輸過程:
- HTTP以
流
的形式將數據報文通過TCP連接
進行按序傳輸 - TCP收到數據流后,將數據流砍成小數據塊(稱作
段
),將段
封裝在IP分組
中,通過因特網傳輸。
IP分組包括:一個IP分組首部(通常20字節) + 一個TCP段首部(通常20字節) + 一個TCP數據塊(0或多個字節)
- IP分組首部:源和目的IP地址、長度、其他標記
- TCP段首部:TCP端口號、TCP控制標記、其他數據排序和完整性檢查的數值
TCP連接識別:<源IP地址、源端口號、目的IP地址、目的端口號>
TCP通過端口號保持多個連接持續運行,這4個值唯一的定義了一條連接,兩條不同TCP連接不能擁有4個完全相同的地址組件值。
套接字API允許用戶創建TCP的端點數據結構,將這些端點與遠程服務器的TCP端點進行連接,并對數據流進行讀寫。
對TCP接口進行編程所需的常見套接字接口函數偽代碼:
TCP客戶端與服務器是如何通過TCP套接字接口進行通信的:
HTTP事務的時延
回顧一下:HTTP事務 = 請求命令(HTTP方法)+ 響應結果
串行HTTP事務的時間線 = DNS查詢 + TCP連接&接受應答 + 發送請求 + 處理 + 響應 + 關閉連接
HTTP事務的時延原因:
- DNS解析URI中的主機名為IP地址
- TCP請求連接需要等待接受應答
- 發送報文及處理請求報文
- 服務器回送HTTP響應
TCP網絡時延的大小取決于:硬件速度
、網絡
、服務器負載
、報文尺寸
、客戶端與服務器之間的距離
。TCP協議的技術復雜性
也會對時延產生影響。
常見的TCP時延:
- TCP建立握手
- TCP慢啟動擁塞控制
- 數據聚集的Nagle算法
- 用于捎帶確認的TCP延遲確認算法
- TIME_WAIT時延和端口耗盡
TCP連接的握手時延
在發送數據前,TCP要傳送兩個分組來建立連接。
TCP三次握手:a SYN請求建立連接
>> b SYN+ACK接受連接
>> c ACK確認成功建立連接
延遲確認
如果是小的HTTP事務,那么TCP連接就占了大量時間,由于確認報文很小,所以TCP允許在發往相同方向的輸出數據分組中對其進行“捎帶”(有點像夾帶私貨
…),將返回的確認信息與輸出的數據分組結合,剩下一次請求報文的時間,此行為稱為“延遲確認算法
”。
延遲確認算法會在特定窗口時間(200~300ms)內將輸出確認存放在緩存區中,等待能夠捎帶的輸出數據分組,沒有就單獨發送確認。
TCP慢啟動
TCP數據傳輸的性能還取決于TCP連接的使用期
。
TCP連接會隨著時間自我“調諧”,起初會限制連接的最大速度,如果數據傳輸成功,會隨著時間推移提高傳輸的速度,這種調諧稱為“TCP慢啟動
”,用于防止因特網突然過載和擁塞。
TCP慢啟動限制了一個TCP端點在任意時間可以傳輸的分組數。每成功接收一個分組,發送端就有了發送2個分組的權限。有大量數據要發送也無法一次發送出去。必須發一個等待確認,然后發2個,都確認再發4個,以此類推。這種方式稱為“打開擁塞窗口
“,所以新連接比已交換過數據的連接慢一些。
Nagle算法和TCP NODELAY
Nagle算法鼓勵發送全尺寸的段,將已確認的分組數據緩存起來,等待傳輸中的分組被確認,當緩存積累了足夠發送一個全尺寸的段再將緩存數據發出去。
但小的報文可能無法填滿一個分組會因為等待永不會到來的額外數據而產生時延。且確認分組自身因“延遲確認算法”而延遲100~200毫秒。
所以HTTP應用程序通常會設置參數TCP NODELAY
來禁用Nagle算法。
TIME_WAIT累計和端口耗盡
當TCP端點關閉連接時,會在內存中維護一個小的控制快,用來記錄最近關閉的IP地址和端口號,保證在一段時間內不會創建相同地址和端口的新連接。這段時間通常是最大分段使用期的兩倍(2MSL
,通常2分鐘)。
TCP連接的4個值中:源IP、源端口、目標IP、目標端口,只有源端口是可以隨意改變的。
由于源端口的數量有限(比如6萬),2MSL(比如120秒)內連接不能重用,那么連接率上限就為6萬?120秒=500次/秒。不超過500就不會遇到端口耗盡的問題。
要修正這個問題,可以增加客戶端會負載生成機器的數量,或者確保循環使用幾個虛擬IP來增加更多的連接組合。
HTTP連接的處理
串型事務處理時延
連接時延和慢啟動時延疊加
HTTP優化1:并行連接
HTTP允許客戶端打開多條連接,并行地執行多個HTTP事務。
??注意:如果客戶端網絡帶寬不足,那么大部分時間可能都在傳送數據,多個事務的連接很快會耗盡所有可用的帶寬。同時請求也會造成服務器性能嚴重下降,為了均衡,瀏覽器會限制并行連接的總數為一個較小的值(通常是4個)。
HTTP優化2:持久連接
初始化了對某服務器HTTP請求的應用程序很可能在不久后對該服務器發起更多的請求,這種性質被稱為“站點局部性
”。
因此HTTP/1.1允許HTTP設備在事務處理結束后將TCP連接保持打開狀態,以便后續請求重用連接。在事務處理結束后仍保持打開狀態的TCP連接被稱為“持久連接
”。
持久連接降低了時延和連接建立的開銷(已調諧),但是容易產生大量空閑連接,需要特別注意。
持久連接類型1:HTTP/1.0的keep-alive連接
通過Connection: Keep-Alive
保持連接
支持max
設置希望保持連接的上限數,timeout
設置希望保持連接的時間,如:Keep-Alive: max=5, timeout=120
,但對方不一定會同意。
如果服務器支持keep-alive參數,需要回送一個Connection: Keep-Alive
,否則不回送。
很多老的代理都是“盲中繼
”,只轉發不對Connection首部作處理。當通過作為盲中繼使用的啞代理
連接時,會出現圖示情形:
服務器返回響應后,啞代理返回數據后由于等待連接關閉,會忽略連接上的新請求,導致了下一條請求被掛起。這種錯誤的通信會使瀏覽器一直處于掛起狀態,直到連接超時關閉。
為避免出現上述通信問題,現代的代理都絕不能轉發Connection
首部和出現在該首部值中的首部。
一個變通做法是,發送非標準的Proxy-Connection
擴展首部,啞代理轉發后服務器會忽略此首部,聰明的代理會轉換為Connection
首部發送,以收到預期效果。但是如果代理中有啞代理又有聰明的代理就會再次出現問題…😂
持久連接類型2:HTTP/1.1的persistent連接
HTTP/1.1默認所有連接都是持久連接,如果關閉連接就顯式添加一個Connection: Close
。
HTTP優化3:管道化連接
HTTP/1.1允許在持久連接上可選地使用請求管道,在響應到達之前,將多條請求放入隊列。
管道化連接的限制:
- 若HTTP客戶端無法確認連接是持久的,就不應該使用管道
- 必須按照與請求相同的順序回送HTTP響應
- HTTP客戶端必須做好連接會在任意時刻關閉的準備,還要準備好重發未完成的請求
- HTTP客戶端不應該用管道化的方式發送可能會產生副作用的請求(比如POST),因為出錯時無法安全的重試POST這樣的非冪等請求
冪等(Idempotent): 一個操作如果執行一次和多次對系統狀態產生的改變完全相同(或者說效果等同于只執行一次),那么這個操作就是冪等的。
非冪等(Non-idempotent): 一個操作如果執行多次與執行一次對系統狀態產生的改變不同(通常會產生額外的副作用),那么這個操作就是非冪等的。
要發送一條非冪等的請求,需要等待來自前一條請求的響應狀態。
一定不能自動重試非冪等的方法。比如,大多數瀏覽器會在重載一個緩存的POST響應時提供一個對話框,詢問用戶是否再次發起事務處理。
正常關閉連接
完全關閉
:TCP輸入和輸出都關閉
半關閉
:單獨關閉TCP輸入或輸出
關閉連接的輸出信道總是安全的,關閉連接的輸入信道比較危險。
如果另一端向你已關閉的輸入信道發送數據,操作系統就會向另一端回送一條TCP“連接被對端重置”的報文,將刪除對端還未讀取的所有緩存數據。
正常關閉的應用程序應該首先關閉輸出信道,兩端都告訴對方不再發送任何數據之后,再完全關閉連接,就不會有重置的危險。