Http1.1的Keep-Alive机制和Http2.0的多路复用机制,在实现上都需要引入连接池来维护网络连接。OkHttp中连接池的实现——连接拦截器ConnectInterceptor。
ConnectInterceptor
ConnectInterceptor#intercept
连接拦截器代码如下:
public final class ConnectInterceptor implements Interceptor {
public final OkHttpClient client;
public ConnectInterceptor(OkHttpClient client) {
this.client = client;
}
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
Transmitter transmitter = realChain.transmitter();
// We need the network to satisfy this request. Possibly for validating a conditional GET.
boolean doExtensiveHealthChecks = !request.method().equals("GET");
Exchange exchange = transmitter.newExchange(chain, doExtensiveHealthChecks);
return realChain.proceed(request, transmitter, exchange);
}
}
这里使用transmitter.newExchange获取Exchange实例。作为参数传入调用拦截器链的proceed方法。(proceed方法有三个参数,因为若没有配置网络拦截器发的话,就会使用CallServerInterceptor进行真正的网络IO操作)。
Exchange负责从创建连接的IO流中写入请求和读取响应,完成一次请求/响应的过程。在CallServerInterceptor会看到它真正的作用,这里先忽略。继续看Transmitter的newChange方法。
Transmitter是okhttp中应用层和网络层的桥梁,管理同一个Call的所有连接、请求、响应和IO流之间的关系。
Exchange#newExchange
//Transmitter.java
Exchange newExchange(Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
//...省略异常处理
//通过ExchangeFinder的find方法找到一个ExchangeCodec(编码解码)
ExchangeCodec codec = exchangeFinder.find(client, chain, doExtensiveHealthChecks);
//创建Exchange,并把ExchangeCodec实例codec传进去,所以Exchange内部持有ExchangeCodec实例
Exchange result = new Exchange(this, call, eventListener, exchangeFinder, codec);
//...
return result;
}
ExchangeFinder对象早在RetryAndFollowUpInterceptor中通过Transmitter的prepareToConnect方法创建,它的find方法是连接真正创建的地方。
ExchangFinder就是负责连接的创建,把创建好的连接放入连接池,如果连接池中已经有该链接,就直接取出来复用,所以ExchangeFinder管理着两个重要的角色:RealConnection、RealConnectionPool。
ExchangeFinder#find
public ExchangeCodec find(
OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
int connectTimeout = chain.connectTimeoutMillis();
int readTimeout = chain.readTimeoutMillis();
int writeTimeout = chain.writeTimeoutMillis();
int pingIntervalMillis = client.pingIntervalMillis();
boolean connectionRetryEnabled = client.retryOnConnectionFailure();
try {
//找到一个健康的连接
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
//利用连接实例化ExchangeCodec对象,如果是HTTP2.0返回Http2ExchangeCodec,否则返回Http1ExchangeCodec
return resultConnection.newCodec(client, chain);
} catch (RouteException e) {
trackFailure();
throw e;
} catch (IOException e) {
trackFailure();
throw new RouteException(e);
}
}
findHealthyConnection方法就是去寻找可用TCP连接的这个方法内部和连接池有紧密的联系。
ExchangeFinder#findHealthyConnection
private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,int writeTimeout,int pingIntervalMillis, boolean connectionRetryEnabled,
boolean doExtensiveHealthChecks) throws IOException {
while (true) {
//找连接
RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
pingIntervalMillis, connectionRetryEnabled);
// 是新连接,不用检测
synchronized (connectionPool) {
if (candidate.successCount == 0 && !candidate.isMultiplexed()) {
return candidate;
}
}
// 检测不合格,继续找
if (!candidate.isHealthy(doExtensiveHealthChecks)) {
candidate.noNewExchanges();
continue;
}
return candidate;
}
这里循环找连接,如果是不健康的连接,标记不可用(标记会移除,后面的连接池会有),然后继续找。健康是指连接可以承载新的数据流,socket是连接状态。下面进入findConnection方法。
ExchangeFinder#findConnection
//为承载新的数据流寻找连接。寻找的顺序是已分配的连接、连接池、新建连接
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
boolean foundPooledConnection = false;
RealConnection result = null;
Route selectedRoute = null;
RealConnection releasedConnection;
Socket toClose;
synchronized (connectionPool) {
//请求已被取消
if (transmitter.isCanceled()) throw new IOException("Canceled");
hasStreamFailure = false;
// 尝试使用已经创建过的连接,已经创建过的连接可能已经被限制创建新的流
releasedConnection = transmitter.connection;
// 如果已经创建过的连接已经被限制创建新的流,就释放该连接(releaseConnectionNoEvents中会把该连接置空,并返回该连接的socket已关闭)
toClose = transmitter.connection != null && transmitter.connection.noNewExchanges
? transmitter.releaseConnectionNoEvents()
: null;
//已经创建过的连接还能使用,就直接使用它当做结果
if (transmitter.connection != null) {
result = transmitter.connection;
releasedConnection = null;
}
//已经创建过的连接不能使用
if (result == null) {
// 尝试从连接池中找可用的连接,如果找到,这个连接会赋值先保存在Transmitter中
if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, null, false)) {
//从连接池中找到可用的连接,如果找到,这个连接会赋值先保存在Transmitter中
foundPooledConnection = true;
result = transmitter.connection;
} else if (nextRouteToTry != null) {
selectedRoute = nextRouteToTry;
nextRouteToTry = null;
} else if (retryCurrentRoute()) {
selectedRoute = transmitter.connection.route();
}
}
}
closeQuietly(toClose);
if (releasedConnection != null) {
eventListener.connectionReleased(call, releasedConnection);
}
if (foundPooledConnection) {
eventListener.connectionAcquired(call, result);
}
if (result != null) {
// 如果有已分配可用连接 或 从连接池获取到连接,结束。没有就走下面的新建连接过程
return result;
}
// 看是否需要路由选择,多IP操作
boolean newRouteSelection = false;
if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) {
newRouteSelection = true;
routeSelection = routeSelector.next();
}
List routes = null;
synchronized (connectionPool) {
if (transmitter.isCanceled()) throw new IOException("Canceled");
//如果有下一个路由
if (newRouteSelection) {
routes = routeSelection.getAll();
// 这里第二次尝试从连接池中找可用连接
if (connectionPool.transmitterAcquirePooledConnection(
address, transmitter, routes, false)) {
//从连接池中找到了可用的连接
foundPooledConnection = true;
result = transmitter.connection;
}
}
//在连接池中没有找到可用连接
if (!foundPooledConnection) {
if (selectedRoute == null) {
selectedRoute = routeSelection.next();
}
// 所以这里新创建一个连接,后面会进行socket连接
result = new RealConnection(connectionPool, selectedRoute);
connectingConnection = result;
}
}
// 如果在连接池中找到可用的连接,直接返回该连接
if (foundPooledConnection) {
eventListener.connectionAcquired(call, result);
return result;
}
// 第二次没成功,就把新建的连接进行TCP+TLS握手,与服务端建立连接,是阻塞操作
result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
connectionRetryEnabled, call, eventListener);
connectionPool.routeDatabase.connected(result.route());
Socket socket = null;
synchronized (connectionPool) {
connectingConnection = null;
// 最后一次尝试从连接池获取,最后一个参数为true,即要求多路复用
// 为了保证多路复用,会再次确认连接池中此时是否有同样的连接
if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, routes, true)) {
// 如果获取到,就关闭在创建里的连接,返回获取的连接
result.noNewExchanges = true;
socket = result.socket();
result = transmitter.connection;
// 这个刚刚连接成功的路由,可以用作下次 尝试的路由
nextRouteToTry = selectedRoute;
} else {
//最后一次尝试也没有的话就把刚刚新建的连接存入连接池
connectionPool.put(result);
transmitter.acquireConnectionNoEvents(result);
}
}
closeQuietly(socket);
eventListener.connectionAcquired(call, result);
return result;
}
这一节的代码比较长,所以这里再来总结下流程:
findConnection方法目的就是为承载新的数据流寻找连接,寻找的顺序是已分配的连接、连接池、新建连接。、
- 首先会尝试使用已给数据流分配的连接(transmitter.connection),已分配连接的情况如重定向时的再次请求,说明上次已经有了连接;
- 若没有已分配的可用连接,就尝试从连接池中匹配获取。因为此时没有路由信息,所以匹配条件:address一致——host、port、代理等一致,且匹配的连接可以接受新的数据流;
- 若从连接池没有获取到,则取下一个代理的路由信息(多个route,即多个IP地址),再次尝试从连接池获取,此时可能因为连接合并而匹配到;
- 若第二次也没有获取到,就创建RealConnection实例,进行TCP+TLS握手,与服务端建立连接;
- 此时为了确保Http2.0连接的多路复用性,会第三次从连接池匹配。因为新建立的连接的握手过程是非线程安全的,所以此时可能连接池新存入了相同的连接;
- 第三次若匹配到,就使用已有连接,释放刚刚新建的连接;若未匹配到,则把新连接存入连接池并返回。
这段代码里面包含了太多的信息,RealConnection是什么、RealConnectionPool内部到底是如何实现的,路由是计网里学到的有路由表那样的操作么。
RealConnection-连接实现
RealConnection实现了Connection接口,内部利用Socket建立连接,如下:
public interface Connection {
//返回这个连接使用的Route
Route route();
//返回这个连接使用的socket
Socket socket();
//如果是HTTPS,返回TLS握手信息用于建立连接,否则返回null
Handshake handshake();
//返回应用层使用的协议,Protocol是一个枚举,如HTTP1.1、HTTP2
Protocol protocol();
}
public final class RealConnection extends Http2Connection.Listener implements Connection {
public final RealConnectionPool connectionPool;
//路由
private final Route route;
//内部使用这个rawSocket在TCP层建立连接
private Socket rawSocket;
//如果没有使用HTTPS,那么socket == rawSocket,否则这个socket == SSLSocket
private Socket socket;
//TLS握手
private Handshake handshake;
//应用层协议
private Protocol protocol;
//HTTP2连接
private Http2Connection http2Connection;
//okio库的BufferedSource和BufferedSink,相当于javaIO的输入输出流
private BufferedSource source;
private BufferedSink sink;
public RealConnection(RealConnectionPool connectionPool, Route route) {
this.connectionPool = connectionPool;
this.route = route;
}
public void connect(int connectTimeout, int readTimeout, int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled, Call call, EventListener eventListener) {
//...
}
//...
}
这里的connect方法,可以和外部服务器建立连接。
//RealConnection.java
public void connect(int connectTimeout, int readTimeout, int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled, Call call, EventListener eventListener) {
if (protocol != null) throw new IllegalStateException("already connected");
RouteException routeException = null;
List connectionSpecs = route.address().connectionSpecs();
ConnectionSpecSelector connectionSpecSelector = new ConnectionSpecSelector(connectionSpecs);
//路由选择
if (route.address().sslSocketFactory() == null) {
if (!connectionSpecs.contains(ConnectionSpec.CLEARTEXT)) {
throw new RouteException(new UnknownServiceException(
"CLEARTEXT communication not enabled for client"));
}
String host = route.address().url().host();
if (!Platform.get().isCleartextTrafficPermitted(host)) {
throw new RouteException(new UnknownServiceException(
"CLEARTEXT communication to " + host + " not permitted by network security policy"));
}
} else {
if (route.address().protocols().contains(Protocol.H2_PRIOR_KNOWLEDGE)) {
throw new RouteException(new UnknownServiceException(
"H2_PRIOR_KNOWLEDGE cannot be used with HTTPS"));
}
}
//开始连接
while (true) {
try {
if (route.requiresTunnel()) {//如果是通道模式,则建立通道连接
connectTunnel(connectTimeout, readTimeout, writeTimeout, call, eventListener);
if (rawSocket == null) {
// We were unable to connect the tunnel but properly closed down our resources.
break;
}
} else {//否则进行Socket连接,大部分是这种情况
connectSocket(connectTimeout, readTimeout, call, eventListener);
}
//建立HTTPS连接
establishProtocol(connectionSpecSelector, pingIntervalMillis, call, eventListener);
break;
}
//...省略异常处理
if (http2Connection != null) {
synchronized (connectionPool) {
allocationLimit = http2Connection.maxConcurrentStreams();
}
}
}
一般都会调用connectSocket方法建立socket连接。
//RealConnection.java
private void connectSocket(int connectTimeout, int readTimeout, Call call,
EventListener eventListener) throws IOException {
Proxy proxy = route.proxy();
Address address = route.address();
//根据代理类型的不同创建Socket
rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
? address.socketFactory().createSocket()
: new Socket(proxy);
eventListener.connectStart(call, route.socketAddress(), proxy);
rawSocket.setSoTimeout(readTimeout);
try {
//1、建立Socket连接
Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);
}
//...省略异常处理
try {
//获得Socket的输入输出流
source = Okio.buffer(Okio.source(rawSocket));
sink = Okio.buffer(Okio.sink(rawSocket));
}
//...省略异常处理
}
Platform是okhttp中根据不同Android版本平台差异实现的一个兼容类。Platform的connectSocket方法最终会调用rawSocket的connect()方法建立其Socket连接,建立socket连接后,就可以通过Socket连接获得输入输出流source和sink,okhttp就可以从source读取或往sink写入数据。source和sink是BufferedSource和BufferedSink类型,他们来自于okio库。
RealConnectionPool-连接池
ConnectionPool,用于管理http1.1/http2.0连接重用,以减少网络延迟。相同Address的http请求可以共享一个连接,ConnectionPool就是实现了连接的复用。
public final class RealConnectionPool {
//线程池
private static final Executor executor = new ThreadPoolExecutor(0 /* corePoolSize */,
Integer.MAX_VALUE /* maximumPoolSize */,
60L /* keepAliveTime */,
TimeUnit.SECONDS,
new SynchronousQueue<>(), Util.threadFactory("OkHttp ConnectionPool", true));
/** The maximum number of idle connections for each address. */
private final int maxIdleConnections;
private final long keepAliveDurationNs;
//清理连接任务,在executor中执行
private final Runnable cleanupRunnable = () -> {
while (true) {
//调用cleanup方法执行清理逻辑
long waitNanos = cleanup(System.nanoTime());
if (waitNanos == -1) return;
if (waitNanos > 0) {
long waitMillis = waitNanos / 1000000L;
waitNanos -= (waitMillis * 1000000L);
synchronized (RealConnectionPool.this) {
try {
//调用wait方法进入等待
RealConnectionPool.this.wait(waitMillis, (int) waitNanos);
} catch (InterruptedException ignored) {
}
}
}
}
};
//双端队列,保存连接
private final Deque connections = new ArrayDeque<>();
final RouteDatabase routeDatabase = new RouteDatabase();
boolean cleanupRunning;
public RealConnectionPool(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);
}
}
//返回空连接数
public synchronized int idleConnectionCount() {
int total = 0;
for (RealConnection connection : connections) {
if (connection.transmitters.isEmpty()) total++;
}
return total;
}
//返回池子中的连接数
public synchronized int connectionCount() {
return connections.size();
}
}
从连接池的部分代码可看到,其内部维护了一个连接池,用来执行清理连接任务是线程cleanRunnable,还维护了一个双端队列connections,用来缓存已经创建的连接。创建一次连接要经历TCP握手,如果是HTTP还要经历TLS握手,握手的过程都是耗时的,所以为了提高效率,就需要connections来对连接进行缓存,从而可以复用;还有如果连接使用完毕,长时间不释放,也会造成资源的浪费,所以就需要cleanupRunnable定时清理无用的连接,okhttp支持5个并发连接,默认每个连接keep-alive 5分钟。
- cleanup()
//RealConnectionPool.java
long cleanup(long now) {
int inUseConnectionCount = 0;//正在使用连接数
int idleConnectionCount = 0;//空闲连接数
RealConnection longestIdleConnection = null;
long longestIdleDurationNs = Long.MIN_VALUE;
synchronized (this) {
//遍历所有连接,记录空闲连接和正在使用连接各自的数量
for (Iterator i = connections.iterator(); i.hasNext(); ) {
RealConnection connection = i.next();
//如果该连接还在使用,pruneAndGetAllocationCount种通过引用计数的方式判断一个连接是否空闲
if (pruneAndGetAllocationCount(connection, now) > 0) {
//使用连接数加1
inUseConnectionCount++;
continue;
}
//该连接没有在使用
//空闲连接数加1
idleConnectionCount++;
//记录keepalive时间最长的那个空闲连接
long idleDurationNs = now - connection.idleAtNanos;
if (idleDurationNs > longestIdleDurationNs) {
longestIdleDurationNs = idleDurationNs;
//这个连接很可能被移除,因为空闲时间太长
longestIdleConnection = connection;
}
}
//跳出循环后
//默认keepalive时间keepAliveDurationNs最长为5分钟,空闲连接数idleConnectionCount最大为5个
if (longestIdleDurationNs >= this.keepAliveDurationNs || idleConnectionCount > this.maxIdleConnections) {//如果longestIdleConnection的keepalive时间大于5分钟 或 空闲连接数超过5个
//把longestIdleConnection连接从队列清理掉
connections.remove(longestIdleConnection);
} else if (idleConnectionCount > 0) {//如果空闲连接数小于5个 并且 longestIdleConnection连接还没到期清理
//返回该连接的到期时间,下次再清理
return keepAliveDurationNs - longestIdleDurationNs;
} else if (inUseConnectionCount > 0) {//如果没有空闲连接 且 所有连接都还在使用
//返回keepAliveDurationNs,5分钟后再清理
return keepAliveDurationNs;
} else {
// 没有任何连接,把cleanupRunning复位
cleanupRunning = false;
return -1;
}
}
//把longestIdleConnection连接从队列清理掉后,关闭该连接的socket,返回0,立即再次进行清理
closeQuietly(longestIdleConnection.socket());
return 0;
}
cleanup清理连接的逻辑如下:
- 首先遍历所有连接,记录空闲连接数idleConnectionCount和正在使用连接数inUseConnectionCount,记录空闲连接数时,还要找出空闲时间最长的空闲连接longestIdleConnection,这个连接是很有可能被清理的;
- 遍历完后,根据最大空闲时长和最大空闲连接数来决定是否清理longestIdleConnection.
- 如果longestIdleConnection空闲时间>最大空闲时长或空闲连接数>最大空闲连接数,那么该连接就会从队列中移除,然后关闭该连接的socket,返回0,立即再次进行清理;
- 如果空闲连接数<5 且 longestIdleConnection空闲时间<最大空闲时长,还没到期清理,就返回该连接的到期时间,下次再清理;
- 如果没有空闲连接 且 所有连接都还在使用,那么返回默认的keepAlive时间,5分钟后再清理;
- 没有任何的连接,idleConnectionCount和inUseConnectionCount都为0,把cleanupRunning复位,等待下一次put连接时,再次使用线程池执行cleanupRunnable。
private int pruneAndGetAllocationCount(RealConnection connection, long now) {
//连接上的数据流,弱引用列表
List> references = connection.transmitters;
for (int i = 0; i < references.size(); ) {
Reference reference = references.get(i);
if (reference.get() != null) {
i++;
continue;
}
// 到这里,transmitter是泄漏的,要移除,且此连接不能再承载新的数据流(泄漏的原因就是下面的message)
TransmitterReference transmitterRef = (TransmitterReference) reference;
String message = "A connection to " + connection.route().address().url()
+ " was leaked. Did you forget to close a response body?";
Platform.get().logCloseableLeak(message, transmitterRef.callStackTrace);
references.remove(i);
connection.noNewExchanges = true;
//连接因为泄漏没有数据流了,那么可以立即移除了。所以设置 开始空闲时间 是5分钟前(厉害厉害!)
if (references.isEmpty()) {
connection.idleAtNanos = now - keepAliveDurationNs;
return 0;
}
}
//返回连接上的数据流数量,大于0说明正在使用。
return references.size();
}
connection.transmitters表示在此连接上的数据流,transmitter size大于1表示多个请求复用此连接。Transmitter是弱引用。
下面看看连接池除了清理空闲线程的其他操作:
存
void put(RealConnection connection) {
assert (Thread.holdsLock(this));
if (!cleanupRunning) {
cleanupRunning = true;
executor.execute(cleanupRunnable);
}
connections.add(connection);
}
这里就是把连接存入队列,同时开始循环尝试清理过期连接。
取
//为transmitter 从连接池 获取 对应address的连接。若果routes不为空,可能会因为 连接合并(复用) 而获取到HTTP/2连接
boolean transmitterAcquirePooledConnection(Address address, Transmitter transmitter,
@Nullable List routes, boolean requireMultiplexed) {
assert (Thread.holdsLock(this));
for (RealConnection connection : connections) {
if (requireMultiplexed && !connection.isMultiplexed()) continue;
if (!connection.isEligible(address, routes)) continue;
transmitter.acquireConnectionNoEvents(connection);
return true;
}
return false;
}
void acquireConnectionNoEvents(RealConnection connection) {
assert (Thread.holdsLock(connectionPool));
if (this.connection != null) throw new IllegalStateException();
this.connection = connection;
connection.transmitters.add(new TransmitterReference(this, callStackTrace));
}
transmitterAcquirePooledConnection意思是 为transmitter 从连接池 获取连接,实际上transmitter就代表一个数据流,也就是一个http请求。注意到,在遍历中 经过判断后是transmitter的acquireConnectionNoEvents方法,即把匹配到的connection赋给transmitter。
删
//关闭并移除所有空闲连接
public void evictAll() {
List evictedConnections = new ArrayList<>();
synchronized (this) {
for (Iterator i = connections.iterator(); i.hasNext(); ) {
RealConnection connection = i.next();
if (connection.transmitters.isEmpty()) {
connection.noNewExchanges = true;
evictedConnections.add(connection);
i.remove();
}
}
}
for (RealConnection connection : evictedConnections) {
closeQuietly(connection.socket());
}
}
遍历连接池,如果连接上的数据流是空,那么就从连接池移除并且关闭。
CallServerIntercepter
请求服务拦截器,进行真正的网络IO读写——写入http请求的header和body数据、读取响应的header和body。
在ConnectInterceptor中主要介绍了如何寻找连接以及连接池如何管理连接。在获取到连接后,调用了RealConnection的newCodec方法返回ExchangeCodec实例。然后使用ExchangeCodec实例创建了Exchange实例传入CallServerInterceptor了。上面提到过ExchangeCodec负责请求和响应的IO读写,我们先来看看ExchangeCodec创建过程——RealConnection的newCodec方法:
ExchangeCodec newCodec(OkHttpClient client, Interceptor.Chain chain) throws SocketException {
if (http2Connection != null) {
return new Http2ExchangeCodec(client, this, chain, http2Connection);
} else {
socket.setSoTimeout(chain.readTimeoutMillis());
source.timeout().timeout(chain.readTimeoutMillis(), MILLISECONDS);
sink.timeout().timeout(chain.writeTimeoutMillis(), MILLISECONDS);
return new Http1ExchangeCodec(client, this, source, sink);
}
}
http2Connection不为空就创建Http2ExchangeCodec,否则是Http1ExchangeCodec。而http2Connection的创建是连接进行TCP、TLS握手的时候,即在RealConnection的connect方法中,具体就是connect方法中调用的establishProtocol方法:
private void establishProtocol(ConnectionSpecSelector connectionSpecSelector,
int pingIntervalMillis, Call call, EventListener eventListener) throws IOException {
//针对http请求,如果配置的协议包含Protocol.H2_PRIOR_KNOWLEDGE,则开启Http2连接
if (route.address().sslSocketFactory() == null) {
if (route.address().protocols().contains(Protocol.H2_PRIOR_KNOWLEDGE)) {
socket = rawSocket;
protocol = Protocol.H2_PRIOR_KNOWLEDGE;
startHttp2(pingIntervalMillis);
return;
}
socket = rawSocket;
protocol = Protocol.HTTP_1_1;
return;
}
//针对https请求,会在TLS握手后,根据平台获取协议(),如果协议是Protocol.HTTP_2,则开启Http2连接
eventListener.secureConnectStart(call);
connectTls(connectionSpecSelector);
eventListener.secureConnectEnd(call, handshake);
if (protocol == Protocol.HTTP_2) {
startHttp2(pingIntervalMillis);
}
}
private void startHttp2(int pingIntervalMillis) throws IOException {
socket.setSoTimeout(0); // HTTP/2 connection timeouts are set per-stream.
http2Connection = new Http2Connection.Builder(true)
.socket(socket, route.address().url().host(), source, sink)
.listener(this)
.pingIntervalMillis(pingIntervalMillis)
.build();
http2Connection.start();
}
拦截器:
public final class CallServerInterceptor implements Interceptor {
private final boolean forWebSocket;
public CallServerInterceptor(boolean forWebSocket) {
this.forWebSocket = forWebSocket;
}
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Exchange exchange = realChain.exchange();//上个拦截器传入的exchange
Request request = realChain.request();
long sentRequestMillis = System.currentTimeMillis();
//写请求头
exchange.writeRequestHeaders(request);
boolean responseHeadersStarted = false;
Response.Builder responseBuilder = null;
//含body的请求
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
// 若请求头包含 "Expect: 100-continue" , 就会等服务端返回含有 "HTTP/1.1 100 Continue"的响应,然后再发送请求body.
//如果没有收到这个响应(例如收到的响应是4xx),那就不发送body了。
if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
exchange.flushRequest();
responseHeadersStarted = true;
exchange.responseHeadersStart();
responseBuilder = exchange.readResponseHeaders(true);
}
//responseBuilder为null说明服务端返回了100,也就是可以继续发送body了
if (responseBuilder == null) {
if (request.body().isDuplex()) {//默认是false不会进入
// Prepare a duplex body so that the application can send a request body later.
exchange.flushRequest();
BufferedSink bufferedRequestBody = Okio.buffer(
exchange.createRequestBody(request, true));
request.body().writeTo(bufferedRequestBody);
} else {
// 满足了 "Expect: 100-continue" ,写请求body
BufferedSink bufferedRequestBody = Okio.buffer(
exchange.createRequestBody(request, false));
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
}
} else {
//没有满足 "Expect: 100-continue" ,请求发送结束
exchange.noRequestBody();
if (!exchange.connection().isMultiplexed()) {
// If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection
// from being reused. Otherwise we're still obligated to transmit the request body to
// leave the connection in a consistent state.
exchange.noNewExchangesOnConnection();
}
}
} else {
//没有body,请求发送结束
exchange.noRequestBody();
}
//请求发送结束
if (request.body() == null || !request.body().isDuplex()) {
exchange.finishRequest();
}
//回调 读响应头开始事件(如果上面没有)
if (!responseHeadersStarted) {
exchange.responseHeadersStart();
}
//读响应头(如果上面没有)
if (responseBuilder == null) {
responseBuilder = exchange.readResponseHeaders(false);
}
//构建response
Response response = responseBuilder
.request(request)
.handshake(exchange.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
int code = response.code();
if (code == 100) {
//这里服务端又返回了个100,就再尝试获取真正的响应()
response = exchange.readResponseHeaders(false)
.request(request)
.handshake(exchange.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
code = response.code();
}
//回调读响应头结束
exchange.responseHeadersEnd(response);
//这里就是获取响应body了
if (forWebSocket && code == 101) {
// Connection is upgrading, but we need to ensure interceptors see a non-null response body.
response = response.newBuilder()
.body(Util.EMPTY_RESPONSE)
.build();
} else {
response = response.newBuilder()
.body(exchange.openResponseBody(response))
.build();
}
//请求头中Connection是close,表示请求完成后要关闭连接
if ("close".equalsIgnoreCase(response.request().header("Connection"))
|| "close".equalsIgnoreCase(response.header("Connection"))) {
exchange.noNewExchangesOnConnection();
}
//204(无内容)、205(充值内容),body应该是空
if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
throw new ProtocolException(
"HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
}
return response;
}
}
最终返回response。
参考文章
网络请求框架OkHttp3全解系列 - (四)拦截器详解2:连接、请求服务(重点)
okhttp3源码分析之拦截器
面试官:听说你熟悉OkHttp原理?