经过上面的缓存拦截器,如果没有获取到可用的缓存,还是需要建立Socket连接。为当前请求找到合适的连接,可能复用已有连接也可能是重新创建的连接。
接下来就看一下ConnectInterceptor:
ConnectInterceptor
public final class ConnectInterceptor implements Interceptor {
public final OkHttpClient client;//之前使用的OkHttpClient对象
public ConnectInterceptor(OkHttpClient client) {
this.client = client;
}
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;//依旧是下一个拦截器链
Request request = realChain.request();//依旧是上面传过来的Request对象
StreamAllocation streamAllocation = realChain.streamAllocation();//StreamAllocation对象
// We need the network to satisfy this request. Possibly for validating a conditional GET.
boolean doExtensiveHealthChecks = !request.method().equals("GET");//如果不是GET方法,需要做额外的状态检测
HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);//用来编码HTTP requests和解码HTTP responses
RealConnection connection = streamAllocation.connection();//当前请求对应的连接对象
return realChain.proceed(request, streamAllocation, httpCodec, connection);//执行下一个拦截器,也就是CallServerInterceptor,进行网络请求和结果传递操作
}
}
上面的过程其实主要流程就是:
-
获取我们之前已经创建的RealInterceptorChain对象,Request对象,StreamAllocation对象;
-
创建一个HttpCodec对象,用来编码HTTP requests和解码HTTP responses;
-
获取一个RealConnection对象,也就是一个真实的网络连接;
-
继续执行下一个拦截器。
需要重点了解一下RealConnection类,ConnectionPool类,HttpCodec类,StreamAllocation类。
RealConnection
RealConnection类实现了Connection接口,首先看一下Connection接口:
public interface Connection {
/** Returns the route used by this connection. */
//这个连接的路由信息
Route route();
/**
* Returns the socket that this connection is using. Returns an {@linkplain
* javax.net.ssl.SSLSocket SSL socket} if this connection is HTTPS. If this is an HTTP/2
* connection the socket may be shared by multiple concurrent calls.
*/
//连接的套接字信息
Socket socket();
/**
* Returns the TLS handshake used to establish this connection, or null if the connection is not
* HTTPS.
*/
//建立连接的握手信息
@Nullable Handshake handshake();
/**
* Returns the protocol negotiated by this connection, or {@link Protocol#HTTP_1_1} if no protocol
* has been negotiated. This method returns {@link Protocol#HTTP_1_1} even if the remote peer is
* using {@link Protocol#HTTP_1_0}.
*/
//连接的协议信息
Protocol protocol();
}
关于Connection接口,需要提以下几点:
-
它是Socket和HTTP, HTTPS, or HTTPS+HTTP/2协议编码的Streams的连接链路;
-
和HttpURLConnection不一样,它并不单只单一的请求/响应的交互的连接,也是多次请求/响应交互的连接,即一个连接用于多次请求/响应的交互,也就是连接复用;
-
每个连接可以负载不同数量的流,HTTP/1.x协议基础上的连接每次最多只可以负载一个流,HTTP/2每次可以负载多个;
-
负载0个流的连接是空闲连接。保活空闲连接,因为重用现有的连接通常比建立新的连接更快。
-
一个逻辑上的请求Call可能需要多个Http Streams,有可能需要重定向或者证书验证,我们可以将这序列中的所有流都是用同一个物理连接。
再看一下RealConnection,它实现了Connection接口:
RealConnection
public final class RealConnection extends Http2Connection.Listener implements Connection {
private static final String NPE_THROW_WITH_NULL = "throw with null exception";
private static final int MAX_TUNNEL_ATTEMPTS = 21;
private final ConnectionPool connectionPool;//连接池
private final Route route;//路由信息
// The fields below are initialized by connect() and never reassigned.
//下面这些成员属性在connect方法中完成初始化,并且,不会再次赋值;
/** The low-level TCP socket. */
private Socket rawSocket;//底层的套接字
private Socket socket;//应用层的Socket,要么是SSLSocket,要么是rawSocket本身
private Handshake handshake;//握手信息
private Protocol protocol;//协议信息,也就是HTTP_1_1或者HTTP_2
private Http2Connection http2Connection;//http2连接
private BufferedSource source;//和服务器交互相关的输入输出流
private BufferedSink sink;//和服务器交互相关的输入输出流
// The fields below track connection state and are guarded by connectionPool.
//下面这些字段是属于表示链接状态的字段,并且有connectPool统一管理
/** If true, no new streams can be created on this connection. Once true this is always true. */
//如果noNewStreams被设为true,则noNewStreams一直为true,不会被改变,并且表示这个链接不会再创新的stream流
public boolean noNewStreams;
//成功的次数
public int successCount;
/**
* The maximum number of concurrent streams that can be carried by this connection. If {@code
* allocations.size() < allocationLimit} then new streams can be created on this connection.
*/
//此链接可以承载最大并发流的限制,如果不超过限制,可以在这个连接上增加Http流
public int allocationLimit = 1;
/** Current streams carried by this connection. */
//allocations是关联StreamAllocation,它用来统计在一个连接上建立了哪些流,
//通过StreamAllocation的acquire方法和release方法可以将一个allcation添加到链表或者移除链表,
public final List<Reference<StreamAllocation>> allocations = new ArrayList<>();
//构造器
public RealConnection(ConnectionPool connectionPool, Route route) {
this.connectionPool = connectionPool;//连接池,已经在OkHttpClient中创建完成了
this.route = route;//路由信息
}
}
通过以上代码,可以得到以下结论:
-
除了route和connectionPool,其他都是在connect方法中进行初始化;
-
通过sink和source和服务器以流的形式交互;
-
noNewStream表示该连接不可用,这个值一旦被设为true,则这个connection上将不会再创建stream;
-
allocationLimit表示此链接可以承载最大并发流的限制;
-
allocations表示当前连接上分配的Http流;
-
route和connectionPool对象在之前已经创建。
再看一下连接池ConnectionPool:
ConnectionPool
/**
* Manages reuse of HTTP and HTTP/2 connections for reduced network latency. HTTP requests that
* share the same {@link Address} may share a {@link Connection}. This class implements the policy
* of which connections to keep open for future use.
*/
//管理Http和Http/2连接,降低网络延迟。
//同一个Address可以共享一个连接。这个类实现了那些连接可以保活以便后续的请求可以使用。
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.
*/
//线程池中的线程用于清理过期的连接。在线程池中最多会有一个线程。这个线程池允许自己被GC回收
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 Deque<RealConnection> connections = new ArrayDeque<>();
final RouteDatabase routeDatabase = new RouteDatabase();//路由数据库
boolean cleanupRunning;//清理连接任务正在执行的标志
//清理连接的任务
private final Runnable cleanupRunnable = new Runnable() {
@Override public void run() {
while (true) {//无限循环
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) {
}
}
}
}
}
};
/**
* Create a new connection pool with tuning parameters appropriate for a single-user application.
* The tuning parameters in this pool are subject to change in future OkHttp releases. Currently
* this pool holds up to 5 idle connections which will be evicted after 5 minutes of inactivity.
*/
//创建一个连接池。目前这个连接池默认可以存活5个空闲线程,并且,5分钟之内空闲的话,该连接会被关闭回收。
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);
}
}
/**
* Performs maintenance on this pool, evicting the connection that has been idle the longest if
* either it has exceeded the keep alive limit or the idle connections limit.
*
* <p>Returns the duration in nanos to sleep until the next scheduled call to this method. Returns
* -1 if no further cleanups are required.
*/
//对连接池执行维护,如果这个链接未超过保活时长限制或空闲链接数量限制,将空闲时间最长的链接移除连接池。
long cleanup(long now) {
int inUseConnectionCount = 0;//正在使用的连接数量
int idleConnectionCount = 0;//空闲连接数量
RealConnection longestIdleConnection = null;//记录空闲时间最长的链接
long longestIdleDurationNs = Long.MIN_VALUE;//最长的空闲时长
// Find either a connection to evict, or the time that the next eviction is due.
//找到需要移除的线程,或者找到下一次查找的时间
synchronized (this) {
//循环
for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
RealConnection connection = i.next();
// If the connection is in use, keep searching.
//正在使用
if (pruneAndGetAllocationCount(connection, now) > 0) {
inUseConnectionCount++;
continue;
}
//执行到这里说明是空闲链接
idleConnectionCount++;
// If the connection is ready to be evicted, we're done.
//计算当前链接的空闲时长
long idleDurationNs = now - connection.idleAtNanos;
//当前链接的空闲时长大于其他所有链接中空闲最长的时长,记录longestIdleDurationNs和longestIdleConnection
if (idleDurationNs > longestIdleDurationNs) {
longestIdleDurationNs = idleDurationNs;
longestIdleConnection = connection;
}
}
//判断最长空闲时长
if (longestIdleDurationNs >= this.keepAliveDurationNs
|| idleConnectionCount > this.maxIdleConnections) {
// We've found a connection to evict. Remove it from the list, then close it below (outside
// of the synchronized block).
//从连接池中移除
connections.remove(longestIdleConnection);
} else if (idleConnectionCount > 0) {//有空闲链接
// A connection will be ready to evict soon.
//返回我们规定的可以保活的最长时间和当前所有链接中空闲的最长时长。
return keepAliveDurationNs - longestIdleDurationNs;
} else if (inUseConnectionCount > 0) {//没有空闲链接,有正在使用的链接
// All connections are in use. It'll be at least the keep alive duration 'til we run again.
return keepAliveDurationNs;//直接返回我们规定的链接可以空闲的时长
} else {
// No connections, idle or in use.//没有链接,没有空闲或者正在使用的
cleanupRunning = false;
return -1;
}
}
//如果执行到这里,说明已经remove了一个空闲时间做长的链接,这个时候需要关闭它的socket
closeQuietly(longestIdleConnection.socket());
// Cleanup again immediately.
//立刻执行下一次清理任务
return 0;
}
}
以上代码说明:
-
连接池的作用是管理Http和Http/2连接,实现网络复用,降低网络延迟;
-
同一个Address的请求可以服用同一个连接;
-
清理过期链接是通过线程池中的线程实现的,清理过期连接的任务以一个Runnable的形式交给线程池;
-
连接池中的连接存放在队列中,即连接池的数据结构是队列;
-
当前版本的连接池,默认可以保存5个空闲线程,并且5分钟内依旧空闲的话,将被关闭回收。
-
连接池实例是在OkHttpClient中创建的,一个应用只会有一个连接池。
接下来看一下HttpCodec这个类:
HttpCodec
HttpCodec也是一个接口,作用是编码HTTP requests和解码HTTP responses,即再次将前面的Request对象转成Http协议下的数据流,并解析服务器返回来的Http Response。
public interface HttpCodec {
/**
* The timeout to use while discarding a stream of input data. Since this is used for connection
* reuse, this timeout should be significantly less than the time it takes to establish a new
* connection.
*/
int DISCARD_STREAM_TIMEOUT_MILLIS = 100;
/** Returns an output stream where the request body can be streamed. */
//写入请求内容
Sink createRequestBody(Request request, long contentLength);
/** This should update the HTTP engine's sentRequestMillis field. */
//写入请求头
void writeRequestHeaders(Request request) throws IOException;
/** Flush the request to the underlying socket. */
//将请求写入到底层Socket
void flushRequest() throws IOException;
/** Flush the request to the underlying socket and signal no more bytes will be transmitted. */
//完成请求,也有写入请求至底层Socket的功能,同时标记为没有其他的内容需要发送了。
void finishRequest() throws IOException;
/**
* Parses bytes of a response header from an HTTP transport.
*
* @param expectContinue true to return null if this is an intermediate response with a "100"
* response code. Otherwise this method never returns null.
*/
//读取响应头
Response.Builder readResponseHeaders(boolean expectContinue) throws IOException;
/** Returns a stream that reads the response body. */
//读取响应内容
ResponseBody openResponseBody(Response response) throws IOException;
/**
* Cancel this stream. Resources held by this stream will be cleaned up, though not synchronously.
* That may happen later by the connection pool thread.
*/
//取消这个流。这个流占用的资源将被会释放。这个清理工作可能会在连接池线程来执行,并不是同步的。
void cancel();
}
/**
* A socket connection that can be used to send HTTP/1.1 messages. This class strictly enforces the
* following lifecycle:
*/
//一个可以用来发送HTTP/1.1信息的socket链接,它将严格遵循以下的流程:
//写请求头部
//打开一个sink,写入请求内容体。
//关闭这个sink
//读取响应内容体
//打开一个source,读取响应内容体
//关闭这个source
//如果请求没有请求内容体,可以不用经过创建和关闭请求内容体
//如果响应没有响应内容体,可以不用经过读取和关闭source
public final class Http1Codec implements HttpCodec {
}
/** Encode requests and responses using HTTP/2 frames. */
//编码基于HTTP/2的请求和响应
public final class Http2Codec implements HttpCodec {
}
上面的代码说明:
-
HttpCodec的对象功能有:
-
写入请求头部;
-
写入请求内容体;
-
读取响应头部;
-
读取响应内容体;
-
取消请求,清理资源;
-
-
HttpCodec的两个实现类Http1Codec和Http2Codec,分别用于HTTP/1.1协议和HTTP/2协议下的请求/响应。根据需求,创建响应的HttpCodec对象。处理过程都是:
-
写请求头部;
-
打开一个sink,写入请求内容体;
-
关闭这个sink;
-
读取响应内容体;
-
打开一个source,读取响应内容体;
-
关闭这个source。
-
接下来看一下协调上面这几个类:RealConnection类,ConnectionPool类,HttpCodec类的StreamAllocation类。
StreamAllocation
-
StreamAllocation类,它的作用是协调了这三个类的关系:
-
Connections:到远程服务器的物理连接,涉及到RealConnection类和ConnectionPool类;
-
Streams:依赖于物理连接的Http请求/响应对。每一个连接都有最大并发流的限制。HTTP/1.x协议基础上的连接每次最多只可以负载一个流,HTTP/2每次可以负载多个。涉及到HttpCodec类。
-
Calls:定义了流的逻辑序列,这个序列通常是一个初始请求以及它的重定向请求。
public final class StreamAllocation {
public final Address address;//请求网络地址
private RouteSelector.Selection routeSelection;//路由选择信息
private Route route;//路由信息
private final ConnectionPool connectionPool;//连接池
public final Call call;//前面创建的Call
public final EventListener eventListener;//时间监听器,将网络请求过程中的事件通过接口回调的形式通知前端
private final Object callStackTrace;//日志信息
// State guarded by connectionPool.
private final RouteSelector routeSelector;//路由选择器
private int refusedStreamCount;//流被拒绝的次数
private RealConnection connection;//网络链接
private boolean reportedAcquired;
private boolean released;//是否已经被释放
private boolean canceled;//是否已经被取消
private HttpCodec codec;//用来编码HTTP requests和解码HTTP responses。其实就是Http Stream。
}
从上面代码中看到,StreamAllocation的成员属性有:
-
网络地址Address;
-
路由选择器和路由选择信息,以及路由信息;
-
连接池ConnectionPool,在OkHttpClient中已经创建连接池对象;
-
网络连接RealConnection,将从连接池中获取,或者重新创建;
-
请求信息Call;
-
请求/响应流HttpCodec,需要基于网络连接RealConnection;