多路复用
多路复用,代替原来的序列和阻塞机制。所有就是请求的都是通过一个 TCP连接并发完成。 HTTP 1.x 中,如果想并发多个请求,必须使用多个 TCP 链接,且浏览器为了控制资源,还会对单个域名有 6-8个的TCP链接请求限制,如下图,红色圈出来的请求就因域名链接数已超过限制,而被挂起等待了一段时间:
- HTTP/2 中,同域名下所有通信都在单个连接上完成,该连接可以承载任意数量的双向数据流。每个数据流都以消息的形式发送,而消息又由一个或多个帧组成。多个帧之间可以乱序发送,根据帧首部的流标识可以重新组装。
头部压缩
HTTP 1.1请求的大小变得越来越大,有时甚至会大于TCP窗口的初始大小,因为它们需要等待带着ACK的响应回来以后才能继续被发送。HTTP/2对消息头采用HPACK(专为http/2头部设计的压缩格式)进行压缩传输,能够节省消息头占用的网络的流量。而HTTP/1.x每次请求,都会携带大量冗余头信息,浪费了很多带宽资源。
- 为了减少这块的资源消耗并提升性能, HTTP/2对这些首部采取了压缩策略:
HTTP/2在客户端和服务器端使用“首部表”来跟踪和存储之前发送的键-值对,对于相同的数据,不再通过每次请求和响应发送;
首部表在HTTP/2的连接存续期内始终存在,由客户端和服务器共同渐进地更新;
每个新的首部键-值对要么被追加到当前表的末尾,要么替换表中之前的值。
例如:下图中的两个请求, 请求一发送了所有的头部字段,第二个请求则只需要发送差异数据,这样可以减少冗余数据,降低开销。
okHttp 链接复用
那么到底是如何判断一个RealConnection是否可以让StreamAllocation使用呢?其实一个链接能否复用的条件我们能想到无外乎地址一样、端口一样、一条链接线路的负载不超过指定负载数等等这些条件。Okhttp对复用条件做了更多的限制,详见isEligible代码:
public boolean isEligible(Address address, @Nullable Route route) {
//1、负载超过指定最大负载,不可复用
if (allocations.size() >= allocationLimit || noNewStreams) return false;
//2、Address对象的非主机部分不相等,不可复用
if (!Internal.instance.equalsNonHost(this.route.address(), address)) return false;
//3、非主机部分不相等,不可复用
if(address.url().host().equals(this.route().address().url().host())) {
//这个链接完美的匹配
return true; // This connection is a perfect match.
}
//
// https://hpbn.co/optimizing-application-delivery/#eliminate-domain-sharding
// https://daniel.haxx.se/blog/2016/08/18/http2-connection-coalescing/
// 4. This connection must be HTTP/2.
if (http2Connection == null) return false;
// The routes must share an IP address. This requires us to have a DNS address for both
// hosts, which only happens after route planning. We can't coalesce connections that use a
// proxy, since proxies don't tell us the origin server's IP address.
//5
if (route == null) return false;
//6
if (route.proxy().type() != Proxy.Type.DIRECT) return false;
//7
if (this.route.proxy().type() != Proxy.Type.DIRECT) return false;
//8
if (!this.route.socketAddress().equals(route.socketAddress())) return false;
// 9
if (route.address().hostnameVerifier() != OkHostnameVerifier.INSTANCE) return false;
if (!supportsUrl(address.url())) return false;
// 10. Certificate pinning must match the host.
try {
address.certificatePinner().check(address.url().host(), handshake().peerCertificates());
} catch (SSLPeerUnverifiedException e) {
return false;
}
//最终可以复用
return true;
}