在写这篇博客前也思考过一个问题,现在主流的都在使用 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 协程的篇章!!!