【Android】浅析OkHttp(1)

【Android】浅析OkHttp(1)

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

今天主要介绍OkHttp3。

OkHttp基本用法

使用前

配置gradle

compile 'com.squareup.okhttp3:okhttp:3.2.0'
compile 'com.squareup.okio:okio:1.7.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() {
		@Override
		public void onFailure(Call call, IOException e) {

		}
		@Override
 		public 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() {
	@Override
	public void onFailure(Call call, IOException e) {
	
	}
	@Override
	public 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 后取消 call
	executor.schedule(new Runnable() {
		@Override
		public void run() {
			finalCall.cancel()}
	}, 100, TimeUnit.MILLISECONDS);
	call.enqueue(new Callback() {
		@Override
		public void onFailure(Call call, IOException e) {
    
        }
        @Override
        public 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(拦截器)完成请求的过程。

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

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

关于分发器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)//1
       if (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)//1
		if (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//1
     long 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)//1
		if (retryEngine != null) {
			releaseConnection = false;
         	engine = retryEngine;
			continue}
 		throw e.getLastConnectException()} catch (IOException e) {
 		HttpEngine retryEngine = engine.recover(e, null)//2
		if (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 并返回,用来完成重连。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

大模型~合集13

我自己的原文哦~ https://blog.51cto.com/whaosoft/12302606 #TextRCNN、TextCNN、RNN 小小搬运工周末也要学习一下~~虽然和世界没关 但还是地铁上看书吧, 大老勿怪 今天来说一下 文本分类必备经典模型 模型 SOTA!模型资源站收录情况 模型来源论文 RAE ​​ht…

STM32外设应用技术博文:基于UART的温度传感器数据采集

✅作者简介:2022年博客新星 第八。热爱国学的Java后端开发者,修心和技术同步精进。 🍎个人主页:Java Fans的博客 🍊个人信条:不迁怒,不贰过。小知识,大智慧。 💞当前专栏…

深入理解Redis锁与Backoff重试机制在Go中的实现

文章目录 流程图Redis锁的深入实现Backoff重试策略的深入探讨结合Redis锁与Backoff策略的高级应用具体实现结论 在构建分布式系统时,确保数据的一致性和操作的原子性是至关重要的。Redis锁作为一种高效且广泛使用的分布式锁机制,能够帮助我们在多进程或分…

构建高效在线考试平台:Spring Boot与JavaWeb的融合

1系统概述 1.1 研究背景 随着计算机技术的发展以及计算机网络的逐渐普及,互联网成为人们查找信息的重要场所,二十一世纪是信息的时代,所以信息的管理显得特别重要。因此,使用计算机来管理基于JavaWeb技术的在线考试系统设计与实现…

汽车零部件行业CRM应用数字化解决方案解析

1.行业背景与挑战分析 近年来,随着国家对新能源汽车行业的大力支持,国内汽车产业不仅在国内市场实现了弯道超车,而且新能源汽车的海外出口也开拓了新的市场,为自主品牌的新能源战略贡献了新的增长点;这一迅猛发展的趋…

Pollard‘s p-1算法

概述 光滑数 (Smooth number):指可以分解为多个小素数乘积的正整数 当p是N 的因数,并且p−1是光滑数,可以考虑使用Pollards p-1算法来分解N 当p是N的因数,并且p1是光滑数,可以考虑使用Williamss p1算法来分解N 这里…

打造卓越APP体验:13款界面设计软件推荐

你知道如何选择正确的UI设计软件吗?你知道设计美观的用户界面,及带来良好用户体验的APP,需要什么界面设计软件吗?基于APP界面的功能不同,选择的APP界面设计软件也会有所不同。然而,并不是要把所有APP界面设…

权限(补充)

在上一篇Linux权限(想了解的可以点击看看哦)中已经见识了一部分权限,但是少了很重要的一部分: 那就是用户之间的转换,文件读写的关系,这里就简单的介绍一些; 我们在Linux权限知道了目录权限的关…

无人机之放电速率篇

无人机的放电速率是指电池在一定时间内放出其储存电能的能力,这一参数对无人机的飞行时间、性能以及安全性都有重要影响。 一、放电速率的表示方法 放电速率通常用C数来表示。C数越大,表示放电速率越快。例如,一个2C的电池可以在1/2小时内放…

屏蔽小米电视广告的方法

小米电视那个广告,太多,时间太长,影响观看感受,经过处理,成功屏蔽了小米电视的广告,提升了观影体验。 手动添加AD域名到 hosts 列表 小米(红米)电视关闭开机AD屏蔽hosts方法。 在路由器的hosts中配置。 …

Redis --- 第四讲 --- 常用数据结构 --- Hash、List

一、Hash哈希类型的基本介绍。 哈希表:之前学过的所有数据结构中,最最重要的。 1、日常开发中,出场频率非常高。 2、面试中,非常重要的考点。 Redis自身已经是键值对结构了。Redis自身的键值对就是通过哈希的方式来组织的。把…

12月17-19日 | 2024南京软博会,持续升级中!

2024中国(南京)软件产业博览会(简称“南京软博会”)自2005年以来,已连续举办19年,集新品发布、产品交易、人才交流、项目对接等功能为一体,成为全国软件产业品牌展会和对外窗口。 今年是南京软…

ubuntu 安装keepalived+haproxy

一、安装keepalived sudo apt update sudo apt install keepalived sudo systemctl start keepalived sudo systemctl enable keepalived sudo systemctl status keepalived#配置Keepalived sudo cp /etc/keepalived/keepalived.conf.sample /etc/keepalived/keepalived.conf …

informer学习笔记

一、informer讲解 infomer 要解决的三大问题: Attention计算的更快Decoder要一次性输出所有预测堆叠encoder也要更快 1. Attention 在长序列中,并非每一个位置的Attention都重要,对于每一个Q来说,只有一小部分的K与其有较强的…

java互联网医院智能导诊系统源码,Uniapp前端开发框架,支持一次编写,多端运行

智慧导诊系统源码,java源码,大屏机自动导诊,互联网医院智能导诊系统源码 老是全身无力,挂什么科? 经常头痛,挂什么科? 总是失眠,又得挂哪个科? 世界上最遥远的距离再加…

企业内训|LLM大模型技术在金融领域的应用及实践-某商业银行分行IT团队

本企业培训是TsingtaoAI技术团队专们为某商业银行分行IT团队开发的LLM大模型技术课程。课程深入分析大模型在金融行业中的发展趋势、底层技术及应用场景,重点提升学员在大模型应用中的实际操作能力与业务场景适应力。通过对全球商用 LLM 产品及国内外技术生态的深度…

每天五分钟深度学习框架pytorch:基于pytorch搭建多变量回归模型

本文重点 前面我们学习了基于pytorch搭建多项式回归模型,我们也知道了它和多变量回归模型的区别,本文我们使用pytorch搭建多变量回归模型 模型搭建 class Multivariable_Regression(nn.Module):def __init__(self):super(Multivariable_Regression,self).__init__()self.po…

基于PHP+MySQL+Vue的网上订餐系统

摘要 本文介绍了一个基于PHPMySQLVue技术的网上订餐系统。该系统旨在为用户提供便捷的在线订餐服务,同时提高餐厅的运营效率。系统后端采用PHP语言开发,利用MySQL数据库进行数据存储与管理,实现了用户注册登录、菜品浏览、购物车管理、订单提…

hi3798mv100 linux 移植

# Linux开发环境搭建 ## uboot编译 1. 必须先安装gcc,要不然make 等命令无法使用 2. 配置arm 交叉编译链 # gcc sudo apt-get install gcc-9 gcc -v# 安装 Linaro gcc-arm-linux-gnueabihf,注意不是arm-linux-gnueabihf-gcc sudo apt-get install ar…

老机MicroServer Gen8再玩 OCP万兆光口+IT直通

手上有一台放了很久的GEN8微型服务器,放了很多年,具体什么时候买的我居然已经记不清了 只记得开始装修的时候搬家出去就没用了,结果搬出去有了第1个孩子,孩子小的时候也没时间折腾,等孩子大一点的时候,又有…