上节我们讲解了Okhttp网络请求和响应的处理过程,其中我们知道了请求之前是需要建立网络连接的,也就是http请求是需要建立TCP连接之上的。这也是符合TCP/IP四层模型和OSI七层模型中,传输层的TCP协议,应用层的HTTP协议应用。Okhttp在网络连接的管理方面有哪些特性和优势呢?我们来列举一下。
网络连接池的引入和管理机制。内部维护网络连接池,查找当前请求是否有对应可用的连接,避免每次请求重新建立和断开TCP连接。
网络连接智能路由机制。重试查找可用IP,建立连接,同时记录连接失败的IP,避免重复请求无效IP。
支持主流HTTP协议,HTTPS请求和Proxy代理服务。支持HTTP 1.1/2和SPDY协议,支持HTTPS,支持HTTP代理和SOCKS代理。
下面我们围绕这3点,去探究它们是如何实现的。
网络连接的引入
上节我们知道网络请求到查找可用连接的流程是这样的。
那我们就从StreamAllocation的newStream看看它是如何创建用于HTTP通信的封装流对象,也就是获取用于当前HTTP通信的TCP连接。这里分为两步。
查找可用连接。
这里会从连接池中查找可用的连接,没有找到则新建一个连接对象(RealConnection),加入到连接池(ConnectionPool),然后调用连接,最后返回连接完成的连接对象(RealConnection)。
创建基于TCP连接之上的用于HTTP流操作的封装对象。
这里根据获取的连接对象(RealConnection)是否包含framedConnection帧流对象来判断创建Http2xStream还是Http1xStream对象。Http1xStream用于HTTP 1.1协议的数据传输,而Http2xStream用于HTTP 2/SPDY协议的数据传输。
网络连接池的引入和管理机制StreamAllocation.findHealthyConnection查找可用连接
从findHealthyConnection就开始进入网络连接的获取,以及网络连接池管理的范围了。
这里获取健康可用的连接,是在while循环中进行的,意味着会重试多次查找,直到找到可用的连接。也就是说,当找到一个新创建的连接或者是验证了是可重用的连接,就代表找到可用连接了,否则将当前找到的不可用连接加入黑名单,避免下次重复获取它,造成后续的无效请求。
StreamAllocation.findConnection查找连接
接下来看findConnection是如何查找连接的,这里分为以下几步。
如果当前的连接是空闲的,则直接使用该连接(RealConnection)。
根据请求地址(Address)从连接池查找缓存的连接(RealConnection)。
接如果没有从连接池找到,则从路由选择器(RouteSelector)中获取其中的下一个路由(Route),然后用这个路由去创建连接对象(RealConnection)。
将当前的StreamAllocation流分配对象记录在这个新创建的连接对象中。这里的用意是记录当前的连接对象(RealConnection)之上分配了多少个请求,连接上没有分配请求,连接池会考虑回收这个连接。采用了引用计数来管理连接的回收。
将新创建的连接加入到连接池。
新创建的连接对象(RealConnection)开始建立连接。
连接建立了,把这个连接对应的路由信息从黑名单中移除,以便以后可以继续访问它。
总体说就是先从连接池获取该Address对应的连接,这里不管它是否是有效的。如果获取不到,则新建一个连接,新建的连接要做4件事情。
通过acquire记录,表示这个连接之上分配了一个流对象了。
将新创建的连接加入到连接池,交给连接池管理。
建立连接。
预先把这个连接对应的路由信息从黑名单删除,待后面findHealthyConnection健康连接检查时,如果不符合,再重新加入黑名单,这是对路由信息黑名单的管理。先从黑名单删除,判断不可用之后再加入黑名单。
到这里我想大家应该能想到连接池中的连接是采用引用计数的方式来记录是否空闲,那它到底是怎么管理的呢?我们继续分析
ConnectionPool网络连接池
我们先分析ConnectionPool的属性和构造方法。
主要描述了以下内容:
用于清理连接的线程池executor,以及进行清理操作的cleanupRunnable,cleanupRunnable中的while循环和wait等待配合,直到完成了清理操作。
最大空闲连接数,最长空闲连接时间,当前是否正在清理。
默认的构造方法是,支持最多5个空闲连接,如果有一个空闲连接超过5分钟,就从连接队列里将它移除并关闭连接。
我们看缓存连接的方法put
首先判断是否正在清理,如果没有,则将清理任务放入线程池执行。
会将连接添加到连接队列里。
清理连接的线程模型
配合之前的线程池executor定义,我们能得出,因为cleanupRunning的判断,线程池中通常只会有一个线程进行清理工作,就是说清理工作使用的是单线程模型,这里没有直接new一个线程,而是使用这个线程池,是因为这个线程池定义了最大空闲线程数是0,最长空闲时间是60秒,也就是说,当前清理线程空闲时,超过60秒,线程池会释放该线程,如果在时间之内,线程可以重用,兼顾了线程重用和无用超时时释放的优点。
连接的清理机制
我们接下来看清理工作是如果进行的,看cleanup方法
从以上代码可以看到,这里在三种情况下会多次执行。
连接的空闲时间大于最大空闲时间,也就是说只要空闲时间超过了,就会去清理。
空闲连接数据大于最大空闲连接数,也就是说空闲连接数超过了,那就会去清理,直到没有超过最大空闲连接数。
如果还是有空闲连接,但是没超过最大空闲连接,那我就等那个空闲最长的连接到达最大空闲时间,然后再次调用cleanup清理它。
如果没有空闲连接的话,那我就等个最大空闲时间,默认的话就是5分钟之后,再试试有没有可以清理的。
综上所述,清理只要开始了,那么它就会一直守候着,没达到它的清理条件,就继续等待,等待下次继续试试能不能清理,直到连接池中没有一个连接存在了,才停止清理工作。
连接是否空闲的判断机制
我们接下来分析是如何判断一个连接是否是空闲还是正在被使用着的。
这里会判断连接中是否存在泄漏的分配流,如果存在分配流的泄漏,就标记该连接不能再分配流了。再分析获取连接的方法
遍历连接队列,如果该连接的流分配数量没有超过限制,然后路由信息一致,并且连接中没有发生流的泄漏,可以分配新的流,就代表找到可用的连接了。然后acquire表示要增加对这个连接的引用。路由信息是否一样的判断
可以看到会匹配url,dns,协议,代理等等内容是否相同。到这里清楚了连接池管理连接的原理了。那么连接上分配的流是在哪里释放的呢?
连接上分配流的引用
我们知道一个连接是否空闲是根据它上面的分配流引用数来判断的,采用弱引用是为了不影响分配流的回收。我们来分析它
连接上分配流的释放
我们知道在获取可重用的连接和新建连接时都会调用acquire来标记当前请求要对连接作一次引用,即需要在该连接上分配流,进行数据请求。那么release释放连接引用是在哪里调用的呢?这里指出一条释放的支线流程。
在Http1xStream这个这个HTTP 1.1协议的流传输对象中,其中的FixedLengthSource对象读取响应流完成时,会调用StreamAllocation的streamFinished标记流读取完成,接着标记释放当前流到release。我们分析Http1xStream
接着是StreamAllocation
分析ConnectionPool的connectionBecameIdle方法
我们发现,原来当一个连接空闲时,除了连接池中的清理线程负责清理关闭之外,本身在StreamAllocation的deallocate中也会直接对判定为空闲的连接进行关闭操作,这样做可以尽快地关闭空闲的连接。
下节分析
本来打算这节一起讲连接的建立和管理,犹豫篇幅原因和连接建立过程的重要性,觉得有必要单独一节进行讲解,这节就到这吧,嘿嘿。
领取专属 10元无门槛券
私享最新 技术干货