1、基本介绍:
Connection连接层:
- 管理网络连接,发送新的请求,接收服务器访问,都是在ConnectInterceptor中完成的。
Connection:
- 客户端通过HTTP协议与服务器进行通信,首先需要建立连接,okhttp并没有使用URLConnection, 而是对socket直接进行封装,在socket之上建立了connection的概念,描述一个物理Socket连接,由连接池ConnectionPool管理。
RealConnection:
- Connection的一个具体实现类。
- 物理连接的封装,其内部有
List<WeakReference<StreamAllocation>>
的引用计数来管理当前Connection上的流。
ConnectionPool:【管理connection】
- 在连接层中有一个连接池,统一管理并复用所有的Socket连接,实现HTTP/1.1的Keep-Alive机制、HTTP/2的多路复用机制。
- 一个OkHttpClient只包含一个ConnectionPool,其实例化过程也是在OkHttpClient的实例化过程中实现。
- ConnectionPool各个方法的调用并没有直接对外暴露,而是通过OkHttpClient的Internal抽象类统一对外暴露。
- 当用户新发起一个网络请求时,OkHttp会首先从连接池中查找是否有符合要求的连接,如果有则直接通过该连接发送网络请求;否则新创建一个网络连接。
- 连接池中有socket回收,而这个回收是以RealConnection的弱引用
List<Reference<StreamAllocation>>
是否为0来为依据的。 - ConnectionPool有一个独立的线程cleanupRunnable来清理连接池,其触发时机有两个:
- 当连接池中put新的连接时
- 当connectionBecameIdle方法被调用时
Stream:
- okhttp3中使用建立在connection的物理连接之上的流的逻辑【stream】来进行数据通信。
- 在HTTP1.1以及之前的协议中,一个连接只能同时支持单个流,而SPDY和HTTP2.0协议则可以同时支持多个流。【Http2的多路复用机制】
StreamAllocation [ˌæləˈkeɪʃn] :【管理stream】
- RealConenction的引用计数器,管理一个连接上所有的流。
- okhttp通过一个StreamAllocation的引用列表来管理一个连接上的流,从而使得连接与流之间解耦。
- 从逻辑上一个Stream对应一个Call请求,但在实际网络请求过程中一个Call请求常常涉及到多次请求。如重定向,Authenticate等场景。所以准确地说,一个Stream对应一次call请求,而一个Call请求对应一组有逻辑关联的Stream。
- 一个RealConnection对应一个或多个StreamAllocation,所以StreamAllocation可以看做是RealConenction的计数器,当RealConnection的引用计数变为0,且长时间没有被其他请求重新占用,这个连接就将被释放。
HttpCodec:
- 编码Http的Request、解码Http的Response。
- connection负责与远程服务器建立连接, HttpCodec负责请求的写入、响应的读出、取消一个请求对应的流、释放完成任务的流等操作。
2、重要类:
2.1、ConnectionPool:
public final class ConnectionPool {
//用于清理过期的连接。【用线程池实现】
private static final Executor executor = new ThreadPoolExecutor(0 ,
Integer.MAX_VALUE, 60L , 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;
//独立的线程cleanupRunnable来清理连接池。
private final Runnable cleanupRunnable = new Runnable() {
@Override public void run() {
......
}
};
//Deque双端队列,用于维护当前所有连接的容器。
private final Deque<RealConnection> connections = new ArrayDeque<>();
//用来记录连接失败的Route的黑名单,当连接失败的时候就会把失败的线路加进去。
final RouteDatabase routeDatabase = new RouteDatabase();
boolean cleanupRunning;
......
/**
*返回符合要求的可重用连接,如果没有返回NULL
*/
RealConnection get(Address address, StreamAllocation streamAllocation, Route route) {
......
}
/*
* 清除重复的多路复用线程。
*/
Socket deduplicate(Address address, StreamAllocation streamAllocation) {
......
}
/*
* 将连接加入连接池
*/
void put(RealConnection connection) {
......
}
/*
* 当有连接空闲时唤起清理线程cleanup清理连接池
*/
boolean connectionBecameIdle(RealConnection connection) {
......
}
/**
* 扫描连接池,清除空闲连接
*/
long cleanup(long now) {
......
}
/*
* 标记泄露连接
*/
private int pruneAndGetAllocationCount(RealConnection connection, long now) {
......
}
}
2.2、StreamAllocation.findConnection():
- StreamAllocation在其findConnection方法内部通过调用get方法为其stream找到合适的连接,如果没有则新建一个连接。
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
boolean connectionRetryEnabled) throws IOException {
Route selectedRoute;
synchronized (connectionPool) {
if (released) throw new IllegalStateException("released");
if (codec != null) throw new IllegalStateException("codec != null");
if (canceled) throw new IOException("Canceled");
// 一个StreamAllocation刻画的是一个Call的数据流动,一个Call可能存在多次请求(重定向,Authenticate等),所以当发生类似重定向等事件时优先使用原有的连接
RealConnection allocatedConnection = this.connection;
if (allocatedConnection != null && !allocatedConnection.noNewStreams) {
return allocatedConnection;
}
// 试图从连接池中找到可复用的连接
Internal.instance.get(connectionPool, address, this, null);
if (connection != null) {
return connection;
}
selectedRoute = route;
}
// 获取路由配置,所谓路由其实就是代理,ip地址等参数的一个组合
if (selectedRoute == null) {
selectedRoute = routeSelector.next();
}
RealConnection result;
synchronized (connectionPool) {
if (canceled) throw new IOException("Canceled");
//拿到路由后可以尝试重新从连接池中获取连接,这里主要针对http2协议下清除域名碎片机制
Internal.instance.get(connectionPool, address, this, selectedRoute);
if (connection != null) return connection;
//新建连接
route = selectedRoute;
refusedStreamCount = 0;
result = new RealConnection(connectionPool, selectedRoute);
//修改result连接stream计数,方便connection标记清理
acquire(result);
}
// Do TCP + TLS handshakes. This is a blocking operation.
result.connect(connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled);
routeDatabase().connected(result.route());
Socket socket = null;
synchronized (connectionPool) {
// 将新建的连接放入到连接池中
Internal.instance.put(connectionPool, result);
// 如果同时存在多个连向同一个地址的多路复用连接,则关闭多余连接,只保留一个
if (result.isMultiplexed()) {
socket = Internal.instance.deduplicate(connectionPool, address, this);
result = connection;
}
}
closeQuietly(socket);
return result;
}
//1、查看当前streamAllocation是否有之前已经分配过的连接,有则直接使用该连接
//2、从连接池中查找可复用的连接,有则返回该连接
//3、配置路由,配置后再次从连接池中查找是否有可复用连接,有则直接返回
//4、新建一个连接,并修改其StreamAllocation标记计数,将其放入连接池中
//5、查看连接池是否有重复的多路复用连接,有则清除