OKHttp 3.10源码解析(四):连接机制

OKhttp的底层用的是Socket连接而不是URLConnection,所以整体来说还是比较复杂的,涉及到Http协议的封装和解封装、TLS/SSL安全协议的封装、Http2的封装等等,但还是非常值得我们去学习一下的,本篇文章不会对这些底层原理有详细的解析,但读完之后至少大概知道在网络请求中这些底层是如何实现的一.HTTP请求的优化1.keep-alive机制我们知道,一个HTTP的...
摘要由CSDN通过智能技术生成

OKhttp的底层用的是Socket连接而不是URLConnection,所以整体来说还是比较复杂的,涉及到Http协议的封装和解封装、TLS/SSL安全协议的封装、Http2的封装等等,但还是非常值得我们去学习一下的,本篇文章不会对这些底层原理有详细的解析,但读完之后至少大概知道Http请求的底层是如何实现的

一.HTTP请求的优化

1.keep-alive机制

我们知道,一个HTTP的请求需要经过三次握手和四次挥手,一般的流程为先tcp握手,然后传输数据,后面释放资源。在HTTP1.0是没有keep-alive机制的,它的流程大概如下:

 

 这种请求方式本来没什么问题,但是在复杂的请求场景中就显示出它的缺点了。比如在一个网页中同时有十几个资源要请求,按照HTTP1.0的做法, 每一个请求都要创建一个tcp连接,这样就创建了十几个tcp连接,而每一个连接都要经过三次握手和四次挥手,这种情景下,资源消耗是很大的,极大得影响了网络请求效率。

针对这种情况,在HTTP1.x中提出了keep-alive机制,即当一个tcp连接请求完成以后不立即释放,如果下一个http请求的host跟上一次的相同,那么就复用上一次的tcp连接,这样就免去了tcp重复的创建和销毁的开销,流程如下

一般在浏览器中,都会保持6~8个keep-alive的socket连接,且保持一定的生命周期,当不需要的时候才会关闭。

2.HTTP/2

HTTP/2致力于解决HTTP1.x中存在的一些不足,HTTP1.x中客户端想发起多个并行的请求就要建立多个tcp连接,HTTP1.x不会压缩请求头和响应头,导致流量的浪费,HTTP1.x不支持资源的优先级传输,所以针对这些问题,HTTP2实现了如下功能:

           ① 报头压缩:HTTP/2使用HPACK压缩格式压缩请求和响应报头数据,减少了传输流量的输出

           ② 多路复用:在HTTP1.x中每个tcp连接只能同时处理一个请求-响应,浏览器按照FIFO原则处理请求的执行,上一个请求未完成会阻塞下一个请求的执行。在HTTP/2中,我们可以把一个HTTP请求当作一个流,每个流分解成不同的帧,帧是传输的最小单位,而且每个帧都有标记来识别它属于哪个流,不同流的帧可以在同一个连接中交错发送,然后在另一端重新组装,这样就实现了一个连接可以有多个请求-响应并行执行

           ③ 数据流的优先级:HTTP/2中每个帧都是可以交错发送的,这样一来发送的顺序就很关键了,我们可以设置每个数据流的权重和依赖关系,这样就可以做到权重高的流优先发送

HTTP/2功能增强的核心是新增了二进制分帧层Binary Framing,它定义了如何封装HTTP消息并在客户端和服务端之间进行传输

HTTP/2的数据传输是以帧的方式交错发送的,如下图示:

 

二.关键类:ConnectionPool、RealConnection、StreamAllocation

上面讲了http1.x和http2的相关知识,在OKhttp中不管是哪种连接都需要用到连接池来维护,通过连接池可以有效地提高连接的使用效率,连接池的实现类为ConnectionPool

1.ConnectionPool类详解

主要用来管理http1.x或者http/2的链接,该类实现了链接的复用和清理工作,来看看它的主要属性

    //清除过期链接的线程池
    private static final Executor executor = new ThreadPoolExecutor(0 /* corePoolSize */,
      Integer.MAX_VALUE /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS,
      new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp ConnectionPool", true));

  //最大的空闲链接数和保活时间
  private final int maxIdleConnections;
  private final long keepAliveDurationNs;
  //双端队列,保存链接
  private final Deque<RealConnection> connections = new ArrayDeque<>();
  final RouteDatabase routeDatabase = new RouteDatabase();
  boolean cleanupRunning;
  //创建连接池,默认最大空闲连接为5个,连接的最长空闲时间为5分钟
  public ConnectionPool() {
    this(5, 5, TimeUnit.MINUTES);
  }

  public ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {
    this.maxIdleConnections = maxIdleConnections;
    this.keepAliveDurationNs = timeUnit.toNanos(keepAliveDuration);

    // Put a floor on the keep alive duration, otherwise cleanup will spin loop.
    if (keepAliveDuration <= 0) {
      throw new IllegalArgumentException("keepAliveDuration <= 0: " + keepAliveDuration);
    }
  }

既然是Http链接的管理,那么就会有添加连接、获取连接、删除连接等行为的实现

get方法:获取连接

//获取连接
@Nullable RealConnection get(Address address, StreamAllocation streamAllocation, Route route) {
    assert (Thread.holdsLock(this));
    //遍历已有的连接
    for (RealConnection connection : connections) {
      //如果地址和路由都符合要求,则返回  
      if (connection.isEligible(address, route)) {
        streamAllocation.acquire(connection, true);
        return connection;
      }
    }
    return null;
  }

put方法:添加连接

 void put(RealConnection connection) {
    assert (Thread.holdsLock(this));
    //如果连接清除工作已停止,则触发清除工作
    if (!cleanupRunning) {
      cleanupRunning = true;
      executor.execute(cleanupRunnable);
    }
    //将连接添加到连接池
    connections.add(connection);
  }

每当添加一个新的连接,如果当前连接的清理工作已经停止,则会触发连接的清理工作,把符合清除条件的连接清理掉或者在指定时间删除即将过期的连接,连接清除工作需要启动一个工作线程来执行,来看看

连接的清理:

  private final Runnable cleanupRunnable = new Runnable() {
    @Override public void run() {
      //一个无限循环
      while (true) {
        //调用leanup方法清除,返回下一次清理的等待时间
        long waitNanos = cleanup(System.nanoTime());
        if (waitNanos == -1) return; //没有连接可清理了,结束清理线程
        //等待一定的时间
        if (waitNanos > 0) {
          long waitMillis = waitNanos / 1000000L;
          waitNanos -= (waitMillis * 1000000L);
          synchronized (ConnectionPool.this) {
            try {
              ConnectionPool.this.wait(waitMillis, (int) waitNanos);
            } catch (InterruptedException ignored) {
            }
          }
        }
      }
    }
  };

清理连接的核心在cleanup方法

  long cleanup(long now) {
    int inUseConnectionCount = 0; //正在使用的连接数
    int idleConnectionCount = 0; //空闲的连接数
    RealConnection longestIdleConnection = null; //空闲时间最长的连接
    long longestIdleDurationNs = Long.MIN_VALUE; 

    //遍历所有的连接,找出符合条件的连接并且清除
    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++;
       
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值