關于 iOS HTTP2.0 的一次學習實踐

前面的文章也提到了目前的移動端網絡常見性能問題,以及對應的優化策略,如果把HTTP1.1 替換為 HTTP2.0,可以說是網絡性能優化的一步大棋。這幾天對 iOS HTTP2.0 進行了簡單的調研、測試,在此做個簡單的總結

本文的大概思路是介紹 HTTP1.1 的弊端、HTTP2.0 的優勢、HTTP2.0 的協商機制、iOS 客戶端如何接入 HTTP2.0,以及如何對其進行調試。主要還是加深記憶、方便后期查閱,文末的資料相比本文或許是更有價值的。

HTTP 1.1

  • 雖然 HTTP1.1 默認是開啟 Keep-Alive 長連接的,一定程度上彌補了HTTP1.0每次請求都要創建連接的缺點,但是依然存在 head of line blocking,如果出現一個較差的網絡請求,會影響后續的網絡請求。為什么呢?如果你發出1、2、3 三個網絡請求,那么 Response 的順序 2、3 要在第一個網絡請求之后,以此類推

  • 針對同一域名,在請求較多的情況下,HTTP1.1 會開辟多個連接,據說瀏覽器一般是6-8 個,較多連接也會導致延遲增大,資源消耗等問題

  • HTTP1.1 不安全,可能存在被篡改、被竊聽、被偽裝等問題。當然,前陣子 Apple 推廣 HTTPS 的時候,相信很多人已經接入 HTTPS

  • HTTP 的頭部沒有壓縮,header 的大小也是傳輸的負擔,帶來更多的流量消耗和傳輸延遲。并且很多 header 是相同的,重復傳輸是沒有必要的。

  • 服務端無法主動推送資源到客戶端

  • HTTP1.1的格式是文本格式,基于文本做一些擴展、優化相對比較困難,但是文本格式易于閱讀和調試,但HTTPS之后,也變成二進制格式了,這個優勢也不復存在

HTTP2.0

在 HTTP2.0中,上面的問題幾乎都不存在了。HTTP2.0 的設計來源于 Google 的 SPDY 協議,如果對 SPDY 協議不了解的話,也可以先對 SPDY 進行了解,不過這不影響繼續閱讀本文

  • HTTP 2.0 使用新的二進制格式:基本的協議單位是幀,每個幀都有不同的類型和用途,規范中定義了10種不同的幀。例如,報頭(HEADERS)和數據(DATA)幀組成了基本的HTTP 請求和響應;其他幀例如 設置(SETTINGS),窗口更新(WINDOW_UPDATE), 和推送承諾(PUSH_PROMISE)是用來實現HTTP/2的其他功能。那些請求和響應的幀數據通過流來進行數據交換。新的二進制格式是流量控制、優先級、server push等功能的基礎。

流(Stream):一個Stream是包含一條或多條信息、ID和優先級的雙向通道

消息(Message):消息由幀組成

幀(Frame):幀有不同的類型,并且是混合的。他們通過stream id被重新組裝進消息中

  • 多路復用:也就是連接共享,剛才說到 HTTP1.1的 head of line blocking,那么在多路復用的情況下,blocking 已經不存在了。每個連接中 可以包含多個流,而每個流中交錯包含著來自兩端的幀。也就是說同一個連接中是來自不同流的數據包混合在一起,如下圖所示,每一塊代表幀,而相同顏色塊來自同一個流,每個流都有自己的 ID,在接收端會根據 ID 進行重裝組合,就是通過這樣一種方式來實現多路復用。
  • 單一連接:剛才也說到 1.1 在請求多的時候,會開啟6-8個連接,而 HTTP2 只會開啟一個連接,這樣就減少握手帶來的延遲。

  • 頭部壓縮:HTTP2.0 通過 HPACK 格式來壓縮頭部,使用了哈夫曼編碼壓縮、索引表來對頭部大小做優化。索引表是把字符串和數字之間做一個匹配,比如method: GET對應索引表中的2,那么如果之前發送過這個值是,就會緩存起來,之后使用時發現之前發送過該Header字段,并且值相同,就會沿用之前的索引來指代那個Header值。具體實驗數據可以參考這里:HTTP/2 頭部壓縮技術介紹

  • Server Push:就是服務端可以主動推送一些東西給客戶端,也被稱為緩存推送。推送的資源可以備客戶端日后之需,需要的時候直接拿出來用,提升了速率。具體的實驗可以參考這里:iOS HTTP/2 Server Push 探索

除了上面講到的特性,HTTP2.0 還有流量控制、流優先級和依賴性等功能。更多細節可以參考:Hypertext Transfer Protocol Version 2 (HTTP/2)

iOS 客戶端接入HTTP 2.0

iOS 如何接入 HTTP 2.0呢?其實很簡單:

  • 保證服務端支持 HTTP2.0,并且留意下 NPN 或 ALPN
  • 客戶端系統版本 iOS 9 +
  • 使用 NSURLSession 代替 NSURLConnection
  • 客戶端是使用 h2c 還是 h2,它們可以說是 HTTP2.0的兩個版本,h2 是使用 TLS 的HTTP2.0協議,h2c是運行在明文 TCP 協議上的 HTTP2.0協議。瀏覽器目前只支持h2,也就是說必須基于HTTPS部署,但是客戶端可以不部署HTTPS,因為我司早已部署HTTPS,所以我這里的實踐都是基于h2的

HTTP 2.0的協商機制

上面說了一堆名次,什么NPN、ALPN呀,還有h2、h2c之類的,有點懵逼。NPN(Next Protocol Negotiation)是一個 TLS 擴展,由 Google 在開發 SPDY 協議時提出。隨著 SPDY 被 HTTP/2 取代,NPN 也被修訂為 ALPN(Application Layer Protocol Negotiation,應用層協議協商)。二者目標一致,但實現細節不一樣,相互不兼容。以下是它們主要差別:

  • NPN 是服務端發送所支持的 HTTP 協議列表,由客戶端選擇;而 ALPN 是客戶端發送所支持的 HTTP 協議列表,由服務端選擇;
  • NPN 的協商結果是在 Change Cipher Spec 之后加密發送給服務端;而 ALPN 的協商結果是通過 Server Hello 明文發給客戶端

同時,目前很多地方開始停止對NPN的支持,僅支持 ALPN,所以公司使用的話,最佳是直接使用 ALPN。

下面就直接來看看 ALPN 的協商過程是怎樣的,ALPN 作為 TLS 的一個擴展,其過程可以通過 WireShark 查看 TLS握手過程來查看

下面通過 WireShark 來進行調試,接入真機,然后終端輸入
rvictl -s 設備 UDID來創建一個映射到 iPhone 的虛擬網卡,UUID 可以在 iTunes 中獲取到,運行命令后會看到成功創建 rvi0 虛擬網卡的,雙擊 rvi0 開始調試。

進入之后,在手機上訪問頁面會有源源不斷的請求顯示在 WireShark 的界面上,數據太多而不利于我們針對性調試,你可以過濾下域名,只關注你想測試的 ip 地址,比如: ip.addr==111.89.211.191 ,當然你的 ip 要支持 HTTP2.0才會有預想的效果哦

下面,就開始通過查看 TLS 握手的過程分析HTTP2.0 的協商過程,剛才也說道 ALPN 協商結果是在 Client hello 和 Server hello 中顯示的,那就先來看一下Client hello

可以看到客戶端在 Client hello 中列出了自己支持的各種應用層協議,比如 spdy3、h2。那么接著看 Server hello 是如何回復的

服務端會根據 client hello 中的協議列表,發過去自己支持的網絡協議,假如服務端支持 h2,則直接返回h2,協商成功,如果不支持 h2,則返回一個其他支持的協議,比如HTTP1.1、spdy3

這個是h2的協商過程,對于剛才提到的 h2c 的協商過程,與此不同,h2c 利用的是HTTP Upgrade 機制,客戶端會發送一個 http 1.1的請求到服務端,這個請求中包含了 http2的升級字段,例如:

  GET /default.htm HTTP/1.1
  Host: server.example.com
  Connection: Upgrade, HTTP2-Settings
  Upgrade: h2c
  HTTP2-Settings: <base64url encoding of HTTP/2 SETTINGS payload>

服務端收到這個請求后,如果支持 Upgrade 中 列舉的協議,這里是 h2c,就會返回支持的響應:

  HTTP/1.1 101 Switching Protocols
  Connection: Upgrade
  Upgrade: h2c

  [ HTTP/2 connection ...

當然,不支持的話,服務器會返回一個不包含 Upgrade 的報頭字段的響應。

我的客戶端支持了嗎?

一切準備就緒之后,也是時候對結果進行驗證了,除了剛才提到的 WireShark 之外,你還可以使用下面的幾個工具來對 HTTP 2.0 進行測試

  • Chrome 上的一個插件,HTTP/2 and SPDY indicator 會在你訪問 http2.0 的網頁的時候,以小閃電的形式進行指示

點擊小閃電,會進入一個頁面,列舉了當前瀏覽器訪問的全部 http2.0的請求,所以,你可以把你想要測試的客戶端接口在瀏覽器訪問,然后在這個頁面驗證下是否支持 http2.0

  • charles:這個大家應該都用過,4.0 以上的新版本對 HTTP2.0做了支持,為了方便,你也可以在 charles 上進行調試,但是我發現好像存在 http2.0的一些 bug,目前還沒搞清楚什么原因

  • 使用 nghttp2 來調試,這是一個 C 語言實現的 HTTP2.0的庫,具體使用方法可以參考:使用 nghttp2 調試 HTTP/2 流量

  • 再者簡單粗暴,直接在 iOS 代碼中打印,_CFURLResponse 中包含了 httpversion,獲取方法就是基于 CFNetwork 相關的 API 來做,這里直接丟出關鍵代碼,完整代碼可以參考 getHTTPVersion

      #import "NSURLResponse+Help.h"
      #import <dlfcn.h>
      @implementation NSURLResponse (Help)
      typedef CFHTTPMessageRef (*MYURLResponseGetHTTPResponse)(CFURLRef response);
    
      - (NSString *)getHTTPVersion {
          NSURLResponse *response = self;
          NSString *version;
          NSString *funName = @"CFURLResponseGetHTTPResponse";
          MYURLResponseGetHTTPResponse originURLResponseGetHTTPResponse =
          dlsym(RTLD_DEFAULT, [funName UTF8String]);
          SEL theSelector = NSSelectorFromString(@"_CFURLResponse");
          if ([response respondsToSelector:theSelector] &&
              NULL != originURLResponseGetHTTPResponse) {
              CFTypeRef cfResponse = CFBridgingRetain([response performSelector:theSelector]);
              if (NULL != cfResponse) {
                  CFHTTPMessageRef message = originURLResponseGetHTTPResponse(cfResponse);
                  CFStringRef cfVersion = CFHTTPMessageCopyVersion(message);
                  if (NULL != cfVersion) {
                      version = (__bridge NSString *)cfVersion;
                      CFRelease(cfVersion);
                  }
                  CFRelease(cfResponse);
              }
          }
          if (nil == version || 0 == version.length) {
              version = @"獲取失敗";
          }
          return version;
      }
      @end      
    

大禮包

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,517評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,087評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,521評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,493評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,207評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,603評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,624評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,813評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,364評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,110評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,305評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,874評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,532評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,953評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,209評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,033評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,268評論 2 375

推薦閱讀更多精彩內容