由浅入深,聊聊OkHttp的那些事(易懂,不繁琐)

news2024/12/27 0:13:55

作者:Petterp

引言

Android 开发的世界中,有一些组件,无论应用层技术再怎么迭代,作为基础支持,它们依然在那里。 比如当我们提到网络库时,总会下意识想到一个名字,即 OkHttp

尽管对于大多数开发者而言,通常情况下使用的是往往它的封装版本 Retrofit ,不过其底层依然离不开 Okhttp 作为基础支撑。而无论是自研网络库的二次封装,还是个人使用,OkHttp 也往往都是不二之选。

故本篇将以最新视角开始,用力一瞥 OkHttp 的设计魅力。

本文对应的 OkHttp 版本: 4.10.0

本篇定位 中高难度,将从背景到使用方式,再到设计思想与源码解析,尽可能全面、易懂。

背景

每一个技术都有其变迁的历史背景与特性,本小节,我们将聊一聊 Android网络库 的迭代史,作为开篇引语,润润眼。 🔖

关于 Android网络库 的迭代历史,如下图所示:

具体进展如下:

  • HttpClient

    Android1.0 时推出。但存在诸多问题,比如内存泄漏,频繁的GC等。5.0后,已被弃用;

  • HttpURLConnection

    Android2.2 时推出,比 HttpClient 更快更稳定,Android4.4 之后底层已经被 Okhttp 替代;

  • volley

    Google 2013年开源,基于 HttpURLConnection 的封装,具有良好的扩展性和适用性,不过对于复杂请求或者大量网络请求时,性能较差。目前依然有不少项目使用(通常是老代码的维护);

  • okhttp

Square 2013年开源,基于 原生Http 的底层设计,具有 快速稳定节省资源 等特点。是目前诸多热门网络请求库的底层实现,比如 RetrofitRxHttp 等;

  • Retrofit

    Square 2013年开源,基于 OkHttp 的封装,目前 主流 的网络请求库。

    通过注解方式配置网络请求、REST风格 api、解耦彻底、经常会搭配 Rx等 实现 框架联动;

上述的整个过程,也正是伴随了 Android 开发的各个时期,如果将上述分为 5个阶段 的话,那么则为:

HttpClient -> HttpURLConnection -> volley -> okhttp -> Retrofit*

通过 Android网络库 的迭代历史,我们不难发现,技术变迁越来越趋于稳定,而 OkHttp 也已经成为了基础组件中不可所缺的一员。

设计思想

当聊到OkHttp的设计思想,我们想知道什么?

应用层去看,熟练的开发者会直接喊出拦截器,巴拉巴拉…

而作为初学者,可能更希望的事广度与解惑,OkHttp 到底牛在了什么地方,或者说常说的 拦截器到底是什么 ? 🧐

在官方的描述中,OkHttp 是一个高效的 Http请求框架 ,旨在 简化 客户端网络请求,提高 请求效率。

具体设计思想与特性如下:

  • 连接复用 :避免在每个请求之间重新建立连接。
  • 连接池 降低了请求延迟 (HTTP/2不可用情况下);
  • 自动重试 :在请求失败时自动重试请求,从而提高请求可靠性。
  • 自动处理缓存 :会按照预定的缓存策略处理缓存,以便最大化网络效率。
  • 支持HTTP/2, 并且允许对同一个主机的所有请求共享一个套接字(HTTP/2);
  • 简化Api:Api设计简单明了,易于使用,可以轻松发起请求获取响应,并处理异常。
  • 支持gzip压缩 :OkHttp支持gzip压缩,以便通过减少网络数据的大小来提高网络效率。

特别的,如果我们的服务器或者域名有 多个IP地址OkHttp 将在 第一次 连接失败时尝试替代原有的地址(对于 IPv4+IPv6 和托管在冗余数据中心的服务是必需的)。并且支持现代 TLS 功能(TLS 1.3、ALPN、证书固定)。它可以配置为回退以实现广泛的连接。

总的来说,其设计思想是通过 简化请求过程提高请求效率提高请求可靠性,从而提供 更快的响应速度

应用层的整个请求框架图如下:

使用方式

在开始探究设计原理与思想之前,我们还是要先看看最基础的使用方式,以便为后续做一些铺垫。

// build.gradle
implementation "com.squareup.okhttp3:okhttp:4.10.0"

// Android Manifest
<uses-permission android:name="android.permission.INTERNET" />

发起一个get请求

拦截器的使用

总结起来就是下面几步:

  1. 创建 OkHttpClient 对象;
  2. 构建 Request ;
  3. 调用 OkHttpClient 执行 request 请求 ;
  4. 同步阻塞 或者 异步回调 方式接收结果;

更多使用方式,可以在搜索其他同学的教程,这里仅仅只是作为后续解析原理时的必要基础支撑。

源码分析

基础配置

OkHttpClient

val client = OkHttpClient.Builder().xxx.build()

由上述调用方式,我们便可以猜出,这里使用了 构建者模式 去配置默认的参数,所以直接去看 OkHttpClient.Builder 支持的参数即可,具体如下:

具体的属性意思在代码中也都有注释,这里我们就不在多提了。

需要注意的是,在使用过程中,对于 OkHttpClient 我们还是应该缓存下来或者使用单例模式以便后续复用,因为其相对而言还是比较重。


Request

指客户端发送到服务器的 HTTP请求

OkHttp 中,可以使用 Request 对象来构建请求,然后使用 OkHttpClient 对象来发送请求。 通常情况下,一个请求包括了 请求头请求方法请求路径请求参数url地址 等信息。主要是用来请求服务器返回某些资源,如网页、图片、数据等。

具体源码如下所示:

Request.Builder().url("https://www.baidu.com").build()

open class Builder {
  // url地址
  internal var url: HttpUrl? = null		 	
  // 请求方式
  internal var method: String					 			
  // 请求头
  internal var headers: Headers.Builder		 			
  // 请求体
  internal var body: RequestBody? = null	 			
  // 请求tag
  internal var tags: MutableMap<Class<*>, Any>  	
}

发起请求

execute()

用于执行 同步请求 时调用,具体源码如下:

client.newCall(request).execute()

接下来我们再去看看 client.newCall() , 即请求发起时的逻辑。

当我们使用 OkHttpClient.newCall() 方法时,实际是创建了一个新的 RealCall 对象,用于 应用层与网络层之间的桥梁,用于处理连接、请求、响应以及流 ,其默认构造函数中需要传递 okhttpClient 对象以及 request

接着,使用了 RealCall 对象调用了其 execute() 方法开始发起请求,该方法内部会将当前的 call 加入我们 Dispatcher 分发器内部的 runningSyncCalls 队列中取,等待被执行。接着调用 getResponseWithInterceptorChain() ,使用拦截器获取本次请求响应的内容,这也即我们接下来要关注的步骤。


enqueue()

执行 异步请求 时调用,具体源码如下:

client.newCall(request).enqueue(CallBack)

当我们调用 RealCall.enqueue() 执行异步请求时,会先将本次请求加入 Dispather.readyAsyncCalls 队列中等待执行,如果当前请求是 webSocket 请求,则查找与当前请求是同一个 host 的请求,如果存在一致的请求,则复用先前的请求。

接下来调用 promoteAndExecute() 将所有符合条件可以请求的 Call 从等待队列中添加到 可请求队列 中,再遍历该请求队列,将其添加到 线程池 中去执行。

继续沿着上面的源码,我们去看 asyncCall.executeOn(executorService) ,如下所示:

上述逻辑也很简单,当我们将任务添加到线程池后,当任务被执行时,即触发 run() 方法的调用。该方法中会去调用 getResponseWithInterceptorChain() 从而使用拦截器链获取服务器响应,从而完成本次请求。请求成功后则调用我们开始时的 callback对象 的 onResponse() 方法,异常(即失败时)则调用 onFailure() 方法。


拦截器链

在上面我们知道,他们最终都走到了 RealCall.getResponseWithInterceptorChain() 方法,即使用 拦截器链 获取本次请求的响应内容。不过对于初看OkHttp源码的同学,这一步应用会有点迷惑,拦截器链 是什么东东👾?

在解释 拦截器链 之前,我们不妨先看一下 RealCall.getResponseWithInterceptorChain() 方法对应的源码实现,然后再去解释为什么,也许更容易理解。

具体源码如下:

上述的逻辑非常简单,内部会先创建一个局部拦截器集合,然后将我们自己设置的普通拦截器添加到该集合中,然后添加核心的5大拦截器,接着再将我们自定义的网络拦截器也添加到该集合中,最终才添加了真正用于执行网络请求的拦截器。接着创建了一个拦截器责任链 RealInterceptorChain ,并调用其 proceed() 方法开始执行本次请求。


责任链模式

在上面我们说到了,要解释 OkHttp 的拦截器链,我们有必要简单聊一下什么是责任链模式?

责任链模式(Chain of Responsibility)是一种处理请求的模式,它让多个处理器都有机会处理该请求,直到其中某个处理成功为止。责任链模式把多个处理器串成链,然后让请求在链上传递。

Android 中常见的事件分发为例:当我们的手指点击屏幕开始,用户的触摸事件从 Activity 开始分发,接着从 windows 开始分发到具体的 contentView(ViewGroup) 上,开始调用其 dispatchTouEvent() 方法进行事件分发。在这个方法内,如果当前 ViewGroup 不进行拦截,则默认会继续向下分发,寻找当前 ViewGroup 下对应的触摸位置 View ,如果该 View 是一个 ViewGroup ,则重复上述步骤。如果事件被某个 view 拦截,则触发其 onTouchEvent() 方法,接着交由该view去消费该事件。而如果事件传递到最上层 view 还是没人消费,则该事件开始按照原路返回,先交给当前 view 自己的 onTouchEvent() ,因为自己不消费,则调用其 父ViewGrouponTouchEvent() ,如此层层传递,最终又交给了 Act 自行处理。上述这个流程,就是 责任链模式 的一种体现。

如下图所示:


看完什么是责任链模式,让我们将思路转回到 OkHttp 上面,我们再去看一下 RealInterceptorChain 源码。

上述逻辑如下:

  • getResponseWithInterceptorChain() 方法内部最终调用 RealInterceptorChain.proceed() 时,内部传入了一个默认的index ,这个 index 就代表了当前要调用的 拦截器item ,并在方法内部每次创建一个新的 RealInterceptorChain 链,index+1,再调用当前拦截器 intercept() 方法时,然后将下一个链传入;

  • 最开始调用的是用户自定义的 普通拦截器,如果上述我们添加了一个 CustomLogInterceptor 的拦截器,当获取 response 时,我们需要调用 Interceptor.Chain.proceed() ,而此时的 chain 正是下一个拦截器对应的 RealInterceptorChain

  • 上述流程里,index从0开始,以此类推,一直到链条末尾,即 拦截器集合长度-1处;

  • 当遇到最后一个拦截器 CallServerInterceptor 时,此时因为已经是最后一个拦截器,链条肯定要结束了,所以其内部肯定也不会调用 proceed() 方法。

    相应的,为什么我们在前面说 它 是真正执行与服务器建立实际通讯的拦截器?

    因为这个里会获取与服务器通讯的 response ,即最初响应结果,然后将其返回上一个拦截器,即我们的网络拦截器,再接着又向上返回,最终返回到我们的普通拦截器处,从而完成整个链路的路由。

参照上面的流程,即大致思路图如下:


拦截器

RetryAndFollowUpInterceptor

见名知意,用于 请求失败重试 工作以及 重定向 的后续请求工作,同时还会对 连接 做一些初始化工作。

上述的逻辑,我们分为四段进行分析:

  1. 请求时如果遇到异常,则根据情况去尝试恢复,如果不能恢复,则抛出异常,跳过本次请求;如果请求成功,则在 finally 里释放资源;
  2. 如果请求是重试之后的请求,那么将重试前请求的响应体设置为null,并添加到当前响应体的 priorResponse 字段中;
  3. 根据当前的responseCode判断是否需要重试,若不需要,则返回 response ;若需要,则返回 request ,并在后续检查当前重试次数是否达到阈值;
  4. 重复上述步骤,直到步骤三成功。

在第一步时,获取 response 时,需要调用 realChain.proceed(request) ,如果你还记得上述的责任链,所以这里触发了下面的拦截器执行,即 BridgeInterceptor


BridgeInterceptor

用于 客户端和服务器 之间的沟通 桥梁 ,负责将用户构建的请求转换为服务器需要的请求。比如添加 content-typecookie 等,再将服务器返回的 response 做一些处理,转换为客户端所需要的 response,比如移除 Content-Encoding ,具体见下面源码所示:

上述逻辑如下:

  1. 首先调用 chain.request() 获取原始请求数据,然后开始重新构建请求头,添加 header 以及 cookie 等信息;
  2. 将第一步构建好的新的 request 传入 chain.proceed() ,从而触发下一个拦截器的执行,并得到 服务器返回的 response。然后保存 response 携带的 cookie,并移除 header 中的 Content-EncodingContent-Length,并同步修改 body

CacheInterceptor

见名知意,其用于网络缓存,开发者可以通过 OkHttpClient.cache() 方法来配置缓存,在底层的实现处,缓存拦截器通过 CacheStrategy 来判断是使用网络还是缓存来构建 response。具体的 cache 策略采用的是 DiskLruCache

Cache的策略如下图所示:

具体源码如下所示:

具体的逻辑如上图所示,具体可以参照上述的 Cache 流程图,这里我们再说一下 CacheStrategy 这个类,即决定何时使用 网络请求、响应缓存。

CacheStrategy


ConnectInterceptor

实现与服务器真正的连接。

上述流程如下:

  • 初始化 一个 exchange 对象;
  • 根据 exchange 对象来复制创建一个新的连接责任链;
  • 执行该连接责任链。

那 Exchange 是什么呢?

在官方的解释里,其用于 传递单个 HTTP 请求和响应对,在 ExchangeCode 的基础上担负了一些管理及事件分发的作用。

具体而言,ExchangeRequest 相对应,新建一个请求时就会创建一个 Exchange,该 Exchange 负责将这个请求发送出去并读取到响应数据,而具体的发送与接收数据使用的则是 ExchangeCodec

相应的,ExchangeCode 又是什么呢?

ExchangeCodec 负责对 request 编码及解码 Response ,即写入请求及读取响应,我们的请求及响应数据都是通过它来读写。

通俗一点就是,ExchangeCodec 是请求处理器,它内部封装了 OkHttp 中执行网络请求的细节实现,其通过接受一个 Request 对象,并在内部进行处理,最终生成一个符合 HTTP 协议标准的网络请求,然后接受服务器返回的HTTP响应,并生成一个 Response 对象,从而完成网络请求的整个过程。

额外的,我们还需要再提一个类,ExchangeFinder

用于寻找可用的 Exchange ,然后发送下一个请求并接受下一个响应。

虽然上述流程看起来似乎很简单,但我们还是要分析下具体的流程,源码如下所示:

RealCall.initExchange()

初始化 Exchage 的过程。

ExchangeFinder 找到一个新的或者已经存在的 ExchangeCodec,然后初始化 Exchange ,以此来承载接下来的HTTP请求和响应对。


ExchangeFinder.find()

查找 ExchangeCodec(请求响应编码器) 的过程。

接下来我们看看查找 RealConnection 的具体过程:

上述的整个流程如下:

上述会先通过 ExchangeFinderRealConnecionPool 中尝试寻找已经存在的连接,未找到则会重新创建一个 RealConnection(连接) 对象,并将其添加到连接池里,开始连接。然后根据找到或者新创建 RealConnection 对象,并根据当前请求协议创建不同的 ExchangeCodec 对象并返回,最后初始化一个 Exchange 交换器并返回,从而实现了 Exchange 的初始化过程。

在具体找寻 RealConnection 的过程中,一共尝试了5次,具体如下:

  1. 尝试重连 call 中的 connection,此时不需要重新获取连接;
  2. 尝试从连接池中获取一个连接,不带路由与多路复用;
  3. 再次尝试从连接池中获取一个连接,带路由,不带多路复用;
  4. 手动创建一个新连接;
  5. 再次尝试从连接池中获取一个连接,带路由与多路复用;

Exchange 初始化完成后,再复制该对象创建一个新的 Exchange ,并执行下一个责任链,从而完成连接的建立。


networkInterceptors

网络拦截器,即 client.networkInterceptors 中自定义拦截器,与普通的拦截器 client.interceptors 不同的是:

由于网络拦截器处于倒数第二层,在 RetryAndFollowUpInterceptor 失败或者 CacheInterceptor 返回缓存的情况下,网络拦截器无法被执行。而普通拦截器由于第一步就被就执行到,所以不受这个限制。


CallServerInterceptor

链中的最后一个拦截器,也即与服务器进行通信的拦截器,利用 HttpCodec 进行数据请求、响应数据的读写。

具体源码如下:

先写入要发送的请求头,然后根据条件判断是否写入要发送的请求体。当请求结束后,解析服务器返回的响应头,构建一个新的 response 并返回;如果 response.code100,则重新读取响应体并构建新的 response。因为这是最底层的拦截器,所以这里肯定不会再调用 proceed() 再往下执行。

小结

至此,关于 OkHttp 的分析,到这里就结束了。为了便于理解,我们再串一遍整个思路:

OkHttp 中,RealCallCall 的实现类,其负责 执行网络请求 。其中,请求 requestDispatcher 进行调度,其中 异步调用 时,会将请求放到到线程池中去执行; 而同步的请求则只是会添加到 Dispatcher 中去管理,并不会有线程池参与协调执行。

在具体的请求过程中,网络请求依次会经过下列拦截器组成的责任链,最后发送到服务器。

  1. 普通拦截器,client.interceptors()
  2. 重试、重定向拦截器 RetryAndFollowUpInterceptor
  3. 用于客户端与服务器桥梁,将用户请求转换为服务器请求,将服务器响应转换为用户响应的的 BridgeInterceptor
  4. 决定是否需要请求服务器并写入缓存再返回还是直接返回服务器响应缓存的 CacheInterceptor;
  5. 与服务器建立连接的 ConnectInterceptor
  6. 网络拦截器,client.networkInterceptors();
  7. 执行网络请求的 CallServerInterceptor;

而相应的服务器响应体则会从 CallServerInterceptor 开始依次往前开始返回,最后由客户端进行处理。

需要注意的是,当我们 RetryAndFollowUpInterceptor 异常或者 CacheInterceptor 拦截器直接返回了有效缓存,后续的拦截器将不会执行。

常见问题

OkHttp如何判断缓存有效性?

这里其实主要说的是 CacheInterceptor 拦截器里的逻辑,具体如下:

OkHttp 使用 HTTP协议 中的 缓存控制机制 来判断缓存是否有效。如果请求头中包含 "Cache-Control""If-None-Match" / "If-Modified-Since" 字段,OkHttp 将根据这些字段的值来决定是否使用缓存或从网络请求响应。

Cache-Control 指 包含缓存控制的指令,例如 “no-cache”“max-age” ;

If-None-Match 指 客户端缓存的响应的ETag值,如果服务器返回相同的 ETag 值,则说明响应未修改,缓存有效;

If-Modified-Since 指 客户端缓存的响应的最后修改时间,如果服务器确定响应在此时间后未更改,则返回304 Not Modified状态码,表示缓存有效。

相应的,OkHttp 也支持自定义缓存有效性控制,开发者可以创建一个 CacheControl 对象,并将其作为请求头添加到 Request 中,如下所示:

// 禁止OkHttp使用缓存
val cacheControl = CacheControl.Builder()
            .noCache()
            .build()
val request = Request.Builder()
            .cacheControl(cacheControl)
            .url("https://www.baidu.com")
            .build()

OkHttp如何复用TCP连接?

这个其实主要说的是 ConnectInterceptor 拦截器中初始化 Exchange 时内部做的事,具体如下:

OkHttp 使用连接池 RealConnectionPool 管理所有连接,连接池将所有活动的连接存储在池中,并维护了一个空闲的连接列表(TaskQueue),当需要新的连接时,优先尝试从这个池中找,如果没找到,则 重新创建 一个 RealConnection 连接对象,并将其添加到连接池中。在具体的寻找连接的过程中,一共进行了下面5次尝试:

  1. 尝试重连 RealCall 中的 connection,此时不需要重新获取连接;
  2. 尝试从连接池中获取一个连接,不带路由与多路复用;
  3. 再次尝试从连接池中获取一个连接,带路由,不带多路复用;
  4. 手动创建一个新连接;
  5. 再次尝试从连接池中获取一个连接,带路由与多路复用;

当然 OkHttp 也支持自定义连接池,具体如下:

上述代码中,创建了一个新的连接池,并设置其保留最多 maxIdleConnections 个空闲连接,并且连接的存活期为 keepAliveDuration 分钟。

OKHttp复用TCP连接的好处是什么?

OkHttp 是由连接池管理所有连接,通过连接池,从而可以限制连接的 最大数量,并且对于空闲的连接有相应的 存活期限 ,以便在长时间不使用后关闭连接。当请求结束时,并且将保留该连接,便于后续 复用 。从而实现了在多个请求之间共享连接,避免多次建立和关闭TCP连接的开销,提高请求效率。

OkHttp中的请求和响应 与 网络请求和响应,这两者有什么不同?

OkHttp 中的的请求和响应指的是客户端创建的请求对象 Request 和 服务端返回的响应对象 Response,这两个对象用于定义请求和响应的信息。网络请求和响应指的是客户端向服务端发送请求,服务端返回相应的过程。

总的来说就是,请求和响应是应用程序内部自己的事,网络请求和响应则是发生在网络上的请求和响应过程

OkHttp 应用拦截器和网络拦截器的区别?

  • 从调用方式上而言,应用拦截器指的是 OkhttpClient.intercetors ,网络拦截器指的是 OkHttpClient.netIntercetors
  • 从整个责任链的调用来看,应用拦截器一定会被执行一次,而网络拦截器不一定会执行或者执行多次情况,比如当我们 RetryAndFollowUpInterceptor 异常或者 CacheInterceptor 拦截器直接返回了有效缓存,后续的拦截器将不会执行,相应的网络拦截器也自然不会执行到;当我们发生 错误重试 或者 网络重定向 时,网络拦截器此时可能就会执行多次。
  • 其次,除了 CallServerInterceptorCacheIntercerceptor 缓存有效之外,每个拦截器都应该至少调用一次 realChain.proceed() 方法。但应用拦截器可以调用多次 processed() 方法,因为其在请求流程中是可以递归调用;而网络拦截器只能调用一次 processed() 方法,否则将导致请求重复提交,影响性能,另外,网络拦截器没有对请求做修改的可能性,因此不需要再次调用 processed() 方法。
  • 使用方式的 本质而言,应用拦截器可以 拦截和修改请求和响应 ,但 不能修改网络请求和响应 。比如使用应用拦截器添加请求参数、缓存请求结果;网络拦截器可以拦截和修改网络请求和响应。例如使用网络拦截器添加请求头、修改请求内容、检查响应码等。
  • 在相应的执行顺序上,网络拦截器是 先进先出(FIFO) ,应用拦截器是 先进后出(FILO) 的方式执行。

结语

本篇中,我们从网络库的迭代历史,一直到 OkHttp 的使用方式、设计思想、源码探索,最后又聊了聊常见的一些问题,从而较系统的了解了 OkHttp 的方方面面,也解释了 OkHttp应用层 的相关问题,当然这些问题我相信也仅仅只是冰山一角🧩。 更多面试相关,或者实际问题,仍需要我们自己再进行完善,从而形成全面的透析力。

这篇文章断断续续写了将近两周,其中肯定有不少部分存在缺陷或者逻辑漏洞,如果您发现了,也可以告诉我。

通过这篇文章,于我个人而言,也是完成了对于 OkHttp应用层 一次较系统的了解,从而也完善了知识拼图中重要的一块,期待作为读者的你也能有如此或者更深的体会。🏃🏻


如果想弄清楚OkHttp的全部知识点,光看这篇文章是远远不够的,为了帮助到大家更详细的了解OkHttp 里面的知识点,这里有份《OkHttp 源码解析》的学习笔记,希望对大家有所帮助。

OkHttp 源码解析:https://qr18.cn/Cw0pBD

1.OKHttp源码解析(一)–初阶
2.OKHttp源码解析(二):“前戏”——HTTP的那些事
3.OKHttp源码解析(三)–线程池和消息队列
4.OKHttp源码解析(四)–拦截器及调用链
5.OKHttp源码解析(五)–OKIO简介及FileSystem
6.OKHttp源码解析(六)–缓存基础
7.OKHttp源码解析(七)–缓存机制
8.OKHttp源码解析(八)–连接与请求前奏
9.OKHttp源码解析(九):OKHTTP连接中三个"核心"RealConnection、ConnectionPool、StreamAllocation
10.OkHttp源码解析(十) OKHTTP中连接与请求及总结
11.……

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/506453.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LVS和nginx和keepalived四层和7层的一些测试,nginx和keepalived共用环境的部署,lvs,nginx客户端IP透传

LVS和nginx的测试 实验DR模式 服务器IP备注mysql192.168.137.178测试服务器lvs**vip ** 192.168.137.99 RIP 192.168.137.100lvs服务器nginx1RIP 192.168.137.101nginx2RIP 192.168.137.102 LVS四层代理 test----lvs vip—nginx1/ngin2 LVS服务器的配置 [rootlvs openres…

Databend 开源周报第 92 期

Databend 是一款现代云数仓。专为弹性和高效设计&#xff0c;为您的大规模分析需求保驾护航。自由且开源。即刻体验云服务&#xff1a;https://app.databend.cn 。 Whats On In Databend 探索 Databend 本周新进展&#xff0c;遇到更贴近你心意的 Databend 。 在 WHERE 子句中…

算法记录 | Day56 动态规划

583.两个字符串的删除操作 思路&#xff1a; 1.确定dp数组&#xff08;dp table&#xff09;以及下标的含义&#xff1a;dp[i][j]&#xff1a;以i-1为结尾的字符串word1&#xff0c;和以j-1位结尾的字符串word2&#xff0c;想要达到相等&#xff0c;所需要删除元素的最少次数…

别去外包,干了3年,彻底废了......

先说一下自己的情况。大专生&#xff0c;19年通过校招进入湖南某软件公司&#xff0c;干了接近3年的测试&#xff0c;今年年上旬&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落&#xff01;而我已经在一个企业干了三年&#xff0c…

存储迁移到vSAN后将oracle rac的共享虚拟磁盘由“精简置备”转换为“厚置备快速置零”格式

在vSAN 6.5.0环境中&#xff0c;将Oracle RAC虚拟机的存储迁移到vSAN数据存储后&#xff0c;共享磁盘在迁移后全部变成了“精简置备”类型&#xff0c;如下所示&#xff1a; 注&#xff1a;从vSAN 6.7 Patch 01开始&#xff0c;vSAN上的Oracle RAC 不再要求共享的 VMDKs为厚置…

【论文阅读】COPA:验证针对中毒攻击的离线强化学习的稳健策略

COPA: Certifying Robust Policies for Offline Reinforcement Learning against Poisoning Attacks 作者&#xff1a;Fan Wu, Linyi Li, Chejian Xu 发表会议&#xff1a;2022ICRL 摘要 目前强化学习完成任务的水平已经和人类相接近&#xff0c;因此研究人员的目光开始转向…

Springboot Security 认证鉴权——使用JSON格式参数登录

在 Spring Security 中&#xff0c;默认的登陆方式是以表单形式进行提交参数的。可以参考前面的几篇文章&#xff0c;但是在前后端分离的项目&#xff0c;前后端都是以 JSON 形式交互的。一般不会使用表单形式提交参数。所以&#xff0c;在 Spring Security 中如果要使用 JSON …

Ansys Lumerical | 单行载流子光电探测器仿真方法

综述 在本例中&#xff0c;我们将研究混合硅基光电探测器的各项性能。单行载流子&#xff08;uni-traveling carrier&#xff0c;UTC&#xff09;光电探测器&#xff08;PD&#xff09;由InP/InGaAs制成&#xff0c;其通过渐变耦合的方式与硅波导相连。在本次仿真中&#xff0c…

04-Docker镜像

镜像 镜像是一种轻量级、可执行的独立软件包&#xff0c;它包含运行某个软件所需的所有内容&#xff0c;我们把应用程序和配置依赖打包好形成一个可交付的运行环境&#xff08;包括代码、运行时需要的库、环境变量和配置文件等&#xff09;&#xff0c;这个打包好的运行环境就是…

上海亚商投顾:沪指午后跳水跌超1% 两市超4000只个股下跌

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 市场情绪 沪指今日冲高回落&#xff0c;盘中一度站上3400点关口&#xff0c;午后跳水跌超1%&#xff0c;深成指、创业板指同…

本科生学数据分析转行,能学会吗?

当然可以&#xff0c;大专及以上都可以学习数据分析转行&#xff0c;本科学历学习更有优势。数据分析职业对于学历方面还是比较看重的&#xff0c;同样技能情况下&#xff0c;学历越高&#xff0c;入行薪资起点也会高个至少一两千&#xff1b;入行以后的升职加薪就看个人的能力…

vue3学习七 toRef 和 toRefs

toRef 和 ref 的作用差不多是一样的&#xff0c;都是可以把一个数据变成响应式的 我们一般使用toRef 或都 toRefs 是用来&#xff0c;简化 template 中的 数据的写法的&#xff0c; 使一个深层次的数据不用书写的时候那么麻烦。 如果不嫌麻烦的话&#xff0c; 这两个api 可以不…

尚硅谷-宋红康-JVM上中下篇完整笔记-JVM中篇

一.Class文件结构 1.概述 1.1 字节码文件的跨平台性 所有的JVM全部遵守Java虚拟机规范:Java SE Specifications&#xff0c;也就是说所有的JV环境都是一样的&#xff0c;这样一来字节码文件可以在各种JVM上运行。 1.2 Java的前端编译器 想要让一个Java程序正确地运行在JVM中&am…

安装Nacos

什么是Nacos 官网中如此说道&#xff1a; Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集&#xff0c;帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。 Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。 Nacos 是构建以…

CSS选择器的常见用法

文章目录 CSS是什么CSS的引入方式内部样式表行内样式表外部样式 选择器基础选择器类选择器id选择器通配符选择器 复合选择器后代选择器 CSS是什么 CSS就是&#xff08;Cascading Style Sheets&#xff09;就是层叠样式表&#xff0c;CSS 能够对网页中元素位置的排版进行像素级…

从项目到技能,软件测试面试高频题总结 (附答案),收割10个offer...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 软件测试面试题简历…

农村供水调度系统在河北某地的建设案例

项目背景 农村饮水安全事关广大农村居民的切身利益&#xff0c;是脱贫攻坚、乡村振兴的基础条件。该县为加快推进农村人饮安全运行管理工作&#xff0c;建立健全运管服务体系&#xff0c;改善当前农村人饮安全运营现状&#xff0c;积极实施城乡供水一体化工程&#xff0c;进一步…

八、使用代码对道路结果进行后处理及iou优化步骤详解

老师又给我画了大饼 没办法 只能按照他们的想法做个尝试 上一篇的方法还没进行下去 就被叫停 又更新了一个新的想法 这里记录一下 我的尝试过程 一、图片膨胀 首先使用代码对道路进行膨胀 这里的代码 import cv2 import numpy as np img cv2.imread(gt_dirname, 0) ke…

李白、高适、杜甫,情义深深,抵不过乱世游离

李白&#xff0c;字太白&#xff0c;是唐朝浪漫主义诗人&#xff0c;被后人誉为“诗仙”&#xff0c;杜甫&#xff0c;字子美&#xff0c;唐代现实主义诗人&#xff0c;李白和杜甫合称为“李杜”。高适&#xff0c;字达夫&#xff0c;唐代诗人。李白&#xff0c;杜甫&#xff0…

JVM学习随笔03——Java堆中new一个对象的步骤

目录 一、进行类加载 二、堆中分配内存 1、怎么输出GC日志&#xff1a; 2、内存分配的两种方式&#xff1a; 3、内存分配过程中并发控制的两种方式&#xff1a; 三、内存空间初始化 四、对象头初始化&#xff08;对象头包含哪些信息&#xff1f;&#xff09; 五、执行构…