asyncCall.executeOn(executorService)
}
返回值正在运行
}
异步请求通过首先将AsyncCall添加到双向队列readyAsyncCalls(即准备执行但尚未执行的队列)中来准备请求。然后它遍历就绪运行队列readyAsyncCalls,找到满足条件的请求,并将它们添加到有效请求、executableCalls和运行队列runningAsyncCalls列表中。过滤条件主要有两个:
if (runningAsyncCalls.size=this.maxRequests) Break:并发运行请求数小于最大请求数64。
if (asyncCall.callsPerHost.get()=this.maxRequestsPerHost) continue: 特定主机的并发请求数不能超过最大请求数5。
这意味着,如果您的并发请求数超过64 个或特定主机上的请求数超过5 个,则超出的请求暂时无法执行,必须等待它们被添加到执行队列中。
过滤并保存有效请求后,我们立即开始遍历请求,并使用调度器Dispatcher的ExecutorService来一一执行Runnable任务。即遍历完后,将这些有效网络添加到线程池中执行。要求。
类:RealCall.AsyncCall
重写fun run() {
threadName(\”OkHttp ${redactedUrl()}\”) {
……
尝试{
val 响应=getResponseWithInterceptorChain()
信号回调=true
responseCallback.onResponse(this@RealCall, 响应)
} catch(e: IOException) {
……
responseCallback.onFailure(this@RealCall, e)
}
}
}
上面的代码是一个运行在线程池中的请求任务。在try-catch 块内,您可以看到以下语句,该语句检索网络请求的结果响应并通知用户返回的响应。或由于回调而发生错误。该回调是第一次使用OkHttp时注册用于监控的回调。
另外,这个方法是不是很熟悉?因为,正如我在讨论上面三个主要核心类时提到的,任何RealCall 同步或异步请求最终都会到达getResponseWithInterceptorChain() 步骤。
通过getResponseWithInterceptorChain()方法返回网络请求结果响应。网络请求结果是如何获取的? 我们如何与服务器交互呢?我们来分析一下这个方法的内部结构。
拦截器的内部实现
==========================================================================
从上面OkHttp的结构分析我们可以看到,所有的网络请求细节都封装在核心方法getResponseWithInterceptorChain()中。接下来我们来考虑它的具体实现。
类别: 纯酒精
内心乐趣getResponseWithInterceptorChain(): 响应{
//构建完整的拦截器堆栈。
val 拦截器=mutableListOf()
拦截器+=client.interceptors
拦截器+=RetryAndFollowUpInterceptor(客户端)
拦截器+=BridgeInterceptor(client.cookieJar)
拦截器+=CacheInterceptor(client.cache)
拦截器+=ConnectInterceptor
如果(!forWebSocket){
拦截器+=client.networkInterceptors
}
Interceptor +=CallServerInterceptor(对于WebSocket)
val链=RealInterceptorChain(
电话=这个,
拦截器=拦截器,
索引=0,
替换=空,
请求=原始请求,
connectTimeoutMillis=client.connectTimeoutMillis,
readTimeoutMillis=client.readTimeoutMillis,
writeTimeoutMillis=客户端.writeTimeoutMillis
)
……
尝试{
val 响应=chain.proceed(originalRequest)
……
返回响应
}
……
}
getResponseWithInterceptorChain()的内部实现是通过责任链模型完成的,将网络请求的每个阶段封装到每个链(即每个拦截器)中,配置好每个拦截器后,将其放在一个列表中使用。创建一个RealInterceptorChain 对象作为参数,并调用chain.proceed(request) 发起请求并获取响应。
每个拦截器首先执行一些准备操作,例如确定请求是否可用,将请求转换为服务器可以解析的格式,然后对请求进行chain.proceed(request) 。前面提到,getResponseWithInterceptorChain()的内部实现是一个责任链模型,而chain.proceed(request)的作用是责任链模型的核心,将请求传递给下一个拦截器。
OkHttp总共包含7个拦截器。每个拦截器都封装了整个网络请求的细节,只要清楚就可以了。一切都会变得清晰起来。我们来一一分析一下这些窃听者的作用。
7种拦截器的职责
1、用户自定义拦截器interceptors
用户定义的拦截器优先于所有其他拦截器。开发者可以根据业务需求定制网络拦截器。例如自定义token处理拦截器、日志拦截器等。
2、RetryAndFollowUpInterceptor
RetryAndFollowUpInterceptor 是一个拦截器,如果请求失败并被重定向,它会重试。这会在内部启动一个请求循环,每个循环首先执行一个准备操作(call.enterNetworkInterceptorExchange(request, newExchangeFinder))。此准备操作的主要目的是创建ExchangeFinder 来查找请求的可用Tcl 或TSL 连接。然后设置一些与连接相关的参数,比如连接编解码器。 ExchangeFinder 稍后将在连接网络时详细说明。
准备就绪后,将发起网络请求(response=realChain.proceed(request))。这段代码的目的是将请求传递给下面的拦截器。同时判断当前请求是否出错,是否需要重定向。如果发生错误或需要重定向,则重新开始新的循环,直到没有错误发生且需要重定向。
我们还将在这里简要解释确定错误和重定向的标准。
错误标准:使用try-catch 块捕获请求中的异常。这里捕获RouteException和IOException,发生错误后首先判断当前请求是否可以重试。
重定向标准:判断是否需要重定向,检查响应的状态码,如果状态码为3xx,则表示需要重定向,发起新的请求,重试操作。
3、BridgeInterceptor
BridgeInterceptor是一个用于连接应用程序代码和网络代码的拦截器。换句话说,拦截器帮助用户为服务器请求准备必要的配置。这个定义可能过于抽象。首先,让我们看一下请求URL 的服务器请求标头是什么样的。
网址:wanandroid.com/wxarticle/c.
方法:获取
对应的请求头是:
获取/wxarticle/chapters/json HTTP/1.1
主机:wanandroid.com
Accept: application/json, text/plain,/
Accept-Encoding: gzip、deflate、br
接受语言: zh-CN,zh;q=0.9
连接:保持活动
用户代理: Mozilla/5.0 xxx
……
您可能想知道BridgeInterceptor 拦截器与此有何关系。事实上,BridgeInterceptor的作用就是让用户处理网络请求。这有助于用户输入服务器请求所需的配置信息,例如上面提到的User-Agent、Connection、Host和Accept-Encoding。同时,对请求的结果也进行相应的处理。
BridgeInterceptor的内部实现主要分为三个步骤:
设置用户网络请求的Content-Type、Content-Length、Host、Connection、Cookie等参数。即它将常见的请求转换为适合服务器解析的格式,以便适应服务器端。
通过chain.proceed(requestBuilder.build()) 方法将转换后的request 传递给下一个拦截器CacheInterceptor,并接收返回的结果Response。
生成的响应也会转换为gzip 和Content-Type,以便在应用程序端进行适配。
因此,BridgeInterceptor是应用程序和服务器之间的桥梁。
4、CacheInterceptor
CacheInterceptor 是一个处理网络请求缓存的拦截器。它的内部处理类似于一些图像缓存的逻辑。首先判断是否有可用的缓存,如果没有则调用chain.proceed(networkRequest) 方法。请求下一个拦截器。得到结果后,缓存结果。
5、ConnectInterceptor
ConnectInterceptor是一个建立连接和请求的拦截器。
内部乐趣initExchange(chain: RealInterceptorChain): Exchange {
……
valexchangeFinder=this.exchangeFinder!
val 编解码器=ExchangeFinder.find(客户端, 链)
val 结果=Exchange(this, eventListener, ExchangeFinder, 编解码器)
this.interceptorScopedExchange=结果
this.exchange=结果
……
如果(取消)抛出IOException(“取消”)
返回结果
}
从源码中可以看到我们首先通过ExchangeFinder查询编解码器。你知道这个ExchangeFinder吗?在上面RetryAndFollowUpInterceptor的分析中,每个循环首先准备创建一个ExchangeFinder。
这个编解码器是什么?这是确定请求是通过Http1 还是Http2 发出的编解码器。
找到合适的编解码器后,使用Exchange 作为参数来创建它。 Exchange 包括许多网络连接实现,我们将在稍后更详细地讨论。首先,让我们看看如何找到正确的编解码器。
如何找到可用连接?
要找到合适的编解码器,您必须首先找到可用的网络连接,然后使用此可用连接创建新的编解码器。 内部使用大约五种过滤方法来查找可用连接。
第一:从连接池中查找
if (connectionPool.callAcquirePooledConnection(地址, 调用, null, false)) {
val 结果=call.connection!
返回结果
}
搜索连接池中的连接以确定每个连接是否可用。条件是:
请求数必须小于连接可以承受的最大请求数。在HTTP2 以下,最大请求数为1,并且可以在此连接上创建新的交换。
连接的主机与请求的主机匹配。
如果从连接池中获取到符合条件的连接,则直接返回。
如果没有,请继续执行第二种方法来获取可用连接。
第二种:传递路由并从连接池中检索
if (connectionPool.callAcquirePooledConnection(地址, 呼叫, 路线, false)) {
val 结果=call.connection!
返回结果
}
第二种方法仍然是从连接池中获取,但是这次的不同之处在于,这个路由是作为参数传递的。该路由是一个包含路由路由的列表集合,其中路由实际上指的是连接的IP地址、TCP端口和端口。代理模式。
这次我们主要从Http2的连接池中获取。路由必须共享IP 地址,此连接的服务器证书必须包含新主机,并且证书必须与主机匹配。
第三种方法:自己创建连接
如果前两次没有从连接池中获得可用连接,请自行创建一个。
val newConnection=RealConnection(连接池, 路由)
call.connectionToCancel=newConnection
尝试{
newConnection.connect(
连接超时,
读取超时,
写入超时,
Ping 间隔毫秒,
启用连接重试,
电话,
事件监听器
)
}
连接创建实际上是内部套接字和TLS 连接。 “TCP/TLS 连接是如何实现的?”这个问题将在稍后回答。
自己创建连接后,又从连接池中查找。
第四种方法:将多路复用设置为true,继续从连接池中查找。
if (connectionPool.callAcquirePooledConnection(地址、呼叫、路由、true)) {
val 结果=call.connection!
newConnection.socket().closeQuietly()
返回结果
}
这次,我们将搜索连接池并将requireMultiplexed 设置为true 以仅查找支持多路复用的连接。连接建立后,新连接将存储在连接池中。
如何找到Http1和Http2的编/解码器?
我们已经分析了上面几种寻找可用且成功的连接的方法。要创建编解码器,我们需要根据这些连接来区分Http1 和Http2。如果http2Connection 不为null,则创建Http2ExchangeCodec,否则创建Http1ExchangeCodec。
找到编解码器后,返回到ConnectInterceptor 的开头并使用该编解码器创建Exchange。在内部,此Exchange 实际上使用Http1 或Http2 解码器来写入请求标头或构造并发送请求正文。到服务器。
Exchange 初始化成功后,请求将传递到下一个拦截器CallServerInterceptor。
6、CallServerInterceptor
CallServerInterceptor是链中的最后一个拦截器,主要用于向服务器发送内容,主要发送http header和body信息。
在内部,它使用我们上面创建的Exchange 来写入请求标头、构造请求正文、发送请求、检索结果、解析结果并将其发回。
7、NetworkInterceptor
networkInterceptor也是一个用户自定义的拦截器,它的位置在ConnectInterceptor之后、CallServerInterceptor之前。我知道第一个拦截器是用户定义的,但是这个和这个有什么区别?
在networkInterceptor之前已经有多个拦截器在使用,并且请求信息已经非常复杂,包括RetryAndFollowUpInterceptor中的重试拦截器。同样是调用一次,所以networkInterceptor会被调用多次,但是第一个自定义拦截器只被调用一次。如果您需要自定义拦截器(例如令牌或日志)以进行资源消耗,通常使用第一个拦截器。
到目前为止,所有七个拦截器均已分析完毕。在分析ConnectInterceptor时,出现了一个问题:TCP/TLS连接是如何实现的?
如何建立TCP/TLS 连接?
================================================================================
TCP连接
有趣的连接(
connectTimeout: 整数,
readTimeout: 整数,
writeTimeout: 整数,
pingIntervalMillis: Int,
connectionRetryEnabled: 布尔值,
拨打:电话,
事件监听器: 事件监听器
){
……
而(真){
尝试{
if (route.requiresTunnel()) {
connectTunnel(connectTimeout, readTimeout, writeTimeout, call, eventListener)
如果(rawSocket==null){
//无法连接到隧道,但正确关闭了资源。
休息
}
} 除此之外{
connectSocket(connectTimeout, readTimeout, 调用, eventListener)
}
建立协议(connectionSpecSelector、pingIntervalMillis、call、eventListener)
eventListener.connectEnd(调用,route.socketAddress,route.proxy,协议)
休息
} catch(e: IOException) {
……
}
可以看到connect内部开启了一个while循环。第一步是确定route.requiresTunnel()。此requireTunnel() 方法指示请求是否使用Proxy.Type.HTTP 代理并且目标是HTTPS 连接。
在这种情况下,请创建代理隧道连接隧道(connectTunnel)。创建此隧道的目的是使用HTTP 来代理HTTPS 请求。
否则,直接建立TCP连接(connectSocket)。
建立请求协议。
代理隧道是如何创建的?在内部,您首先通过HTTP 代理发出TLS 请求。即,将Host、Proxy-Connection 和User-Agent 标头添加到地址URL 中。然后,它使用connectSocket 打开TCP 连接,并尝试使用TLS 请求创建代理隧道最多21 次。
正如您所看到的,无论是否需要代理隧道,都会建立TCP 连接(connectSocket)。那么TCP连接是如何建立的呢?
私人乐趣connectSocket(
connectTimeout: 整数,
readTimeout: 整数,
拨打:电话,
事件监听器: 事件监听器
){
val proxy=root.proxy
val 地址=root.地址
val rawSocket=when (proxy.type()) {
Proxy.Type.DIRECT、Proxy.Type.HTTP – 地址.socketFactory.createSocket()!
else-socket(代理)
}
this.rawSocket=原始套接字
eventListener.connectStart(调用,route.socketAddress,代理)
rawSocket.soTimeout=readTimeo
唔
尝试{
Platform.get().connectSocket(rawSocket,route.socketAddress,connectTimeout)
} catch(e: ConnectException) {
throw ConnectException(\”无法连接到${route.socketAddress}\”).apply {
初始化原因(e)
}
}
……
}
从源码来看,如果代理类型是直连或者HTTP/FTP代理,则直接创建socket。否则,将指定并创建代理类型。创建后,您将看到返回一个代表TCP 连接的rawSocket。最后调用Platform.get().connectSocket。这实际上是
调用socket的connect方法来打开一个TCP连接。
TLS连接
在建立TCP连接或者创建Http代理隧道后,就会开始建立连接协议(establishProtocol)。
private fun establishProtocol(
connectionSpecSelector: ConnectionSpecSelector,
pingIntervalMillis: Int,
call: Call,
eventListener: EventListener
) {
if (route.address.sslSocketFactory == null) {
if (Protocol.H2_PRIOR_KNOWLEDGE in route.address.protocols) {
socket = rawSocket
protocol = Protocol.H2_PRIOR_KNOWLEDGE
startHttp2(pingIntervalMillis)
return
}
socket = rawSocket
protocol = Protocol.HTTP_1_1
return
}
eventListener.secureConnectStart(call)
connectTls(connectionSpecSelector)
eventListener.secureConnectEnd(call, handshake)
if (protocol === Protocol.HTTP_2) {
startHttp2(pingIntervalMillis)
}
}
判断当前地址是否是HTTPS;
如果不是HTTPS,则判断当前协议是否是明文HTTP2,如果是的则调用startHttp2,开始Http2的握手动作,如果是Http/1.1则直接return返回;
如果是HTTPS,就开始建立TLS安全协议连接了(connectTls);
如果是HTTPS且为HTTP2,除了建立TLS连接外,还会调用startHttp2,开始Http2的握手动作。
在上述第3步时就提到了TLS的连接(connectTls),那我们就来看一下它的内部实现:
private fun connectTls(connectionSpecSelector: ConnectionSpecSelector) {
val address = route.address
val sslSocketFactory = address.sslSocketFactory
var success = false
var sslSocket: SSLSocket? = null
try {
// Create the wrapper over the connected socket.
sslSocket = sslSocketFactory!!.createSocket(
rawSocket, address.url.host, address.url.port, true /* autoClose */) as SSLSocket
// Configure the socket’s ciphers, TLS versions, and extensions.
val connectionSpec = connectionSpecSelector.configureSecureSocket(sslSocket)
if (connectionSpec.supportsTlsExtensions) {
Platform.get().configureTlsExtensions(sslSocket, address.url.host, address.protocols)
}
// Force handshake. This can throw!
sslSocket.startHandshake()
// block for session establishment
val sslSocketSession = sslSocket.session
val unverifiedHandshake = sslSocketSession.handshake()
// Verify that the socket’s certificates are acceptable for the target host.
if (!address.hostnameVerifier!!.verify(address.url.host, sslSocketSession)) {
val peerCertificates = unverifiedHandshake.peerCertificates
最后
小编这些年深知大多数初中级Android工程师,想要提升自己,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。
因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人
都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
资料⬅专栏获取
ureTlsExtensions(sslSocket, address.url.host, address.protocols)
}
// Force handshake. This can throw!
sslSocket.startHandshake()
// block for session establishment
val sslSocketSession = sslSocket.session
val unverifiedHandshake = sslSocketSession.handshake()
// Verify that the socket’s certificates are acceptable for the target host.
if (!address.hostnameVerifier!!.verify(address.url.host, sslSocketSession)) {
val peerCertificates = unverifiedHandshake.peerCertificates
最后
小编这些年深知大多数初中级Android工程师,想要提升自己,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。
因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
[外链图片转存中…(img-FYWGgGwL-1718990421526)]
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人
都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
资料⬅专栏获取
#以上关于Android开发老生新谈:从OkHttp原理看网络请求的相关内容来源网络仅供参考,相关信息请以官方公告为准!
原创文章,作者:CSDN,如若转载,请注明出处:https://www.sudun.com/ask/91614.html