转载

关于 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被重新组装进消息中

关于 iOS HTTP2.0 的一次学习实践

  • 多路复用:也就是连接共享,刚才说到 HTTP1.1的 head of line blocking,那么在多路复用的情况下,blocking 已经不存在了。每个连接中 可以包含多个流,而每个流中交错包含着来自两端的帧。也就是说同一个连接中是来自不同流的数据包混合在一起,如下图所示,每一块代表帧,而相同颜色块来自同一个流,每个流都有自己的 ID,在接收端会根据 ID 进行重装组合,就是通过这样一种方式来实现多路复用。

    关于 iOS HTTP2.0 的一次学习实践

  • 单一连接:刚才也说到 1.1 在请求多的时候,会开启6-8个连接,而 HTTP2 只会开启一个连接,这样就减少握手带来的延迟。

  • 头部压缩:HTTP2.0 通过 HPACK 格式来压缩头部,使用了哈夫曼编码压缩、索引表来对头部大小做优化。索引表是把字符串和数字之间做一个匹配,比如method: GET对应索引表中的2,那么如果之前发送过这个值是,就会缓存起来,之后使用时发现之前发送过该Header字段,并且值相同,就会沿用之前的索引来指代那个Header值。具体实验数据可以参考这里:HTTP/2 头部压缩技术介绍

    关于 iOS HTTP2.0 的一次学习实践

  • Server Push:就是服务端可以主动推送一些东西给客户端,也被称为缓存推送。推送的资源可以备客户端日后之需,需要的时候直接拿出来用,提升了速率。具体的实验可以参考这里:iOS HTTP/2 Server Push 探索

关于 iOS HTTP2.0 的一次学习实践

除了上面讲到的特性,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握手过程来查看
关于 iOS HTTP2.0 的一次学习实践

下面通过 WireShark 来进行调试,接入真机,然后终端输入
rvictl -s 设备 UDID来创建一个映射到 iPhone 的虚拟网卡,UUID 可以在 iTunes 中获取到,运行命令后会看到成功创建 rvi0 虚拟网卡的,双击 rvi0 开始调试。
关于 iOS HTTP2.0 的一次学习实践进入之后,在手机上访问页面会有源源不断的请求显示在 WireShark 的界面上,数据太多而不利于我们针对性调试,你可以过滤下域名,只关注你想测试的 ip 地址,比如: ip.addr==111.89.211.191 ,当然你的 ip 要支持 HTTP2.0才会有预想的效果哦
关于 iOS HTTP2.0 的一次学习实践

下面,就开始通过查看 TLS 握手的过程分析HTTP2.0 的协商过程,刚才也说道 ALPN 协商结果是在 Client hello 和 Server hello 中显示的,那就先来看一下Client hello
关于 iOS HTTP2.0 的一次学习实践

可以看到客户端在 Client hello 中列出了自己支持的各种应用层协议,比如 spdy3、h2。

那么接着看 Server hello 是如何回复的

关于 iOS HTTP2.0 的一次学习实践

服务端会根据 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:

服务端收到这个请求后,如果支持 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 的网页的时候,以小闪电的形式进行指示

    关于 iOS HTTP2.0 的一次学习实践

    点击小闪电,会进入一个页面,列举了当前浏览器访问的全部 http2.0的请求,所以,你可以把你想要测试的客户端接口在浏览器访问,然后在这个页面验证下是否支持 http2.0

    关于 iOS 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       @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

    大礼包

  • Jerry Qu的HTTP2.0合辑

  • http2-协议协商过程

  • h2-13 中文版

  •  Hypertext Transfer Protocol Version 2 (HTTP/2)

  • HPACK: Header Compression for HTTP/2

  • Wireshark抓包iOS入门教程

  • iOS HTTP/2 Server Push 探索

  • HTTP/2 on iOS

  • HTTPS 与 HTTP2 协议分析

  • http2讲解

  • How to get HTTP protocol version from a given NSHTTPURLResponse



正文到此结束
Loading...