在《OKHTTP3源码和设计模式(上篇)》,中整体介绍了 OKHttp3 的源码架构,重点讲解了请求任务的分发管理和线程池以及请求执行过程中的拦截器。这一章我们接着往下走认识一下 OKHttp3 底层连接和连接池工作机制。

RealCall 封装了请求过程, 组织了用户和内置拦截器,其中内置拦截器 retryAndFollowUpInterceptor -> BridgeInterceptor -> CacheInterceptor 完执行层的大部分逻辑 ,ConnectInterceptor -> CallServerInterceptor 两个拦截器开始迈向连接层最终完成网络请求。

连接层连接器

ConnectInterceptor 的工作很简单, 负责打开连接; CallServerIntercerceptor 是核心连接器链上的最后一个连接器,
负责从当前连接中写入和读取数据。

连接的打开

    /** Opens a connection to the target server and proceeds to the next interceptor. */// 打开一个和目标服务器的连接,并把处理交个下一个拦截器public final class ConnectInterceptor implements Interceptor {public final OkHttpClient client;public ConnectInterceptor(OkHttpClient client) {this.client = client;}@Override public Response intercept(Chain chain) throws IOException {RealInterceptorChain realChain = (RealInterceptorChain) chain;Request request = realChain.request();StreamAllocation streamAllocation = realChain.streamAllocation();// We need the network to satisfy this request. Possibly for validating a conditional GET.boolean doExtensiveHealthChecks = !request.method().equals("GET");// 打开连接HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);RealConnection connection = streamAllocation.connection();// 交个下一个拦截器return realChain.proceed(request, streamAllocation, httpCodec, connection);}
}
复制代码

单独看 ConnectInterceptor 的代码很简单,不过连接正在打开的过程需要看看 streamAllocation.newStream(client, doExtensiveHealthChecks),内部执行过程。还是先整体上了看看 StreamAllocation 这个类的作用。

StreamAllocation

StreamAllocation 处于上层请求和底层连接池直接 , 协调请求和连接池直接的关系。先来看看 StreamAllocation 对象在哪里创建的? 回到之前文章中介绍的 RetryAndFollowUpInterceptor, 这是核心拦截器链上的顶层拦截器其中源码:

    @Override public Response intercept(Chain chain) throws IOException {Request request = chain.request();streamAllocation = new StreamAllocation(client.connectionPool(), createAddress(request.url()), callStackTrace);...省略代码}
复制代码

这里, 每一次请求创建了一个 StreamAllocation 对象, 那么问题来了? 之前我们说过每一个 OkHttpClient 对象只有一个对应的连接池, 刚刚又说到 StreamAllocation 打开连接, 那么 StreamAllocation 是如何创建连接池的呢?我们很容易就去 StreamAllocation 中找连接池创建的逻辑,但是找不到。 连接池创建的地方在 OkHttpClient 中:

   public Builder() {dispatcher = new Dispatcher();protocols = DEFAULT_PROTOCOLS;connectionSpecs = DEFAULT_CONNECTION_SPECS;eventListenerFactory = EventListener.factory(EventListener.NONE);proxySelector = ProxySelector.getDefault();cookieJar = CookieJar.NO_COOKIES;socketFactory = SocketFactory.getDefault();hostnameVerifier = OkHostnameVerifier.INSTANCE;certificatePinner = CertificatePinner.DEFAULT;proxyAuthenticator = Authenticator.NONE;authenticator = Authenticator.NONE;// 创建连接池connectionPool = new ConnectionPool();dns = Dns.SYSTEM;followSslRedirects = true;followRedirects = true;retryOnConnectionFailure = true;connectTimeout = 10_000;readTimeout = 10_000;writeTimeout = 10_000;pingInterval = 0;
}
复制代码

OkHttpClient 默认构造函数的 Builder , 在这里创建了连接池。所以这里我们也可以看到, 如果我们对默认连接池不满,我们是可以直通过 builder 接指定的。
搞懂了 StreamAllocation 和 ConnectionPool 的创建 , 我们再来看看 StreamAllocation 是怎么打开连接的?直接兜源码可能有点绕 ,先给一个粗略流程图,然后逐点分析。

链接池实现

相信大家都有一些 Http 协议的基础(如果没有就去补了,不然看不懂)都知道 Http 的下层协议是 TCP。TCP 连接的创建和断开是有性能开销的,在 Http1.0 中,每一次请求就打开一个连接,在一些老的旧的浏览器上,如果还是基于 Http1.0,体验会非常差; Http1.1 以后支持长连接, 运行一个请求打开连接完成请求后, 连接可以不关闭, 下次请求时复用此连接,从而提高连接的利用率。当然并不是连接打开后一直开着不关,这样又会造成连接浪费,怎么管理?
在OKHttp3 的默认实现中,使用一个双向队列来缓存所有连接, 这些连接中最多只能存在 5 个空闲连接,空闲连接最多只能存活 5 分钟。

定期清理实现

     public final class ConnectionPool {/*** Background threads are used to cleanup expired connections. There will be at most a single* thread running per connection pool. The thread pool executor permits the pool itself to be* garbage collected.*/// 后台定期清理连接的线程池private static final Executor executor = new ThreadPoolExecutor(0 /* corePoolSize */,Integer.MAX_VALUE /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS,new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp ConnectionPool", true));/** The maximum number of idle connections for each address. */private final int maxIdleConnections;private final long keepAliveDurationNs;// 后台定期清理连接的任务private final Runnable cleanupRunnable = new Runnable() {@Override public void run() {while (true) {// cleanup 执行清理long waitNanos = cleanup(System.nanoTime());if (waitNanos == -1) return;if (waitNanos > 0) {long waitMillis = waitNanos / 1000000L;waitNanos -= (waitMillis * 1000000L);synchronized (ConnectionPool.this) {try {ConnectionPool.this.wait(waitMillis, (int) waitNanos);} catch (InterruptedException ignored) {}}}}}};
复制代码

双向队列

 // 存储连接的双向队列
private final Deque<RealConnection> connections = new ArrayDeque<>();
复制代码

放入连接

void put(RealConnection connection) {assert (Thread.holdsLock(this));if (!cleanupRunning) {cleanupRunning = true;executor.execute(cleanupRunnable);}connections.add(connection);}
复制代码

获取连接

RealConnection get(Address address, StreamAllocation streamAllocation, Route route) {assert (Thread.holdsLock(this));for (RealConnection connection : connections) {if (connection.isEligible(address, route)) {streamAllocation.acquire(connection);return connection;}}return null;
}
复制代码

StreamAllocation.连接创建和复用

ConnectionPool 的源码逻辑还是相当比较简单, 主要提供一个双向列表来存取连接, 使用一个定时任务定期清理无用连接。 二连接的创建和复用逻辑主要在 StreamAllocation 中。

寻找连接

private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,int writeTimeout, boolean connectionRetryEnabled, boolean doExtensiveHealthChecks)throws IOException {
while (true) {//  核心逻辑在 findConnection()中RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,connectionRetryEnabled);// If this is a brand new connection, we can skip the extensive health checks.synchronized (connectionPool) {if (candidate.successCount == 0) {return candidate;}}// Do a (potentially slow) check to confirm that the pooled connection is still good. If it// isn't, take it out of the pool and start again.if (!candidate.isHealthy(doExtensiveHealthChecks)) {noNewStreams();continue;}return candidate;
}
}
复制代码

findConnection():

private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,boolean connectionRetryEnabled) throws IOException {
Route selectedRoute;
synchronized (connectionPool) {// 省略部分代码...// Attempt to get a connection from the pool. Internal.instance 就是 ConnectionPool 的实例Internal.instance.get(connectionPool, address, this, null);if (connection != null) {// 复用此连接return connection;}// 省略部分代码...// 创建新新连接result = new RealConnection(connectionPool, selectedRoute);// 引用计数acquire(result);
}synchronized (connectionPool) {// Pool the connection. 放入连接池Internal.instance.put(connectionPool, result);}// 省略部分代码...return result;
}
复制代码

StreamAllocation 主要是为上层提供一个连接, 如果连接池中有复用的连接则复用连接, 如果没有则创建新的。无论是拿到可复用的还是创建新的, 都要为此连接计算一下引用计数。

public void acquire(RealConnection connection) {assert (Thread.holdsLock(connectionPool));if (this.connection != null) throw new IllegalStateException();this.connection = connection;//  连接使用allocations列表来记录每一个引用connection.allocations.add(new StreamAllocationReference(this, callStackTrace));
}
复制代码

Realconnection

Realconnection 封装了底层 socket 连接, 同时使用 OKio 来进行数据读写, OKio 是 square 公司的另一个独立的开源项目, 大家感兴趣可以去深入读下 OKio 源码, 这里不展开。

/** Does all the work necessary to build a full HTTP or HTTPS connection on a raw socket. */private void connectSocket(int connectTimeout, int readTimeout) throws IOException {Proxy proxy = route.proxy();Address address = route.address();rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP? address.socketFactory().createSocket(): new Socket(proxy);rawSocket.setSoTimeout(readTimeout);try {// 打开 socket 连接Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);} catch (ConnectException e) {ConnectException ce = new ConnectException("Failed to connect to " + route.socketAddress());ce.initCause(e);throw ce;}// 使用 OKil 连上 socket 后续读写使用 Okiosource = Okio.buffer(Okio.source(rawSocket));sink = Okio.buffer(Okio.sink(rawSocket));}复制代码

OKHTTP3源码和设计模式(下篇)相关推荐

  1. OkHttp3源码详解

    前言:为什么有些人宁愿吃生活的苦也不愿吃学习的苦,大概是因为懒惰吧,学习的苦是需要自己主动去吃的,而生活的苦,你躺着不动它就会来找你了. 一.概述 OKHttp是一个非常优秀的网络请求框架,已经被谷歌 ...

  2. OkHttp3源码解析(三)——连接池复用

    OKHttp3源码解析系列 OkHttp3源码解析(一)之请求流程 OkHttp3源码解析(二)--拦截器链和缓存策略 本文基于OkHttp3的3.11.0版本 implementation 'com ...

  3. 结合JDK源码看设计模式——桥接模式

    前言: 在我们还没学习框架之前,肯定都学过JDBC.百度百科对JDBC是这样介绍的[JDBC(Java DataBase Connectivity,java数据库连接)是一种用于执行SQL语句的Jav ...

  4. 小视频源码,设计模式单例模式

    小视频源码,设计模式单例模式实现的相关代码 .一,单线程时候推荐 /*** Created by Shinelon on 2018/10/11.* 单利模式 懒汉式 -->单线程推荐使用*/pu ...

  5. okhttp3源码初探

    okhttp3源码初探 简介 GET请求 使用 源码阅读 发起请求 eventListener的由来 真正的网络请求 拦截器 RetryAndFollowUpInterceptor拦截器 Bridge ...

  6. Mybatis源码解读-设计模式总结

    虽然我们都知道有26个设计模式,但是大多停留在概念层面,真实开发中很少遇到,Mybatis源码中使用了大量的设计模式,阅读源码并观察设计模式在其中的应用,能够更深入的理解设计模式. Mybatis至少 ...

  7. OkHttp3源码详解(三) 拦截器-RetryAndFollowUpInterceptor

    最大恢复追逐次数: private static final int MAX_FOLLOW_UPS = 20; 处理的业务: 实例化StreamAllocation,初始化一个Socket连接对象,获 ...

  8. 阅读源码学设计模式-单例模式

    有些编码套路是公认的,大家都参照其编写符合可观赏性的代码,那就是设计模式 现在.NETcore 默认提供了DI功能,那我想设计一个全局的引擎类,进行注入服务.解析服务.配置中间件.并且要求该引擎类全局 ...

  9. okHttp3 源码分析

    一, 前言 在上一篇博客OkHttp3 使用详解里,我们已经介绍了 OkHttp 发送同步请求和异步请求的基本使用方法. OkHttp 提交网络请求需要经过这样四个步骤: 初始化 OkHttpClie ...

最新文章

  1. Excel ,三步 快速实现应用一个公式到一列或一行中
  2. Python-面向对象的编程语言
  3. 宝塔面板网站一打开cpu百分百_BT宝塔面板打开这个功能网站快到起飞,降低宝塔面板内存和CPU使用率,降低运行负载...
  4. java(8)——和、|和||、!、^及三目运算符
  5. java bean工厂_从零构建轻量级Java Web框架
  6. WebApiClient的JsonPatch局部更新
  7. 三年半Java后端面试经历
  8. 漫步线性代数一——引言
  9. Magic Maze dfs + dp
  10. 使用 Repeater方式和完全静态页面使用AJAX读取和提交数据
  11. 电脑投屏电视怎么设置_夏普电视怎么投屏?投屏功能在哪?
  12. 会翻页GridView-1
  13. 1-EDA技术实用教程【名词解释】
  14. 数学建模写作指导20篇(一)-如何写好数学建模论文?
  15. 视频教程-鼎捷易飞ERP视频教程-ERP
  16. 一部手机失窃而揭露的窃取个人信息实现资金盗取的黑色产业链
  17. 我的第一个小程序(Discuz! + 微信小程序)
  18. 通俗易懂学Docker
  19. 怎么在微信公众平台上传PPT?
  20. Android---universal-image-loader应用

热门文章

  1. Y项目逸事之中国人设计的全球模板
  2. 专访唐杰:万亿参数大模型只是一个开始
  3. SAP PM入门系列20 - IH08 Equipment报表
  4. 应用于真实世界机器人的强化学习
  5. AI人脸识别 生物识别 活体检测 的发展历程
  6. SAP MIGO 报错-在例程WERT_SIMULIEREN字段NEUER_PREIS中字段溢出-
  7. 1亿参数4万样本BERT仍听不懂人话,我们离通用NLP能还有多远?
  8. 无人驾驶汽车想要“普渡众生”,还要经历15个磨难
  9. 快速了解Alias method/别名采样方法
  10. 城市智能化发展中,AI公司应该做什么?