ConnectInterceptor
详解http
、https
、http2
三种连接类型。ConnectionPool#ConnectionPool(int, long, java.util.concurrent.TimeUnit)
。findHealthyConnection
函数生成一个RealConnection
,然后构造一个ExchangeCodec
对象用于网络请求。RealConnection
基于如下规则:ConnectionPool
连接池中寻找可用连接,进行连接服用。/**
* Attempts to find the connections for a sequence of exchanges. This uses the following strategies:
*
*
* - 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.
*
*
- 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.
*
*
- 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.
*
*
* 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.
*/
ExchangeFinder#findConnection
源码分析transmitter
中的connection
不为空,而且是可用的直接返回当前连接。transmitter
中的connection
为空,尝试通过transmitterAcquirePooledConnection
获取ConnectionPool
连接池中的可用连接。nextRouteToTry
是在注释13处赋值的,它并不是记录的routes列表中的下一个路由地址。它只是记录一种异常情况:在ConnectionPool
获取到可用连接,但是这个连接又马上unhealthy
。transmitter
本身自带的,也有可能是ConnectionPool
中获取的Proxy
的路由表,此处会进行DNS
。返回RouteSelector.Selection
对象。每个Selection
包含一个路由表。ConnectionPool
连接池中的可用连接。ConnectionPool
没有找到可用连接,获取一个Route
路由对象,每个。RealConnection
对象,并且传入Route
路由对象。RealConnection
后,在尝试在ConnectionPool
获取是否有可用连接,如果找到了可用连接,优先复用在连接池中找到的连接。并且关闭注释11中建立的RealConnection
,给nextRouteToTry
赋值当前的Route
路由对象(因为这个route对象是可用的)。注意传入的最后一个参数是true
,表明是要求http2.0多路复用。ConnectionPool
中找到可用连接,将本次新建立的连接放到连接池中。transmitter
对象中的RealConnection
变量赋值。/**
* 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; // 注释1
if (transmitter.connection != null) { // 注释1
// We had an already-allocated connection and it's good.
result = transmitter.connection;
releasedConnection = null;
}
if (result == null) { // 注释2
// Attempt to get a connection from the pool.
if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, null, false)) {
foundPooledConnection = true;
result = transmitter.connection;
} else if (nextRouteToTry != null) { // 注释3
selectedRoute = nextRouteToTry;
nextRouteToTry = null;
} else if (retryCurrentRoute()) { // 注释4
selectedRoute = transmitter.connection.route();
}
}
}
// 省略代码...
if (result != null) {
// If we found an already-allocated or pooled connection, we're done.
return result; // 注释5
}
// 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(); // 注释6
}
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)) { // 注释7
foundPooledConnection = true;
result = transmitter.connection;
}
}
if (!foundPooledConnection) {
if (selectedRoute == null) {
selectedRoute = routeSelection.next(); // 注释8
}
// 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); // 注释9
connectingConnection = result;
}
}
// If we found a pooled connection on the 2nd time around, we're done.
if (foundPooledConnection) { // 注释10
eventListener.connectionAcquired(call, result);
return result;
}
// Do TCP + TLS handshakes. This is a blocking operation.
result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
connectionRetryEnabled, call, eventListener); // 注释11
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.
// 注释12
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; // 注释13
} else {
connectionPool.put(result); // 注释14
transmitter.acquireConnectionNoEvents(result); // 注释15
}
}
closeQuietly(socket);
eventListener.connectionAcquired(call, result);
return result;
}
socket
连接。RealConnection#connect
是一个重要的函数,这个函数建立了TCP socket、sslSocket
连接、进行https TLS安全协商
过程、HTTP 2.0协议协商
。socket
的连接动作都是在这个函数完成的。几个关键函数RealConnection#connect
、connectSocket
、createTunnel
、establishProtocol
、connectTls
、startHttp2
。createTunnel
中发送的CONNECT
消息,参考资料https://www.jianshu.com/p/54357cdd4736。http协议版本 | 连接流程 |
---|---|
http/1.1 | 1. DNS 2. TCP Socket(未加密) |
https/1.1 | 1. DNS 2. TCP Socket(未加密) 3. TLS安全隧道。"CONNECT"消息、TLS协商 |
http/2.0 | 1. DNS 2. TCP Socket(未加密) 3. TLS安全隧道。"CONNECT"消息、TLS协商 4. Http2协商。sendConnectionPreface、settings |
transmitterAcquirePooledConnection
获取缓存池中的可用连接。put
函数,向缓存池中增加可用连接。DIRECT
、HTTP
、SOCKS
HTTP代理
和HTTP协议
是两个不同的概念。SOCKS
代理是不需要在本地DNS解析的,可以直接将域名发送到SOCKS
代理服务器上。DIRECT
、HTTP
代理需要进行DNS
解析。参考方法RouteSelector#resetNextInetSocketAddress
。HTTP代理
分为普通代理和隧道代理。普通代理是明文传输,即http请求;隧道代理是加密传输,仅仅转发TCP包,即https和http2.0。隧道代理在建立时,会要求先发送connect
请求建立隧道,进行SSL
握手,因为数据是加密的,代理服务器没有办法解析数据,只能转发数据。ProxySelector
类是sun.net.spi.DefaultProxySelector
,在OkHttpClient.Builder#Builder()
初始化默认值。参考方法java.net.ProxySelector#getDefault
。java.net.InetAddress#getAllByName
。可以解析IPV4 和 IPV6地址。Dns
接口定义了lookup
方法寻找IP地址。Dns#SYSTEM
变量实现了该接口。匿名内部类。DNS
寻址动作在 RouteSelector#resetNextInetSocketAddress
方法中实现//okhttp3.internal.connection.RouteSelector#resetNextInetSocketAddress
if (proxy.type() == Proxy.Type.SOCKS) { // 如果是socks代理,是不需要DNS解析的,可以直接将域名发送到服务器进行解析。
inetSocketAddresses.add(InetSocketAddress.createUnresolved(socketHost, socketPort));
} else { // DIRECT、HTTP代理需要进行DNS解析
eventListener.dnsStart(call, socketHost);
// Try each address for best behavior in mixed IPv4/IPv6 environments.
List<InetAddress> addresses = address.dns().lookup(socketHost); // DNS解析
if (addresses.isEmpty()) {
throw new UnknownHostException(address.dns() + " returned no addresses for " + socketHost);
}
eventListener.dnsEnd(call, socketHost, addresses);
for (int i = 0, size = addresses.size(); i < size; i++) {
InetAddress inetAddress = addresses.get(i);
inetSocketAddresses.add(new InetSocketAddress(inetAddress, socketPort));
}
}