美文网首页
Android - 剖析OKHttp(4)- 拦截器Connec

Android - 剖析OKHttp(4)- 拦截器Connec

作者: 杨0612 | 来源:发表于2020-11-09 17:58 被阅读0次

    源码分析基于 3.14.4

    ConnectInterceptor作用

    主要负责建立或者复用Socket连接;

    //ConnectInterceptor类中
      @Override public Response intercept(Chain chain) throws IOException {
        ......
        Exchange exchange = transmitter.newExchange(chain, doExtensiveHealthChecks);//1
        return realChain.proceed(request, transmitter, exchange);//2
      }
    
    • 注释1:主要逻辑都在transmitter.newExchange里面;
    • 注释2:调用下一个拦截器;
    //Transmitter类中
     Exchange newExchange(Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
        ......
        ExchangeCodec codec = exchangeFinder.find(client, chain, doExtensiveHealthChecks);//调用下面的函数,查找一个可以使用的Connection
        ......
      }
      
    //ExchangeFinder类中  
    public ExchangeCodec find(
         ......
         RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
              writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);//最终会调到ExchangeFinder.findConnection
         ......
      }
    //ExchangeFinder类中
    private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
          int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
          .......
          if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, null, false)) {//1
              foundPooledConnection = true;
              result = transmitter.connection;
            }
           .......
           if (result != null) {//找到可以复用的连接,则返回
               return result;
           }
            ......
            result = new RealConnection(connectionPool, selectedRoute);//2
            connectingConnection = result;
            ......
            result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
            connectionRetryEnabled, call, eventListener);//3
            ......
            return result;
      }
    
    • 注释1: 从连接池里查找可复用的Connection,查找条件是:HTTP版本、支持加密版本、TLS版本、代理、SSL工厂、端口、Host等,找到了直接返回;
    • 连接池默认保存5个空闲连接,超时时间5分钟,如果5分钟内没有任何连接,则断开连接;
    • 注释2:如果没有找到,则构建一个RealConnection;
    • 注释3:result.connect,建立TCP+TLS连接;内部调用RealConnection.connectSocket建立Socket连接,也就是TCP;内部调用RealConnection.establishProtocol建立TLS连接,也就是HTTPS;

    RealConnection.connect建立连接

    //RealConnection类中
    public void connect(int connectTimeout, int readTimeout, int writeTimeout,
          int pingIntervalMillis, boolean connectionRetryEnabled, Call call,
          EventListener eventListener) {
        .......
        while (true) {
          try {
            if (route.requiresTunnel()) {//1
              connectTunnel(connectTimeout, readTimeout, writeTimeout, call, eventListener);
              ......
            } else {//2
              connectSocket(connectTimeout, readTimeout, call, eventListener);
            }
            ......
      }
    
    • 注释1:表示需要用到隧道,通常是在Http代理连接上发送https请求;
    //普通连接的请求行
    GET /user HTTP/1.1 
    //使用HTTP代理发送HTTPS请求
    GET CONNECT www.baidu.com  HTTP/1.1
    
    • 如果使用http代理发送https请求,建立Socket连接以后发出CONNECT ,成功了就表示可以正常发送请求,后续的请求行就跟普通的一样;
    • 注释2:如果不使用隧道则connectSocket,建立Socket连接;

    RealConnection.connectSocket建立Socket连接

      private void connectSocket(int connectTimeout, int readTimeout, Call call,
          EventListener eventListener) throws IOException {
        Proxy proxy = route.proxy();
        Address address = route.address();
    
        rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
            ? address.socketFactory().createSocket()//1
            : new Socket(proxy);//2
        ......
        Platform.get().connectSocket(rawSocket, route.socketAddress(), 
        connectTimeout);//3
     ......
      }
    
    • 注释1:创建普通连接;
    • 注释2:创建代理的连接new Socket(proxy);
    • 注释3:建立Socket连接;

    连接池

    public final class ConnectionPool {
      final RealConnectionPool delegate;
      public ConnectionPool() {
        this(5, 5, TimeUnit.MINUTES);//1
      }
      public ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {
        this.delegate = new RealConnectionPool(maxIdleConnections, keepAliveDuration, timeUnit);
      }
    
    • 空闲连接数大于5个或空闲时长大于5分钟,则清理;
      1.缓存连接
    //RealConnectionPool类中
      void put(RealConnection connection) {
        assert (Thread.holdsLock(this));
        if (!cleanupRunning) {
          cleanupRunning = true;
          executor.execute(cleanupRunnable);//1
        }
        connections.add(connection);//2
      }
    
    • 注释1:启动cleanupRunnable线程开始清理空闲连接;
    • 注释2:缓存连接;
    1. 清理空闲连接
     //RealConnectionPool类中
      private final Runnable cleanupRunnable = () -> {
        while (true) {
          long waitNanos = cleanup(System.nanoTime());//调用下面的cleanup函数
          if (waitNanos == -1) return;//7
          if (waitNanos > 0) {
            long waitMillis = waitNanos / 1000000L;
            waitNanos -= (waitMillis * 1000000L);
            synchronized (RealConnectionPool.this) {
              try {
                RealConnectionPool.this.wait(waitMillis, (int) waitNanos);//8
              } catch (InterruptedException ignored) {
              }
            }
          }
        }
      };
     
    //RealConnectionPool类中
    long cleanup(long now) {
        synchronized (this) {
          for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {//1
            RealConnection connection = i.next();
            if (pruneAndGetAllocationCount(connection, now) > 0) {//判断连接是否使用,每个连接都有一个弱引用计数,当计数大于0,则表示在使用中
              inUseConnectionCount++;
              continue;
            }
    
            idleConnectionCount++;//空闲连接计数
    
            long idleDurationNs = now - connection.idleAtNanos;//计算空闲了多久
            if (idleDurationNs > longestIdleDurationNs) {//记录空闲最久的连接
              longestIdleDurationNs = idleDurationNs;
              longestIdleConnection = connection;
            }
          }
    
          if (longestIdleDurationNs >= this.keepAliveDurationNs
              || idleConnectionCount > this.maxIdleConnections) {//2
            connections.remove(longestIdleConnection);
          } else if (idleConnectionCount > 0) {//3
            return keepAliveDurationNs - longestIdleDurationNs;
          } else if (inUseConnectionCount > 0) {//4
            return keepAliveDurationNs;
          } else {
            cleanupRunning = false;
            return -1;//5
          }
        }
        ```
        closeQuietly(longestIdleConnection.socket());//6
        return 0;//9
      }
    
    • 注释1:遍历的目的,统计空闲连接数以及查找空闲最长的连接;
    • 注释2:当空闲连接闲置时间等于5分钟,则从集合移除;当空闲连接数大于5个,也从集合移除;
    • 注释3:当有空闲连接且注释2两个条件不满足,则返回等待时间(keepAliveDurationNs - longestIdleDurationNs),再开始清理,对应注释8;
    • 注释4:当没有空闲连接且注释2两个条件不满足,则返回等待时间5分钟,再开始清理;
    • 注释5:返回-1,表示即没有使用也没有空闲的,退出清理,对应注释7;
    • 注释6:关闭从集合移除的连接;
    • 注释9:返回0,不wait,继续查找;

    总结

    • ConnectInterceptor连接拦截器,主要负责建立Socket连接,其实Okttp本身用的就是Socket;
    • 如果连接池中有可以使用的,则复用,复用的条件是:HTTP版本、支持加密版本、TLS版本、代理、SSL工厂、端口、Host等;
    • 连接池清理由cleanupRunnable 任务实现;

    以上分析有不对的地方,请指出,互相学习,谢谢哦!

    相关文章

      网友评论

          本文标题:Android - 剖析OKHttp(4)- 拦截器Connec

          本文链接:https://www.haomeiwen.com/subject/dkitbktx.html