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提供了。