- 本文章所使用的 OkHttp 源码版本:3.12.10
上一篇:OkHttp 精讲:CacheInterceptor
开胃菜
-
在讲这个拦截器之前,我们先补充一点网络基础,大部分人应该都知道 Http 和 Https 之间最大的区别,就是使用了加密传输让请求更加安全,但是大多数人却不知道 Http 不同版本之间的区别。
-
Http 1.0 和 Http 1.1 变化最大的地方在于,Http 1.1 拥有长连接的特性,那么问题来了,什么是长连接?
-
我们都知道 Http 是基于 TCP 衍生出来的一种应用层协议,我们大可认为 Http 是 TCP 的子类,而一提及 TCP,大家可能第一想到的就是三次握手,每次建立连接之前都会发三次网络信号来确定是否建立连接,这种模式在 Http 上面也有,只不过在 Http 1.0 和 Http 1.1 有一些区别,Http 1.0 每次请求网络都需要进行三次握手,请求完毕之后连接就自动断开了,而 Http 1.1 对这块的内容进行了优化,在每次请求完毕之后并没有立即把连接断开,而是将这个连接复用起来,下次请求网络的时候不经过三次握手,而是直接建立连接,这个就是 Http 1.1 的长连接特性。
-
这样的好处在于,因为三次握手是需要消耗时间和资源的,如果每次请求网络都需要三次握手才能建立连接,那么在这种条件下,无疑对服务器形成了一定的压力负担,同样的客户端也会受到一定的影响,所以复用三次握手可以极大减轻服务器的压力和提升客户端的响应速度。
-
-
Http 1.1 和 Http 2.0 变大最大的地方在于,Http 2.0 拥有多路复用的特性,那么问题又来了,什么是多路复用?
- 由于 Http 1.1 的长连接特性只能处理单个请求, 这样的话请求一多就会导致排队的情况出现,若干个请求排队串行化单线程处理,后面的请求等待前面请求的返回才能获得执行机会,一旦有某请求超时等,后续请求只能被阻塞,并且毫无办法,也就是人们常说的线头阻塞。而 Http 2.0 能在多个请求上同时在一个连接上并行执行,如果某个请求任务耗时严重,则不会影响到其它请求的正常执行。
源码解析
![](https://img.haomeiwen.com/i6038844/6a884f969c15db3b.png)
-
大家看到这个拦截器的第一感觉是不是:代码那么少,感觉 so easy
-
没错,我第一次看的时候也是那么想的,只是看完之后我的想法就变了
![](https://img.haomeiwen.com/i6038844/75e96def805ebf40.png)
![](https://img.haomeiwen.com/i6038844/0fc7eee4cf8fb229.png)
- 这个一看就知道不是重点代码,这里略过
![](https://img.haomeiwen.com/i6038844/e45e9cf954dbf518.png)
![](https://img.haomeiwen.com/i6038844/9aa8d7246e3f5176.png)
- 这个也是,直接跳过
![](https://img.haomeiwen.com/i6038844/a6533289f56f83a9.png)
![](https://img.haomeiwen.com/i6038844/1fc4696efe70466e.png)
- 这个看起来有点意思,今天的主菜应该就是它不会有错了,让我们接着看
![](https://img.haomeiwen.com/i6038844/a5c1965969ff161f.png)
- 从翻译上我们隐约能猜到这个方法是在寻找健康的网络连接,让我们接着往下看
![](https://img.haomeiwen.com/i6038844/4c0adb76eb041163.png)
- 是不是感觉有点懵?让我们先看看方法上面的注释
![](https://img.haomeiwen.com/i6038844/260119dc96ae3636.png)
![](https://img.haomeiwen.com/i6038844/02e6556499dd5f5d.png)
-
结合源码和注释,我们可以判断出,它会从连接池中找连接,如果取出来的连接是不健康的,那么会继续找,直到找到一个健康的连接为止。
-
那么问题来了,什么样的连接是不健康的?
![](https://img.haomeiwen.com/i6038844/5795e6b8a37fe499.png)
- 让我们看一下这句代码是怎么判断的
![](https://img.haomeiwen.com/i6038844/7465d6bce22f1adf.png)
-
从这里我们可以得到一个额外的信息,OkHttp 是基于 Socket 实现的,而不是基于 HttpURLConnection,这个就是看源码的好处,原来 OkHttp 的实现方式是直接基于 Socket。
-
这个只是题外话,接下来让我们看看这三个判断是什么?
![](https://img.haomeiwen.com/i6038844/93131f586df2bb18.png)
![](https://img.haomeiwen.com/i6038844/7c1929561fefd5ad.png)
![](https://img.haomeiwen.com/i6038844/4a00f7600a9484bf.png)
- 一个不健康的流表现为:Socket 连接被关闭了又或者输入输出连接被关闭了
![](https://img.haomeiwen.com/i6038844/3167e14194555663.png)
- 让我们再回去看看它是如何寻找连接的?
![](https://img.haomeiwen.com/i6038844/7a560cc20a1a3301.png)
- 注释是看懂了,这个方法会优先去复用已有的连接,如果复用不了或者没有连接,最后才去创建一个新连接
![](https://img.haomeiwen.com/i6038844/1bcd8cb06e7e6924.png)
![](https://img.haomeiwen.com/i6038844/c37226dd7758250b.png)
- 我们可以看到,这个方法会从连接池中获取一个连接,接着看源码
![](https://img.haomeiwen.com/i6038844/3f4d8667ba8bc6b7.png)
![](https://img.haomeiwen.com/i6038844/884cb9066095654a.png)
![](https://img.haomeiwen.com/i6038844/b5441a54031c2c3e.png)
![](https://img.haomeiwen.com/i6038844/591fc5a61a581905.png)
-
它会从遍历整个连接池,判断连接是否符合要求,如果连接符合要求就直接赋值给 StreamAllocation 对象的 connection 字段并返回,如果不符合就继续遍历,如果都没有则返回空
-
那么问题来了,在哪些情况下符合要求呢?
![](https://img.haomeiwen.com/i6038844/8432d239263967c1.png)
![](https://img.haomeiwen.com/i6038844/765726c6a862b7df.png)
- 如果这个连接对象不支持传输,那么就是不符合的
![](https://img.haomeiwen.com/i6038844/14927b35c6ebceb7.png)
![](https://img.haomeiwen.com/i6038844/cf7abc6676d3a127.png)
- 如果这个连接的配置和要求的不一致(除了主机地址不比对),那么也是不符合要求的
![](https://img.haomeiwen.com/i6038844/a93faab65783b876.png)
-
刚刚没有比对主机地址,如果比对之后是一致的,那么证明所有的参数都匹配,这个连接是可以复用的
-
如果主机地址不一样,还需要判断什么呢?让我们接着往下看
![](https://img.haomeiwen.com/i6038844/39028f04d3b64cad.png)
![](https://img.haomeiwen.com/i6038844/0e6f7704438f24b9.png)
- 如果这个连接不支持 Http 2.0 协议,那么也不支持复用连接
![](https://img.haomeiwen.com/i6038844/42ac0df90cfbd344.png)
- 接下来就判断两个路由的信息是否匹配,那么问题来了,什么是路由?有什么用?
![](https://img.haomeiwen.com/i6038844/a756e4c4396a2cbe.png)
其实包含两部分,一个是 HTTP 代理服务器地址,另一个是由 DNS 解析出来的 IP 地址,因为 HTTP 代理服务器可以有多个,而 DNS 解析出来的 IP 地址也会有多个,那么这个时候需要一个路由将这两者进行拼合,例如代理服务器有 A 和 B,DNS 地址有 1 和 2,那么生成的路由有四个,分别是 A1、A2、B1、B2。代理服务器的作用是,例如我们平时访问不到 Google,但是使用 VPN 就可以,这个 VPN 就是代理服务器,海外的代理服务器会替我们访问这个网址并把数据返回给我们。而 DNS 解析出来的 IP 地址堆,则用于当一个 IP 地址的主机无法访问的时候,则会切换到下一个 IP 地址再进行请求。
![](https://img.haomeiwen.com/i6038844/85553f3c567a5075.png)
![](https://img.haomeiwen.com/i6038844/3e60e80305871839.png)
- 这里判断了是否开启了代理,如果没有则不能复用连接
![](https://img.haomeiwen.com/i6038844/357f29a0cdfaaf19.png)
- 如果开启了代理,但是代理服务器的地址不一样,同样也不能复用连接
![](https://img.haomeiwen.com/i6038844/6d1067ae800fc281.png)
-
接着就是对网站的证书进行校验了,如果证书上的 Host 地址和要请求的 Host 地址不一致,这个连接也是不能复用的
-
通过这些信息,我们可以得出,从连接池中取出来的连接对象,并不能直接复用,而是需要满足一定的条件才可以复用,被复用的连接必须和要当前用户要请求的连接某些信息相匹配才可以。
-
看完了这部分源码,让我们接着回去看之前的源码
![](https://img.haomeiwen.com/i6038844/0fcd6ee23737185a.png)
- 如果从连接池找到合适的连接对象,那么会直接返回给上层,如果没有呢?
![](https://img.haomeiwen.com/i6038844/916e4282f3ea0bf3.png)
-
它会获取路由列表,然后把路由分发给连接池去寻找是否有匹配的连接
-
如果还是没有找到符合要求的连接对象呢?让我们接着往下面看
![](https://img.haomeiwen.com/i6038844/b3906817bf2586fb.png)
- 如果有找到符合要求的连接对象,那么就直接返回回去,否则就创建一个新的连接对象,并进行 TCP 三次握手和 Https 身份校验
![](https://img.haomeiwen.com/i6038844/38e3f055e980b95c.png)
- 最后再将这个新的连接对象添加到连接池中备用
源码总结
- 这个拦截器主要的作用就是负责从连接池获取健康的连接(判断连接的 Socket 传输通道是否可用),如果在连接池中没有找到符合复用的连接(除了主机地址之外的信息必须要全部匹配,例如 DNS、代理、协议、证书),那么就会使用路由再找一遍(路由的结构是一个代理服务器的地址 + DNS 中的一个 IP 地址),如果还是没有,那么会直接创建一个新的连接对象并进行三次握手,再将这个新的连接对象添加到连接池中,最后将可复用的连接返回回去。
网友评论