分析RouteSelector时, 由于网络知识积累的不够, 分析起来比较吃力, 对网络知识进行总结之后再来阅读RouteSelector源码流程, 如果遇到吃力的地方, 继续对 okhttp中涉及到的网络基础知识 进行完善补充.
1. Transmitter {
1. 在RealCall中进行初始化, 一个Request对应一个Transmitter.
2. 一个Transmitter对应一个RealConnectionPool.
3. 一个Transmitter对应一个RealConnection.
}
2. RealConnection {
1. 一个Request对应一个RealConnection.
2. 一个RealConnection可以对应多个Request.
3. 一个RealConnection对应一个Route.
4. 结合Transmitter也就是多个Transmitter可以持有同一个RealConnection.
}
3. RealConnectionPool {
1. 提供RealConnection
}
4. RouteSelector {
1. 提供Selection
}
5. RouteSelector.Selection {
1. 持有Route的集合
}
6. Route {
1. 一个Route对应一个IP地址与一个Proxy
}
一、RouteSelector初始化
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);
}
- RouteSelector涉及到四个参数: Address, RealConnectionPool.routeDatabase, Call, EventListener;
1.1 Address参数
private Address createAddress(HttpUrl url) {
SSLSocketFactory sslSocketFactory = null;
HostnameVerifier hostnameVerifier = null;
CertificatePinner certificatePinner = null;
if (url.isHttps()) {
sslSocketFactory = client.sslSocketFactory();
hostnameVerifier = client.hostnameVerifier();
certificatePinner = client.certificatePinner();
}
return new Address(url.host(), url.port(), client.dns(), client.socketFactory(),
sslSocketFactory, hostnameVerifier, certificatePinner, client.proxyAuthenticator(),
client.proxy(), client.protocols(), client.connectionSpecs(), client.proxySelector());
}
- Address在Transmitter中被创建, 从代码上来看, 一个url对应一个address, 一个address持有了请求需要的主机名, 域名, dns, 端口号, 协议等, 到这里先暂时把一个Address理解成一个容器.
1.2 RouteSelector
RouteSelector(Address address, RouteDatabase routeDatabase, Call call,
EventListener eventListener) {
this.address = address;
this.routeDatabase = routeDatabase;
this.call = call;
this.eventListener = eventListener;
resetNextProxy(address.url(), address.proxy());
}
- RouteSelector初始化时通过resetNextProxy对代理服务器进行操作.
1.3 RouteSelector.resetNextProxy
private void resetNextProxy(HttpUrl url, Proxy proxy) {
if (proxy != null) {
// If the user specifies a proxy, try that and only that.
proxies = Collections.singletonList(proxy);
} else {
// Try each of the ProxySelector choices until one connection succeeds.
List<Proxy> proxiesOrNull = address.proxySelector().select(url.uri());
proxies = proxiesOrNull != null && !proxiesOrNull.isEmpty()
? Util.immutableList(proxiesOrNull)
: Util.immutableList(Proxy.NO_PROXY);
}
nextProxyIndex = 0;
}
- 1、如果proxy不为空, 也就是自定义过代理服务器, 则使用自定义的代理服务器. 如果没有自定义服务器, 则使用默认的代理服务器
- 2、关于ProxySelector在OkHttpClient中进行初始化
- 3、ProxySelector通过传入的uri获取对应的代理服务器
- 4、这里有一个很大的疑问啊,
1.3 ProxySelector
ProxySelector代理选择器, 通过传入的url获取对应的代理服务器.
public Builder() {
proxySelector = ProxySelector.getDefault();
if (proxySelector == null) {
proxySelector = new NullProxySelector();
}
}
- 通过ProxySelector.getDefault()获取系统默认的ProxySelector, 如果获取失败, 则返回NullProxySelector
private static ProxySelector theProxySelector;
static {
try {
Class<?> c = Class.forName("sun.net.spi.DefaultProxySelector");
if (c != null && ProxySelector.class.isAssignableFrom(c)) {
theProxySelector = (ProxySelector) c.newInstance();
}
} catch (Exception e) {
theProxySelector = null;
}
}
- 截止到目前其实还是不知道ProxySelector, RouteSelector, Proxy, Route到底用什么用. 接下来通过对ExchangeFinder.find的分析来尝试了解这几个类的作用
二、对ExchangeFinder.find
ExchangeFinder.find
---ExchangeFinder.findHealthyConnection
------ExchangeFinder.findConnection
---RealConnection.newCodec
2.1 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);
return resultConnection.newCodec(client, chain);
} catch (RouteException e) {
trackFailure();
throw e;
} catch (IOException e) {
trackFailure();
throw new RouteException(e);
}
}
2.2 ExchangeFinder.findHealthyConnection
private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled,
boolean doExtensiveHealthChecks) throws IOException {
while (true) {
// 1.获取连接
RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
pingIntervalMillis, connectionRetryEnabled);
// If this is a brand new connection, we can skip the extensive health checks.
synchronized (connectionPool) {
// 2.如果是新创建的连接, 不需要对连接进行校验
if (candidate.successCount == 0 && !candidate.isMultiplexed()) {
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.
// 3.如果是从连接池中获取的连接, 需要对该连接进行校验, 判断是否可用
if (!candidate.isHealthy(doExtensiveHealthChecks)) {
candidate.noNewExchanges();
continue;
}
return candidate;
}
}
- 1、获取连接RealConnection
- 2、如果该连接不是从连接池中获取,而是通过new的方式新创建的, 对于新创建的连接, 会与服务器建立通信, 建立成功之后才会返回该连接, 所以不需要进行连接可用的校验
- 3、对于从来连接池中获取的复用的连接, 此时还需要对该连接进行校验, 校验通过才能使用.
2.3 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; // 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. RealCall初始化中对transmitter进行初始化, connection默认为null.
//2. 如果成功获取了连接, 则直接使用该连接.
if (transmitter.connection != null) {
// We had an already-allocated connection and it's good.
result = transmitter.connection;
releasedConnection = null;
}
if (result == null) {
// transmitter没有与RealConnection进行过绑定, 则尝试从连接池中获取可用连接.
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;
// 获取路由选择器.
// 这里涉及到代理服务器, 主机名与IP地址的关系的知识点
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();
// 因为到这里, 已经切换到下一个代理服务器了, 所以此时再次尝试获取可复用的连接RealConnection
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.
// 如果没有获取到可复用的连接RealConnection, 重新构建一个连接
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;
}
...
synchronized (connectionPool) {
...
// 将新建的连接RealConnection添加到连接池中
connectionPool.put(result);
transmitter.acquireConnectionNoEvents(result);
...
}
...
return result;
}
- 大致总结一下获取连接的过程:
- 1、尝试从连接池中获取可复用的连接
- 2、如果当前连接池中没有找到可复用的连接, 切换到下一个代理服务器
(如果是直连, 就没有代理服务器这一说, 不需要切换), 然后获取下一个代理服务器对应的IP地址的集合, 再次尝试获取可复用的连接. - 3、如果没有找到, 则重新创建连接.
- 4、连接RealConnection创建完成之后, 与服务器建立通信.
- 5、如果建立成功, 将该连接添加到连接池中.
2.4 从连接池中获取可用连接RealConnectionPool.transmitterAcquirePooledConnection
boolean transmitterAcquirePooledConnection(Address address, Transmitter transmitter,
@Nullable List<Route> routes, boolean requireMultiplexed) {
assert (Thread.holdsLock(this));
// 1.遍历连接池, 获取可复用的连接
for (RealConnection connection : connections) {
// 2.如果这是一个HTTP/2连接,则返回true。这样的连接可以同时用于多个HTTP请求。
if (requireMultiplexed && !connection.isMultiplexed()) continue;
// 3.判断连接是否可以复用
if (!connection.isEligible(address, routes)) continue;
// 4.如果连接可复用, 将Transmitter与RealConnection进行绑定.
transmitter.acquireConnectionNoEvents(connection);
return true;
}
return false;
}
- 这里涉及到HTTP1.x与HTTP2.x的知识:
2.5 判断连接是否可复用RealConnection.isEligible
boolean isEligible(Address address, @Nullable List<Route> routes) {
// 1. 这里涉及到HTTP2.x的一个知识, HTTP2.0支持多路复用, 同一个连接可以并发处理多个请求.
// 每一个请求Request对应一个Transmitter, 如果一个RealConnection上的Transmitter数量超过
// 设定的负载, 则认为该连接已处于饱和状态不可再复用.
if (transmitters.size() >= allocationLimit || noNewExchanges) return false;
// 2. 判断是否满足连接可复用的条件
if (!Internal.instance.equalsNonHost(this.route.address(), address)) return false;
// 3. 如果两个请求的主机名一致, 连接可复用.
if (address.url().host().equals(this.route().address().url().host())) {
return true; // This connection is a perfect match.
}
// 执行到这里, 说明request与当前RealConnection对应的主机名不一致, 但是主机名不一致
// 并不代表不是同一个连接, 这里涉及到了IP与域名的关系. 同一个IP可以对应多个主机名.
// 1. 主机名不同的情况下, 如果当前连接不是HTTP2.0协议, 则无法进行连接复用
if (http2Connection == null) return false;
// 2. 主机名不同的情况下, 直连并且IP地址必须相同.
// 这里的判断要求必须是直连并且IP地址必须相同, 这里的原因是因为如果采用了代理服务器,
// 代理服务器是无法告诉我们源服务器的IP地址.
if (routes == null || !routeMatchesAny(routes)) return false
// 3. This connection's server certificate's must cover the new host.
if (address.hostnameVerifier() != OkHostnameVerifier.INSTANCE) return false;
if (!supportsUrl(address.url())) return false;
// 4. Certificate pinning must match the host.
try {
address.certificatePinner().check(address.url().host(), handshake().peerCertificates());
} catch (SSLPeerUnverifiedException e) {
return false;
}
return true; // The caller's address can be carried by this connection.
}
判断连接是否可用涉及到一下几个知识:- 1、HTTP2.0多路复用机制, 同一个连接可以并发处理多个请求
- 2、IP与域名是1:N的关系, 也就是多个域名可以对应同一个IP地址
- 3、代理服务器的工作流程, 客户端访问代理服务器, 代理服务器替客户端完成网络访问
- 4、路由节点
2.6 获取路由选择器RouteSelector.next
public Selection next() throws IOException {
// Compute the next set of routes to attempt.
List<Route> routes = new ArrayList<>();
// 1.如果有代理服务器
while (hasNextProxy()) {
// Postponed routes are always tried last. For example, if we have 2 proxies and all the
// routes for proxy1 should be postponed, we'll move to proxy2. Only after we've exhausted
// all the good routes will we attempt the postponed routes.
// 2.获取下一个代理服务器. 获取路由选择器的重点都在nextProxy()这里
Proxy proxy = nextProxy();
for (int i = 0, size = inetSocketAddresses.size(); i < size; i++) {
// 3.创建Route与IP地址进行绑定
Route route = new Route(address, proxy, inetSocketAddresses.get(i));
if (routeDatabase.shouldPostpone(route)) {
// 3.过滤掉不可用的IP地址, 避免对不可用的IP进行连接, 浪费资源
postponedRoutes.add(route);
} else {
routes.add(route);
}
}
if (!routes.isEmpty()) {
break;
}
}
if (routes.isEmpty()) {
// We've exhausted all Proxies so fallback to the postponed routes.
routes.addAll(postponedRoutes);
postponedRoutes.clear();
}
// 4.返回路由选择器
return new Selection(routes);
}
- 获取路由选择器的重点都是在
nextProxy(), 涉及到的知识点比较多
2.7 获取下一个代理服务器RouteSelector.nextProxy
nextProxy()
---resetNextInetSocketAddress()
/** Prepares the socket addresses to attempt for the current proxy or host. */
private void resetNextInetSocketAddress(Proxy proxy) throws IOException {
// Clear the addresses. Necessary if getAllByName() below throws!
// 1.遍历过程中每次获取下一个代理服务器并对url进行解析时, 都会首先清掉缓存
inetSocketAddresses = new ArrayList<>();
String socketHost;
int socketPort;
// 2.如果是直连或者SOCKS代理服务器, 主机名和端口号就是url中的主机名与端口号
if (proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.SOCKS) {
socketHost = address.url().host();
socketPort = address.url().port();
} else {
// 2.如果是HTTP代理服务器, 获取代理服务器的主机名与端口号
SocketAddress proxyAddress = proxy.address();
InetSocketAddress proxySocketAddress = (InetSocketAddress) proxyAddress;
socketHost = getHostString(proxySocketAddress);
socketPort = proxySocketAddress.getPort();
}
if (proxy.type() == Proxy.Type.SOCKS) {
// 3.如果是SOCKS代理服务器, 直接将主机名与端口号对应的InetSocketAddress放入集合中
inetSocketAddresses.add(InetSocketAddress.createUnresolved(socketHost, socketPort));
} else {
// 4.如果是直连或者是HTTP代理服务器
eventListener.dnsStart(call, socketHost);
// Try each address for best behavior in mixed IPv4/IPv6 environments.
// 5.通过主机名获取对应的IP地址的集合(这里用到的DNS域名解析)
// 如果是直连, 直接获取服务器的IP地址列表;
// 如果是HTTP代理服务器, 获取的是代理服务器的IP地址的集合
List<InetAddress> addresses = address.dns().lookup(socketHost);
eventListener.dnsEnd(call, socketHost, addresses);
for (int i = 0, size = addresses.size(); i < size; i++) {
InetAddress inetAddress = addresses.get(i);
// 6.根据IP列表地址创建多个InetSocketAddress放入到集合中.
inetSocketAddresses.add(new InetSocketAddress(inetAddress, socketPort));
}
}
}
- SOCKS代理服务器、HTTP代理服务器、直连:(blog.csdn.net/qq_37816453…)
- 涉及到的知识点: SOCKS代理服务器、HTTP代理服务器、直连、DNS域名解析.
2.8 对连接进行校验RealConnection.isHealthy
public boolean isHealthy(boolean doExtensiveChecks) {
// 1.如果socket中断, 认为该连接不可用
if (socket.isClosed() || socket.isInputShutdown() || socket.isOutputShutdown()) {
return false;
}
// 2.如果是HTTP2.0,
if (http2Connection != null) {
return http2Connection.isHealthy(System.nanoTime());
}
if (doExtensiveChecks) {
try {
int readTimeout = socket.getSoTimeout();
try {
socket.setSoTimeout(1);
if (source.exhausted()) {
return false; // Stream is exhausted; socket is closed.
}
return true;
} finally {
socket.setSoTimeout(readTimeout);
}
} catch (SocketTimeoutException ignored) {
// Read timed out; socket is good.
} catch (IOException e) {
return false; // Couldn't read; socket is closed.
}
}
return true;
}