OkHttp 源码学习(三) 链接拦截器

在写这篇博客前也思考过一个问题,现在主流的都在使用 okhttp 4.0 了,在研究 okhttp 3.0 还有意义吗,但是自己仔细思考了一下,公司现在还是使用 okhttp 3.0 ,而且 4.0 与 3.0 最大的区别就是由 java 换到了 kotlin ,思想上基本没有太多的变化,就决定还是继续看 3.0 的版本了

下面进入正文

在看代码之前就先聊一下一个 http 请求的过程,我们都知道一个请求从链接开始到请求结束需要经过 3次握手和4次挥手,还要建立一个 socket 通道与stream 流链接,整个过程是一个非常复杂的过程,而且每次请求如果都需要建立链接就比较麻烦,和浪费性能 那么 okhttp 是如何避免的呢

StreamAllocation 链接、通道管理者 流的持有者

先看一下 StreamAllocation 这个类的简单介绍

arduino 复制代码
public final class StreamAllocation {
  public final Address address;
  private Route route;
  // 连接池
  private final ConnectionPool connectionPool;
  // State guarded by connectionPool.
  private final RouteSelector routeSelector;
  private int refusedStreamCount;
  // 链接
  private RealConnection connection;
  private boolean released;
  private boolean canceled;
  // 流  管道
  private HttpStream stream;
}

StreamAllocation 的创建时机是在 第一个功能连接器 重试 重定向拦截器 RetryAndFollowUpInterceptor 中创建的,为什么要这么做呢

原因是在如果发生了异常或者重定向的时候,我们需要重新发起请求,此时如果上一次使用的通过如果还能被快速的复用,这无疑是一个巨大的速度提升

下面看一下ConnectInterceptor 的 intercept 代码

ini 复制代码
@Override public Response intercept(Chain chain) throws IOException {
  RealInterceptorChain realChain = (RealInterceptorChain) chain;
  Request request = realChain.request();
  // 找到 RetryAndFollowUpInterceptor 添加的 StreamAllocation
  StreamAllocation streamAllocation = realChain.streamAllocation();

  // We need the network to satisfy this request. Possibly for validating a conditional GET.
  boolean doExtensiveHealthChecks = !request.method().equals("GET");
  //获取一个流 
  HttpStream httpStream = streamAllocation.newStream(client, doExtensiveHealthChecks);
  RealConnection connection = streamAllocation.connection();

  return realChain.proceed(request, streamAllocation, httpStream, connection);
}

我们来看看 StreamAllocation 是如何找到流的

ini 复制代码
public HttpStream newStream(OkHttpClient client, boolean doExtensiveHealthChecks) {
  int connectTimeout = client.connectTimeoutMillis();
  int readTimeout = client.readTimeoutMillis();
  int writeTimeout = client.writeTimeoutMillis();
  boolean connectionRetryEnabled = client.retryOnConnectionFailure();

  try {
    //1: 先去找到一个链接
    RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
        writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);

    HttpStream resultStream;
    // 从字面意思就是根据条件选择 http1 或者 http2 协议的 Stream 
    if (resultConnection.framedConnection != null) {
      resultStream = new Http2xStream(client, this, resultConnection.framedConnection);
    } else {
      resultConnection.socket().setSoTimeout(readTimeout);
      resultConnection.source.timeout().timeout(readTimeout, MILLISECONDS);
      resultConnection.sink.timeout().timeout(writeTimeout, MILLISECONDS);
      resultStream = new Http1xStream(
          client, this, resultConnection.source, resultConnection.sink);
    }

    synchronized (connectionPool) {
      stream = resultStream;
      return resultStream;
    }
  } catch (IOException e) {
    throw new RouteException(e);
  }
}

先来看看他的 findHealthyConnection 是如何找到链接的

java 复制代码
private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
    int writeTimeout, boolean connectionRetryEnabled, boolean doExtensiveHealthChecks)
    throws IOException {
    // 无线循环
  while (true) {
    //寻找 链接
    RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
        connectionRetryEnabled);

    
    synchronized (connectionPool) {
    //如果是一个新的链接,不用检查
      if (candidate.successCount == 0) {
        return candidate;
      }
    }

    // doExtensiveHealthChecks= !request.method().equals("GET")  不是get请求
    if (!candidate.isHealthy(doExtensiveHealthChecks)) {
      noNewStreams();
      continue;
    }

    return candidate;
  }
}

这个方法开启了无线循环来寻找一个链接, 继续看 findConnection 方法

java 复制代码
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
    boolean connectionRetryEnabled) throws IOException {
  Route selectedRoute;
  synchronized (connectionPool) {
    if (released) throw new IllegalStateException("released");
    if (stream != null) throw new IllegalStateException("stream != null");
    if (canceled) throw new IOException("Canceled");

    RealConnection allocatedConnection = this.connection;
    // 有链接并且是一个新的链接
    if (allocatedConnection != null && !allocatedConnection.noNewStreams) {
      return allocatedConnection;
    }

    // 尝试从缓存池中获取一个 address 相同的链接(包括url  dns 等等)
    RealConnection pooledConnection = Internal.instance.get(connectionPool, address, this);
    if (pooledConnection != null) {
      this.connection = pooledConnection;
      return pooledConnection;
    }

    selectedRoute = route;
  }
    
  if (selectedRoute == null) {
    selectedRoute = routeSelector.next();
    synchronized (connectionPool) {
      route = selectedRoute;
      refusedStreamCount = 0;
    }
  }
  //建立新的链接
  RealConnection newConnection = new RealConnection(selectedRoute);
  acquire(newConnection);

  synchronized (connectionPool) {
     // 将新的链接缓存
    Internal.instance.put(connectionPool, newConnection);
    this.connection = newConnection;
    if (canceled) throw new IOException("Canceled");
  }
  //链接
  newConnection.connect(connectTimeout, readTimeout, writeTimeout, address.connectionSpecs(),
      connectionRetryEnabled);
  routeDatabase().connected(newConnection.route());

  return newConnection;
}

这里就先判断一下 StreamAllocation 他已经绑定的链接是否可用,不可用就去连接池中取一个address equals 的链接 ,连接池中没有则重新创建

在查看 connectionPool.put 方法时发现,他是在每次向里面添加链接的时候做清理工作的,而且这个连接池缓存链接的大小默认是5个,超时时间 5分钟

csharp 复制代码
public ConnectionPool() {
  this(5, 5, TimeUnit.MINUTES);
}

public ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {
  this.maxIdleConnections = maxIdleConnections;
  this.keepAliveDurationNs = timeUnit.toNanos(keepAliveDuration);

  // Put a floor on the keep alive duration, otherwise cleanup will spin loop.
  if (keepAliveDuration <= 0) {
    throw new IllegalArgumentException("keepAliveDuration <= 0: " + keepAliveDuration);
  }
}

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

到了这里整个流程基本也就分析完成了,明天将开始 kotlin 协程的篇章!!!

相关推荐
一只会跑会跳会发疯的猴子2 小时前
ajax访问阿里云天气接口,获取7天天气
前端·ajax·okhttp
消失的旧时光-19432 天前
okhttp 实现长连接的完整方案
okhttp
消失的旧时光-19432 天前
OkHttp 中实现断点续传 demo
okhttp
Qian Xiaoo4 天前
Ajax入门
前端·ajax·okhttp
androidwork5 天前
OkHttp 3.0源码解析:从设计理念到核心实现
android·java·okhttp·kotlin
Lhuu(重开版5 天前
Vue:Ajax
vue.js·ajax·okhttp
知月玄7 天前
网页前端开发(基础进阶4--axios)
okhttp
余渔鱼11237 天前
ajax学习手册
学习·ajax·okhttp
smallluan8 天前
入门AJAX——XMLHttpRequest(Post)
前端·ajax·okhttp
志存高远6611 天前
(面试)OkHttp实现原理
okhttp