okhttp 连接

okhttp 连接

前言

前面一篇,主要分析了OkHttp的整体设计,但是需要重提一句OkHttp是一个网络库。所以这篇开始,来说一说OkHttp如何建立连接和如何交换数据的。

ConnectInterceptor

从前面我们知道了,ConnectInterceptor承载着OkHttp与服务器建立网络连接的任务。下面看看ConnectInterceptor的源码:

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

        // 我们需要保证网络能够满足网络请求。除GET方法外,我们都需要进行严格的检查
        boolean doExtensiveHealthChecks = !request.method().equals("GET");
        // 连接管理与事件的交换桥梁
        Exchange exchange = transmitter.newExchange(chain, doExtensiveHealthChecks);

        // 建立好连接之后,交给CallServerInterceptor读取数据
        return realChain.proceed(request, transmitter, exchange);
    }
}

从这段代码中,实际上并不能看出来什么,但是我们可以知道,这个跟TransmitterExchange关系莫大。

Transmitter

Bridge between OkHttp’s application and network layers. This class exposes high-level application layer primitives: connections, requests, responses, and streams.

翻译:OkHttp应用层与网络层的桥梁。这个类暴露了上层应用层的基本属性:连接、请求、响应和流。

This class supports {@linkplain #cancel asynchronous canceling}. This is intended to have the smallest blast radius possible. If an HTTP/2 stream is active, canceling will cancel that stream but not the other streams sharing its connection. But if the TLS handshake is still in progress then canceling may break the entire connection.

翻译:这个类支持异步取消。为了让取消动作影响最小,如果是HTTP/2的流是活跃的,那么取消动作只取消它自己的流,不会取消共享的连接。但是如果是正在进行TLS握手,那么就会取消整个连接。

从上面的说明,可以简单的了解,但是仍然不清楚它的作用,它既然是应用层和网络层的桥梁,那么它提供给网络层的是什么,又返回给应用层了什么呢。

上面已经说明了,它提供给网络层的是应用层的ConnectionRequestResponse和流。而在ConnectInterceptor方法中,我们可以看出,它给应用层返回的是一个Exchange

我们可以发现,Transmitter的构造只有一个地方:

static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    RealCall call = new RealCall(client, originalRequest, forWebSocket);
    call.transmitter = new Transmitter(client, call);
    return call;
}

在获得一个RealCall的时候,就生成了一个Transmitter

Exchange

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

大致意思应该清楚,ExchangeCodec负责IO操作,而Exchange则是处理HTTP请求和响应。

那么谁又负责连接管理部分呢?

Exchange的构造

反向思考,Exchange是在哪里构造的呢?查看代码,只有一个地方,是在Transmitter.newExchange(Interceptor.Chain, boolean)

/**
 * Returns a new exchange to carry a new request and response.
 * 返回一个Exchange负责请求、响应
 */
Exchange newExchange(Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
    synchronized (connectionPool) {
        if (noMoreExchanges) {
            throw new IllegalStateException("released");
        }
        if (exchange != null) {
            throw new IllegalStateException("cannot make a new request because the previous response "
                    + "is still open: please call response.close()");
        }
    }

    ExchangeCodec codec = exchangeFinder.find(client, chain, doExtensiveHealthChecks);
    Exchange result = new Exchange(this, call, eventListener, exchangeFinder, codec);

    synchronized (connectionPool) {
        this.exchange = result;
        this.exchangeRequestDone = false;
        this.exchangeResponseDone = false;
        return result;
    }
}

除了前后的一些检查、复制代码,我们可以看到,这是通过一个ExchangeFinder,构造了一个ExchangeCodec负责IO操作,并使用这个ExchangeCodec构造了Exchange

那么Transmitter.newExchange(Interceptor.Chain, boolean)什么时候被调用的呢?是在ConnectInterceptor.intercept(Chain)中调用的,并且交由CallServerInterceptor使用。而在CallServerInterceptor进行的是在建立好连接的基础上进行请求。那么我们就应该知道,Exchange内部肯定是已经建立好了连接。

特别注意的是,在构造Exchange时,有一个ExchangeFinder,它是做什么的呢?

ExchangeFinder

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.

翻译:尝试为一系列的Exchange找到Connection连接。使用下列的策略:

  1. 如果当前有Connection能够满足请求Request,那么使用初始时的Exchange构造的Connection
  2. 如果连接池中有Connection能够满足请求Request。注意,共享同一ConnectionExchange可能向不同的主机名发送请求。
  3. 如果没有存在的Connection,创建一个路由列表(获取需要使用阻塞DNS查询),并且尝试建立一个新的连接。如果发生错误,那么将根据路由列表重试。

这下明白了,建立连接的过程是在ExchangeFinder里面呀。看一下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);
        return resultConnection.newCodec(client, chain);
    } 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.
 * 找到可用的Connection。如果不可用,将重复查找直到找到为止。
 */
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;
    }
}

/**
 * Returns a connection to host a new stream. This prefers the existing connection if it exists,
 * then the pool, finally building a new connection.
 * 返回一个Connection用于处理流。最好的先从已有的查找,再从连接池中查找,最后没找到则新建一个Connection
 */
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
                                        int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
    RealConnection result = null;
    // ...
    result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
            connectionRetryEnabled, call, eventListener);
    // ...
    return result;
}
  1. ExchangeFinder.find(Interceptor.Chain, boolean)得到IO操作的ExchangeCodec
  2. 在其中调用了RealConnection ExchangeFinder.findHealthyConnection(int, int, int, int, boolean, boolean)
  3. 继续调用RealConnection ExchangeFinder.findConnection(int, int, int, int, boolean)得到了一个RealConnection
  4. 然后调用RealConnection.connect(int, int, int, int, boolean, Call, EventListener)建立了真正的连接
  5. 最后通过这个RealConnection返回了的ExchangeCodec

RealConnection

现在知道了RealConnection才是真正的服务器连接。通过它进行连接服务器,建立好连接之后,通过ExchangeCodec newCodec(OkHttpClient, Interceptor.Chain)返回ExchangeCodec用于处理IO,并将其封装在Exchange中,供应用层调用(大部分是在CallServerInterceptor使用)。

Java中,连接服务器使用的是Socket,我们查看Socket的使用。下面,我们返回来看,哪里真正调用Socket.connect(SocketAddress, int)了呢。

  1. 两处进行了连接,Platform.connectSocket(Socket, InetSocketAddress, int)AndroidPlatform.connectSocket(Socket, InetSocketAddress, int)AndroidPlatform继承了Platform
  2. 查看Platform.connectSocket(Socket, InetSocketAddress, int)的使用,就发现是RealConnection.connectSocket(int, int, Call, EventListener)
  3. 最后直接或间接都被RealConnection.connect(int, int, int, int, boolean, Call, EventListener)调用

终于,我们来看一下,一个完整的连接过程

okhttp Connection连接

在这张图上,我们可以看到,这个跟ConnectionConnectionPool并没有任何关系。而RealConnection实际上实现了Connection接口

Connection

The sockets and streams of an HTTP, HTTPS, or HTTPS+HTTP/2 connection. May be used for multiple HTTP request/response exchanges. Connections may be direct to the origin server or via a proxy.

翻译:HTTPHTTPSHTTPS+HTTP/2连接的Socket和流。可能会被用于多个HTTP请求/相应交换。可以直接连接服务器,也可以通过代理连接服务器。

Typically instances of this class are created, connected and exercised automatically by the HTTP client. Applications may use this class to monitor HTTP connections as members of a {@linkplain ConnectionPool connection pool}.

翻译:通常,这个类会自动被OkHttp创建、连接、使用。应用层可以使用这个类作为ConnectionPool的成员来监听HTTP连接。

Do not confuse this class with the misnamed {@code HttpURLConnection}, which isn’t so much a connection as a single request/response exchange.

翻译:不要和HttpURLConnection弄混了,后者不是一个单独的请求响应交换的连接。

下面是Connection的代码:

public interface Connection {
    /**
     * Returns the route used by this connection.
     */
    Route route();

    /**
     * Returns the socket that this connection is using. Returns an {@linkplain
     * javax.net.ssl.SSLSocket SSL socket} if this connection is HTTPS. If this is an HTTP/2
     * connection the socket may be shared by multiple concurrent calls.
     */
    Socket socket();

    /**
     * Returns the TLS handshake used to establish this connection, or null if the connection is not
     * HTTPS.
     */
    @Nullable
    Handshake handshake();

    /**
     * Returns the protocol negotiated by this connection, or {@link Protocol#HTTP_1_1} if no protocol
     * has been negotiated. This method returns {@link Protocol#HTTP_1_1} even if the remote peer is
     * using {@link Protocol#HTTP_1_0}.
     */
    Protocol protocol();
}

可以看到,这个类提供了连接的Socket和一些其他属性。使用这个Socket可以直接与服务器进行数据交换。这个类抽象了连接。同时,OkHttp也告诉我们,通常只是通过Connection来用于监听,所以实际上,它并没有做什么实质性的工作,存在的目的是为应用层提供连接的一些信息。

ConnectionPool

Manages reuse of HTTP and HTTP/2 connections for reduced network latency. HTTP requests that share the same {@link Address} may share a {@link Connection}. This class implements the policy of which connections to keep open for future use.

翻译:管理HTTPHTTP/2的连接的重复使用,以减少网络延迟。使用同一地址的HTTP请求可能会分享同一个Connection。这个类实现了哪些连接应该保持打开以供后来使用的策略。

现在,我们知道了ConnectionPool是用来管理连接的,然而,它并没有做实质性的工作,代理了RealConnectionPool,所以实际的连接池是RealConnectionPool

提一下,ConnectionPool默认可以保持5个空的连接,最长5分钟的空置时间。根据需要可以更改这两个属性。

继承与代理

可以思考一下,在应用层,我们是使用的ConnectionConnectionPool,而在OkHttp内部,使用的却是RealConnectionRealConnectionPool。我们就可以明白了,OkHttp在尽量少的暴露内部API

无论是RealConnection继承Connection,还是ConnectionPool代理RealConnectionPool,都是为了让使用者尽量少的知道内部的实现。

RealConnectionPool

明白了OkHttp的设计之后,就比较容易理解每个类的功能了。

RealConnectionPool对外,提供和ConnectionPool一样的功能。那么对内是怎么实现连接池的功能呢?下一篇详说。

总结

现在我们来简单总结一下,每个类的抽象概念。

下面是应用层的概念:

  1. ConnectionInterceptor:实现链的功能,承载着OkHttp建立连接的任务
  2. Transmitter:应用层与网络层的桥梁,通知网络层进行连接,对应用层提供Exchange
  3. Exchange:单个网络请求,使用它可以进行IO操作。在内部,使用ExchangeCodec进行IO操作

下面是网络层的概念:

  1. ExchangeFinder:为Exchange找到合适的RealConnection,并使用RealConnection构造ExchangeCodec
  2. RealConnection:进行网络连接,并提供网络连接的属性

ConnectionConnectionPool则是对外提供使用。上面这些类并不提供外部使用。


   转载规则


《okhttp 连接》 Mycroft Wong 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
okhttp RealConnectionPool okhttp RealConnectionPool
okhttp RealConnectionPool前一篇知道了RealConnection是真正建立连接的地方。现在我们看看RealConnectionPool是如何管理RealConnection的呢。 属性先看看RealConnecti
下一篇 
okhttp 缓存 okhttp 缓存
okhttp 缓存前言缓存的使用可以减少我们程序请求服务器、读取文件等耗时IO的次数,能够极大的提高程序的运行速度、性能,除了在OkHttp中使用了缓存,在很多优秀的库中都使用了缓存,图片库最为明显,如glide,fresco在这方面都是很
  目录