本文涉及的內容主要有:程序員
HTTP是如何使用TCP進行鏈接的算法
HTTP鏈接的限制,包括時延/瓶頸存在的障礙編程
如何優化HTTP鏈接和顯示.瀏覽器
因爲我的緣由,本文只到HTTP鏈接優化的並行鏈接部分.其餘部分得要往後再說了.本文不會對TCP/IP進行過多的闡述,最多隻能到大概能講兩個協議是如何交互的程度.因爲是抄書加上本人自做聰明的一些理解,請各位發現錯誤的時候,有時間能夠說一聲.緩存
TCP/IP協議是一種經常使用的分組交換網絡分層協議.客戶端經過創建與服務器的鏈接,從而交換報文.報文永遠不會丟失,受損或者失序1.性能優化
HTTP鏈接實際上就是創建一條TCP鏈接管道,然後經過TCP提供的可靠的比特傳輸管道 進行交互,一端填入的字節會以原有的順序正確地傳送到另外一端.而這種傳送,是經過分段來實現的.服務器
以傳送報文爲例,鏈接過程是這樣的:網絡
HTTP應用程序打開一條TCP鏈接,並以流的形式將報文數據按序傳輸.併發
TCP鏈接將數據分爲小數據塊,稱做"段"socket
段被封裝到IP分組中經過網絡進行傳輸.
每一個TCP段都由IP分組承載,在IP地址之間發送.其分組包括:
一個IP分組首部
一個TCP段首部
一個TCP數據塊
爲了保持TCP鏈接的正確運行,TCP用的是端口來實現.每一個端口對應一個應用程序,每一個IP地址對應一個計算機.要經過四個值:源IP地址/目標IP地址/源端口/目標端口來識別一個TCP鏈接.因此,每一個鏈接的四個值不能重合(這實際上會致使性能問題,後面會說起)
爲了操縱TCP鏈接,操做系統提供了一系列TCP編程接口.下面就是套接字API提供的主要接口了.其隱藏了全部細節.
套接字API調用 | 描述 |
---|---|
s = socket(<parameters>) |
創建一個新的套接字(未命名/未關聯) |
bind(s,<localIP:port>) |
爲套接字賦予端口和本地IP |
connect(s, remote IP:port) |
創建與遠程服務器和端口的鏈接 |
listen(s,...) |
標識一個本地套接字,使其能夠合法地接受鏈接 |
s2 = accept(s) |
等待他人創建到本地端口的鏈接 |
result = read(s, buffer, n) |
嘗試從套接字向緩衝區讀取n個字節 |
data = write(s, buffer, n) |
嘗試從緩衝區向套接字寫入n個字節 |
close(s) |
徹底關閉TCP鏈接 |
shutdown(s) |
關閉TCP鏈接的一端(輸入或者輸出) |
getsocketopt() |
讀取某個內部套接字配置選項的值 |
setsocketopt() |
修改某個內部套接字配置選項的值 |
套接字API隱藏了全部細節:全部底層網絡協議的握手細節/TCP數據流/IP分組之間的分段和重裝等等.
因爲HTTP協議位於TCP協議的上層,HTTP事務的性能大部分取決於TCP通道的性能.下面會列出幾個TCP的某些基本性能特色,用於輔助咱們理解HTTP的鏈接優化特性.若是對提高TCP的性能要考慮的細節不感興趣,能夠跳過...
HTTP事務在DNS查詢/創建鏈接/傳輸請求和響應報文這四個部分存在時延.與其相比,真正的HTTP事務處理時間很短.產生時延的主要緣由有如下幾點:
客戶端須要經過URI來肯定Web服務器的IP地址和端口號.而若是最近沒有訪問該站點,則經過DNS解析系統可能須要花數十秒的時間查詢IP地址.2
客戶端會發送一條TCP鏈接請求,並等待服務器應答.因此每一個新的TCP鏈接都會存在鏈接創建時延.雖然值很小,可是若是事務數以百計的話,就很可觀了.
創建鏈接之後,客戶端發送請求/服務器處理請求和響應報文等,都須要時間
這些時延的影響因素不少:硬件速度,網絡,服務器的負載等等,甚至TCP協議的複雜性也會產生影響.
常見的TCP相關時延有:
TCP鏈接創建握手
TCP慢啓動擁塞控制
數據彙集的Nagle算法
用於捎帶確認的TCP延遲確認算法
TIME_WAIT的時延和端口耗盡
下面這部分的內容針對的是高性能HTTP軟件編寫,但一我自己水平不夠,二這一等級的性能優化並不老是須要.若是願意,請繼續往下讀.
創建一條新的TCP鏈接時,TCP軟件會交換一系列IP分組,溝通與鏈接有關的參數.若是鏈接只用於傳輸少許數據,則會嚴重下降HTTP的性能.其創建握手的步驟是:
向服務器發送小的TCP分組,其中設置一個特殊的SNY標記,說明這是鏈接請求.
服務器接受鏈接,計算鏈接參數,並返回一個SNY和ACK標記都被置位的TCP分組,說明接受鏈接請求.
客戶端發送一條確認信息,通知其鏈接創建成功.該部分容許客戶端發送數據.
這些分組由TCP/IP軟件管理,只是會致使建立TCP鏈接時,存在時延.因爲一個事務不會交換太多數據,而屢次握手會.因此小的HTTP事務可能在建立TCP鏈接的時候花費大量時間.爲此,後面會討論如何重用現存鏈接來減小該類型的時延.
TCP實現了本身的確認機制保證數據成功傳輸.這個機制的工做過程是這樣的:
賦予每一個TCP段一個序列號和完整性校驗和,發送出去.
服務器收到之後,發送一個小的確認分組
客戶端如何沒有在指定時間內收到確認分組,則認爲分組已經損壞,並從新發送數據.
因爲確認報文很小,能夠將返回的確認信息與數據分組結合.而延遲確認就是:HTTP服務器會在一個特定時間內將輸出確認存儲到緩衝區中,尋找能夠捎帶確認的輸出數據分組.如;若是沒有,則單獨發送.
這與HTTP具備雙峯特徵的請求--應答行爲結合,致使捎帶確認的可能性下降.這種回傳分組不老是那麼多,因此該算法會引入至關大的時延.根據平臺的差別,能夠調整或者禁用該算法.
P.S.對TCP配置進行的任意修改,都要絕對保證應用程序不會引起這些算法所要避免的問題.
TCP數據傳輸的性能還取決於鏈接的使用期(age ).TCP鏈接會隨時間進行自我"調諧",性能會從最初的限制到提升傳輸速度.這被稱爲TCP慢啓動(slow start ),用於防止過載和擁塞.
但這種行爲限制了一個TCP端點在任意時刻能夠傳輸的分組數量.發送的分組數量限制是隨着時間和一次次成功接受分組而減小的(就是說,分組數量愈來愈多).這叫作打開擁塞窗口.
因爲這種特性的存在,新鏈接的傳輸速度老是比較慢的,因而引入了重用先存鏈接的工具,這就是稍後介紹的持久鏈接.
TCP有一個數據流端口,能夠放入任意大小的數據.可是若是發送大量包含少許數據的分組,則會致使性能嚴重降低.3
而Negle算法則試圖在發送一個分組以前,綁定大量TCP數據,以提升網絡的效率.該算法鼓勵發送全尺寸的段(LAN上是1500字節,www上是幾百字節).只有其餘分組都被確認之後,該算法纔會容許發送非全尺寸的分組 ;若是其餘分組仍在傳輸中,那一部分數據就會緩存起來;只有積累的尺寸足夠,或者有掛起分組被確認的時候,其纔會將緩存的數據發送出去.
該算法會引起多種性能問題:
小的HTTP報文可能沒法塞滿全尺寸分組,會一直等待其餘數據而致使時延.
該算法會阻止數據發送,直到有確認分組到達;但確認分組自身因爲延遲確認算法會致使時延.
因此,HTTP程序員會在棧中設置參數TCP_NODELAY
禁用該算法.但這須要保證寫入大塊數據!
這是嚴重的性能問題,會影響性能基準.雖然較少出現,可是遇到性能基準問題時,一般是這個問題.且結果特別糟糕.
當某個TCP鏈接關閉時,內存中會維持一個小控制塊,記錄最近的TCP鏈接的端口號和IP,會維持一段時間(2分鐘左右),一般是所估計的最大分段使用期的兩倍(成爲2MSL).
可是,如今高速路由的出現致使重複分組幾乎不可能在關閉鏈接的幾分鐘以後,出如今服務器上.因此,有的操做系統會改小這個值.但要當心:分組確實會被賦值,若是來自以前的複製分組插入鏈接值相同的新TCP流,則會破壞TCP數據.
在性能基準環境下,這個2MSL的鏈接關閉延遲會成爲大問題.因爲:
測試的機子只有幾臺,IP數量受限
服務器在默認端口上監聽,端口號也受限了.
假設只有一臺測試用機器,這樣四個值(源IP地址/目標IP地址/源端口/目標端口)就只有一個值是可變的了.因爲可用的端口數量有限,鏈接率(每秒鏈接的次數)就會固定在一個較低的次數.要修正該問題,有如下方法:
是增長客戶端負載生成及其的數量
循環使用虛擬IP地址
可是,若是在大量鏈接處於打開狀態的狀況,或者處於等待狀態的鏈接分配大量控制塊的狀況,會致使操做系統的速度大降.
正常狀況下,HTTP鏈接過程當中存在一串HTTP中間實體(代理/高速緩存等等),而中間實體之間會交流一些信息,這些信息就存放在Connection首部中.這在等下會說起;除此以外,HTTP的事務處理一般是串行進行,因此存在性能延時的疊加.這也是咱們要考慮的問題.
兩個相鄰的HTTP應用層程序會在共享的一條鏈接中應用一組選項,而Connection首部則有一個用逗號分割的鏈接標籤 列表,指定了不會給其餘鏈接的選項.好比,使用Connection: close指定發送完下一跳報文後關閉的鏈接.
Connection首部承載的標籤有三種:
HTTP首部字段名.列出只與當前鏈接相關的字段
任意標籤名,用於描述使用的非標準選項
close,關閉鏈接
因爲Connection首部能夠避免對本地首部的無心轉發,因此將逐跳首部名放入其中被稱做"對首部的保護".Connection首部及其列舉字段都會在轉發時被刪除.
若是使用串行事務處理,TCP的性能時延會疊加起來.假設加載一個包含了三張圖片的Web頁面,發起4個HTTP事務來顯示頁面,且每一個事務都須要一個新的鏈接,則會疊加鏈接時延和慢啓動時延.
這種時延除了實際上的等待時間,還有心理上的4.同時加載多幅圖片會比較好.除此以外,若是瀏覽器在對象加載完成以前沒法知曉對象尺寸,同時須要尺寸信息決定如何排版.這時候若是加載對象數量不夠,就不會顯示任何東西.可能加載進度正常,可是用戶只能看着白屏大怒.
如今,有四種方法能夠提升HTTP的鏈接性能.
並行鏈接:經過多條TCP鏈接發起HTTP請求.
持久鏈接:經過重用TCP鏈接,消除鏈接和關閉時延
管道化鏈接:共享TCP鏈接發起併發的HTTP請求.
複用的鏈接:交替傳送請求和響應報文.(實驗中)
該方法容許客戶端打開多條鏈接,並行地執行多個HTTP事務.包含嵌入對象的組合頁面若是能夠克服單條鏈接的空載時間和帶寬限制,就能夠重疊時延.若是帶寬夠大,就可能夠將未用帶寬進行分配.時延並非不存在,可是會使得其大部分重疊.
並行鏈接的速度並不老是更快 .這有兩個緣由:
用戶的帶寬不足.若是帶寬足夠小,則不得不花費大部分時間來傳送數據.這時 若是鏈接的服務器速度較快,則帶寬會迅速耗盡.
若是多個對象競爭優先的帶寬,則會致使每一個對象的速度都慢下來.
打開大量鏈接會消耗大量內存,對於服務器和客戶端都是.因此,瀏覽器會限制並行鏈接的總數,而服務器會隨意關閉來自特定客戶端的超量鏈接.
P.S.安慰劑
用戶一般以爲並行鏈接更快,由於:用戶看得見加載的進展.