整体设计

OkHttp设计较为复杂,但是层次还算清晰,我把主要的类关系画了个图,大概是这样。

OkHttp的类较多,这里只描述下重要的类关系,可以看出OkHttp的主要功能都集中在Interceptor中,通过Interceptor完成构建请求,建立Socket连接,建立SSLSocket连接,证书校验等步骤。

我们看下Interceptor的调用过程:

在这个拦截器设计中,我们可以对Request进行操作,同样也可以对Response进行操作。当然,我们添加的Interceprot是在所有Interceptor之前,意味着Request最先操作,Response最后操作。

RealCall类的getResponseWithInterceptorChain是创建Interceptor的方法,可以看出okHttp共添加了5个Interceptor,它们在网络请求的不同阶段起着不同的作用,下面将详细分析。

OkHttpClient模块

OkHttpClient使用Builder进行构建,同时提供了一个newBuilder()的方法用来创建一个Builder,可以对原有OkHttpClient修改部分参数后创建一个新实例。

OkHttpClient同时实现了CallFactory接口,这个是发起网络请求的关键类。 CallFactory

OkHttpClient中

RealCall中

Router路由选择模块

我们知道,发起网络请求首先涉及到DNS解析的过程,okHttp对这个DNS解析过程了优化,引入了Router的概念,对失效的IP地址进行缓存,一定程度上提高了DNS解析速度。

我们先看下OkHttp的DNS查询逻辑。

核心是调用InetAddress.getAllByName(hostname)进行查询。

建立连接这个过程中是在ConnectInterceptor中完成的,我们看下ConnectInterceptor的代码。

调用到StreamAllocation的newStream方法,StreamAllocation中就涉及到DNS解析->Router选择->建立Socket/SSLSocket连接的过程了。

下面看下Router模块的类关系。

StreamAllocation中通过RouteSelector获取所有IP地址,然后从ConnectionPool中查找可复用的连接,如果没有,则使用第一个Route创建RealConnection对象建立连接。

RealConnection Http(s)网络请求模块

前面的StreamAllocation的分析中可以看出,如果在ConnectionPools中没有取出可以复用的Connection那么会重新创建一个Connection,即Socket连接,下面我们看下创建过程。

建立连接需要两个步骤,建立Socket连接;如果是Https的话,还需要建立一个SSLSocket连接,我们先来看下建立Socket连接。

下面看下建立SSLSocket连接

socket会被包装成sslSocket,source,sink都会被重新生成。

HttpCodec/Response模块

前面介绍了建立Socket连接,路由选择等过程,但是实际上解析网络流数据是通过HttpCodec进行的,先看下HttpCodec的定义。

HttpCodec有两个实现分别为Http1Codec和Http2Codec。

HttpCodec实例是通过RealConnection中进行获取的,最上层的调用是在ConnectInterceptor中。

我们先分析下Http1.1版本的HttpCodec实现,可以看出HttpCodec是将OkHttpClient,StreamAllocation,Source , Sink都封装到一起了。

然后ConnectIntercptor将HttpCodec实例传给Chain,让下一个拦截器记性处理。

HttpCodec真正的调用是在CallServerInterceptor中,通过HttpCodec,将RequestBody写入输入流,同时解码网络传输的数据。

CallServerInterceptor的核心逻辑如下:

上面逻辑中,通过HttpCodec的openResponseBody获取ResponseBody实例,这个ResponseBody会返回给上层调用者。

看下Http1Codec的openResponseBody实现

这个openReponseBody的作用是将HttpCodec的 Source进行包装,然后返回给上层调用。

这个时候ResponseBody的Source类型实际是对Socket的多重包装,所以,当我们不再需要这个Response的数据时,需要将source关闭,否则这个socket可能就泄漏了。

Socket也算一个FD类型,如果FD泄漏达到上限,会触发Crash。

ConnectionPool 连接池模块

ConnectionPool的作用是将Socket连接进行缓存,如果两次请求的同一个Host的话,可以复用之前的连接,减少TCP握手和TLS握手。

在StreamAllocation的findConnection中,会尝试通过ConnecitonPool获取已经缓存的Connection,获取之前会有个比较逻辑,具体是Connection的isEligible方法。

可以看出,Http2的规则和Http1的有所不同。 针对Http1.1来说,满足两个条件即可复用Connection。

  1. 没有正在使用的Http连接
  2. Host相同

这样的话,其实是忽略了Keep-Alive字段了,如果Http(s)连接满足上面的条件,都可以拿来复用。

ConnectionPools不会无限缓存连接,会有时间限制和个数限制,并且超过时间会自动清理连接池中的连接。

发表评论

电子邮件地址不会被公开。 必填项已用*标注