/**
* Transmits a single HTTP request and a response pair. This layers connection management
and events
* on {@link ExchangeCodec}, which handles the actual I/O.
*/
发送一个单独的http请求和响应对,这层连接和管理ExchangeCodec事件,ExchangeCodec是真正发送io的地方
public final class Exchange {
final Transmitter transmitter;
final Call call;
final EventListener eventListener;
final ExchangeFinder finder;
final ExchangeCodec codec;
private boolean duplex;
public Exchange(Transmitter transmitter, Call call, EventListener eventListener,
ExchangeFinder finder, ExchangeCodec codec) {
this.transmitter = transmitter;
this.call = call;
this.eventListener = eventListener;
this.finder = finder;
this.codec = codec;
}
}}
一次http请求也就是一个数据交换,exchange理解为交换吧。构造函数中可以看到几个重要的属性,ExchangeFinder 和 ExchangeCodec ExchangeFinder是从连接池中找到最佳连接的类,ExchangeCodec为真正进行io读写的类
public void writeRequestHeaders(Request request) throws IOException {
try {
eventListener.requestHeadersStart(call);
codec.writeRequestHeaders(request);
eventListener.requestHeadersEnd(call, request);
} catch (IOException e) {
eventListener.requestFailed(call, e);
trackFailure(e);
throw e;
}
}
public Sink createRequestBody(Request request, boolean duplex) throws IOException {
this.duplex = duplex;
long contentLength = request.body().contentLength();
eventListener.requestBodyStart(call);
Sink rawRequestBody = codec.createRequestBody(request, contentLength);
return new RequestBodySink(rawRequestBody, contentLength);
}
public void flushRequest() throws IOException {
try {
codec.flushRequest();
} catch (IOException e) {
eventListener.requestFailed(call, e);
trackFailure(e);
throw e;
}
}
public void finishRequest() throws IOException {
try {
codec.finishRequest();
} catch (IOException e) {
eventListener.requestFailed(call, e);
trackFailure(e);
throw e;
}
}以上
以上函数中可以看到exchange知识对ExchangeFinder 和 ExchangeCodec的一层包装。真正执行的是ExchangeFinder 和 ExchangeCodec
接下来看ExchangeFinder
/**
* Attempts to find the connections for a sequence of exchanges. This uses the following strategies:
*用来为一系列的请求用下面的策略找到客户端和服务器的连接。
* <ol>
假如已经有能完美处理当前请求的连接,复用之前的连接
* <li>If the current call already has a connection that can satisfy the request it is used.
* Using the same connection for an initial exchange and its follow-ups may improve locality.
*加入当前连接池中有能满足处理当前请求的连接,
* <li>If there is a connection in the pool that can satisfy the request it is used. Note that
* it is possible for shared exchanges to make requests to different host names! See {@link
* RealConnection#isEligible} for details.
*
* <li>If there's no existing connection, make a list of routes (which may require blocking DNS
* lookups) and attempt a new connection them. When failures occur, retries iterate the list
* of available routes.
* </ol>
*
* <p>If the pool gains an eligible connection while DNS, TCP, or TLS work is in flight, this finder
* will prefer pooled connections. Only pooled HTTP/2 connections are used for such de-duplication.
*
* <p>It is possible to cancel the finding process.
*/
final class ExchangeFinder {
private final Transmitter transmitter;
private final Address address;
private final RealConnectionPool connectionPool;
private final Call call;
private final EventListener eventListener;
private RouteSelector.Selection routeSelection;
// State guarded by connectionPool.
private final RouteSelector routeSelector;
private RealConnection connectingConnection;
private boolean hasStreamFailure;
private Route nextRouteToTry;
ExchangeFinder(Transmitter transmitter, RealConnectionPool connectionPool,
Address address, Call call, EventListener eventListener) {
this.transmitter = transmitter;
this.connectionPool = connectionPool;
this.address = address;
this.call = call;
this.eventListener = eventListener;
this.routeSelector = new RouteSelector(
address, connectionPool.routeDatabase, call, eventListener);
}
这个类最重要的功能就是找到适合的连接,接下来看方法
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);
return resultConnection.newCodec(client, chain);
//此处返回ExchangeCodec
} catch (RouteException e) {
trackFailure();
throw e;
} catch (IOException e) {
trackFailure();
throw new RouteException(e);
}
}
/**
* Finds a connection and returns it if it is healthy. If it is unhealthy the process is repeated
* until a healthy connection is found.
*/
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);
// If this is a brand new connection, we can skip the extensive health checks.
synchronized (connectionPool) {
if (candidate.successCount == 0) {
return candidate;
}
}
// Do a (potentially slow) check to confirm that the pooled connection is still good. If it
// isn't, take it out of the pool and start again.
if (!candidate.isHealthy(doExtensiveHealthChecks)) {
candidate.noNewExchanges();
continue;
}
return candidate;
}
}
最终会调用这里,新建一个io,最好是复用连接池中已经出现的连接
/**
* Returns a connection to host a new stream. This prefers the existing connection if it exists,
* then the pool, finally building a new connection.
*/
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; // This is a fresh attempt.
// Attempt to use an already-allocated connection. We need to be careful here because our
// already-allocated connection may have been restricted from creating new exchanges.
releasedConnection = transmitter.connection;
toClose = transmitter.connection != null && transmitter.connection.noNewExchanges
? transmitter.releaseConnectionNoEvents()
: null;
if (transmitter.connection != null) {
transmitter已经有连接
// We had an already-allocated connection and it's good.
result = transmitter.connection;
releasedConnection = null;
}
if (result == null) {
// Attempt to get a connection from the pool.连接池中找到
if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, null, false)) {
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) {
// If we found an already-allocated or pooled connection, we're done.
return result;
}
// If we need a route selection, make one. This is a blocking operation.
boolean newRouteSelection = false;
if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) {
newRouteSelection = true;
routeSelection = routeSelector.next();
}
List<Route> routes = null;
synchronized (connectionPool) {
if (transmitter.isCanceled()) throw new IOException("Canceled");
if (newRouteSelection) {
// Now that we have a set of IP addresses, make another attempt at getting a connection from
// the pool. This could match due to connection coalescing.
routes = routeSelection.getAll();
if (connectionPool.transmitterAcquirePooledConnection(
address, transmitter, routes, false)) {
foundPooledConnection = true;
result = transmitter.connection;
}
}
if (!foundPooledConnection) {
if (selectedRoute == null) {
selectedRoute = routeSelection.next();
}
// Create a connection and assign it to this allocation immediately. This makes it possible
// for an asynchronous cancel() to interrupt the handshake we're about to do.
result = new RealConnection(connectionPool, selectedRoute);
connectingConnection = result;
}
}
// If we found a pooled connection on the 2nd time around, we're done.
if (foundPooledConnection) {
eventListener.connectionAcquired(call, result);
return result;
}
此处通过RealConnection的connect方法建立连接
// Do TCP + TLS handshakes. This is a blocking operation.
result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
connectionRetryEnabled, call, eventListener);
connectionPool.routeDatabase.connected(result.route());
Socket socket = null;
synchronized (connectionPool) {
connectingConnection = null;
// Last attempt at connection coalescing, which only occurs if we attempted multiple
// concurrent connections to the same host.
if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, routes, true)) {
// We lost the race! Close the connection we created and return the pooled connection.
result.noNewExchanges = true;
socket = result.socket();
result = transmitter.connection;
// It's possible for us to obtain a coalesced connection that is immediately unhealthy. In
// that case we will retry the route we just successfully connected with.
nextRouteToTry = selectedRoute;
} else {
加入连接池
connectionPool.put(result);
transmitter.acquireConnectionNoEvents(result);
}
}
closeQuietly(socket);
eventListener.connectionAcquired(call, result);
return result;
}
我们看到在find函数中找到一个realconnection,并且通过newCodec返回了一个ExchangeCodec,接下来我们看看这个类
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;
public final RealConnectionPool connectionPool;
private final Route route;
// The fields below are initialized by connect() and never reassigned.
/** The low-level TCP socket. */
private Socket rawSocket;
/**
* The application layer socket. Either an {@link SSLSocket} layered over {@link #rawSocket}, or
* {@link #rawSocket} itself if this connection does not use SSL.
*/
private Socket socket;从中可以看到okhttp建立连接用socket
private Handshake handshake;
private Protocol protocol;
private Http2Connection http2Connection;
private BufferedSource source; 源buffer
private BufferedSink sink;目标buffer
这个类是okhttp中真正发生服务器连接的类。首先看看newCodeC方法
ExchangeCodec newCodec(OkHttpClient client, Interceptor.Chain chain) throws SocketException {
if (http2Connection != null) {
假如是http2请求用Http2ExchangeCodec
return new Http2ExchangeCodec(client, this, chain, http2Connection);
} else {
http1用Http1ExchangeCodec
socket.setSoTimeout(chain.readTimeoutMillis());
source.timeout().timeout(chain.readTimeoutMillis(), MILLISECONDS);
sink.timeout().timeout(chain.writeTimeoutMillis(), MILLISECONDS);
return new Http1ExchangeCodec(client, this, source, sink);
}
}
Http2ExchangeCodec Http1ExchangeCodec都实现了接口类ExchangeCodec 。分别代表http1和http2的编码请求和解码响应的不同的方式。
接下来我们看看realconnection的connect方法
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<ConnectionSpec> 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);
}
establishProtocol(connectionSpecSelector, pingIntervalMillis, call, eventListener);
eventListener.connectEnd(call, route.socketAddress(), route.proxy(), protocol);
break;
} catch (IOException e) {
closeQuietly(socket);
closeQuietly(rawSocket);
socket = null;
rawSocket = null;
source = null;
sink = null;
handshake = null;
protocol = null;
http2Connection = null;
eventListener.connectFailed(call, route.socketAddress(), route.proxy(), null, e);
if (routeException == null) {
routeException = new RouteException(e);
} else {
routeException.addConnectException(e);
}
if (!connectionRetryEnabled || !connectionSpecSelector.connectionFailed(e)) {
throw routeException;
}
}
}
if (route.requiresTunnel() && rawSocket == null) {
ProtocolException exception = new ProtocolException("Too many tunnel connections attempted: "
+ MAX_TUNNEL_ATTEMPTS);
throw new RouteException(exception);
}
if (http2Connection != null) {
synchronized (connectionPool) {
allocationLimit = http2Connection.maxConcurrentStreams();
}
}
}
/** Does all the work necessary to build a full HTTP or HTTPS connection on a raw socket. */
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 {
连接socket
Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);
} catch (ConnectException e) {
ConnectException ce = new ConnectException("Failed to connect to " + route.socketAddress());
ce.initCause(e);
throw ce;
}
// The following try/catch block is a pseudo hacky way to get around a crash on Android 7.0
// More details:
// https://github.com/square/okhttp/issues/3245
// https://android-review.googlesource.com/#/c/271775/
try {
获取数据源和数据目的地
source = Okio.buffer(Okio.source(rawSocket));
sink = Okio.buffer(Okio.sink(rawSocket));
} catch (NullPointerException npe) {
if (NPE_THROW_WITH_NULL.equals(npe.getMessage())) {
throw new IOException(npe);
}
}
}
从中可以看出okhttp的连接是通过socket建立的。,接下来看下连接池。
在okhttp的build中
public Builder() {
。。。
connectionPool = new ConnectionPool();
。。。
}
public ConnectionPool() {
this(5, 5, TimeUnit.MINUTES);
}
public ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {
this.delegate = new RealConnectionPool(maxIdleConnections, keepAliveDuration, timeUnit);
}
}
由构造函数可以看到连接池是有5个空闲连接最长空闲连接5分钟,并且真正的连接池是
RealConnectionPool
public final class RealConnectionPool {
private final Deque<RealConnection> connections = new ArrayDeque<>();void put(RealConnection connection) {
assert (Thread.holdsLock(this));
if (!cleanupRunning) {
cleanupRunning = true;
executor.execute(cleanupRunnable);
}
connections.add(connection);
}
private final Runnable cleanupRunnable = () -> {
while (true) {
long waitNanos = cleanup(System.nanoTime());
if (waitNanos == -1) return;
if (waitNanos > 0) {
long waitMillis = waitNanos / 1000000L;
waitNanos -= (waitMillis * 1000000L);
synchronized (RealConnectionPool.this) {
try {
RealConnectionPool.this.wait(waitMillis, (int) waitNanos);
} catch (InterruptedException ignored) {
}
}
}
}
};
}
连接池中有queue缓存已经连接的connection并且每次put一个新的connection都会运行cleanup线程清理连接池。
okhttp就这样了