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 协程的篇章!!!

相关推荐
allk552 天前
OkHttp源码解析(一)
android·okhttp
allk552 天前
OkHttp源码解析(二)
android·okhttp
aFakeProgramer2 天前
拆分PDF.html 办公小工具
okhttp
一壶浊酒..3 天前
ajax局部更新
前端·ajax·okhttp
洛克大航海6 天前
Ajax基本使用
java·javascript·ajax·okhttp
whltaoin12 天前
Java 网络请求 Jar 包选型指南:从基础到实战
java·http·okhttp·网络请求·retrofit
华农第一蒟蒻13 天前
谈谈跨域问题
java·后端·nginx·安全·okhttp·c5全栈
一直向钱15 天前
android 基于okhttp的socket封装
android·okhttp
linuxxx11015 天前
ajax回调钩子的使用简介
okhttp
一直向钱16 天前
android 基于okhttp 封装一个websocket管理模块,方便开发和使用
android·websocket·okhttp