OkHttp3源码解读四:连接层

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/www1575066083/article/details/81484552

1、基本介绍:

Connection连接层:

  • 管理网络连接,发送新的请求,接收服务器访问,都是在ConnectInterceptor中完成的。

Connection:

  • 客户端通过HTTP协议与服务器进行通信,首先需要建立连接,okhttp并没有使用URLConnection, 而是对socket直接进行封装,在socket之上建立了connection的概念,描述一个物理Socket连接,由连接池ConnectionPool管理。

RealConnection:

  • Connection的一个具体实现类。
  • 物理连接的封装,其内部有List<WeakReference<StreamAllocation>>的引用计数来管理当前Connection上的流。

ConnectionPool:【管理connection】

  • 在连接层中有一个连接池,统一管理并复用所有的Socket连接,实现HTTP/1.1的Keep-Alive机制、HTTP/2的多路复用机制。
  • 一个OkHttpClient只包含一个ConnectionPool,其实例化过程也是在OkHttpClient的实例化过程中实现。
  • ConnectionPool各个方法的调用并没有直接对外暴露,而是通过OkHttpClient的Internal抽象类统一对外暴露。
  • 当用户新发起一个网络请求时,OkHttp会首先从连接池中查找是否有符合要求的连接,如果有则直接通过该连接发送网络请求;否则新创建一个网络连接。
  • 连接池中有socket回收,而这个回收是以RealConnection的弱引用List<Reference<StreamAllocation>>是否为0来为依据的。
  • ConnectionPool有一个独立的线程cleanupRunnable来清理连接池,其触发时机有两个:
    - 当连接池中put新的连接时
    - 当connectionBecameIdle方法被调用时

Stream:

  • okhttp3中使用建立在connection的物理连接之上的流的逻辑【stream】来进行数据通信。
  • 在HTTP1.1以及之前的协议中,一个连接只能同时支持单个流,而SPDY和HTTP2.0协议则可以同时支持多个流。【Http2的多路复用机制】

StreamAllocation [ˌæləˈkeɪʃn] :【管理stream】

  • RealConenction的引用计数器,管理一个连接上所有的流。
  • okhttp通过一个StreamAllocation的引用列表来管理一个连接上的流,从而使得连接与流之间解耦。
  • 从逻辑上一个Stream对应一个Call请求,但在实际网络请求过程中一个Call请求常常涉及到多次请求。如重定向,Authenticate等场景。所以准确地说,一个Stream对应一次call请求,而一个Call请求对应一组有逻辑关联的Stream。
  • 一个RealConnection对应一个或多个StreamAllocation,所以StreamAllocation可以看做是RealConenction的计数器,当RealConnection的引用计数变为0,且长时间没有被其他请求重新占用,这个连接就将被释放。

HttpCodec:

  • 编码Http的Request、解码Http的Response。
  • connection负责与远程服务器建立连接, HttpCodec负责请求的写入、响应的读出、取消一个请求对应的流、释放完成任务的流等操作。

2、重要类:

2.1、ConnectionPool:

public final class ConnectionPool {

      //用于清理过期的连接。【用线程池实现】
      private static final Executor executor = new ThreadPoolExecutor(0 ,
          Integer.MAX_VALUE, 60L , TimeUnit.SECONDS,
          new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp ConnectionPool", true));

      /** The maximum number of idle connections for each address. */
      private final int maxIdleConnections;
      private final long keepAliveDurationNs;

       //独立的线程cleanupRunnable来清理连接池。
      private final Runnable cleanupRunnable = new Runnable() {
        @Override public void run() {
            ......
        }
      };

        //Deque双端队列,用于维护当前所有连接的容器。
      private final Deque<RealConnection> connections = new ArrayDeque<>();

      //用来记录连接失败的Route的黑名单,当连接失败的时候就会把失败的线路加进去。
      final RouteDatabase routeDatabase = new RouteDatabase();

      boolean cleanupRunning;
      ......

      /**
       *返回符合要求的可重用连接,如果没有返回NULL
       */
      RealConnection get(Address address, StreamAllocation streamAllocation, Route route) {
        ......
      }

      /*
         * 清除重复的多路复用线程。
      */
      Socket deduplicate(Address address, StreamAllocation streamAllocation) {
        ......
        }

      /*
      * 将连接加入连接池
      */
      void put(RealConnection connection) {
          ......
      }

      /*
      * 当有连接空闲时唤起清理线程cleanup清理连接池
      */
      boolean connectionBecameIdle(RealConnection connection) {
          ......
      }

      /**
       * 扫描连接池,清除空闲连接
      */
      long cleanup(long now) {
        ......
      }

      /*
       * 标记泄露连接
      */
      private int pruneAndGetAllocationCount(RealConnection connection, long now) {
        ......
      }
    }

2.2、StreamAllocation.findConnection():

  • StreamAllocation在其findConnection方法内部通过调用get方法为其stream找到合适的连接,如果没有则新建一个连接。
    private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
                                            boolean connectionRetryEnabled) throws IOException {
        Route selectedRoute;
        synchronized (connectionPool) {
          if (released) throw new IllegalStateException("released");
          if (codec != null) throw new IllegalStateException("codec != null");
          if (canceled) throw new IOException("Canceled");

          // 一个StreamAllocation刻画的是一个Call的数据流动,一个Call可能存在多次请求(重定向,Authenticate等),所以当发生类似重定向等事件时优先使用原有的连接
          RealConnection allocatedConnection = this.connection;
          if (allocatedConnection != null && !allocatedConnection.noNewStreams) {
            return allocatedConnection;
          }

          // 试图从连接池中找到可复用的连接
          Internal.instance.get(connectionPool, address, this, null);
          if (connection != null) {
            return connection;
          }

          selectedRoute = route;
        }

        // 获取路由配置,所谓路由其实就是代理,ip地址等参数的一个组合
        if (selectedRoute == null) {
          selectedRoute = routeSelector.next();
        }

        RealConnection result;
        synchronized (connectionPool) {
          if (canceled) throw new IOException("Canceled");

          //拿到路由后可以尝试重新从连接池中获取连接,这里主要针对http2协议下清除域名碎片机制
          Internal.instance.get(connectionPool, address, this, selectedRoute);
          if (connection != null) return connection;

          //新建连接
          route = selectedRoute;
          refusedStreamCount = 0;
          result = new RealConnection(connectionPool, selectedRoute);
          //修改result连接stream计数,方便connection标记清理
          acquire(result);
        }

        // Do TCP + TLS handshakes. This is a blocking operation.
        result.connect(connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled);
        routeDatabase().connected(result.route());

        Socket socket = null;
        synchronized (connectionPool) {
          // 将新建的连接放入到连接池中
          Internal.instance.put(connectionPool, result);

          // 如果同时存在多个连向同一个地址的多路复用连接,则关闭多余连接,只保留一个
          if (result.isMultiplexed()) {
            socket = Internal.instance.deduplicate(connectionPool, address, this);
            result = connection;
          }
        }
        closeQuietly(socket);

        return result;
      }

//1、查看当前streamAllocation是否有之前已经分配过的连接,有则直接使用该连接
//2、从连接池中查找可复用的连接,有则返回该连接
//3、配置路由,配置后再次从连接池中查找是否有可复用连接,有则直接返回
//4、新建一个连接,并修改其StreamAllocation标记计数,将其放入连接池中
//5、查看连接池是否有重复的多路复用连接,有则清除

猜你喜欢

转载自blog.csdn.net/www1575066083/article/details/81484552