Android实现Socket通信
问题:
我在Android端对接后端Socket服务实现消息模块的时候,使用WebSocketClient 库,手动构造了订阅和发送消息的 STOMP 帧,,Socket连接成功建立,但是当用户发送消息的时候,对方无法接受到。
然后我Web端使用了成熟的 STOMP 库(Stomp.over 和 SockJS),所有接口测试通过。
排查经历:
- 起初我以为是在安卓端手动构造了订阅和发送消息的 STOMP 帧的格式问题,后来经过抓包排查,没有问题
- 然后我以为是,由于Android虚拟机和我手机调试网络的原因,我在本地创建了两个虚拟机,运行项目发现还是消息发送成功,推送失败。
- 因为问题定位到推送消息的步骤失败,后端采用的是Spring WebSocket的方法
messagingTemplate.convertAndSendToUser()
,我开始研究这个方法的底层逻辑,并在网上查找资料,发现有的人因为Header构建的问题导致推送消息失效,我又开始了更改调试,没有解决。 - 因为我Web端是正常运行的,Android是失败的,所以我使用Wireshark开始抓包对比,到底是哪个步骤出现了问题。
失败案例抓包数据:
成功案例抓包数据:
Socket服务器:
Socket建立连接序列图
问题原因:
web端的代码使用了成熟的 STOMP 库(Stomp.over 和 SockJS),它在建立连接时会自动发送 CONNECT 帧,完成 STOMP 握手;而你 Android 端的代码则是直接用 WebSocketClient 建立连接,并手动构造了订阅和发送消息的 STOMP 帧,但没有看到发送 CONNECT 帧的过程。
- 缺少 CONNECT 握手
STOMP 协议要求客户端在发送其他 STOMP 帧(例如 SUBSCRIBE 或 SEND)之前,先发送 CONNECT 帧并等待服务器返回 CONNECTED 帧。如果没有发送 CONNECT 帧,服务器可能会忽略你后续发送的订阅或消息帧,从而只看到 ACK 包(底层 TCP 的 ACK 应答),而没有实际的消息推送。
解决方案:
- 发送 CONNECT 帧
在 WebSocket 连接成功(onOpen 回调)后,尽快构造并发送一个标准的 CONNECT 帧。例如,在 Android 端的 onOpen 方法中添加如下代码:
java复制编辑
@Override
public void onOpen(ServerHandshake handshakedata) {Log.d(TAG, "WebSocket连接成功");// 发送 CONNECT 帧,指定支持的 STOMP 版本和心跳StringBuilder connectFrame = new StringBuilder();connectFrame.append("CONNECT\n");connectFrame.append("accept-version:1.2\n");connectFrame.append("heart-beat:10000,10000\n");connectFrame.append("satoken:").append(token).append("\n");connectFrame.append("\n"); // 空行分隔头部和正文connectFrame.append("\0"); // STOMP帧结束符client.send(connectFrame.toString());connectionState.postValue(ConnectionState.CONNECTED);reconnectAttempts = 0;startHeartbeat();// 在收到服务器的 CONNECTED 帧之后再订阅个人消息通道// 你可以在 onMessage 中判断收到 CONNECTED 后再调用 subscribeToPersonalMessages()// 这里为了测试可以先延时一会儿再订阅new Handler(Looper.getMainLooper()).postDelayed(() -> subscribeToPersonalMessages(), 500);showToast("WebSocket连接成功");
}
这样确保服务器能够正确识别你的 STOMP 客户端,处理后续的订阅和消息发送。
完整Android端代码
package com.xjl.mobilehotel.websocket;import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.widget.Toast;import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;import com.google.gson.Gson;
import com.xjl.mobilehotel.model.MessageChatBO;
import com.xjl.mobilehotel.model.MessageConversationBO;
import com.xjl.mobilehotel.model.MessageDTO;
import com.xjl.mobilehotel.model.config.MessageSessionTypeEnum;
import com.xjl.mobilehotel.model.config.MessageStatus;
import com.xjl.mobilehotel.model.config.MessageTypeEnum;
import com.xjl.mobilehotel.ui.auth.LoginActivity;
import com.xjl.mobilehotel.utils.SharedPreferencesManager;import org.java_websocket.client.WebSocketClient;
import org.java_websocket.drafts.Draft_6455;
import org.java_websocket.handshake.ServerHandshake;
import org.json.JSONObject;import java.net.URI;
import java.net.URISyntaxException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.TimeUnit;import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;/*** WebSocket管理类*/
public class WebSocketManager {private static final String TAG = "WebSocketManager";// 服务器地址private static final String SERVER_URL = "ws://192.168.167.39:8008";private static final String WS_PATH = "/ws";// 心跳端点private static final String HEARTBEAT_ENDPOINT = "/socket/heartbeat";// 消息端点private static final String MESSAGE_ENDPOINT = "/socket/message/";// 个人消息订阅端点private static final String PERSONAL_MESSAGE_SUBSCRIPTION = "/user/%s/queue/message";// 最大重连次数private static final int MAX_RECONNECT_ATTEMPTS = 5;// 心跳间隔(毫秒)private static final long HEARTBEAT_INTERVAL = 10000;private static WebSocketManager instance;private WebSocketClient client;private Context context;private SharedPreferencesManager prefsManager;private final CompositeDisposable disposables = new CompositeDisposable();private Disposable heartbeatDisposable;private int reconnectAttempts = 0;private boolean intentionalClose = false;private Handler mainHandler;private String token;private Gson gson;private boolean isSubscribed = false;// 连接状态private final MutableLiveData<ConnectionState> connectionState = new MutableLiveData<>(ConnectionState.DISCONNECTED);// 接收到的消息private final MutableLiveData<MessageDTO> receivedMessage = new MutableLiveData<>();private WebSocketManager() {// 私有构造方法mainHandler = new Handler(Looper.getMainLooper());gson = new Gson();}/*** 获取单例实例*/public static synchronized WebSocketManager getInstance() {if (instance == null) {instance = new WebSocketManager();}return instance;}/*** 初始化*/public void init(Context context) {this.context = context.getApplicationContext();this.prefsManager = SharedPreferencesManager.getInstance(context);}/*** 获取连接状态*/public LiveData<ConnectionState> getConnectionState() {return connectionState;}/*** 获取接收到的消息*/public LiveData<MessageDTO> getReceivedMessage() {return receivedMessage;}/*** 连接WebSocket*/public void connect() {// 检查是否已登录token = prefsManager.getToken();if (token == null || token.isEmpty()) {Log.e(TAG, "用户未登录,无法连接WebSocket");showToast("用户未登录,无法连接WebSocket");// 跳转到登录界面Intent intent = new Intent(context, LoginActivity.class);intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);context.startActivity(intent);return;}// 如果已经连接,先断开disconnect();try {// 使用HTTP URL,SockJS会自动处理协议转换String url = SERVER_URL + WS_PATH + "?satoken=" + token;Log.d(TAG, "正在连接WebSocket: " + url);showToast("正在连接WebSocket服务器...");// 设置请求头Map<String, String> headers = new HashMap<>();headers.put("satoken", token);// 创建WebSocket客户端client = new WebSocketClient(new URI(url), new Draft_6455(), headers, 0) {@Overridepublic void onOpen(ServerHandshake handshakedata) {Log.d(TAG, "WebSocket连接成功");connectionState.postValue(ConnectionState.CONNECTED);reconnectAttempts = 0;startHeartbeat();// 连接成功后先发送 CONNECT 帧,完成 STOMP 握手sendConnectFrame();showToast("WebSocket连接成功, 正在发送CONNECT帧");}@Overridepublic void onMessage(String message) {Log.d(TAG, "收到WebSocket消息: " + message);// 打印消息的前100个字符String logMessage = message.length() > 100 ? message.substring(0, 100) + "..." : message;Log.d(TAG, "收到消息预览: " + logMessage);// 处理消息handleReceivedMessage(message);}@Overridepublic void onClose(int code, String reason, boolean remote) {Log.d(TAG, "WebSocket连接关闭: code=" + code + ", reason=" + reason + ", remote=" + remote);connectionState.postValue(ConnectionState.DISCONNECTED);stopHeartbeat();isSubscribed = false;if (remote && !intentionalClose) {showToast("WebSocket连接被服务器关闭: " + reason);}// 如果不是主动关闭,尝试重连if (!intentionalClose && reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {reconnect();}}@Overridepublic void onError(Exception ex) {Log.e(TAG, "WebSocket连接错误", ex);connectionState.postValue(ConnectionState.ERROR);showToast("WebSocket连接失败: " + ex.getMessage());// 尝试重连if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {reconnect();}}};// 连接connectionState.postValue(ConnectionState.CONNECTING);client.connect();} catch (URISyntaxException e) {Log.e(TAG, "WebSocket URI语法错误", e);connectionState.postValue(ConnectionState.ERROR);showToast("WebSocket URI语法错误: " + e.getMessage());}}/*** 断开WebSocket连接*/public void disconnect() {intentionalClose = true;if (client != null && client.isOpen()) {client.close();showToast("WebSocket连接已断开");}stopHeartbeat();connectionState.postValue(ConnectionState.DISCONNECTED);isSubscribed = false;}/*** 重连*/private void reconnect() {reconnectAttempts++;Log.d(TAG, "尝试重连WebSocket,第" + reconnectAttempts + "次");showToast("正在尝试重新连接WebSocket,第" + reconnectAttempts + "次");// 使用指数退避策略long delay = (long) Math.pow(2, reconnectAttempts) * 1000;Disposable disposable = Observable.timer(delay, TimeUnit.MILLISECONDS).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(aLong -> {intentionalClose = false;connect();});disposables.add(disposable);}/*** 开始心跳*/private void startHeartbeat() {stopHeartbeat();heartbeatDisposable = Observable.interval(HEARTBEAT_INTERVAL, TimeUnit.MILLISECONDS).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(aLong -> {if (client != null && client.isOpen()) {// 构建STOMP格式的心跳消息try {StringBuilder stompFrame = new StringBuilder();stompFrame.append("SEND\n");stompFrame.append("destination:").append(HEARTBEAT_ENDPOINT).append("\n");stompFrame.append("content-type:application/json;charset=UTF-8\n");stompFrame.append("satoken:").append(token).append("\n");stompFrame.append("\n"); // 空行分隔头部和正文// 使用空 JSON 对象作为消息体JSONObject heartbeatMessage = new JSONObject();stompFrame.append(heartbeatMessage.toString());stompFrame.append("\0"); // STOMP帧结束符client.send(stompFrame.toString());Log.d(TAG, "发送心跳到: " + HEARTBEAT_ENDPOINT);} catch (Exception e) {Log.e(TAG, "构建心跳消息失败", e);}}});disposables.add(heartbeatDisposable);}/*** 停止心跳*/private void stopHeartbeat() {if (heartbeatDisposable != null && !heartbeatDisposable.isDisposed()) {heartbeatDisposable.dispose();}}/*** 发送 CONNECT 帧以完成 STOMP 握手*/private void sendConnectFrame() {if (client != null && client.isOpen()) {StringBuilder connectFrame = new StringBuilder();connectFrame.append("CONNECT\n");connectFrame.append("accept-version:1.2\n");connectFrame.append("heart-beat:10000,10000\n");connectFrame.append("satoken:").append(token).append("\n");connectFrame.append("\n"); // 空行分隔头部和正文connectFrame.append("\0"); // STOMP帧结束符client.send(connectFrame.toString());Log.d(TAG, "发送CONNECT帧: " + connectFrame.toString());}}/*** 订阅个人消息通道*/private void subscribeToPersonalMessages() {if (client != null && client.isOpen()) {try {// 获取当前用户IDString userId = prefsManager.getUserId();if (userId == null || userId.isEmpty()) {Log.e(TAG, "用户ID为空,无法订阅消息");showToast("用户ID为空,无法订阅消息");return;}// 构建订阅消息StringBuilder subscribeMessage = new StringBuilder();subscribeMessage.append("SUBSCRIBE\n");subscribeMessage.append("id:sub-0\n");subscribeMessage.append("destination:").append(String.format(PERSONAL_MESSAGE_SUBSCRIPTION, userId)).append("\n");subscribeMessage.append("\n"); // 空行分隔头部和正文subscribeMessage.append("\0"); // STOMP帧结束符client.send(subscribeMessage.toString());Log.d(TAG, "发送订阅消息到: " + String.format(PERSONAL_MESSAGE_SUBSCRIPTION, userId));isSubscribed = true;} catch (Exception e) {Log.e(TAG, "发送订阅消息失败", e);}}}/*** 处理接收到的消息(支持 CONNECTED、MESSAGE、ERROR 三种STOMP消息)*/private void handleReceivedMessage(String message) {try {Log.d(TAG, "开始处理接收到的消息: " + message);if (message.startsWith("CONNECTED")) {Log.d(TAG, "收到 CONNECTED 帧,STOMP连接成功");// 连接成功后订阅个人消息通道if (!isSubscribed) {subscribeToPersonalMessages();}} else if (message.startsWith("MESSAGE")) {Log.d(TAG, "收到STOMP MESSAGE消息");int headerEnd = message.indexOf("\n\n");if (headerEnd != -1) {String body = message.substring(headerEnd + 2).replace("\0", "");Log.d(TAG, "收到消息体: " + body);try {MessageDTO messageDTO = gson.fromJson(body, MessageDTO.class);if (messageDTO != null && messageDTO.getMessage() != null) {// 更新消息状态为已接收messageDTO.getMessage().setStatus(MessageStatus.UNREAD);Log.d(TAG, "解析消息成功: " + messageDTO);// 在主线程上发送消息更新mainHandler.post(() -> {receivedMessage.setValue(messageDTO);showToast("收到来自 " + messageDTO.getMessage().getSender() + " 的消息: " + messageDTO.getMessage().getContent());});} else {Log.e(TAG, "解析消息失败或消息内容为空");}} catch (Exception e) {Log.e(TAG, "解析JSON消息体失败", e);e.printStackTrace();}} else {Log.e(TAG, "无法找到消息体");}} else if (message.startsWith("ERROR")) {Log.e(TAG, "收到STOMP ERROR消息: " + message);int headerEnd = message.indexOf("\n\n");if (headerEnd != -1) {String errorBody = message.substring(headerEnd + 2).replace("\0", "");Log.e(TAG, "STOMP错误消息体: " + errorBody);showToast("WebSocket错误: " + errorBody);}} else {Log.d(TAG, "收到其他类型的消息: " + message);}} catch (Exception e) {Log.e(TAG, "处理接收消息失败", e);e.printStackTrace();}}/*** 发送聊天消息* @param content 消息内容* @param receiver 接收者ID* @param conversationId 会话ID* @return 发送的消息对象*/public MessageDTO sendChatMessage(String content, String receiver, String conversationId) {if (client == null || !client.isOpen()) {Log.e(TAG, "WebSocket未连接,无法发送消息");showToast("WebSocket未连接,无法发送消息");return null;}if (token == null || token.isEmpty()) {Log.e(TAG, "用户未登录,无法发送消息");showToast("用户未登录,无法发送消息");return null;}try {// 获取当前用户IDString userId = prefsManager.getUserId();if (userId == null || userId.isEmpty()) {Log.e(TAG, "用户ID为空,无法发送消息");showToast("用户ID为空,无法发送消息");return null;}// 获取当前时间String currentTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(new Date());// 创建消息对象 - 消息ID由后端生成MessageChatBO messageChatBO = MessageChatBO.builder().content(content).type(MessageTypeEnum.TEXT) // 只支持TEXT类型.sender(userId).receiver(receiver).sendTime(currentTime).status(MessageStatus.SENDING).build();// 创建会话参与者列表List<String> participants = new ArrayList<>();participants.add(userId);participants.add(receiver);// 创建会话对象 - 使用当前会话ID,不设置lastMessageIdMessageConversationBO conversationBO = MessageConversationBO.builder().id(conversationId).participantIds(participants).lastUpdateTime(currentTime).unreadCount(0).sessionType(MessageSessionTypeEnum.USER).build();// 创建消息DTOMessageDTO messageDTO = new MessageDTO(messageChatBO, conversationBO);// 构建STOMP格式的消息StringBuilder stompFrame = new StringBuilder();stompFrame.append("SEND\n");stompFrame.append("destination:").append(MESSAGE_ENDPOINT).append(receiver).append("\n");stompFrame.append("content-type:application/json;charset=UTF-8\n");stompFrame.append("satoken:").append(token).append("\n");stompFrame.append("\n"); // 空行分隔头部和正文// 将消息对象转换为JSONString messageJson = gson.toJson(messageDTO);stompFrame.append(messageJson);stompFrame.append("\0"); // STOMP帧结束符client.send(stompFrame.toString());Log.d(TAG, "发送消息到: " + MESSAGE_ENDPOINT + receiver + ", 内容: " + messageJson);return messageDTO;} catch (Exception e) {Log.e(TAG, "发送消息失败", e);showToast("发送消息失败: " + e.getMessage());return null;}}/*** 显示Toast提示*/private void showToast(final String message) {mainHandler.post(() -> Toast.makeText(context, message, Toast.LENGTH_SHORT).show());}/*** 释放资源*/public void release() {disconnect();disposables.clear();instance = null;}/*** 连接状态枚举*/public enum ConnectionState {DISCONNECTED, // 未连接CONNECTING, // 连接中CONNECTED, // 已连接ERROR // 错误}
}
相关文章:
Android实现Socket通信
问题: 我在Android端对接后端Socket服务实现消息模块的时候,使用WebSocketClient 库,手动构造了订阅和发送消息的 STOMP 帧,,Socket连接成功建立,但是当用户发送消息的时候,对方无法接受到。 …...
✅ Vue 3 响应式写法小抄表(Composition API 实战模板)
📦 引入核心响应式工具 import { ref, reactive, computed, watch, toRefs } from vue1️⃣ 基本类型(string / number / boolean) → 推荐使用 ref() const username ref() const count ref(0) const isLoading ref(false)2️⃣ 对象/表…...
Python数据分析之数据可视化
Python 数据分析重点知识点 本系列不同其他的知识点讲解,力求通过例子让新同学学习用法,帮助老同学快速回忆知识点 可视化系列: Python基础数据分析工具数据处理与分析数据可视化机器学习基础 四、数据可视化 图表类型与选择 根据数据特…...
TCP协议支持全双工原因TCP发送接收数据是生产者消费者模型
一、TCP支持全双工的原因 TCP协议支持全双工,即使用TCP协议进行通信时,服务端和客户端可以同时进行数据的发送和接收,互不干扰,实现同时双向传输数据。 这是因为使用TCP协议通信时,读写套接字的文件描述符既用来发送…...
【ODHead】BEVDet的 CenterHead的推理和拓展到蒸馏损失的算法细节
文章目录 背景常识1、BEVDet的CenterHead整体方案2、蒸馏部分3、输出 preds_dicts 部分3.1、headmap3.2、bbox3.3、Mask掩膜3.4、损失 背景常识 在BEVDet和BEVFormer里,使用了不同的3D detection head(BEVDet用了centerhead,BEVFormer用了de…...
在 CentOS 7 上安装 PHP 7.3
在 CentOS 7 上安装 PHP 7.3 可以按照以下步骤进行操作: 1. 安装必要的依赖和 EPEL 仓库 EPEL(Extra Packages for Enterprise Linux)是为企业级 Linux 提供额外软件包的仓库,yum-utils 用于管理 yum 仓库。 sudo yum install -…...
vue+dhtmlx-gantt 实现甘特图-快速入门【甘特图】
文章目录 一、前言二、使用说明2.1 引入依赖2.2 引入组件2.3 引入dhtmlx-gantt2.4 甘特图数据配置2.5 初始化配置 三、代码示例3.1 Vue2完整示例3.2 Vue3 完整示例 四、效果图 一、前言 dhtmlxGantt 是一款功能强大的甘特图组件,支持 Vue 3 集成。它提供了丰富的功…...
关于ModbusTCP/RTU协议对接Ethernet/IP(CIP)协议的方案
IGT-DSER智能网关模块支持西门子、倍福(BECKHOFF)、罗克韦尔AB,以及三菱、欧姆龙等各种品牌的PLC之间通讯,支持Ethernet/IP(CIP)、Profinet(S7),以及FINS、MC等工业自动化常用协议,同时也支持PLC与Modbus协议的工业机器人、智能仪…...
python-leetcode 49.二叉树中的最大路径和
题目: 二叉树中的路径被定义为一条节点序列,序列中每对相邻节点之间都存在一条边,同一个节点在一条路径序列中至多出现一次,该路径至少包含一个节点,且不一定经过根节点。 路径和是路径中各节点值得总和,…...
C语言基础知识04
指针 指针概念 指针保存地址,地址是字节的编号 指针类型和保存的地址类型要一直 使用时注意,把地址转换为&变量的格式来看 int a[3]; a转为&a[0] 指针的大小 64bit 固定8字节, 32bit 固定4字节 指针…...
使用 Golang 操作 MySQL
在Go语言中,操作SQL数据库,通常会用到一些第三方库来简化数据库的连接、查询和操作过程。其中原生的 database/sql go-sql-driver/mysql 库更符合sql语句使用习惯。 安装 go get github.com/go-sql-driver/mysql 直接上代码来演示基本的创建ÿ…...
前端面试:cookie 可以实现不同域共享吗?
在前端开发中,Cookie 不能直接实现不同域之间的共享。Cookie 的作用域受到域的限制,浏览器不会允许一个域下的 Cookie 被另一个域访问。这是为了保护用户隐私及安全,防止跨站请求伪造(CSRF)等安全问题。 Cookie 的基本…...
MyBatis-Plus接入和简单使用
如何接入 https://baomidou.com/getting-started/ 简单使用方法 使用 MyBatis-Plus 时,大多数场景下不需要编写 XML 和 SQL,因为它提供了强大的通用 CRUD 操作和条件构造器。但以下情况可能需要手动编写 SQL: 1. 不需要写 XML/SQL 的场景 …...
【Go万字洗髓经】Golang内存模型与内存分配管理
本文目录 1. 操作系统中的虚拟内存分页与进程管理虚拟内存与内存隔离 2. Golang中的内存模型内存分配流程内存单元mspan线程缓存mcache中心缓存mcentral全局堆缓存mheapheapArena空闲页索引pageAlloc 3. Go对象分配mallocgc函数tiny对象分配内存 4.结合GMP模型来看内存模型tiny…...
mov格式视频如何转换mp4?
mov格式视频如何转换mp4?在日常的视频处理中,经常需要将MOV格式的视频转换为MP4格式,以兼容更多的播放设备和平台。下面给大家分享如何将MOV视频转换为MP4,4款视频格式转换工具分享。 一、牛学长转码大师 牛学长转码大师是一款功…...
鸿蒙OS开发ForEach循环渲染
摘要 在ForEach循环渲染过程中,如果修改列表项中的数据,但是UI页面不会刷新。在最近开发公司app时遇到了这个问题,经过查看官方文档找到了解决方式 官方地址:数据变化不刷新 一、具体解决方案 思路:通过父子组件传…...
【算法】DFS、BFS、拓扑排序
⭐️个人主页:小羊 ⭐️所属专栏:算法 很荣幸您能阅读我的文章,诚请评论指点,欢迎欢迎 ~ 目录 持续更新中...1、DFS2、BFSN 叉树的层序遍历二叉树的锯齿形层序遍历二叉树最大宽度 3、多源BFS腐烂的苹果 4、拓扑排序 持续更新中…...
【Godot4.0】贝塞尔曲线在游戏中的实际应用
概述 之前研究贝塞尔曲线绘制,完全是以绘图函数,以及实现节点连接为思考。并没有实际考虑贝塞尔曲线在游戏中的应用。今日偶然看到悦千简一年多前发的一个用贝塞尔曲线实现追踪弹或箭矢效果,还有玩物不丧志的老李杀戮尖塔系列中的卡牌动态箭…...
MongoDB 数据导出与导入实战指南(附完整命令)
1. 场景说明 在 MongoDB 运维中,数据备份与恢复是核心操作。本文使用 mongodump 和 mongorestore 工具,演示如何通过命令行导出和导入数据,解决副本集连接、路径指定等关键问题。 2. 数据导出(mongodump) 2.1 导出命…...
『Rust』Rust运行环境搭建
文章目录 rust编译工具rustupVisual Studio VS Code测试编译手动编译VSCode编译配置 参考完 rust编译工具rustup https://www.rust-lang.org/zh-CN/tools/install 换源 RUSTUP_DIST_SERVER https://rsproxy.cn RUSTUP_UPDATE_ROOT https://rsproxy.cn修改rustup和cargo的安…...
CPU+GPU结合的主板设计思路与应用探讨
在高性能计算和图形处理需求不断增长的背景下,CPUGPU结合的主板设计逐渐成为硬件架构的重要趋势。本文将探讨基于CPUGPU架构的主板设计思路、关键技术考量以及应用前景。 1. 设计思路概述 CPU(中央处理器)擅长处理复杂的逻辑运算和多任务控制…...
latex问题汇总
latex问题汇总 环境问题1 环境 texlive2024 TeXstudio 4.8.6 (git 4.8.6) 问题1 编译过程有如下错 ! Misplaced alignment tab character &. l.173 International Conference on Infrared &Millimeter Waves, 2004: 667--... I cant figure out why you would wa…...
学习springboot-Bean管理(Bean 注册,Bean 扫描)
Bean 扫描 可以浏览下面的博客链接 :spring 学习 (注解)-CSDN博客 在学习spring 注解时,我们使用 Component ,Service,Controller等 这样的注解,将目标类信息,传递给IOC容器,为其创…...
iOS开发,SQLite.swift, Missing argument label ‘value:‘ in call问题
Xcode16中,集成使用SQLite.swift,创建表的时候: let id Expression<Int64>("id"),报错Missing argument label value: in call 直接使用SQLite.Expression<Int64>("id") 或者定义一个全局typ…...
【GIT】重新初始化远程仓库
有的时候我们克隆远端仓库会出错: git clone --depth 1 git116.*.*.*:/srv/customs.git D:\dev\projects\kdy\customs11\customs Cloning into D:\dev\projects\kdy\customs11\customs... remote: Enumerating objects: 1494, done. remote: Counting objects: 100…...
Vue3中 ref 与 reactive区别
ref 用途: ref 通常用于创建一个响应式的基本类型数据(如 string、number、boolean 等),但它也可以用于对象或数组 返回值: ref 返回一个带有 .value 属性的对象,访问或修改数据需要通过 .value 进行 使用场景: …...
apollo3录音到wav播放解决方法
SDK DEMO项目:ap3bp_evb_vos_pcm_recorder_20210901 pcm_recorder.c //***************************************************************************** // // Options // //***************************************************************************** #define PRINT…...
信号处理抽取多项滤波的数学推导与仿真
昨天的《信号处理之插值、抽取与多项滤波》,已经介绍了插值抽取的多项滤率,今天详细介绍多项滤波的数学推导,并附上实战仿真代码。 一、数学变换推导 1. 多相分解的核心思想 将FIR滤波器的系数 h ( n ) h(n) h(n)按相位分组,每…...
Java网络多线程
网络相关概念: 关于访问: IP端口 因为一个主机上可能有多个服务, 一个服务监听一个端口,当你访问的时候主机通过端口号就能知道要和哪个端口发生通讯.因此一个主机上不能有两个及以上的服务监听同一个端口. 协议简单来说就是数据的组织形式 好像是两个人交流一样,要保证自己说…...
linux centos 忘记root密码拯救
在CentOS 7中,如果忘记root密码,可以通过修改系统启动参数进入单用户模式或紧急模式进行重置。以下是两种常用方法,适用于物理机或虚拟机环境: 方法一:通过rd.break参数重置密码 步骤: 重启系统并进入GRU…...
C# 事件使用详解
总目录 前言 在C#中,事件(Events)是一种基于委托的重要机制,用于实现对象之间的松耦合通信。它通过发布-订阅模式(Publisher-Subscriber Pattern),允许一个对象(发布者)…...
flink cdc同步mysql数据
一、api 添加依赖 <dependency><groupId>org.apache.flink</groupId><artifactId>flink-connector-mysql-cdc</artifactId><!-- 请使用已发布的版本依赖,snapshot 版本的依赖需要本地自行编译。 --><version>3.3-SNAP…...
tomcat负载均衡配置
这里拿Nginx和之前做的Tomcat 多实例来实现tomcat负载均衡 1.准备多实例与nginx tomcat单机多实例部署-CSDN博客 2.配置nginx做负载均衡 upstream tomcat{ server 192.168.60.11:8081; server 192.168.60.11:8082; server 192.168.60.11:8083; } ser…...
Ceph(1):分布式存储技术简介
1 分布式存储技术简介 1.1 分布式存储系统的特性 (1)可扩展 分布式存储系统可以扩展到几百台甚至几千台的集群规模,而且随着集群规模的增长,系统整体性能表现为线性增长。分布式存储的水平扩展有以下几个特性: 节点…...
16、JavaEE核心技术-EL与 JSTL
EL与 JSTL 实践 一. EL(Expression Language) EL(表达式语言)是 JSP 2.0 中引入的一种简单的脚本语言,用于在 JSP 页面中简化数据的访问和显示。它通过一种类似于 JavaScript 的语法,允许开发者在 JSP 页面…...
RabbitMQ报错:Shutdown Signal channel error; protocol method
报错信息: Shutdown Signal: channel error; protocol method: #method<channel.close>(reply-code406, reply-textPRECONDITION_FAILED - unknown delivery tag 1, class-id60, method-id80) 原因 默认情况下 RabbitMQ 是自动ACK(确认签收&…...
使用DeepSeek完成一个简单嵌入式开发
开启DeepSeek对话 请帮我使用Altium Designer设计原理图、PCB,使用keil完成代码编写;要求:使用stm32F103RCT6为主控芯片,控制3个流水灯的原理图 这里需要注意,每次DeepSeek的回答都不太一样。 DeepSeek回答 以下是使…...
NLP技术介绍
NLP技术介绍 语言分析技术分词词性标注命令实体识别句法分析语义分析文本处理技术文本分类文本聚类情感分析文本生成机器翻译对话系统与交互技术聊天机器人问答系统语音识别与合成知识图谱与语义理解技术知识图谱语义搜索语义推理深度学习与预训练模型循环神经网络(RNN)及其变…...
pycharm + anaconda + yolo11(ultralytics) 的视频流实时检测,保存推流简单实现
目录 背景pycharm安装配置代码实现创建本地视频配置 和 推流配置视频帧的处理和检测框绘制主要流程遇到的一些问题 背景 首先这个基于完整安装配置了anaconda和yolo11的环境,如果需要配置开始的话,先看下专栏里另一个文章。 这次的目的是实现拉取视频流…...
C++编译问题——1模板函数的实现必须在头文件中
今天编译数据结构时,遇见一个编译错误 假设你有一个头文件 SeqList.h 和一个源文件 SeqList.cpp。 SeqList.h #ifndef SEQLIST_H #define SEQLIST_H#include <stdexcept> #include <iostream>template<typename T> class SeqList { private:sta…...
深度学习PyTorch之数据加载DataLoader
深度学习pytorch之简单方法自定义9类卷积即插即用 文章目录 数据加载基础架构1、Dataset类详解2、DataLoader核心参数解析3、数据增强 数据加载基础架构 核心类关系图 torch.utils.data ├── Dataset (抽象基类) ├── DataLoader (数据加载器) ├── Sampler (采样策略)…...
使用Beanshell前置处理器对Jmeter的请求body进行加密
这里我们用HmacSHA256来进行加密举例: 步骤: 1.先获取请求参数并对请求参数进行处理(处理成String类型) //处理请求参数的两种方法: //方法一: //获取请求 Arguments args sampler.getArguments(); //转…...
前端面试:如何减少项目里面 if-else?
在前端开发中,大量使用 if-else 结构可能导致代码调试困难、可读性降低和冗长的逻辑。不妨考虑以下多种策略来减少项目中的 if-else 语句,提高代码的可维护性和可读性: 1. 使用对象字面量替代 用对象字面量来替代 if-else 语句,…...
05.基于 TCP 的远程计算器:从协议设计到高并发实现
📖 目录 📌 前言🔍 需求分析 🤔 我们需要解决哪些问题? 🎯 方案设计 💡 服务器架构 🚀 什么是协议?为什么要设计协议? 📌 结构化数据的传输问题 …...
Matlab:矩阵运算篇——矩阵数学运算
目录 1.矩阵的加法运算 实例——验证加法法则 实例——矩阵求和 实例——矩阵求差 2.矩阵的乘法运算 1.数乘运算 2.乘运算 3.点乘运算 实例——矩阵乘法运算 3.矩阵的除法运算 1.左除运算 实例——验证矩阵的除法 2.右除运算 实例——矩阵的除法 ヾ( ̄…...
git reset的使用,以及解决还原后如何找回
文章目录 git reset 详解命令作用常用参数1. --soft2. --mixed(默认参数,可省略)3. --hard4. 提交引用 总结 git reset --hard HEAD^ 还原代码如何找回?利用 git reflog找回 git reset 详解 git reset 是 Git 中一个功能强大且较…...
react中字段响应式
class中用法: import React, { Component } from react export default class Index extends Component<any, any> { constructor(props) { super(props) this.state { settingInfo: {}, } } async componentDidMount() { let settingInfo awa…...
vue中,watch里,this为undefined的两种解决办法
提示:vue中,watch里,this为undefined的两种解决办法 文章目录 [TOC](文章目录) 前言一、问题二、方法1——使用function函数代替箭头函数()>{}三、方法2——使用that总结 前言 尽量使用方法1——使用function函数代替箭头函数()…...
智能客服意图识别:结合知识库数据构建训练语料的专业流程
智能客服意图识别:结合知识库数据构建训练语料的专业流程 构建基于知识库的智能客服意图识别模型,需要综合运用 NLP(自然语言处理)、知识图谱、机器学习 等技术,确保意图识别的准确性和覆盖度。以下是专业的流程&…...
Spring Boot集成Spring Statemachine
Spring Statemachine 是 Spring 框架下的一个模块,用于简化状态机的创建和管理,它允许开发者使用 Spring 的特性(如依赖注入、AOP 等)来构建复杂的状态机应用。以下是关于 Spring Statemachine 的详细介绍: 主要特性 …...