One, foreword

[1.1] Other chapters in OkHttp series:

  1. The implementation flow of synchronous requests.
  2. The implementation flow of asynchronous requests
  3. Important interceptor: parsing of the CacheInterceptor.
  4. Important interceptor: Resolution of the ConnectInterceptor.
  5. Important interceptor: resolution of the CallServerInterceptor.

[1.2] Statement

Finally, we come to OkHttp’s network connection module, which is the core of OkHttp. We know that an Http connection requires three handshakes and four waves to disconnect. Each handshake of the connection requires Socket connection and release, which is a very troublesome and time-consuming process. Therefore, the connection is particularly important. If the connection of the same address is kept open after running out, the connection can be reused in the next request, saving the connection time. This is especially important for mobile networks that require frequent and repeated visits to the same server address most of the time.

In this article, we will take the ConnectIntercepter as a starting point, follow the process of network connection acquisition, and delve deeply into the process involved: connection search, connection reuse, network connection establishment (three-way handshake, Http2 protocol processing, etc.). Java and ConnectionPool. Java will be introduced to further understand the logic of connection establishment and cache lookup. In addition, we need to take a look at another class: Transmitter. Java, which will play an important role in the connect process.

Transmmiter: A bridge between applications and Http

[2.1] History and function

RealCall.java
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);
    returncall; } Transmitter.java public final class Transmitter { private final OkHttpClient client; Private final RealConnectionPool connectionPool; Private final Call Call; private Request request; // Emphasis: the connection finder, which will do the main connection lookup. private ExchangeFinder exchangeFinder; // The implementation class of Connecttion, which represents the connection to the server. public RealConnection connection; Private @nullable Exchange; // Whether the request has been canceled private Boolean canceled; . public Transmitter(OkHttpClient client, Call call) { this.client = client; this.connectionPool = Internal.instance.realConnectionPool(client.connectionPool()); this.call = call; this.eventListener = client.eventListenerFactory().create(call); this.timeout.timeout(client.callTimeoutMillis(), MILLISECONDS); }Copy the code

Summary: The Transmitter is created when the RealCall is created, which requires OkHttpClient and the current request Call as parameters. So we know that a request corresponds to a Transmitter. Furthermore, it has classes such as ExchangeFinder in its member variables that are responsible for finding a suitable request for that request.

【 2.2 】 releaseConnectionNoEvents ()

This method is to release a connection, which will be covered later in finding a connection and is described here first.

Transmitter.java
@Nullable Socket releaseConnectionNoEvents() {... int index = -1; // There can be multiple transmitters, that is, for multiple requests. So here you need // find your own one.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(); // Remove yourself from the connection. RealConnection released = this.connection; released.transmitters.remove(index); this.connection = null; // If the connection is not used for other requests after the request is released, the connection pool is called to make the connection an idle connection.if(released.transmitters.isEmpty()) { released.idleAtNanos = System.nanoTime(); // See [5.4] for details.if(connectionPool. ConnectionBecameIdle (released)) {/ / no one is in use, put Socket back to back.returnreleased.socket(); }} // If there are other requests in use, do not return the socket.return null;
  }
Copy the code

Summary: This is a request to close a connection.

  1. First find the index of the pair in the connection and disconnect.
  2. Determine whether the connection is still in use without making it idle.

[2.1] prepareToConnect() : Preparation of the connection

[2.2.1]
RetryAndFollowUpInterceptor.java @Override public Response intercept(Chain chain) throws IOException { ... Transmitter transmitter = realChain.transmitter(); .while (true) {
      transmitter.prepareToConnect(request);

      if (transmitter.isCanceled()) {
        throw new IOException("Canceled"); }... }}Copy the code

From the above you can see, the first default interceptor in the implementation of the logic, call transmitter. PrepareToConnect () method. So let’s go ahead and look at the method and do the preparatory work.

[2.2.2] prepareToConnect ()
Transmitter.java
 public void prepareToConnect(Request request) {
 
    if(this.request ! = null) {// If this Transmitter already has a request // and their URL points to the same address, then this link can be reused, directly return.if (sameConnection(this.request.url(), request.url())) return; // If the Transmitter of the last request is not empty, it means that the request is not finished yet. // Then the Transmitter can not be used for the new request.if(exchange ! = null) throw new IllegalStateException(); // Release the last connection.if(exchangeFinder ! = null) { maybeReleaseConnection(null,true); exchangeFinder = null; }} // The first time I came in, I went straight here. this.request = request; // Create a connection finder for yourself. Note CreateAddress(), which returns an Adrees object representing an address of the remote server. this.exchangeFinder = new ExchangeFinder(this, connectionPool, createAddress(request.url()), call, eventListener); }Copy the code

Conclusion: The main point of this approach is to prepare the connection. But the main goal is to find connections that can be reused. Its logic is as follows:

  1. If the Transmiiter has a previous request and the address is the same as the new request, the connection can be reused and returned directly.
  2. If it is not the same address and the last request has not been used up (Exchange! = null), then this Tranmitter cannot be reused. Report an error directly. If the previous request has finished, release the connection from the previous request.
  3. If the Tranmitter is new, create a new ExcahgeFinder for the Transmiiter. Note that this class is important and will do most of the connection finding.

【 2.2 】 acquireConnectionNoEvents

Transmitter.java
 void acquireConnectionNoEvents(RealConnection connection) {
    ...
    this.connection = connection;
    connection.transmitters.add(new TransmitterReference(this, callStackTrace));
  }
Copy the code

Summary: This method represents a Transmitter to obtain a usable connection. So what it does is it saves this connection. Then register yourself with RealConnection. This method will come in handy later, but I’ll show you a little bit about it.

Find the connection

With the basics of Chapter 2 in mind, we can look at the ConnectIntercepter. But it only triggers the button that opens the connection, and the lookup and connection logic for the actual connection is in Exchange Finder.java and exchage.java. Anyway, let’s take a look at where we started.

【 3.1 】 ConnectIntercepter

ConnectIntercepter.java
@Override public Response intercept(Chain chain) throws IOException {
    ...
    boolean doExtensiveHealthChecks = ! request.method().equals("GET"); // See [3.2] Exchange = transmitter. NewExchange (chain,doExtensiveHealthChecks);

    return realChain.proceed(request, transmitter, exchange);
  }
Copy the code

Call the transmitter newExcahge() method, get an Exchage that can be exchanged with the remote address, and throw it to the next interceptor. By the way, we learned in the first article that the next interceptor following the ConnectIntercepter is the ServerIntercepter, so we can easily infer that it gets the ConnectIntercepter excahge. Data transmission and data reception.

【 3.2 】 newExchange ()

Transmitter.java
Exchange newExchange(Interceptor.Chain chain, boolean doExtensiveHealthChecks) { synchronized (connectionPool) { ... Java 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;
      returnresult; }}Copy the code

Call ExchangeFinder.find () to find a connection and return ExchangeCodec. ExchangeCodec is an interface that represents the encryption of Http requests and the decryption of responses. It has two concrete implementations: Http1ExchangeCodec and Http2ExchangeCodec, which are detailed in [4]. Let’s continue looking at connection lookup.

【 3.4 】 the find ()

ExcahgeFinder.java
 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(); See [3.5] RealConnection resultConnection = findHealthyConnection(connectTimeout,readTimeout,
          writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks); // See [3.7]returnresultConnection.newCodec(client, chain); } catch (RouteException e) { trackFailure(); throw e; } catch (IOException e) { trackFailure(); throw new RouteException(e); }}Copy the code

【 3.5 】 findHealthyConnection ()

ExcahgeFinder.java
  private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
      int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled,
      boolean doExtensiveHealthChecks) throws IOException {
    while (true) {// See: [3.6] RealConnection candidate = findConnection(connectTimeout,readTimeout, writeTimeout, pingIntervalMillis, connectionRetryEnabled); Synchronized (connectionPool) {synchronized (connectionPool) {synchronized (connectionPool) {if (candidate.successCount == 0) {
          returncandidate; }} // Here we need to check if the connection is healthyif(! candidate.isHealthy(doExtensiveHealthChecks)) {/ / if not, call RealConnection. NoNewExcahge () method, which will be discarded the connection and continue to find. candidate.noNewExchanges();continue;
      }

      returncandidate; }}Copy the code

Conclusion: As the name implies, this method is to continuously find a connection candidate through a while(true), then check if it is healthy and available, if it is not available, mark it and discard it. Details are as follows:

  1. Call findConnection() to find a connection.
  2. If brand new use directly.
  3. If unhealthy call RealConnection. NoNewExcahge (), its internal main do is noNewExchanges = true; This flag will be used later to drop connections.
  4. Invisible judgment: isHealthy() does not expand, which is to determine whether the connection socket, etc., is closed, and thus to determine whether the connection isHealthy.

【 3.6 】 findConnection ()

Next is the most important, let us taste together this very fragrant search logic.

  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) { //1. If the request has already been canceled, the request throws an error again.if (transmitter.isCanceled()) throw new IOException("Canceled"); . Route previousRoute = retryCurrentRoute()? transmitter.connection.route() : null; //3. Try to use an already allocated connection here, but as seen above [3.5] // he will check its noNewExchange flag as, if sotrueNot only will the connection not work, but it will be copied to toClose and closed. / / see [2.3] : releaseConnectionNoEvents releasedConnection = transmitter (). The connection; toClose = transmitter.connection ! = null && transmitter.connection.noNewExchanges ? transmitter.releaseConnectionNoEvents() : null; //4. If the transmitter connection has gone through the above logic and is not null, it indicates that the connection is available and is set to result.if(transmitter.connection ! = null) { result = transmitter.connection; releasedConnection = null; // This connection can be used, but it cannot be freed and reset to null. } //5. If result is null, the connection failed to be allocated.if(result == null) {// see [5.3] : attempts to obtain a connectionif (connectionPool.transmitterAcquirePooledConnection(address, transmitter, null, falseFoundPooledConnection = foundPooledConnection =true;
          result = transmitter.connection;
        } elseSelectedRoute = previousRoute; selectedRoute = previousRoute; }}} // Close the connection you just want to close. closeQuietly(toClose); . //result is not empty, find an available connection, return directly.if(result ! = null) {returnresult; } // 6. Make route selection 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) {//7. Because of the new route, use the new IP set selected by the route to find the connection pool can be reused. routes = routeSelection.getAll();if (connectionPool.transmitterAcquirePooledConnection(
            address, transmitter, routes, false)) {
          foundPooledConnection = true; result = transmitter.connection; }} //8. Create a new RealConnection.if(! foundPooledConnection) {if(selectedRoute == null) { selectedRoute = routeSelection.next(); } result = new RealConnection(connectionPool, selectedRoute); connectingConnection = result; }} // 9. Return the connection if it was just found in the connection pool a second time.if (foundPooledConnection) {
      eventListener.connectionAcquired(call, result);
      returnresult; } // connect(connectTimeout, connectTimeout, connectTimeout);readTimeout, writeTimeout, pingIntervalMillis, connectionRetryEnabled, call, eventListener); connectionPool.routeDatabase.connected(result.route()); Socket socket = null; synchronized (connectionPool) { connectingConnection = null; //11. When multiple connections are connected to the same host, connection merging is performed here. This is the last attemptif (connectionPool.transmitterAcquirePooledConnection(address, transmitter, routes, true) {// Indicates that there is already one connection available in the connection pool, and the newly created connection is not required. pooled connection. result.noNewExchanges =true;
        socket = result.socket();
        result = transmitter.connection;
      } else{//12. See [5.5] that the newly created connection is working properly, add it to the pool connectionPool.put(result); transmitter.acquireConnectionNoEvents(result); }} // If necessary, drop the newly created connection closeQuietly(socket); eventListener.connectionAcquired(call, result); // Finally returnreturn result;
  }
Copy the code

Conclusion: This is a connection search process, in the search, the comprehensive consideration of their own connection, the result of routing, connection pool reuse, and new several schemes. The details are as follows:

  1. Try to get the connection that the request has been assigned to, if it exists and is available, then use this directly and return. Otherwise, the socket for this connection will be closed.
  2. Try to retrieve it from the connection pool and return the result if available.
  3. Try routing out of the result set using the router to look up available connections again in the connection pool.
  4. If no route can be found, create a TCP+TSL connection.
  5. Go to the connection pool again to see if any connections are available to avoid creating multiple connections. If found, close the new connection. The result is replaced with the newly found connection. If not, add the new connection to the connection pool.
  6. Returns the result.

[3.7] newCodec(): Get data encryption and decryption

ExchangeCodec newCodec(OkHttpClient client, Interceptor.Chain chain) throws SocketException {
    if(http2Connection ! = null) {return new Http2ExchangeCodec(client, this, chain, http2Connection);
    } else {
      socket.setSoTimeout(chain.readTimeoutMillis());
      source.timeout().timeout(chain.readTimeoutMillis(), MILLISECONDS);
      sink.timeout().timeout(chain.writeTimeoutMillis(), MILLISECONDS);
      return new Http1ExchangeCodec(client, this, source, sink); }}Copy the code

Conclusion: different data encryption and decryption devices are generated according to different connection properties.

This section starts with the ConnectIntercepter, tracing how a connection is obtained. It involves creating a connection, routing, connection pool reuse, etc. The final product is Exchange, which goes to the next interceptor: ServerIntercepter Carries out network transmission. Exchange and RealConnectionPool play important roles, which will be explained in the next section


Four, RealConnection

RealConnection describes a connection to a remote server, so it needs to have the ability to establish a connection with a remote address and pass through. We can see this in its subsequent member variables and methods. As usual, let’s look at the constructor and member variables of.

[4.1] Member variables and constructs

public final class RealConnection extends Http2Connection.Listener implements Connection { ... private static final int MAX_TUNNEL_ATTEMPTS = 21; Public Final RealConnectionPool connectionPool; // Private final Route Route; // The socket will be assigned in the connect() method and will not be reassigned. It is used for underlying Socket communication. private Socket rawSocket; // Represents the application layer Socket private Socket Socket; // Describe the object of a complete handshake. private Handshake handshake; // Protocol enumeration classes, including HTTP /1.0 and HTTP /3.1. private Protocol protocol; Private Http2Connection Http2Connection; // The stream operation object that interacts with the server. private BufferedSourcesource; private BufferedSink sink; // A flag bit representing a connection, managed by connectionPool, and oncetrue, will always betrue. This means that the connection does not require a new Exchage. boolean noNewExchanges; . */ final List<Reference<Transmitter>> CCD = new ArrayList<>(); . // The constructor requires a connection pool and router. public RealConnection(RealConnectionPool connectionPool, Route route) { this.connectionPool = connectionPool; this.route = route; }Copy the code

Summary: Some of the main member variables are commented above. Let’s start with its most important method, connect(), to understand what it does.

[4.2] Connect () : Establish a connection with the remote server

public void connect(int connectTimeout, int readTimeout, int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled, Call call, EventListener EventListener) {// If portACol is not empty, the connection has been established, and an error is raised.if(protocol ! = null) throw new IllegalStateException("already connected"); RouteException routeException = null; // Note the ConnectSpec object here, which represents the configuration of Http Socket communication. For example, it specifies the TLS protocol version. List<ConnectionSpec> connectionSpecs = route.address().connectionSpecs(); ConnectionSpecSelector connectionSpecSelector = new ConnectionSpecSelector(connectionSpecs); If the configuration is invalid, an error will be thrown. // If HTTP is invalid, check whether plaintext transmission is not allowed or whether The Android platform does not allow plaintext transmission. An unsatisfied throw error.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 the connection is Https, determine whether to configure H2_prior_knowledge.if (route.address().protocols().contains(Protocol.H2_PRIOR_KNOWLEDGE)) {
        throw new RouteException(new UnknownServiceException(
            "H2_PRIOR_KNOWLEDGE cannot be used with HTTPS")); }} // Connect from here.while (true) {try {//2. Check whether tunnel mode is required and establish a tunnel connection if so. // If the destination address is Https, but the proxy is Http, the check will be satisfied.if(route.requirestunnel ()) {// See [4.3] connectTunnel(connectTimeout,readTimeout, writeTimeout, call, eventListener); // If the rawSocket is empty, the tunnel connection cannot be established. Exit.if (rawSocket == null) {
            break; }}else{//3. See [4.4] to establish a common Socket connection.readTimeout, call, eventListener); } //4. See [4.5] to establish the agreement. establishProtocol(connectionSpecSelector, pingIntervalMillis, call, eventListener); eventListener.connectEnd(call, route.socketAddress(), route.proxy(), protocol);break; } catch (IOException e) { ... }} //5. Handle the failure of establishing a tunnel connectionif (route.requiresTunnel() && rawSocket == null) {
      ProtocolException exception = new ProtocolException("Too many tunnel connections attempted: "+ MAX_TUNNEL_ATTEMPTS); throw new RouteException(exception); } // If Http2 protocol, get the maximum concurrent flow limitif(http2Connection ! = null) { synchronized (connectionPool) { allocationLimit = http2Connection.maxConcurrentStreams(); }}}Copy the code

Conclusion: This method is where Connection handles Connection logic, including the following points:

  1. The protocol is used to determine whether the connection has been established. If the connection is not empty, it has been established. In this case, an error is thrown.
  2. Check the configuration based on the Http connection protocol.
  3. Check whether a tunnel connection is established. If yes, go to the tunnel connection process. It determines whether the HTTP proxy is Http2 or Https. The Http proxy that establishes the tunnel connection will no longer parse the data, but will forward it directly.
  4. Set up a common Socket connection.
  5. Establish protocol: TCL handshake, HTTP/2 negotiation, etc.
  6. Some handling of connections after they have been established or failed

[4.3] connectTunnel() : establishes a tunnel connection

private void connectTunnel(int connectTimeout, int readTimeout, int writeTimeout, Call call, EventListener eventListener) throws IOException { //1. Create a request for a tunnel connection. Request tunnelRequest = createTunnelRequest(); HttpUrl url = tunnelRequest.url();for(int i = 0; i < MAX_TUNNEL_ATTEMPTS; I++) {//2. See [4.4] for details, see [4.4].readTimeout, call, eventListener); TunnelRequest = createTunnel(readTimeout, writeTimeout, tunnelRequest, url);

      if (tunnelRequest == null) break; // Tunnel successfully created.

      // The proxy decided to close the connection after an auth challenge. We need to create a new
      // connection, but this time with the auth credentials.
      closeQuietly(rawSocket);
      rawSocket = null;
      sink = null;
      source= null; eventListener.connectEnd(call, route.socketAddress(), route.proxy(), null); }}Copy the code

Conclusion: To create a tunnel connection is to establish an Https connection over an Http proxy. The main things are as follows:

  1. Create a tunnelRequest: a request to set up a TLS tunnel through a proxy. Because it is unencrypted, its header information contains only the smallest set of headers, which is also to avoid passing sensitive data to Http, such as cookies.
  2. Socket connection.
  3. Make a tunnel connection: Pass in the tunnelRequset you just created and build an Http1ExchangeCodec object for the Http1 protocol’s stream operation implementation class to make a connection request to the Http proxy.

【 4.4 】 connectSocket ()

 private void connectSocket(int connectTimeout, int readTimeout, Call call, EventListener eventListener) throws IOException { Proxy proxy = route.proxy(); Address address = route.address(); //1. Select different Socket generation policies based on different proxy types. rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP ? address.socketFactory().createSocket() : new Socket(proxy); eventListener.connectStart(call, route.socketAddress(), proxy); // Set timeout rawsocket.setsotimeout (readTimeout);
    try {
    //2. 采用平台上的连接socket方式
      Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);
    } catch (ConnectException e) {
      ...
    }

   //得到Socket的输出输入流
    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); }}}Copy the code

Conclusion: This method is to establish a Socket connection with the remote server address, and get input and output streams. The details are as follows:

  1. If the connection is direct or Http, create a Socket through the SocketFactory. Otherwise, it is a proxy Socket. Pass the proxy to the Socket and create a proxy Socket.
  2. Set up the Socket connection. Platform.get() gets the Android Platform here, and what it does internally is that
socket.connect(address, connectTimeout);
Copy the code

After this step of connect, the socket completes the three-way handshake to establish the TCP connection. 3. Obtain the output and input streams of the Socket.

#### [4.5] establishProtocol()

private void establishProtocol(ConnectionSpecSelector connectionSpecSelector, int pingIntervalMillis, Call call, EventListener eventListener) throws IOException { //1. Check whether the request is HTTPif(route.address().sslSocketFactory() == null) { //2. If the HTTP request contains the "H2_prior_knowledge" protocol, it is an HTTP2 request that supports plaintext, so the HTTP2 connection is still openif(route.address().protocols().contains(Protocol.H2_PRIOR_KNOWLEDGE)) { socket = rawSocket; protocol = Protocol.H2_PRIOR_KNOWLEDGE; //3. Establish an http2 connection to startHttp2(pingIntervalMillis);return; } //4. Set up HTTP connection socket = rawSocket. protocol = Protocol.HTTP_1_1;return; } eventListener.secureConnectStart(call); See [4.6] to establish Tls protocol connectTls(connectionSpecSelector). eventListener.secureConnectEnd(call, handshake); // Establish an HTTP2 connectionif(protocol == Protocol.HTTP_2) { startHttp2(pingIntervalMillis); }}Copy the code

Summary: Based on the request protocol, this method determines whether the established connection requires further protocol processing. The details are as follows:

  1. If it is an HTTP request but contains either “H2_prior_knowledge” or the HTTP2 protocol, the HTPP2 connection is further constructed.
  2. Others are normal HTTP requests that directly assign the rawSocket representing the bottom layer to the socket representing the application layer.

【 4.6 】 connectTls ()

private void connectTls(ConnectionSpecSelector connectionSpecSelector) throws IOException {
    Address address = route.address();
    SSLSocketFactory sslSocketFactory = address.sslSocketFactory();
    boolean success = false; SSLSocket sslSocket = null; Try {// 1. Wrap the socket with sslSocketFactory // to get an SSLSocket object. sslSocket = (SSLSocket) sslSocketFactory.createSocket( rawSocket, address.url().host(), address.url().port(),true/* autoClose */); // 2. See [4.7] to configure sslSocket. ConnectionSpec connectionSpec = connectionSpecSelector.configureSecureSocket(sslSocket); //3. Determine whether to configure Tls extensionif(connectionSpec.supportsTlsExtensions()) { Platform.get().configureTlsExtensions( sslSocket, address.url().host(), address.protocols()); Sslsocket.starthandshake (); SSLSession sslSocketSession = sslSocket.getSession(); Handshake unverifiedHandshake = Handshake.get(sslSocketSession); //5. Verify that the sslSocket address is consistent with the host address.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)"); }} / / 6. Certificate check address. CertificatePinner (). The check (address. The url (). The host (), unverifiedHandshake. PeerCertificates ()); //7. If extension is configured in 3, the result of protocol negotiation will be fetched here. String maybeProtocol = connectionSpec.supportsTlsExtensions() ? Platform.get().getSelectedProtocol(sslSocket) : null; // Save the sslSocket that completed the handshake and protocol verificationsourceSink 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) { ... } finally { ... }}Copy the code

Summary: In this method, the connection will be SSL configuration, three-way handshake, certificate verification, etc. The details are as follows:

  1. Wrap the socket as SLLSocket.
  2. Configure the SLLSocket protocol.
  3. If necessary, extend the SLL protocol.
  4. Start shaking hands three times.
  5. Verify host address consistency to prevent IP address loss during handshake.
  6. Verify the validity line of the certificate returned by the server.
  7. If necessary, obtain the protocol selected during the handshake negotiation.
  8. Save the SSLSocket that completes the handshake and protocol verification, and obtain the source and sink for I/O transmission.

[4.7] configureSecureSocket() configureSecureSocket() configures the SSLScoket protocol

ConnectionSpecSelector.java
 ConnectionSpec configureSecureSocket(SSLSocket sslSocket) throws IOException {
    ConnectionSpec tlsConfiguration = null;
    for (int i = nextModeIndex, size = connectionSpecs.size(); i < size; i++) {
      ConnectionSpec connectionSpec = connectionSpecs.get(i);
      if (connectionSpec.isCompatible(sslSocket)) {
        tlsConfiguration = connectionSpec;
        nextModeIndex = i + 1;
        break; }}... Internal.instance.apply(tlsConfiguration, sslSocket, isFallback);return tlsConfiguration;
  }

Copy the code

Summary: As you can see, configuring SSLScoket is a matter of walking through the connectionSpecs collection, picking out the configuration that is appropriate for this SSLScoket, and then using it. The details are as follows:

  1. ConnectionSpecs collection: has a default value when OkHttpClient is created:
OkHttpClient.java
 static final List<ConnectionSpec> DEFAULT_CONNECTION_SPECS = Util.immutableList(
      ConnectionSpec.MODERN_TLS, ConnectionSpec.CLEARTEXT);
Copy the code
  1. The application of the protocol eventually calls the ConnectionSpec.apply() method to make the TSL version of SSLScoket and set up the cipher suite.

[4.8] connectionSpec.apply (): protocol application

ConnectionSpec.java
 void apply(SSLSocket sslSocket, boolean isFallback) {
    ConnectionSpec specToApply = supportedSpec(sslSocket, isFallback);

    if(specToApply.tlsVersions ! = null) { sslSocket.setEnabledProtocols(specToApply.tlsVersions); }if (specToApply.cipherSuites != null) {
      sslSocket.setEnabledCipherSuites(specToApply.cipherSuites);
    }
  }
Copy the code

Summary: Set TLS version and password suite for this socket

[4.9] isprompts () : logic that judges whether connections are available for reuse

Realconnection. Java Boolean isEligible(Address Address, @nullable List<Route> routes) {// This connection cannot be reused if it contains the maximum number of requestsif (transmitters.size() >= allocationLimit || noNewExchanges) return false; // If not the Host field, see if their addresses are exactly the same.if(! Internal.instance.equalsNonHost(this.route.address(), address))return false; // if the Host field is the same, the result can be reused.if (address.url().host().equals(this.route().address().url().host())) {
      return true; } // Here are the Http2 connection multiplexing dependencies. .return true; 
  }
Copy the code

Conclusion: This method will be covered in the subsequent parsing, so I will cover it here. It is mainly used to determine whether the connection can be reused. The judgment conditions are described in the comments.


ConnectiongPool: indicates a connection pool

In the findConnetion process of 3.6, we have seen many times the figure of connection pool, which is absolutely important to the reuse of connections. If we do not carefully understand it, we will lose a lot of logic to find connections. As usual, you get to know it from its birth, member variables, and constructors.

[5.1] The birth of RealConnection

OkHttpClient.Builder.java
public Builder() {... connectionPool = new ConnectionPool(); }Copy the code

Create the default connection pool in Builder().

public final class ConnectionPool {
  final RealConnectionPool delegate;
  public ConnectionPool() {
    this(5, 5, TimeUnit.MINUTES);
  }

  public ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {
    this.delegate = new RealConnectionPool(maxIdleConnections, keepAliveDuration, timeUnit);
  }
Copy the code

Summary: The proxy mode is used for ConectionPool, and the actual logic is handed over to RealConnection(). 5 maximum idle connections, each connection lasts 5 minutes.

[5.2] Member variables and constructors

public final class RealConnectionPool{
 //
 private static final Executor executor = new ThreadPoolExecutor(0 /* corePoolSize */,
      Integer.MAX_VALUE /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS,
      new SynchronousQueue<>(), Util.threadFactory("OkHttp ConnectionPool".true)); */ private final int maxIdleConnections; Private final Long keepAliveDurationNs; Private Final Runnable cleanupRunnable = () -> {while (true) {// see [5.6] longwaitNanos = cleanup(System.nanoTime());
      if (waitNanos == -1) return;
      if (waitNanos > 0) {
        long waitMillis = waitNanos / 1000000L;
        waitNanos -= (waitMillis * 1000000L); Synchronized (realConnectionPool. this) {try {// Wait to wake up to perform cleanup tasks. RealConnectionPool.this.wait(waitMillis, (int) waitNanos); } catch (InterruptedException ignored) { } } } } }; Private final Deque<RealConnection> connections = new ArrayDeque<>(); // Final RouteDatabase RouteDatabase = new RouteDatabase(); Boolean cleanupRunning; /** * constructor */ public RealConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) { this.maxIdleConnections = maxIdleConnections; this.keepAliveDurationNs = timeUnit.toNanos(keepAliveDuration); . }}Copy the code

Summary: As you can see, this connection pool is used to manage connections to the same address. It provides functions such as finding available connections by address and clearing connections. Here are some of its important methods.

【 5.3 】 transmitterAcquirePooledConnection () : obtain the connection

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; [See details in 4.9]if(! connection.isEligible(address, routes))continue; Transmitter. See 2.2 】 【 acquireConnectionNoEvents (connection);return true;
    }
    return false;
  }
Copy the code

Conclusion: traversal to save the connection, call RealConnection. IsEligible () to determine whether the connection is eligible. Register this requested Transmitter to RealConnection.

【 5.4 】 connectionBecameIdle ()

boolean connectionBecameIdle(RealConnection connection) {
    assert (Thread.holdsLock(this));
    if (connection.noNewExchanges || maxIdleConnections == 0) {
      connections.remove(connection);
      return true;
    } else{// Notify cleanup task execution. notifyAll(); connection limit.return false; }}Copy the code

Summary: Make a connection idle. If the connection is not available at this point, it is removed from the connection set and returns true. If it still works, notify the cleanup task to execute and return false.

【 5.5 】 the put ()

void put(RealConnection connection) {
    assert (Thread.holdsLock(this));
    if(! cleanupRunning) { cleanupRunning =true;
      executor.execute(cleanupRunnable);
    }
    connections.add(connection);
  }
Copy the code

Summary: This method puts a connection into the connection pool and performs cleanup, but it is blocked until method [5.4] fires.

【 5.6 】 the cleanup ()

long cleanup(long now) {
    int inUseConnectionCount = 0; int idleConnectionCount = 0; RealConnection longestIdleConnection = null; long longestIdleDurationNs = Long.MIN_VALUE; Synchronized (this) {//1for(Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) { RealConnection connection = i.next(); //2. If the connection is still in use, continue traversingif (pruneAndGetAllocationCount(connection, now) > 0) {
          inUseConnectionCount++;
          continue; } idleConnectionCount++; //3. Find the longest idle time and the correct connectionif(idleDurationNs > longestIdleDurationNs) { longestIdleDurationNs = idleDurationNs; longestIdleConnection = connection; }} //4. Clear the longest idle connection when the following conditions are met: //a. The idle time exceeds the maximum keepalive time. B. The number of idle connections exceeds the maximum numberif (longestIdleDurationNs >= this.keepAliveDurationNs
          || idleConnectionCount > this.maxIdleConnections) {
        connections.remove(longestIdleConnection);
      } else if(idleConnectionCount > 0) {// No cleanup, return the time needed for the next cleanupreturn keepAliveDurationNs - longestIdleDurationNs;
      } else if (inUseConnectionCount > 0) {// No idle connection, keepAliveDuration is returned, keepAliveDuration is executed after.return keepAliveDurationNs;
      } else{// There are no idle or active connections. cleanupRunning =false;
        return- 1; } } closeQuietly(longestIdleConnection.socket()); // The cleanup task will be performed immediately.return 0;
  }
Copy the code

Summary: This is a method to clean up the connection, which does the following:

  1. Traverse the connection, skip if the connection is still in use.
  2. Find the maximum idle time and connect to it.
  3. If the conditions are met, the connection is removed from the connection pool. It then triggers another cleanup task.
  4. If no, return the next cleaning time or -1 indicates the end of cleaning.

Conclusion: This article introduces the network connection establishment of OkHttp. Trasnmitter is an important class, which is introduced in the beginning. Then, it starts from the ConnectIntercepter, and deeply studies the acquisition logic of Connection. In the process of fetching, we will deal with the connection cache. When the cache is not available, a new network connection is created, which does the Http three-way handshake and so on. In the last two sections, the central class, the object to be searched: RealConnection, is introduced. And the management cache ConnectionPool for Connection. Finally, a graph summarizes the process