Okhttp源码解析

Socket和TCP是什么关系?

Socket对TCP/IP的一个抽象的实现

OSI七层模型 TCP/IP

HTTP是处于最底层应用层的协议,负责进行数据的收发,规定类数据的格式,用什么传输?

传输借助于下一层协议 TCP协议 传输层协议 传输数据,确定往哪里传?

借助TCP的下一层协议 网络层协议: IP协议 确定传输的目的地

TCP和IP的连接就是借助Socket连接,建立好了Socket连接,我们要来完成HTTP通信的话,就是把我们的数据组装成HTTP的报文,

使用Socket的OutputStream把它给发出去。

如果开启OKHTTP缓存,缓存分为强制缓存和协商缓存,当我们发起请求的时候,OKHTTP会自动判断是否有缓存,缓存是否可用,

如果可用的话就不会发起网络的请求,直接从缓存中获取响应,把响应返回给用户,就可以减少网络请求的过程,

如果服务器有变化的话,还有缓存吗?

缓存会有一个有效期,过了这个有效期就需要跟服务器发起请求,服务器如果没有发生变化就返回304,如果发生变化就返回200,再去更新缓存。

分发器:内部维护队列与线程池,完成请求调配

拦截器:完成整个请求过程

使用内部维护的线程池,创建子线程来跑异步任务。加入到线程池中去执行,

使用线程池管理线程

HTTP的请求过程是怎么样?

(1)DNS解析:把域名解析获得到IP

(2)根据获得的IP建立TCP连接,三次握手

(3)建立好了连接,得到Socket对象,把我们的请求组装成HTTP的报文,通过Socket的OutputStream输出流把它给发出去,就完成了我们的OKHTTP的请求

 

接下来看下Dispatcher分发器

分发器,负责任务的调配,请求任务什么时候被执行。

RealCall.enqueue

//RealCall.kt
override fun enqueue(responseCallback: Callback) {
  //检查是否执行了两遍
  check(executed.compareAndSet(false, true)) { "Already Executed" }
  //请求后会立即调用,相当于监听请求的开始事件
  callStart()
  //将请求交给调度器来决定什么时候请求
  client.dispatcher.enqueue(AsyncCall(responseCallback))
}

AsyncCall可以看成式一个请求

最后的请求是交给Diapatcher处理

//Dispatcher.kt
internal fun enqueue(call: AsyncCall) {
  //确保线程安全
  synchronized(this) {
    
    readyAsyncCalls.add(call)
    //如果不是websocket,前面可知forWebSocket为false
    if (!call.call.forWebSocket) {
     
      val existingCall = findExistingCallWithHost(call.host)
     
      if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
    }
  }
 
  promoteAndExecute()
}

看下Dispatcher有些什么成员:

class Dispatcher constructor() {
 //最大请求数
  @get:Synchronized var maxRequests = 64  
	//同一主机最大请求数量,
  @get:Synchronized var maxRequestsPerHost = 5

	//线程安全的单例模式,线程池的获取用于线程调度。
  @get:Synchronized
  @get:JvmName("executorService") val executorService: ExecutorService
    get() {
      if (executorServiceOrNull == null) {
        executorServiceOrNull = ThreadPoolExecutor(0, Int.MAX_VALUE, 60, TimeUnit.SECONDS,
            SynchronousQueue(), threadFactory("$okHttpName Dispatcher", false))
      }
      return executorServiceOrNull!!
}

 //准备的队列
  private val readyAsyncCalls = ArrayDeque<AsyncCall>()
  //异步发送队列
  private val runningAsyncCalls = ArrayDeque<AsyncCall>()
  //同步发送队列
  private val runningSyncCalls = ArrayDeque<RealCall>()
 	...
}

这里有三个队列

(1)readyAsyncCalls  等待执行的请求对列

(2)runningAsyncCalls 正在执行的异步请求队列

(3)runningSyncCalls 正在执行的同步请求队列

这里为啥使用ArrayDeque,不用LinkedList?因为ArrayDeque比LinkedList更快

private fun promoteAndExecute(): Boolean {
  this.assertThreadDoesntHoldLock()

  val executableCalls = mutableListOf<AsyncCall>()
  val isRunning: Boolean
  synchronized(this) {
  
   //遍历ready队列
    val i = readyAsyncCalls.iterator()
    while (i.hasNext()) {
     
      //从ready队列中拿出一个请求
      val asyncCall = i.next()
     
      //正在请求的异步请求个数大于等于64
      if (runningAsyncCalls.size >= this.maxRequests) break 
     
       //同个域名的请求最大数大于等于5
      if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) continue // Host max capacity.
      //上面都通过了。从ready队列中remove掉
      i.remove()
     
      asyncCall.callsPerHost.incrementAndGet()
      
      executableCalls.add(asyncCall)
      //从ready队列中拿一个请求放到running队列中
      runningAsyncCalls.add(asyncCall)
    }
    isRunning = runningCallsCount() > 0
  }
  //循环遍历
  for (i in 0 until executableCalls.size) {
    val asyncCall = executableCalls[i]
    asyncCall.executeOn(executorService)
  }
  return isRunning
}
      //正在请求的异步请求个数大于等于64
      if (runningAsyncCalls.size >= this.maxRequests) break 
     
       //同个域名的请求最大数大于等于5
      if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) continue 

Q:如何决定请求放入ready还是running?

只有满足

1.正在请求的异步请求个数小于64

2.同个域名的请求最大数不能大于5个才能 放入running队列

才会放到running队列,否则放到ready队列

override fun run() {
    threadName("OkHttp ${redactedUrl()}") {
      //定义响应标志位,用于表示请求是否成功
      var signalledCallback = false
      timeout.enter()
      try {
        //执行完了拦截器就拿到了response
        val response = getResponseWithInterceptorChain()
        //走到这步没出错,就表示请求成功了 
        signalledCallback = true
        //成功回调
        responseCallback.onResponse(this@RealCall, response)
      } catch (e: IOException) {
        //如果寄快递过程没出错,但是try/catch还是报异常了只能说 onResponse中出错了,这就是用户的锅
        if (signalledCallback) {
          Platform.get().log("Callback failure for ${toLoggableString()}", Platform.INFO, e)
        } else {
          //失败回调  
          responseCallback.onFailure(this@RealCall, e)
        }
      } catch (t: Throwable) {
        cancel()//取消请求
        //okhttp自己的锅,发送过程中出现了错误  
        if (!signalledCallback) {
          val canceledException = IOException("canceled due to $t")
          canceledException.addSuppressed(t)
          //失败回调  
          responseCallback.onFailure(this@RealCall, canceledException)
        }
        throw t
      } finally {
        //结束  
        client.dispatcher.finished(this)
      }
    }
  }
}

Q:什么时候从ready队列拿一个任务来执行?

任务结束再判断一遍

 //执行完了拦截器就拿到了response
 val response = getResponseWithInterceptorChain()

 跑拦截器的过程有可能请求成功,有可能请求失败,

   override fun run() {
      threadName("OkHttp ${redactedUrl()}") {
        var signalledCallback = false
        timeout.enter()
        try {
          val response = getResponseWithInterceptorChain()
          signalledCallback = true
          responseCallback.onResponse(this@RealCall, response)
        } catch (e: IOException) {
          if (signalledCallback) {
            // Do not signal the callback twice!
            Platform.get().log("Callback failure for ${toLoggableString()}", Platform.INFO, e)
          } else {
            responseCallback.onFailure(this@RealCall, e)
          }
        } catch (t: Throwable) {
          cancel()
          if (!signalledCallback) {
            val canceledException = IOException("canceled due to $t")
            canceledException.addSuppressed(t)
            responseCallback.onFailure(this@RealCall, canceledException)
          }
          throw t
        } finally {
          client.dispatcher.finished(this)
        }
      }
    }

不管成功还是失败,都会执行finally 方法 

​  client.dispatcher.finished(this)

 完成一次请求都会执行这一行代码,会执行分发器的finish方法

  private fun <T> finished(calls: Deque<T>, call: T) {
    val idleCallback: Runnable?
    synchronized(this) {
      if (!calls.remove(call)) throw AssertionError("Call wasn't in-flight!")
      idleCallback = this.idleCallback
    }

    val isRunning = promoteAndExecute()

    if (!isRunning && idleCallback != null) {
      idleCallback.run()
    }
  }

这个方法里面所做的就是把我们的请求从running队列里面remove掉

然后从ready获取任务放到running执行,所以每完成一次请求就会从ready当中取一个任务到running当中去执行

  private fun promoteAndExecute(): Boolean {
    this.assertThreadDoesntHoldLock()

    val executableCalls = mutableListOf<AsyncCall>()
    val isRunning: Boolean
    synchronized(this) {
      val i = readyAsyncCalls.iterator()
      while (i.hasNext()) {
        val asyncCall = i.next()

        if (runningAsyncCalls.size >= this.maxRequests) break // Max capacity.
        if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) continue // Host max capacity.

        i.remove()
        asyncCall.callsPerHost.incrementAndGet()
        executableCalls.add(asyncCall)
        runningAsyncCalls.add(asyncCall)
      }
      isRunning = runningCallsCount() > 0
    }

    for (i in 0 until executableCalls.size) {
      val asyncCall = executableCalls[i]
      asyncCall.executeOn(executorService)
    }
@get:Synchronized
@get:JvmName("executorService") val executorService: ExecutorService
  get() {
    if (executorServiceOrNull == null) {
      //定义了一个缓存线程池
      executorServiceOrNull = ThreadPoolExecutor(0, Int.MAX_VALUE, 60, TimeUnit.SECONDS,
          SynchronousQueue(), threadFactory("$okHttpName Dispatcher", false))
    }
    return executorServiceOrNull!!
}

这里就是定义了一个缓存的线程池,什么是缓存线程池?就是具有缓存功能的,如果一个线程工作完了并且60s之内又有请求过来,就复用刚才那个线程,这提高了性能。

ThreadPoolExecutor(0, Int.MAX_VALUE, 60, TimeUnit.SECONDS,
            SynchronousQueue(), threadFactory("$okHttpName Dispatcher", false))

参数

核心线程数X :  一直维持X个线程

最大线程数:同时执行的最大线程数量

闲置时间:空闲了60秒超过核心线程数的线程会被回收

任务队列:SynchronousQueue

线程的机制如下:

判断是否小于核心线程数,或者说是线程数是0,会马上创建一个线程处理任务,如果提交的任务的时候达到了核心线程数,提交的任务会被加入到队列里面去,

如果添加队列里面失败了,就会判断是否达到最大线程数,如果小于最大线程数,就会创建一个线程,如果超过了最大线程数,就会使用拒绝策略来处理这个请求,默认的拒绝策略是抛出异常。

如果这里使用ArrayBlockingQueue会产生的问题:

ArrayBlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(1);

如果有2个任务,分别是任务1和任务2

那么可能只有有任务1执行,任务2被放到ArrayBlockingQueue等待执行队列里面去了,需要等到有空闲线程才能执行

如果有3个任务,分别是任务1、任务2、任务3

任务2会被放进ArrayBlockingQueue里面去,然后任务3没有容量了,当ArrayBlockingQueue放不下了,就会看是否达到最大线程数,如果没有达到就会马上创建一个线程去跑任务3

任务3一跑完,这个线程就空闲下来了,就会跑任务2

所以执行顺序变成任务1-->  任务3-->任务2

OKHTTP如果使用ArrayBlockingQueue 会产生2个问题

1.容量给多大的

2.后提交的任务反而先执行,请求一直阻塞得不到执行

我们提交的请求希望会被立即执行,不希望被卡在那边 ArrayBlockingQueue被淘汰

ArrayBlockingQueue和LinkedBlockingDeque 的区别就是数据结构的区别,ArrayBlockingQueue一个是数组, LinkedBlockingDeque一个是链表

所以使用SynchronousQueue 没有容量的,往里面添加肯定失败,失败了看是否达到最大线程数,没有达到就会创建一个线程去跑我们的任务

现在的线程池的特点 不需要等待,最大并发的一个线程池,决定了线程池的排队机制

OKHTTP没有提供线程的切换,没有提供数据的转换,Retrofit提供了。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值