websocket实现

由于安卓资源管理器展示的路径不尽相同,各种软件保存文件的位置也不一定一样.对于普通用户上传文件时,查找文件可能是一个麻烦的事情.后来想到了一个办法,使用pc端进行辅助上传.

文章目录

  • 实现思路
  • 1.0 实现
    • 定义web与客户端通信数据类型和数据格式
    • web端websocket实现
      • web端对客户端数据的管理
    • pc端实现
    • OkHttp3建立websocket连接
  • 2.0版本
    • spring-boot放到nginx后面
    • spring-boot 放到gateway后面
    • spring-boot 放到nginx gateway后面
    • ws升级为wss
  • 其他
    • springboot打包

实现思路

  1. pc端与服务器建立websocket连接;
  2. 服务器将sessionId传递到pc端;
  3. pc端生成二维码;
  4. 手机端扫描二维码,读取pc端sessionId;
  5. 手机端与服务器建立websocket连接;
  6. 手机端将fileId(后面再解释)、pc端sessionId、token等参数传递给服务器;
  7. 服务器更新pc端session 对应的fileId;
  8. 服务器将fileId、token等发送到pc端;
  9. pc使用token、fileId等请求文件列表并进行展示;
  10. 手机端、pc端进行文件修改后,向服务器发送给更新信号,服务器将更新信号转发到对端。

1.0 实现

定义web与客户端通信数据类型和数据格式

  1. 定义web与客户端通信数据类型
public class MsgType {public static final int UPDATE = 0; //提示客户端数据发生更新public static final int REQ = 1; //发送/接受fileId等字段public static final int SELF = 3; //建立连接后,web端发送client其sessionIdpublic static final int ERR_SESSION = 4; //提示session不存在或已close public static final int HEART_BEAT = 100; //心跳包
}
  1. 定义web与客户端通信数据格式
@Data
public class MsgData {private int type;  //对应 MsgTypeprivate String sessionId; //SELF 对应自身sessionId; REQ 对应pc端sessionId;private String fileId; //建立连接后,向pc端发送fileId等字段

web端websocket实现

创建spring-boot项目,添加web\websocket相关依赖

使用maven引入websocket依赖;

        <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency>

配置websocket和访问路径的映射关系

@Configuration   //配置websocket和访问路径的映射关系
@EnableWebSocket // 全局开启WebSocket支持
public class WebSocketConfig implements WebSocketConfigurer {@Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {registry.addHandler(new WebSocketServer(), "/websocket").setAllowedOrigins("*");}
}

web端对客户端数据的管理

  1. 定义web管理session的数据结构
@Data
public class SessionData {private int sessionType; // 1 master(app). 0 pcprivate String fileId; //pc会话IDprivate WebSocketSession session;private String sessionId;//虽然可以通过session.getId()获取到sessionId,但session关闭后,读取就会报错

web端对session的管理逻辑

  1. 新创建的连接添加到链表上,web向客户端发送SELF,告知其对应的sessionId;

  2. 断开连接时,如果是pc端session直接从链表中删除,如果是app端session,将其他相同fileId的session全部关闭并从链表删除;

  3. 接收到新消息后,根据消息类型进行分类处理:

    1. 心跳包,则直接返回;
    2. REQ app发送的fileId\pc端sessionId等字段,修改sessions上app连接和pc端SessionData内的fileId字段;
      并将fileId等字段发送给pc端;
    3. UPDATE 给所有相同fileId的session发送更新信号;

注意: sessions遍历\删除\添加必须添加synchronized,否则ConcurrentModificationException

package com.example.im.ws;import com.google.gson.Gson;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;/*** @ClassName WebSocketServer* @Description 处理websocket 连接* @Author guchuanhang* @date 2025/1/25 14:01* @Version 1.0**/@Slf4j
public class WebSocketServer extends TextWebSocketHandler {private final Object syncObject = new Object();private final List<SessionData> sessions =new ArrayList<>();@Overridepublic void afterConnectionEstablished(WebSocketSession session) throws Exception {log.info("New connection established: " + session.getId());SessionData sessionData = new SessionData(session);synchronized (syncObject) {sessions.add(sessionData);}MsgData msgData = new MsgData();msgData.setType(MsgType.SELF);msgData.setSessionId(session.getId());session.sendMessage(new TextMessage(new Gson().toJson(msgData)));}@Overrideprotected void handleTextMessage(WebSocketSession session, TextMessage message)throws Exception {String payload = message.getPayload();log.info("handleTextMessage: " + session.getId());log.info("Received message: " + payload);final MsgData msgData = new Gson().fromJson(payload, MsgData.class);//master 发来的需求.switch (msgData.getType()) {case MsgType.HEART_BEAT: {//heart beatbreak;}case MsgType.REQ: {//set master{SessionData sessionData = null;synchronized (syncObject) {final Optional<SessionData> any = sessions.stream().filter(s -> s.getSessionId().equals(session.getId())).findAny();if (any.isPresent()) {sessionData = any.get();}}if (null != sessionData) {//set master.sessionData.setSessionType(ClientType.MASTER);sessionData.setFileId(msgData.getFileId());}}//set slave{SessionData sessionData = null;synchronized (syncObject) {final Optional<SessionData> any = sessions.stream().filter(s -> s.getSessionId().equals(msgData.getSessionId())).findAny();if (any.isPresent()) {sessionData = any.get();}}if (null != sessionData) {sessionData.setSessionType(ClientType.SALVER);sessionData.setFileId(msgData.getFileId());MsgData msgData1 = new MsgData();msgData1.setType(MsgType.REQ);msgData1.setFileId(msgData.getFileId());sessionData.getSession().sendMessage(new TextMessage(new Gson().toJson(msgData1)));} else {//pc session error.MsgData msgData1 = new MsgData();msgData1.setType(MsgType.ERR_SESSION);session.sendMessage(new TextMessage(new Gson().toJson(msgData1)));}}break;}case MsgType.UPDATE: {//slfSessionData sessionData = null;synchronized (syncObject) {final Optional<SessionData> any = sessions.stream().filter(s -> s.getSessionId().equals(session.getId())).findAny();if (any.isPresent()) {sessionData = any.get();}}if (null != sessionData) {final String fileId = sessionData.getFileId();List<SessionData> collect;synchronized (syncObject) {collect =sessions.stream().filter(s -> (null != s.getFileId() && s.getFileId().equals(fileId)) || (null == s.getSession() || !s.getSession().isOpen())).collect(Collectors.toList());}if (collect.isEmpty()) {return;}List<SessionData> errList = new ArrayList<>();for (SessionData s : collect) {if (null == s.getSession() || !s.getSession().isOpen()) {errList.add(s);continue;}//不需要给自己发送了if (s.getSessionId().equals(session.getId())) {continue;}MsgData msgData1 = new MsgData();msgData1.setType(MsgType.UPDATE);try {s.getSession().sendMessage(new TextMessage(new Gson().toJson(msgData1)));} catch (Exception e) {e.printStackTrace();errList.add(s);}}synchronized (syncObject) {sessions.removeAll(errList);}}break;}}}@Overridepublic void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {log.info("Connection closed: " + session.getId() + ", Status: " + status);SessionData sessionData = null;synchronized (syncObject) {Optional<SessionData> any = sessions.stream().filter(s -> s.getSessionId().equals(session.getId())).findAny();if (any.isPresent()) {sessionData = any.get();}}if (null == sessionData) {return;}final String fileId = sessionData.getFileId();//slave just ignore and delete.if (ClientType.SALVER == sessionData.getSessionType()) {sessions.remove(sessionData);return;}if (ClientType.MASTER == sessionData.getSessionType()) {List<SessionData> collect;synchronized (syncObject) {collect =sessions.stream().filter(s ->(null != s.getFileId()&& s.getFileId().equals(fileId)) ||(null == s.getSession() || !s.getSession().isOpen())).collect(Collectors.toList());}if (collect.isEmpty()) {return;}for (SessionData s : collect) {final WebSocketSession session1 = s.getSession();if (null == session1 || !session1.isOpen()) {continue;}session1.close();}synchronized (syncObject) {sessions.removeAll(collect);}}}
}

pc端实现

  1. 页面创建时创建websocket,销毁时关闭websocket
  2. 根据和服务器约定的消息格式 在websocket回调函数onmessage接受数据类型进行二维码生成\文件列表查询等操作
  3. 添加心跳机制,让websocket更健壮

fileId是一个key,通过fileId可以查询最新的数据. pc端接受到刷新信号后,请求获取最新数据; pc端更新数据后,发送数据已更新信号.

<template><div v-if="fileId"><div>{{ fileId }}</div><el-button @click="updateData" type="primary">更新数据</el-button><div>发送给服务端更新信号时间: {{ sndUpdateSignalTime }}</div><div>收到服务端更新信号时间: {{ rcvUpdateSignalTime }}</div><div>心跳最新时间: {{ heartBeatSignalTime }}</div><div>服务器返回最新内容: {{ serverContent }}</div></div><div v-else-if="sessionId"><div>sessionId:{{ sessionId }}</div><img width="200px" height="200px" :src="qrCode" alt="QR Code"/></div></template><script>import QRCode from "qrcode";export default {name: "HelloWorld",data() {return {wsuri: "ws://192.168.0.110:7890/websocket",ws: null,sessionId: '',qrCode: null,fileId: '',rcvUpdateSignalTime: '',sndUpdateSignalTime: '',heartBeatSignalTime: '',serverContent: '',heartbeatInterval: null,heartbeatIntervalTime: 3000, // 心跳间隔时间,单位为毫秒}},created() {//页面打开时,初始化WebSocket连接this.initWebSocket()},beforeDestroy() {// 页面销毁时,关闭WebSocket连接this.stopHeartbeat()this.fileId = ''try {this.ws.close()} catch (e) {}this.ws = null;this.sessionId = ''},methods: {// pc端更新附件数据后,向服务器端发送更新信号updateData() {console.error('snd update signal')this.ws.send(JSON.stringify({type: 0}))//格式化为  yyyy-MM-dd HH:mm:ssthis.sndUpdateSignalTime = new Date().toLocaleTimeString()this.resetHeartbeat();},async generateQRCode() {try {this.qrCode = await QRCode.toDataURL(this.sessionId);} catch (error) {console.error('生成二维码时出错:', error);}},// 周期性发送心跳包startHeartbeat() {this.heartbeatInterval = setInterval(() => {if (this.ws && this.ws.readyState === WebSocket.OPEN) {this.ws.send(JSON.stringify({type: 100}))this.heartBeatSignalTime = new Date().toLocaleTimeString()console.error('snd heartbeat signal')} else {this.stopHeartbeat();}}, this.heartbeatIntervalTime);},//在发送或接受数据后,重置下一次发送心跳包的时间resetHeartbeat() {clearInterval(this.heartbeatInterval);this.startHeartbeat();},// 停止发送心跳包stopHeartbeat() {clearInterval(this.heartbeatInterval);},initWebSocket() {let that = this;this.ws = new WebSocket(this.wsuri);this.ws.onopen = () => {this.startHeartbeat();};// 接收后端消息this.ws.onmessage = function (event) {console.error('RECV:' + event.data)that.serverContent = event.data;let parse = JSON.parse(event.data);that.resetHeartbeat();switch (parse.type) {case 0: {console.error('update')that.rcvUpdateSignalTime = new Date().toLocaleTimeString()//TODO. 请求最新数据break;}case 1: {   //fileId list. 接受数据,进行路径跳转console.error('REQ:' + event.data)that.fileId = parse.fileId;//记录并请求最新数据break;}case 3: {that.sessionId = parse.sessionId;that.generateQRCode();break;}}};// 关闭连接时调用this.ws.onclose = function (event) {alert('连接已关闭');that.stopHeartbeat()// 强制刷新页面(created 会调用)location.reload(true)};}}
}
</script><style scoped></style>

OkHttp3建立websocket连接

  1. 使用okhttp3建立websocket连接,监听onMessage根据消息类型进行不同的处理;
  2. 使用handler 管理心跳包

扫码后, 如果已经建立连接了

package com.example.im.ws;import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.text.TextUtils;
import android.widget.EditText;
import android.widget.TextView;import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;import com.example.im.R;
import com.google.gson.Gson;
import com.google.zxing.integration.android.IntentIntegrator;
import com.google.zxing.integration.android.IntentResult;import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.WebSocket;
import okhttp3.WebSocketListener;public class HelloActivity extends AppCompatActivity {public static final int MSG_HEART = 0x123;public static final int MSG_INTERVAL = 3000;private WebSocket webSocket;public static final String URL = "ws://192.168.0.110:7890/websocket";private TextView msgView;private List<String> sessionIds = new ArrayList<>();Handler mHandler = new Handler(Looper.getMainLooper()) {@Overridepublic void handleMessage(@NonNull Message msg) {super.handleMessage(msg);if (MSG_HEART == msg.what) {MsgData msgData = new MsgData();msgData.setType(MsgType.HEART_BEAT);webSocket.send(new Gson().toJson(msgData));msgView.append(getNowDate() + ":发送消息 heart beat\n");mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_HEART), MSG_INTERVAL);}}};@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);msgView = findViewById(R.id.tv_msg);findViewById(R.id.btn_scan).setOnClickListener(v -> {scanQRCode();});findViewById(R.id.btn_update).setOnClickListener(v -> {MsgData msgData = new MsgData();msgData.setType(MsgType.UPDATE);webSocket.send(new Gson().toJson(msgData));});}@Overrideprotected void onDestroy() {mHandler.removeCallbacksAndMessages(null);super.onDestroy();}private void scanQRCode() {IntentIntegrator integrator = new IntentIntegrator(this);integrator.setDesiredBarcodeFormats(IntentIntegrator.QR_CODE);integrator.setPrompt("提示");integrator.setCameraId(0);  // 使用后置摄像头integrator.setBeepEnabled(false);integrator.setBarcodeImageEnabled(true);integrator.initiateScan();}@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) {IntentResult result = IntentIntegrator.parseActivityResult(requestCode, resultCode, data);if (result != null && !TextUtils.isEmpty(result.getContents())) {String sessionId = result.getContents();if (sessionIds.contains(sessionId)) {return;}sessionIds.add(sessionId);//startif (null == webSocket) {OkHttpClient client = new OkHttpClient();Request request = new Request.Builder().url(URL).build();webSocket = client.newWebSocket(request, new MyWebSocketListener());} else {//这样可以实现扫多个pc端. 同时与多个pc端通信MsgData msgData = new MsgData();msgData.setSessionId(sessionId);msgData.setType(MsgType.REQ);msgData.setFileId("123");webSocket.send(new Gson().toJson(msgData));}}super.onActivityResult(requestCode, resultCode, data);}private String getNowDate() {SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss",Locale.getDefault());return simpleDateFormat.format(new java.util.Date());}private class MyWebSocketListener extends WebSocketListener {@Overridepublic void onOpen(WebSocket webSocket, okhttp3.Response response) {// 连接成功msgView.append(getNowDate() + ":连接成功\n");MsgData msgData = new MsgData();msgData.setSessionId(sessionIds.get(sessionIds.size() - 1));msgData.setType(MsgType.REQ);msgData.setFileId("123");webSocket.send(new Gson().toJson(msgData));mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_HEART), MSG_INTERVAL);}@Overridepublic void onMessage(WebSocket webSocket, String text) {msgView.append(getNowDate() + ":接受消息" + text + "\n");mHandler.removeMessages(MSG_HEART);mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_HEART), MSG_INTERVAL);}@Overridepublic void onFailure(WebSocket webSocket, Throwable t, okhttp3.Response response) {// 连接失败msgView.append(getNowDate() + ":失败" + t.getMessage() + "\n");}}
}

2.0版本

上面的实现确实简单.下面结合实际的系统架构进行适配一下.

spring-boot放到nginx后面

nginx常用来进行负载均衡\防火墙\反向代理等等,这种情况比较常见.

	map $http_upgrade $connection_upgrade { default upgrade; '' close; } server {listen       7777;server_name  localhost;location / {proxy_pass http://127.0.0.1:7890;proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; }}
}

设置Upgrade\Connection请求头,将访问地址修改为nginx的地址,即可实现nginx代理到spring-boot.

spring-boot 放到gateway后面

也就是所谓的spring-cloud 微服务架构.

gateway添加ws协议的路由

        # IM- id: imuri: ws://localhost:7890predicates:- Path=/im/**filters:- StripPrefix=1

访问gateway代理之后的地址,即可实现nginx代理到spring-boot.

spring-boot 放到nginx gateway后面

将前面两者进行结合, nginx保证可以代理到gateway, gateway再路由到spring-boot.

ws升级为wss

网上的做法是, 给gateway\spring-boot都配置证书.

简单才能高效,既然gateway有防火墙验证证书等功能,应用不需要管理才对. nginx要屏蔽这种差异.

配置nginx 直接将wss的请求重写为ws.

nginx重写协议

map $http_upgrade $connection_upgrade { 
default upgrade; 
'' close; 
} 
server
{listen 443 ssl http2;server_name #SSL-START SSL相关配置,请勿删除或修改下一行带注释的404规则ssl on;ssl_certificate   ssl_certificate_key add_header Strict-Transport-Security "max-age=31536000";error_page 497  https://$host$request_uri;location /im/ { proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; proxy_pass http://127.0.0.1:18080/im/;rewrite ^(.*)wss://(.*)$ $1ws://$2 permanent;}
}

这样 wss://域名/im/websocket就可以进行访问了.

其他

源码下载地址: https://gitee.com/guchuanhang/imapplication.git

springboot打包

  1. 注释掉skip
<plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>${spring-boot.version}</version><configuration><mainClass>com.example.im.ImApplication</mainClass><!-- 注释掉,否则不能打包-->
<!--                    <skip>true</skip>--></configuration><executions><execution><id>repackage</id><goals><goal>repackage</goal></goals></execution></executions></plugin>
  1. springboot日志
    spring-boot 默认支持logback

@Slf4j
public class WebSocketServer extends TextWebSocketHandler {log.info("New connection established: " + session.getId());
  1. bootstrap.yml

bootstrap.yml 是 spring-cloud 配置文件.
application.yml applicaition.properties 是 spring-boot 的配置文件.

  1. wss测试工具 wscat
npm install -g wscat  # 安装方式wscat -c wss://www.baidu.com/im/websocket   

图片

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

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

相关文章

【科研建模】Pycaret自动机器学习框架使用流程及多分类项目实战案例详解

Pycaret自动机器学习框架使用流程及项目实战案例详解 1 Pycaret介绍2 安装及版本需求3 Pycaret自动机器学习框架使用流程3.1 Setup3.2 Compare Models3.3 Analyze Model3.4 Prediction3.5 Save Model4 多分类项目实战案例详解4.1 ✅ Setup4.2 ✅ Compare Models4.3 ✅ Experime…

CY T 4 BB 5 CEB Q 1 A EE GS MCAL配置 - MCU组件

1、ResourceM 配置 选择芯片信号: 2、MCU 配置 2.1 General配置 1) McuDevErrorDetect: - 启用或禁用MCU驱动程序模块的开发错误通知功能。 - 注意:采用DET错误检测机制作为安全机制(故障检测)时,不能禁用开发错误检测。2) McuGetRamStateApi - enable/disable th…

docker 安装 mysql 详解

在平常的开发工作中&#xff0c;我们经常需要用到 mysql 数据库。那么在docker容器中&#xff0c;应该怎么安装mysql数据库呢。简单来说&#xff0c;第一步&#xff1a;拉取镜像&#xff1b;第二步&#xff1a;创建挂载目录并设置 my.conf&#xff1b;第三步&#xff1a;启动容…

【2025年数学建模美赛E题】(农业生态系统)完整解析+模型代码+论文

生态共生与数值模拟&#xff1a;生态系统模型的物种种群动态研究 摘要1Introduction1.1Problem Background1.2Restatement of the Problem1.3Our Work 2 Assumptions and Justifications3 Notations4 模型的建立与求解4.1 农业生态系统模型的建立与求解4.1.1 模型建立4.1.2求解…

编码器和扩散模型

目录 摘要abstract1.自动编码器2.变分编码器&#xff08;VAE&#xff09;3.论文阅读3.1 介绍3.2 方法3.3 结论 4.总结参考文献 摘要 本周学习了自动编码器&#xff08;AE&#xff09;和变分自动编码器&#xff08;VAE&#xff09;的基本原理与实现&#xff0c;分析其在数据降维…

【C++】类与对象初级应用篇:打造自定义日期类与日期计算器(2w5k字长文附源码)

文章目录 一、日期类的实现1. 日期类的默认成员函数的分析与实现构造函数其它默认成员函数 2. 各种逻辑比较运算符重载3. 日期加与减天数日期加天数系列日期减天数系列日期加减天数的最后修定和- -系列 4. 日期减日期方法一方法二 5. 流插入与流提取重载流插入重载流提取重载(含…

Redis实战(黑马点评)——关于缓存(缓存更新策略、缓存穿透、缓存雪崩、缓存击穿、Redis工具)

redis实现查询缓存的业务逻辑 service层实现 Overridepublic Result queryById(Long id) {String key CACHE_SHOP_KEY id;// 现查询redis内有没有数据String shopJson (String) redisTemplate.opsForValue().get(key);if(StrUtil.isNotBlank(shopJson)){ // 如果redis的数…

ThinkPhp伪静态设置后,访问静态资源也提示找不到Controller

ThinkPhp没有配置伪静态时&#xff0c;除了默认的IndexController能访问&#xff0c;其他路由Controller都访问不到&#xff0c;提示404错误。配置了伪静态后就解决了这个问题。 但是当我的ThinkPhp后台项目中有静态资源放在public目录&#xff08;或子目录&#xff09;中需要…

2013年蓝桥杯第四届CC++大学B组真题及代码

目录 1A&#xff1a;高斯日记&#xff08;日期计算&#xff09; 2B&#xff1a;马虎的算式&#xff08;暴力模拟&#xff09; 3C&#xff1a;第39级台阶&#xff08;dfs或dp&#xff09; 4D&#xff1a;黄金连分数&#xff08;递推大数运算&#xff09; 5E&#xff1a;前缀…

【数据分享】1929-2024年全球站点的逐月平均能见度(Shp\Excel\免费获取)

气象数据是在各项研究中都经常使用的数据&#xff0c;气象指标包括气温、风速、降水、湿度等指标&#xff01;说到气象数据&#xff0c;最详细的气象数据是具体到气象监测站点的数据&#xff01; 有关气象指标的监测站点数据&#xff0c;之前我们分享过1929-2024年全球气象站点…

【动态规划】--- 斐波那契数模型

Welcome to 9ilks Code World (๑•́ ₃ •̀๑) 个人主页: 9ilk (๑•́ ₃ •̀๑) 文章专栏&#xff1a; 算法Journey &#x1f3e0; 第N个泰波那契数模型 &#x1f4cc; 题目解析 第N个泰波那契数 题目要求的是泰波那契数&#xff0c;并非斐波那契数。 &…

单片机-STM32 WIFI模块--ESP8266 (十二)

1.WIFI模块--ESP8266 名字由来&#xff1a; Wi-Fi这个术语被人们普遍误以为是指无线保真&#xff08;Wireless Fidelity&#xff09;&#xff0c;并且即便是Wi-Fi联盟本身也经常在新闻稿和文件中使用“Wireless Fidelity”这个词&#xff0c;Wi-Fi还出现在ITAA的一个论文中。…

计算机的错误计算(二百二十二)

摘要 利用大模型化简计算 实验表明&#xff0c;虽然结果正确&#xff0c;但是&#xff0c;大模型既绕了弯路&#xff0c;又有数值计算错误。 与前面相同&#xff0c;再利用同一个算式看看另外一个大模型的化简与计算能力。 例1. 化简计算摘要中算式。 下面是与一个大模型的…

ansible自动化运维实战--软件包管理模块、服务模块、文件模块和收集模块setup(4)

文章目录 一、软件包管理模块1.1、功能1.2、常用参数1.3、示例 二、服务模块2.1、功能2.2、服务模块常用参数2.3、示例 三、文件与目录模块3.1、file功能3.2、常用参数3.3、示例 四、收集模块-setup4.1、setup功能4.2、示例 一、软件包管理模块 1.1、功能 Ansible 提供了多种…

高速光模块中的并行光学和WDM波分光学技术

随着AI大模型训练和推理对计算能力的需求呈指数级增长&#xff0c;AI数据中心的网络带宽需求大幅提升&#xff0c;推动了高速光模块的发展。光模块作为数据中心和高性能计算系统中的关键器件&#xff0c;主要用于提供高速和大容量的数据传输服务。 光模块提升带宽的方法有两种…

Linux命令行配置网络代理

在Linux命令行中&#xff0c;你可以使用以下方法设置网络代理服务器。 本文演示代理地址为&#xff1a;http://192.168.1.30:7890 请根据实际代理地址进行替换 临时代理 使用环境变量的方法&#xff1a; 打开终端&#xff0c;并输入以下命令&#xff1a; export http_proxyhtt…

SpringBoot3+Vue3开发学生选课管理系统

功能介绍 分三个角色登录&#xff1a;学生登录&#xff0c;老师登录&#xff0c;教务管理员登录&#xff0c;不同用户功能不同&#xff01; 1.学生用户功能 选课记录&#xff0c;查看选课记录&#xff0c;退选。选课管理&#xff0c;进行选课。通知管理&#xff0c;查看通知消…

牛客周赛 Round 78 A-C

A.时间表查询&#xff01; 链接&#xff1a;https://ac.nowcoder.com/acm/contest/100671/A 来源&#xff1a;牛客网 题目描述 今天是2025年1月25日&#xff0c;今年的六场牛客寒假算法基础集训营中&#xff0c;前两场比赛已经依次于 20250121、20250123 举行&#xff1b;而…

Android - 通过Logcat Manager简单获取Android手机的Log

由于工作需要&#xff0c;经常需要获取Android手机的Log。 平常都是通过adb命令来获取&#xff0c;每次都要写命令。 偶然的一个机会&#xff0c;我从外网发现了一个工具 Logcat Manager&#xff0c;只需要通过简单的双击即可获取Android的Log&#xff0c;这里也分享一下。 目…

苍穹外卖-day10

苍穹外卖-day10 Spring Task Spring Task 是Spring框架提供的任务调度工具&#xff0c;可以按照约定的时间自动执行某个代码逻辑。 cron表达式 cron表达式其实就是一个字符串&#xff0c;通过cron表达式可以定义任务触发的时间 **构成规则&#xff1a;**分为6或7个域&…