【Android】浅析OkHttp(1)

【Android】浅析OkHttp(1)

OkHttp 是一个高效、轻量级的 HTTP 客户端库,主要用于 Android 和 Java 应用开发。它不仅支持同步和异步的 HTTP 请求,还支持许多高级功能,如连接池、透明的 GZIP 压缩、响应缓存、WebSocket 等。OkHttp3 是 OkHttp 的第三个大版本,在稳定性和功能性方面进行了很多改进。

今天主要介绍OkHttp3。

OkHttp基本用法

使用前

配置gradle

implementation ("com.squareup.okhttp3:okhttp:3.2.0")

异步 GET 请求

最简单的 GET 请求,请求博客地址,代码如下所示:

	Request.Builder requestBuilder = new Request.Builder().url("http://blog.csdn.net/itachi85");requestBuilder.method("GET", null)Request request = requestBuilder.build()OkHttpClient mOkHttpClient = new OkHttpClient()Call mcall = mOkHttpClient.newCall(request);mcall.enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {}@Overridepublic void onResponse(Call call, Response response) throws IOException {String str = response.body().string()Log.d(TAG str)}})

其基本步骤就是创建 OkHttpClient、Request 和 Call,最后调用 Call 的 enqueue()方法。但是 每次这么写很麻烦,肯定是要进行封装的。需要注意的是 onResponse 回调并非在 UI 线程。如 果想要调用同步 GET 请求,可以调用 Call 的 execute 方法。

同步 GET 请求

代码示例如下:

OkHttpClient client = new OkHttpClient();// 创建Request对象
Request request = new Request.Builder().url("https://jsonplaceholder.typicode.com/posts").build();// 同步发送请求
try (Response response = client.newCall(request).execute()) {if (response.isSuccessful()) {// 打印响应内容System.out.println(response.body().string());} else {System.out.println("Request Failed");}
} catch (IOException e) {e.printStackTrace();
}

在同步请求中,execute() 方法会阻塞线程,直到服务器返回响应。这里 Response 通过 try-with-resources 自动关闭流。

异步 POST 请求

OkHttp 3 异步 POST 请求和 OkHttp 2.x 有一些差别,就是没有 FormEncodingBuilder 这个类, 替代它的是功能更加强大的 FormBody。这里访问淘宝 IP 库,代码如下所示:

RequestBody formBody = new FormBody.Builder().add("ip",59.108.54.37).build()Request request = new Request.Builder().url("http://ip.taobao.com/service/getIpInfo.php").post(formBody).build()OkHttpClient mOkHttpClient = new OkHttpClient()Call call = mOkHttpClient.newCall(request);
call.enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {}@Overridepublic void onResponse(Call call, Response response) throws IOException {String str = response.body().string()Log.d(TAG, str)}
})

这与异步 GET 请求类似,只是多了用 FormBody 来封装请求的参数,并传递给 Request。

设置超时时间和缓存

和 OkHttp 2.x 有区别的是 OkHttp 3 不能通过 OkHttpClient 直接设置超时时间和缓存了,而 是通过 OkHttpClient.Builder 来设置。通过 builder 配置好 OkHttpClient 后用 builder.build()来返回 OkHttpClient。所以我们通常不会调用 new OkHttpClient()来得到 OkHttpClient,而是通过 builder.build()得到 OkHttpClient。另外,OkHttp 3 支持设置连接、写入和读取的超时时间,如下 所示:

File sdcache = getExternalCacheDir()int cacheSize = 1010241024OkHttpClient.Builder builder = new OkHttpClient.Builder().connectTimeout(15, TimeUnit.SECONDS).writeTimeout(20, TimeUnit.SECONDS).readTimeout(20, TimeUnit.SECONDS).cache(new Cache(sdcache.getAbsoluteFile(), cacheSize));
mOkHttpClient = builder.build()

取消请求

使用 call.cancel()可以立即停止一个正在执行的 call。当用户离开一个应用时或者跳到其他 界面时,使用 call.cancel()可以节约网络资源;另外,不管同步还是异步的 call 都可以取消,也 可以通过 tag 来同时取消多个请求。当构建一个请求时,使用 Request.Builder.tag(Object tag)来 分配一个标签,之后你就可以用 OkHttpClient.cancel(Object tag )来取消所有带有这个 tag 的 call。 具体代码如下所示:

private ScheduledExecutorService executor = Executors.newScheduledThreadPool(1)private void cancel() {final Request request = new Request.Builder().url("http://www.baidu.com").cacheControl(CacheControl.FORCE_NETWORK)//1.build()Call call = null;call = mOkHttpClient.newCall(request)final Call finalCall = call;//100ms 后取消 callexecutor.schedule(new Runnable() {@Overridepublic void run() {finalCall.cancel()}}, 100, TimeUnit.MILLISECONDS);call.enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {}@Overridepublic void onResponse(Call call, Response response) throws IOException {if (null != response.cacheResponse()) {String str = response.cacheResponse().toString()Log.d(TAG, "cache---+ str)} else {String str = response.networkResponse().toString()Log.d(TAG, "network---+ str)}}})}

创建定时线程池,100 ms 后调用 call.cancel()来取消请求。为了能让请求耗时,在上面代码 注释 1 处设置每次请求都要请求网络,运行程序并且不断地调用 cancel 方法。Log 打印结果如 图 5-13 所示。

源码解析OkHttp

首先来学习一下 OkHttp 的请求网络流程。

OkHttp的请求网络流程

大致流程
image-20241020005347737

首先创建出OkHttpClient,而后创建出Request对象,通过这两个对象,获取到Call对象。使用Call开始执行请求后,请求的任务会首先经过Dispatcher(分发器)进行任务的调配,而后Interceptors(拦截器)完成请求的过程。

  1. 通过建造者模式构建OKHttpClientRequest
  2. OKHttpClient通过newCall发起一个新的请求
  3. 通过分发器维护请求队列与线程池,完成请求调配
  4. 通过五大默认拦截器完成请求重试,缓存处理,建立连接等一系列操作
  5. 得到网络请求结果

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

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

关于分发器Dispatcher

image-20241020010349271

RunningAsyncCalls:正在执行的异步请求队列

ReadyAsyncCalls:等待执行的异步请求队列

RunningSyncCalls:正在执行的同步请求队列

关于拦截器Interceptor

重试重定向拦截器

桥接拦截器

缓存拦截器

连接拦截器

请求服务器拦截器

  1. 重试拦截器:负责判断用户是否取消了请求;获得结果之后,会根据响应码判断是否需要重定向,如果满足条件就会重启执行所有拦截器
  2. 桥接拦截器在交出之前,负责将Http协议必备的请求头加入其中;获得结果之后,调用保存cookie接口并解析GZIP数据
  3. 缓存拦截器,交出之前读取并判断是否使用缓存;获得结果之后判断是否缓存。
  4. 连接拦截器,交出之前负责找到或者新建一个链接,并获取对应的socket流,在获得结果之后,不进行额外处理。
  5. 请求服务器拦截器进行真正的与服务器的通信,向服务器发送数据,解析读取的相应数据
从execute/enqueue开始

当我们要请求网络的时候需要用 OkHttpClient.newCall(request)进行 execute 或者 enqueue 操 作;当调用 newCall 方法时,会调用如下代码:

@Override
public Call newCall(Request request) {return new RealCall(this, request)}

其实际返回的是一个RealCall类。我们调用 enqueue 异步请求网络实际上是调用了 RealCall 的 enqueue 方法。查看 RealCall 的 enqueue 方法,如下所示:

void enqueue(Callback responseCallback, boolean forWebSocket) {synchronized (this) {if (executed) throw new IllegalStateException(Already Executed);executed = true}client.dispatcher().enqueue(new AsyncCall(responseCallback, forWebSocket))}

可以看到最终的请求是 dispatcher 来完成的,接下来就开始分析 dispatcher。

Dispatcher 任务调度

Dispatcher 主要用于控制并发的请求,它主要维护了以下变量:

image-20241019174612040

接下来看看 Dispatcher 的构造方法,如下所示:

 public Dispatcher(ExecutorService executorService) {this.executorService = executorService;}public Dispatcher() {}public synchronized ExecutorService executorService() {if (executorService == null) {executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS, new SynchronousQueueRunnable(), Util.threadFactory(OkHttp Dispatcher, false))}return executorService;}

复习:synchronized 关键字可以确保同一时间内只有一个线程可以访问共享资源,从而避免竞争条件(race condition)的问题。

同步方法

可以将整个方法标记为 synchronized,这样该方法在某一时刻只允许一个线程访问。

同步代码块

可以使用 synchronized 块来同步特定的代码块,而不是整个方法。这样可以减少锁的持有时间,提升程序的并发性能。

Dispatcher 有两个构造方法,可以使用自己设定的线程池。如果没有设定线程池,则会在请 求网络前自己创建默认线程池。这个线程池类似于 CachedThreadPool,比较适合执行大量的耗 时比较少的任务。前面讲过,当调用 RealCall 的 enqueue 方法时,实际上是调用了 Dispatcher 的 enqueue 方法,它的代码如下所示:

synchronized void enqueue(AsyncCall call) {if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {runningAsyncCalls.add(call)executorService().execute(call)} else {readyAsyncCalls.add(call)}
}

正在运行的异步请求队列中的数量小于 64 并且同一个主机的请求最大数小于 5 时,把请求 加载到runningAsyncCalls中并在线程池中执行,否则就加入到readyAsyncCalls中进行缓存等待。 线程池中传进来的参数是 AsyncCall,它是 RealCall 的内部类,其内部也实现了 execute 方法, 如下所示:

protected void execute() {boolean signalledCallback = falsetry {...} catch (IOException e) {...} finally {client.dispatcher().finished(this)//1}
}

在上面代码注释 1 处,无论这个请求的结果如何,都会执行 client.dispatcher().finished(this), finished 方法如下所示:

synchronized void finished(AsyncCall call) {if (!runningAsyncCalls.remove(call)) throw new AssertionError(AsyncCall wasn't running!)promoteCalls()}

finished 方法将此次请求从 runningAsyncCalls 移除后还执行了 promoteCalls 方法:

private void promoteCalls() {if (runningAsyncCalls.size()= maxRequests) returnif (readyAsyncCalls.isEmpty()) returnfor (IteratorAsyncCall> i = readyAsyncCalls.iterator(); i.hasNext()) {AsyncCall call = i.next()if (runningCallsForHost(call) < maxRequestsPerHost) {i.remove();runningAsyncCalls.add(call)executorService().execute(call)}if (runningAsyncCalls.size()= maxRequests) return}
}

最关键的一点就是会从 readyAsyncCalls 取出下一个请求,加入 runningAsyncCalls 中并交由 线程池处理。好了,让我们再回到上面 AsyncCall 的 execute 方法:

@Override
protected void execute() {boolean signalledCallback = falsetry {Response response = getResponseWithInterceptorChain(forWebSocket)//1if (canceled) {signalledCallback = true;responseCallback.onFailure(RealCall.this, new IOException(Canceled))} else {signalledCallback = true;responseCallback.onResponse(RealCall.this, response)}} catch (IOException e) {if (signalledCallback) {logger.log(Level.INFO,Callback failure for+ toLoggableString(), e)} else {responseCallback.onFailure(RealCall.this, e)}} finally {client.dispatcher().finished(this)}
}

在上面代码注释 1 处,getResponseWithInterceptorChain 方法返回了 Response,这是在请求网络。

Interceptor 拦截器

接下来查看 getResponseWithInterceptorChain 方法,如下所示:

private Response getResponseWithInterceptorChain(boolean forWebSocket) throws IOException {Interceptor.Chain chain = new ApplicationInterceptorChain(0, originalRequest,forWebSocket)return chain.proceed(originalRequest)}

getResponseWithInterceptorChain 方法中创建了 ApplicationInterceptorChain,这是一个拦截器链。这个类也是 RealCall 的内部类,接下来执行了它的 proceed 方法:

public Response proceed(Request request) throws IOException {if (index < client.interceptors().size()) {Interceptor.Chain chain = new ApplicationInterceptorChain(index + 1, request, forWebSocket)//从拦截器列表中取出拦截器Interceptor interceptor = client.interceptors().get(index)Response interceptedResponse = interceptor.intercept(chain)//1if (interceptedResponse == null) {throw new NullPointerException("application interceptor " + interceptor + " returned null)}return interceptedResponse;}return getResponse(request, forWebSocket)}

proceed 方法每次从拦截器列表中取出拦截器。当存在多个拦截器时都会在上面代码注释 1 处阻塞,并等待下一个拦截器的调用返回。下面分别以拦截器链中有一个、两个拦截器的场景加以模拟,如图 5-14 所示。

image-20241019232536612

拦截器是一种能够监控、重写、重试调用的机制。通常情况下,拦截器用来添加、移除、 转换请求和响应的头部信息。比如将域名替换为 IP 地址,在请求头中添加 host 属性;也可以添 加我们应用中的一些公共参数,比如设备 id、版本号,等等。回到代码上来,查看最后一行 return getResponse(request, forWebSocket),如果没有更多拦截器的话,就会执行网络请求。现在查看 getResponse 方法做了什么,代码如下所示:

Response getResponse(Request request, boolean forWebSocket) throws IOException {...engine = new HttpEngine(client, request, false, false, forWebSocket, null, null, null)int followUpCount = 0while (true) {if (canceled) {engine.releaseStreamAllocation()throw new IOException(Canceled)}boolean releaseConnection = truetry {engine.sendRequest();engine.readResponse();releaseConnection = false} catch (RequestException e) {throw e.getCause()} catch (RouteException e) {... }
}

getResponse 方法比较长,这里省略了一些代码,在此可以看到创建了 HttpEngine 类并且调 用了 HttpEngine 的 sendRequest 方法和 readResponse 方法。

缓存策略

首先来看看 HttpEngine 的 sendRequest 方法,如下:

public void sendRequest() throws RequestException, RouteException, IOException {if (cacheStrategy != null) return// Already sent.if (httpStream != null) throw new IllegalStateException()Request request = networkRequest(userRequest)//获取 client 中的 Cache,同时 Cache 在初始化时会读取缓存目录中曾经请求过的所有信息InternalCache responseCache = Internal.instance.internalCache(client)Response cacheCandidate = responseCache != null? responseCache.get(request)null//1long now = System.currentTimeMillis();cacheStrategy = new CacheStrategy.Factory(now, request, cacheCandidate).get()//网络请求networkRequest = cacheStrategy.networkRequest;//缓存的响应cacheResponse = cacheStrategy.cacheResponse;if (responseCache != null) {//记录当前请求是网络发起还是缓存发起responseCache.trackResponse(cacheStrategy)}if (cacheCandidate != null && cacheResponse == null) {closeQuietly(cacheCandidate.body())}//不进行网络请求并且缓存不存在或者过期,则返回 504 错误if (networkRequest == null && cacheResponse == null) {userResponse = new Response.Builder().request(userRequest).priorResponse(stripBody(priorResponse)).protocol(Protocol.HTTP_1_1).code(504).message(Unsatisfiable Request (only-if-cached)).body(EMPTY_BODY).build()return}// 不进行网络请求而且缓存可以使用,则直接返回缓存if (networkRequest == null) {userResponse = cacheResponse.newBuilder().request(userRequest).priorResponse(stripBody(priorResponse)).cacheResponse(stripBody(cacheResponse)).build();userResponse = unzip(userResponse)return}//需要访问网络时boolean success = falsetry {httpStream = connect();httpStream.setHttpEngine(this)...}}

上面的代码显然是在发送请求,但是最主要的是做了缓存的策略。上面代码注释 1 处的 cacheCandidate 是上次与服务器交互时缓存的 Response,这里的缓存均基于 Map。key 是请求中 url 的 md5,value 是在文件中查询到的缓存,页面置换基于 LRU 算法,我们现在只需要知道 cacheCandidate 是一个可以读取缓存 Header 的 Response 即可。根据 cacheStrategy 的处理得到了 networkRequest 和 cacheResponse 这两个值,根据这两个值的数据是否为 null 来进行进一步的处 理。在 networkRequest 和 cacheResponse 都为 null 的情况下,也就是不进行网络请求并且缓存 不存在或者过期,这时返回 504 错误;当 networkRequest 为 null 时也就是不进行网络请求,如 果缓存可以使用时则直接返回缓存,其他情况则请求网络。

接下来查看 HttpEngine 的 readResponse 方法,如下所示:

image-20241020004448247

这个方法主要用来解析 HTTP 响应报头。如果有缓存并且可用,则用缓存的数据并更新缓存,否则就用网络请求返回的数据。再来查看上面代码注释 1 处的 validate 方法是如何判断缓存是否可用的:

private static boolean validate(Response cached, Response network) {//如果服务器返回 304,则缓存有效if (network.code() == HTTP_NOT_MODIFIED) {return true}//通过缓存和网络请求响应中的 Last-Modified 来计算是否是最新数据。如果是,则缓存有效Date lastModified = cached.headers().getDate(Last-Modified)if (lastModified != null) {Date networkLastModified = network.headers().getDate(Last-Modified)if (networkLastModified != null&& networkLastModified.getTime() < lastModified.getTime()) {return true}}return false}

如果缓存是有效的,则返回 304 Not Modified,否则直接返回 body。如果缓存过期或者强 制放弃缓存,则缓存策略全部交给服务器判断,客户端只需要发送条件 GET 请求即可。条件 GET 请求有两种方式:一种是 Last-Modified-Date,另一种是 ETag。这里采用了 Last-ModifiedDate,通过缓存和网络请求响应中的 Last-Modified 来计算是否是最新数据。如果是,则缓存有效。

失败重连

最后我们再回到 RealCall 的 getResponse 方法,如下所示:

Response getResponse(Request request, boolean forWebSocket) throwsIOException {...boolean releaseConnection = truetry {engine.sendRequest();engine.readResponse();releaseConnection = false} catch (RequestException e) {throw e.getCause()} catch (RouteException e) {HttpEngine retryEngine = engine.recover(e.getLastConnectException(), null)//1if (retryEngine != null) {releaseConnection = false;engine = retryEngine;continue}throw e.getLastConnectException()} catch (IOException e) {HttpEngine retryEngine = engine.recover(e, null)//2if (retryEngine != null) {releaseConnection = false;engine = retryEngine;continue}throw e;} finally {if (releaseConnection) {StreamAllocation streamAllocation = engine.close();streamAllocation.release()}}...engine = new HttpEngine(client, request, false, false, forWebSocket, streamAllocation, null, response)}}

在上面代码注释 1 和注释 2 处,当发生 IOException 或者 RouteException 时都会执行 HttpEngine 的 recover 方法,它的代码如下所示:

public HttpEngine recover(IOException e, Sink requestBodyOut) {if (!streamAllocation.recover(e, requestBodyOut)) {return null}if (!client.retryOnConnectionFailure()) {return null}StreamAllocation streamAllocation = close()return new HttpEngine(client, userRequest, bufferRequestBody, callerWritesRequestBody,forWebSocket,streamAllocation, (RetryableSink)requestBodyOut, priorResponse)}

通过最后一行可以看到,其就是重新创建了 HttpEngine 并返回,用来完成重连。

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

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

相关文章

JUC并发编程面试题总结

文章目录 1、创建线程的三种方式2、线程的状态3、线程的上下文切换4、run和start的区别5、sleep和wait区别6、虚假唤醒,精确唤醒7、两阶段终止模式8、多线程下的线程安全问题9、如何解决线程安全问题10、synchornized的原理11、锁升级的机制12、锁消除13、批量重偏向…

Unity编辑器制作多级下拉菜单

Unity编辑器下拉菜单 大家好,我是阿赵。   在Unity引擎里面编写工具插件,有时候会用到一些特殊的菜单形式,比如下拉选项。 通过下拉菜单,给用户选择不同的选项。   如果只是一层的下拉列表,可以用EditorGUILayout.…

Nginx upstream

什么是Nginx upstream? Nginx 模块一般分为三大类:handler、filter和upstream。 利用 handler、filter 这两个模块,可以使 Nginx 轻松完成任何单机工作。 upstream 模块将使 Nginx 跨越单机的限制,完成网络数据的接收、处理和转…

计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-10-23

计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-10-23 目录 文章目录 计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-10-23目录1. Advancements in Visual Language Models for Remote Sensing: Datasets, Capabilities, and Enhancement Techniques摘…

Zig 语言通用代码生成器:逻辑,冒烟测试版发布二

Zig 语言通用代码生成器:逻辑,冒烟测试版发布二 Zig 语言是一种新的系统编程语言,其生态位类同与 C,是前一段时间大热的 rust 语言的竞品。它某种意义上的确非常像 rust,尤其是在开发过程中无穷无尽抛错的过程&#x…

高等数学-宋浩版2.0-映射

映射:X,Y为非空集合,存在法则F,对X(原像)中每个元素X,按法则F,在Y中有唯一元素与之对应,F为x到Y(镜像)的映射。f:X->Y X原像,Y像,x定义域,Df,Rf &#x…

python之多任务爬虫——线程、进程、协程的介绍与使用(16)

文章目录 1、什么是多任务?1.1 进程和线程的概念1.2 多线程与多进程的区别1.3 并发和并行2、python中的全局解释器锁3、多线程执行机制4、python中实现多线程(threading模块)4.1 模块介绍4.2 模块的使用5、python实现多进行程(Multiprocessing模块)5.1 导入模块5.2 模块的…

Caffeine本地缓存框架

Caffeine本地缓存框架 hi,我是阿昌,今天记录一下Java最强本地缓存Caffeine 1、缓存介绍 缓存(Cache),在软件无处不在。从底层CPU多级缓存,再到客户页面缓存,和服务器数据缓存,导出都存在着缓存的身影&am…

HBuilder X 中Vue.js基础使用2(三)

一、条件渲染 1、条件判断 v-if : 表达式返回真值时才被渲染 v-else :表达式返回为假时不被渲染 2、 分支条件判断 v-else-if :使用v-if , v-else-if 和 v-else 来表示其他的条件分支 3、显示隐藏 v-show v-show true 把节点显示 …

PortQry下载安装使用教程(超详细),Windows测试UDP端口

《网络安全自学教程》 PortQry是微软官方提供的一款TCP/IP连接「排障工具」,用来「检查」TCP/UDP「端口状态」。 平时检查端口状态,最常用的是telnet,但它是基于TCP协议的,无法检测「UDP端口」,这篇文章教大家如何在W…

Axure随机验证码高级交互

亲爱的小伙伴,在您浏览之前,烦请关注一下,在此深表感谢! 课程主题:字母数字随机验证码高级交互 主要内容:4位字母数字随机验证码生成、错误提示与State状态同步 应用场景:登录验证码、其他类…

面试宝典(五):用三个线程按顺序循环打印123三个数字,比如123123123

要使用三个线程按顺序循环打印123三个数字,势必要控制线程的执行顺序,可以使用java.util.concurrent包中的Semaphore类来控制线程的执行顺序。 代码示例 import java.util.concurrent.Semaphore;public class SequentialPrinting123 {private static Se…

leetcode:34. 在排序数组中查找元素的第一个和最后一个位置(python3解法)

#1024程序员节 | 征文# 难度:中等 给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。 如果数组中不存在目标值 target,返回 [-1, -1]。 你必须设计并实现时间复杂度为 O(lo…

初识算法 · 前缀和(1)

目录 前言: 一维数组的前缀和 题目解析 算法原理 算法编写 二维数组的前缀和 题目解析 算法原理 算法编写 前言: ​本文的主题是前缀和,通过两道题目讲解,一道是一维数组的模板,一道是二维数组的模板。 链接…

【WebGIS实例】(18)MapboxGL 绘制矢量——线、面

前言 Mapbox GL JS 版本:3.6.0 该博客仅供学习参考,如果您是计划在实际项目中实现该功能,也推荐您直接使用已有的功能库: 官方案例:Draw a polygon and calculate its areamapbox-gl-draw:mapbox/mapbox-g…

基于Django+python的酒店客房入侵检测系统设计与实现

项目运行 需要先安装Python的相关依赖:pymysql,Django3.2.8,pillow 使用pip install 安装 第一步:创建数据库 第二步:执行SQL语句,.sql文件,运行该文件中的SQL语句 第三步:修改源…

HTTPS讲解

前瞻 HTTP与HTTPS的关系 HTTPS也是一个在应用层的协议,是在HTTP协议基础上的一个加密解密层 明文 密文 秘钥 明文->秘钥 加密 秘钥->明文 解密 例如:明文为7 秘钥为2 7^21015; 5就是密文例子: 因为http的内容是明文传输的,明文…

危险物品图像分割系统:一键训练

危险物品图像分割系统源码&数据集分享 [yolov8-seg-GFPN&yolov8-seg-CSwinTransformer等50全套改进创新点发刊_一键训练教程_Web前端展示] 1.研究背景与意义 项目参考ILSVRC ImageNet Large Scale Visual Recognition Challenge 项目来源AAAI Global…

LabVIEW共享变量通信故障

问题概述: 在LabVIEW项目中,使用IO服务器创建共享变量,并通过LabVIEW作为从站进行数据通信。通讯在最初运行时正常,但在经过一段时间或几个小时后,VI前面板出现错误输出,导致数据传输失败。虽然“分布式系统…

折扣影票接口对接渠道如何选择?

选择折扣影票接口对接渠道需要综合多方面因素考虑,以下是一些建议: 1.合法性和合规性: 确认供应商资质:优先选择具有相关票务经营资质的渠道。比如一些大型的在线票务平台,它们通常经过官方认证和监管,在…