OKhttp的理解-RealConnectionPool

RealConnectionPool

构造方案的参数:
maxIdleConnections: 最大连接数,默认为5。
keepAliveDuration: 存活时间,默认为5分钟。
关键方法:
connectionBecameIdle—— 通知RealConnectionPool该connection已经空闲。返回true表示该connection已经从队列中移除,对应的socket可以关闭了。

public final class RealConnectionPool {
    public RealConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {
        this.maxIdleConnections = maxIdleConnections; //默认为5
        this.keepAliveDurationNs = timeUnit.toNanos(keepAliveDuration); //默认为5分钟

        // Put a floor on the keep alive duration, otherwise cleanup will spin loop.
        if (keepAliveDuration <= 0) {
            throw new IllegalArgumentException("keepAliveDuration <= 0: " + keepAliveDuration);
        }
    }
	
    /**
     * 从连接池中获取一个可用的RealConnection,然后把指定的transmitter绑定到这个链接上,并返回true;
     * 如果没有找到可用的RealConnection,则返回false.
     */
    boolean transmitterAcquirePooledConnection(Address address, Transmitter transmitter,
                                               @Nullable List<Route> 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;
    }
	
    /**
     * 通知RealConnectionPool该connection已经空闲。返回true表示该connection已经从队列中移除,对应的socket可以关闭了。
     * @param connection
     * @return
     */
    boolean connectionBecameIdle(RealConnection connection) {
        assert (Thread.holdsLock(this));
        if (connection.noNewExchanges || maxIdleConnections == 0) {
            connections.remove(connection);
            return true;
        } else {
            notifyAll(); // Awake the cleanup thread: we may have exceeded the idle connection limit.
            return false;
        }
    }
}

清理逻辑

采用一个线程池,核心线程数为0、最大线程数为Inter.MAX_VALUE, 空闲线程存活时间为60秒,等待队列为无缓冲队列(SynchronousQueue),即同一时间只能有一个任务在执行。清除流程为:
遍历connections队列,找到空闲最长时间的连接,如果最长空闲时间超过5分钟、或者空闲连接数量大于5,则从队列中删除该连接并关闭socket。

public final class RealConnectionPool {
    ...

    /**
     * @param now: 当前时间戳
     * @return: 分几种情况:
     *  > 0:距离下次清除的时间间隔;
     *  = 0: 已成功清除了最长空闲时间的连接;
     *  -1: connections队列为空,无空闲或正在使用的连接。
     */
    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;
                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;
            }
        }

        closeQuietly(longestIdleConnection.socket());

        // Cleanup again immediately.
        return 0;
    }
	
    /**
     * 从连接池中获取一个可用的RealConnection,然后把指定的transmitter绑定到这个链接上,并返回true;
     * 如果没有找到可用的RealConnection,则返回false.
     */
    boolean transmitterAcquirePooledConnection(Address address, Transmitter transmitter, @Nullable List<Route> 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;
    }
}

Transmitter

1. 初始化

在调用newRealCall()方法时会new一个RealCall实例,创建成功后会再使用该call作为参数new一个Transmitter实例,并赋值给call的transmitter成员变量。
一个请求对应一个Transmitter实例,Transmitter需要一个OkHttpClient和Call实例作为构造函数的参数。

final class RealCall implements Call {
    /**
     * There is a cycle between the {@link Call} and {@link Transmitter} that makes this awkward.
     * This is set after immediately after creating the call instance.
     */
    private Transmitter transmitter;
    ...
    
    static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
        // Safely publish the Call instance to the EventListener.
        RealCall call = new RealCall(client, originalRequest, forWebSocket);
        call.transmitter = new Transmitter(client, call);
        return call;
    }
}
2. 关键方法

(1)acquireConnectionNoEvents(RealConnection connection)
每个Transmitter只会调用一次acquireConnectionNoEvents(),它会去绑定一个RealConnection,然后把自己的WeakRefrence<Transmitter>添加到该Connection的transmitter列表中。
(2)releaseConnectionNoEvents()
一个RealConnection维护了transmitters弱引用的列表。

public final class Transmitter {
    public RealConnection connection;
	
    public void prepareToConnect(Request request) {
        if (this.request != null) {
            if (sameConnection(this.request.url(), request.url()) && exchangeFinder.hasRouteToTry()) {
                return; // Already ready.
            }
            if (exchange != null) throw new IllegalStateException();

            if (exchangeFinder != null) {
                maybeReleaseConnection(null, true);
                exchangeFinder = null;
            }
        }

        this.request = request;
        this.exchangeFinder = new ExchangeFinder(this, connectionPool, createAddress(request.url()),
                call, eventListener);
    }
	
    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));
    }
    ...
    //将自己从RealConnection的transmitters列表中移除。如果该RealConnection已经没有人在使用了,就返回该Connection的socket,否则表示还有其他请求再用,返回null。
    @Nullable Socket releaseConnectionNoEvents() {
        assert (Thread.holdsLock(connectionPool));

        int index = -1;
        for (int i = 0, size = this.connection.transmitters.size(); i < size; i++) {
            Reference<Transmitter> reference = this.connection.transmitters.get(i);
            if (reference.get() == this) {
                index = i;
                break;
            }
        }

        if (index == -1) throw new IllegalStateException();

        RealConnection released = this.connection;
        released.transmitters.remove(index);
        this.connection = null;

        if (released.transmitters.isEmpty()) {
            released.idleAtNanos = System.nanoTime();
            if (connectionPool.connectionBecameIdle(released)) {
                return released.socket();
            }
        }

        return null;
    }
}

ExchangeFinder

final class ExchangeFinder {
    
	
    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);
            //返回一个新的Http1ExchangeCodec或者Http2ExchangeCodec.
            return resultConnection.newCodec(client, chain);
        } catch (RouteException e) {
            trackFailure();
            throw e;
        } catch (IOException e) {
            trackFailure();
            throw new RouteException(e);
        }
    }
	

    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 && !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.
            if (!candidate.isHealthy(doExtensiveHealthChecks)) {
                candidate.noNewExchanges();
                continue;
            }

            return candidate;
        }
    }
	
	/**
     * 查找一个可用的RealConnection,这是连接复用的核心方法,查找过程为:
     * 1. 先判断transmitter是否已绑定过一个RealConnection、且可用,如果是则直接返回;
     * 2. 如果transmitter已绑定过一个RealConnection,但是不可用,则调用transmitter.releaseConnectionNoEvents()释放之;
     * 3. 从连接池中获取一个可用的RealConnection(ConnectionPool#transmitterAcquirePooledConnection方法),如果获取失败,则继续步骤4:
     * 4. 是否需要一个route selection, 如果需要,则再次尝试从连接池中获取一个可用的RealConnection,如果获取失败,则继续步骤5;
     * 5. 新建一个RealConnection,并调用RealConnection#connect方法进行连接,然后再次尝试从连接池中获取一个可用RealConnection, 如果获取失败,则继续步骤6;
     * 6. 将新建的RealConnection放在connections队列中,并将transmitter添加到该RealConnection的弱引用队列中(List<Reference<Transmitter>>).
     * @param connectTimeout
     * @param readTimeout
     * @param writeTimeout
     * @param pingIntervalMillis
     * @param connectionRetryEnabled
     * @return
     * @throws IOException
     */
    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.

            //如果该transmitter已经绑定过一个RealConnection、且noNewExchanges为true, 则将该transmitter与RealConnection解绑。
            //解绑之后,transmitter.connection等于null.
            releasedConnection = transmitter.connection;
            toClose = transmitter.connection != null && transmitter.connection.noNewExchanges
                    ? transmitter.releaseConnectionNoEvents()
                    : null;

            if (transmitter.connection != null) {
                result = transmitter.connection;
                releasedConnection = null;
            }

            if (result == null) {
                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;
        }

        // 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;
    }

}

ConnectInterceptor

ConnectInterceptor的intercept()方法会调用Transmitter的newExchange()方法。

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);
    }
}

RealConnection

关键方法:
connect —— 执行socket连接,如果是https协议,需要进行TLS握手。
connectTls方法 —— 进行TLS握手。
isEligible—— 判断该RealConnection是否可以复用。
isHealthy —— 判断RealConnection当前的socket是否是健康的。

public final class RealConnection extends Http2Connection.Listener implements Connection {
    ...
  /**
   * 执行连接动作
   * @param connectTimeout 连接超时时间
   * @param readTimeout 用来设置setSoTimeout
   * @param writeTimeout 用于设置connectTunnel中sink的timeout
   * @param pingIntervalMillis 用于Http2Connection中以固定频率执行PingRunnable
   * @param connectionRetryEnabled 是否允许重试
   * @param call
   * @param eventListener 事件监听回调
   */
    public void connect(int connectTimeout, int readTimeout, int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled, Call call, EventListener eventListener) {
	    ...
        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 {
                    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;
                }
            }
        }
    }
	
    private void connectTls(ConnectionSpecSelector connectionSpecSelector) throws IOException {
        Address address = route.address();
        SSLSocketFactory sslSocketFactory = address.sslSocketFactory();
        boolean success = false;
        SSLSocket sslSocket = null;
        try {
            // Create the wrapper over the connected socket.
            sslSocket = (SSLSocket) sslSocketFactory.createSocket(
                    rawSocket, address.url().host(), address.url().port(), true /* autoClose */);

            // Configure the socket's ciphers, TLS versions, and extensions.
            ConnectionSpec connectionSpec = connectionSpecSelector.configureSecureSocket(sslSocket);
            if (connectionSpec.supportsTlsExtensions()) {
                Platform.get().configureTlsExtensions(
                        sslSocket, address.url().host(), address.protocols());
            }

            // Force handshake. This can throw!
            sslSocket.startHandshake();
            // block for session establishment
            SSLSession sslSocketSession = sslSocket.getSession();
            Handshake unverifiedHandshake = Handshake.get(sslSocketSession);

            // Verify that the socket's certificates are acceptable for the target host.
            if (!address.hostnameVerifier().verify(address.url().host(), sslSocketSession)) {
                List<Certificate> peerCertificates = unverifiedHandshake.peerCertificates();
                if (!peerCertificates.isEmpty()) {
                    X509Certificate cert = (X509Certificate) peerCertificates.get(0);
                    throw new SSLPeerUnverifiedException(
                            "Hostname " + address.url().host() + " not verified:"
                                    + "\n    certificate: " + CertificatePinner.pin(cert)
                                    + "\n    DN: " + cert.getSubjectDN().getName()
                                    + "\n    subjectAltNames: " + OkHostnameVerifier.allSubjectAltNames(cert));
                } else {
                    throw new SSLPeerUnverifiedException(
                            "Hostname " + address.url().host() + " not verified (no certificates)");
                }
            }

            // Check that the certificate pinner is satisfied by the certificates presented.
            address.certificatePinner().check(address.url().host(),
                    unverifiedHandshake.peerCertificates());

            // Success! Save the handshake and the ALPN protocol.
            String maybeProtocol = connectionSpec.supportsTlsExtensions()
                    ? Platform.get().getSelectedProtocol(sslSocket)
                    : null;
            socket = sslSocket;
            source = Okio.buffer(Okio.source(socket));
            sink = Okio.buffer(Okio.sink(socket));
            handshake = unverifiedHandshake;
            protocol = maybeProtocol != null
                    ? Protocol.get(maybeProtocol)
                    : Protocol.HTTP_1_1;
            success = true;
        } catch (AssertionError e) {
            if (Util.isAndroidGetsocknameError(e)) throw new IOException(e);
            throw e;
        } finally {
            if (sslSocket != null) {
                Platform.get().afterHandshake(sslSocket);
            }
            if (!success) {
                closeQuietly(sslSocket);
            }
        }
    }
	
    /**
     * 判断当前RealConnection是否可以被复用,判断步骤为:
     * 第一部分:Host匹配
     * 1. 如果该RealConnection上绑定的transmitters已经到达上限,则返回false;
     * 2. 如果noNewExchanges为true,则返回false;
     * 3. 进行非Host域的比较,如果route中address与参数address进行equalsNonHost比较返回false,则返回false;
     * 4. 进行Host比较,如果route中address的host与参数address中的相同,则返回true;
     * 第二部分:Host不匹配时,如果是Http/2,还有可能复用现有连接,继续判断:
     * 5. 如果http2Connection为null,则返回false;
     * 6. 如果routes为null、或者routes中没有能与RealConnection的route匹配的,则返回false;
     * 7. 如果address的hostnameVerifier与OkHostnameVerifier不相等,则返回false;
     * 8. 如果address的HttpUrl的host和port与route中address不相同,则返回false;
     * 9. 如果address的CertificatePinner实例校验失败,则返回false;
     * 如果以上都校验通过,则返回true。
     * @param address
     * @param routes
     * @return
     */
    /**
     * Returns true if this connection can carry a stream allocation to {@code address}. If non-null
     * {@code route} is the resolved route for a connection.
     */
    boolean isEligible(Address address, @Nullable List<Route> routes) {
        // If this connection is not accepting new exchanges, we're done.
        if (transmitters.size() >= allocationLimit || noNewExchanges) return false;

        // If the non-host fields of the address don't overlap, we're done.
        if (!Internal.instance.equalsNonHost(this.route.address(), address)) return false;

        // If the host exactly matches, we're done: this connection can carry the address.
        if (address.url().host().equals(this.route().address().url().host())) {
            return true; // This connection is a perfect match.
        }

        // At this point we don't have a hostname match. But we still be able to carry the request if
        // our connection coalescing requirements are met. See also:
        // https://hpbn.co/optimizing-application-delivery/#eliminate-domain-sharding
        // https://daniel.haxx.se/blog/2016/08/18/http2-connection-coalescing/

        // 1. This connection must be HTTP/2.
        if (http2Connection == null) return false;

        // 2. The routes must share an IP address.
        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.
    }
	
    /**
     * 判断当前的socket是否是健康的。检验标准是:
     * 1. socket未关闭
     * 2. socket的输入流未关闭
     * 3. socket的输出流未关闭
     * 4. 如果是http/2的话,http2未关闭
     * 5. 如果需要doExtensiveChecks, 则将setSoTimeout设置为1ms,判断是否会出现SocketTimeoutException.
     * @param doExtensiveChecks 是否需要进行严格校验
     * @return
     */
    public boolean isHealthy(boolean doExtensiveChecks) {
        if (socket.isClosed() || socket.isInputShutdown() || socket.isOutputShutdown()) {
            return false;
        }

        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;
    }
}
posted @ 2021-05-30 16:45  羊之草原  阅读(2160)  评论(0)    收藏  举报