当前位置: 首页 > news >正文

2025-04-04 Unity 网络基础5——TCP分包与黏包

文章目录

  • 1 分包与黏包
  • 2 解决方案
    • 2.1 数据接口
    • 2.2 定义消息
    • 2.3 NetManager
    • 2.4 分包、黏包处理
  • 3 测试
    • 3.1 服务端
    • 3.2 客户端
    • 3.3 直接发送
    • 3.4 黏包发送
    • 3.5 分包发送
    • 3.6 分包、黏包发送
    • 3.7 其他

1 分包与黏包

​ 分包、黏包指在网络通信中由于各种因素(网络环境、API 规则等)造成的消息与消息之间出现的两种状态。

  • 分包:一个消息分成了多个消息进行发送。
  • 黏包:一个消息和另一个消息黏在了一起。
分包黏包示意图

注意:

​ 分包和黏包可能同时发生。

2 解决方案

​ 为消息添加头部,记录消息的长度。

​ 当接收到消息时,通过消息长度来判断是否分包、黏包,从而对消息进行拆分、合并处理。

​ 每次只处理完整的消息。

2.1 数据接口

  1. 消息接口。
public interface INetMessage
{int MessageId { get; }int BytesLength { get; }byte[] ToBytes();int FromBytes(byte[] bytes, int index);
}
  1. 接口扩展类,用于封装基本类型的序列化。
public static class ByteLengthExtension
{public static int GetBytesLength(this INetMessage message, int value){return sizeof(int);}public static int GetBytesLength(this INetMessage message, string value){return sizeof(int) + Encoding.UTF8.GetByteCount(value);}public static int GetBytesLength(this INetMessage message, bool value){return sizeof(bool);}public static int GetBytesLength(this INetMessage message, float value){return sizeof(float);}
}public static class INetMessageExtension
{public static int Write(this INetMessage message, byte[] bytes, int index, int value){BitConverter.GetBytes(value).CopyTo(bytes, index);return index + sizeof(int);}public static int Read(this INetMessage message, byte[] bytes, int index, ref int value){value = BitConverter.ToInt32(bytes, index);return index + sizeof(int);}public static int Write(this INetMessage message, byte[] bytes, int index, string value){var strBytes = Encoding.UTF8.GetBytes(value);BitConverter.GetBytes(strBytes.Length).CopyTo(bytes, index);index += sizeof(int);strBytes.CopyTo(bytes, index);return index + strBytes.Length;}public static int Read(this INetMessage message, byte[] bytes, int index, ref string value){int length = BitConverter.ToInt32(bytes, index);index += sizeof(int);value = Encoding.UTF8.GetString(bytes, index, length);return index + length;}public static int Write(this INetMessage message, byte[] bytes, int index, bool value){BitConverter.GetBytes(value).CopyTo(bytes, index);return index + sizeof(bool);}public static int Read(this INetMessage message, byte[] bytes, int index, ref bool value){value = BitConverter.ToBoolean(bytes, index);return index + sizeof(bool);}public static int Write(this INetMessage message, byte[] bytes, int index, float value){BitConverter.GetBytes(value).CopyTo(bytes, index);return index + sizeof(float);}public static int Read(this INetMessage message, byte[] bytes, int index, ref float value){value = BitConverter.ToSingle(bytes, index);return index + sizeof(float);}public static int Write(this INetMessage message, byte[] bytes, int index, INetMessage value){value.ToBytes().CopyTo(bytes, index);return index + value.BytesLength;}public static int Read(this INetMessage message, byte[] bytes, int index, ref INetMessage value){value.FromBytes(bytes, index);return index + value.BytesLength;}
}

2.2 定义消息

​ 在 ToBytes() 方法中,先写入消息 Id,然后写入该消息的长度,最后写入消息内容。

public class PlayerMessage : INetMessage
{public int    PlayerId;public string Name;public int    Atk;public int    Lev;public int MessageId { get => 1001; }public int BytesLength{get => this.GetBytesLength(MessageId) + sizeof(int) + // 消息长度this.GetBytesLength(PlayerId) +this.GetBytesLength(Name) +this.GetBytesLength(Atk) +this.GetBytesLength(Lev);}public byte[] ToBytes(){var length = BytesLength;var bytes  = new byte[length];var index  = 0;index = this.Write(bytes, index, MessageId);// 写入消息长度index = this.Write(bytes, index, length - sizeof(int) * 2); // 减去消息长度和消息 Id 的长度index = this.Write(bytes, index, PlayerId);index = this.Write(bytes, index, Name);index = this.Write(bytes, index, Atk);index = this.Write(bytes, index, Lev);return bytes;}public int FromBytes(byte[] bytes, int index){// 反序列化不需要解析 Id,在此之前应解析 Id 从而使用该方法index = this.Read(bytes, index, ref PlayerId);index = this.Read(bytes, index, ref Name);index = this.Read(bytes, index, ref Atk);index = this.Read(bytes, index, ref Lev);return index;}public override string ToString(){return $"PlayerMessage: {PlayerId}, {Name}, {Atk}, {Lev}";}
}

2.3 NetManager

​ 使用单例管理器 NetManager 对消息的发送与接受进行管理。

  • _socket:客户端的 Socket。
  • _sendMessages:发送消息的公共队列,主线程塞消息,发送线程拿消息进行发送。
  • _receiveMessages:接收消息的公共队列,主线程拿消息,接收线程获取消息塞进去。
  • _isConnected:是否与服务器连接。
  • _cacheBytes:消息缓冲区。
  • _cacheBytesLength:当前缓冲长度。
public class NetManager : MonoBehaviour
{public static NetManager Instance { get; private set; } // 单例模式private Socket _socket;private Queue<INetMessage> _sendMessages = new Queue<INetMessage>();private Queue<INetMessage> _receiveMessages = new Queue<INetMessage>();private bool _isConnected = false;private byte[] _cacheBytes = new byte[1024 * 1024]; // 大小为 1MBprivate int _cacheBytesLength;private void Awake() {Instance = this;}private void OnDestroy(){Close();Instance = null;}...
}
  1. 连接与断开
    • Connect():连接指定 ip 与 port 的服务器。
    • Close():断开连接。
public class NetManager : MonoBehaviour
{...public void Connect(string ip, int port){_socket ??= new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);try{_socket.Connect(ip, port);_isConnected = true;// 开启两个线程,一个负责发送消息,一个负责接收消息ThreadPool.QueueUserWorkItem(SendMessageThread);ThreadPool.QueueUserWorkItem(ReceiveMessageThread);}catch (SocketException e){if (e.ErrorCode == 10061){Debug.LogError("服务器拒绝连接");}else{Debug.LogError("连接失败");}}}public void Close(){_isConnected = false;_socket?.Shutdown(SocketShutdown.Both);_socket?.Close();}/// <summary>/// 发送线程的任务/// </summary>private void SendMessageThread(object state){while (_isConnected){if (_sendMessages.Count > 0) // 有消息就发送{var message = _sendMessages.Dequeue();_socket.Send(message.ToBytes());}}}private void ReceiveMessageThread(object state){while (_isConnected){if (_socket.Available > 0){var receiveBytes = new byte[1024];var length       = _socket.Receive(receiveBytes);HandleReceiveMessage(receiveBytes, length); // 核心方法,依据消息长度进行处理}}}...
}
  1. 发送消息
    • Send():发送指定消息。
    • SendTest():发送字节数组(测试分包、黏包使用)。
public class NetManager : MonoBehaviour
{...public void Send(INetMessage message){_sendMessages.Enqueue(message); // 塞入消息队列}public void SendTest(byte[] bytes){_socket.Send(bytes);}...
}
  1. 接收消息

    Update() 方法中不断判断接收消息队列,有消息则打印出来。

public class NetManager : MonoBehaviour
{...private void Update(){if (_receiveMessages.Count > 0){var message = _receiveMessages.Dequeue();Debug.Log(message); // 主线程处理消息}}...
}

2.4 分包、黏包处理

  1. 收到消息时看之前有没有缓存,如果有,直接拼接到后面。
  2. 循环处理消息体
    • 处理前置信息(Id、解析长度)。
    • if 缓冲区 + 该条消息体 >= 一条完整信息时
      • 判断消息 Id。
      • 塞入消息队列。
      • 更新 index_cacheBytesLength
    • else 还没有接收到一条完整消息
      • 解析了前置信息,但是没有成功解析消息体,则回退到解析 Id 的位置。
      • 缓存剩余数据。
public class NetManager : MonoBehaviour
{...private void HandleReceiveMessage(byte[] receiveBytes, int receiveNum){var messageId = 0;var index     = 0; // receiveBytes 的处理进度(下标)// 收到消息时看之前有没有缓存// 如果有,直接拼接到后面receiveBytes.CopyTo(_cacheBytes, _cacheBytesLength);_cacheBytesLength += receiveNum;while (true){var messageLength = -1; // 当前消息长度// 处理前置信息if (_cacheBytesLength - index >= 8){// 解析 IdmessageId =  BitConverter.ToInt32(_cacheBytes, index);index     += sizeof(int);// 解析长度messageLength =  BitConverter.ToInt32(_cacheBytes, index);index         += sizeof(int);}// 处理消息体if (messageLength != -1 && _cacheBytesLength - index >= messageLength){// 解析消息体INetMessage message = default;switch (messageId){case 1001:message = new PlayerMessage();message.FromBytes(_cacheBytes, index);break;}if (message != default){_receiveMessages.Enqueue(message); // 塞入消息队列}index += messageLength;// 如果消息体长度等于缓存长度,证明缓存已经处理完毕if (index == _cacheBytesLength){_cacheBytesLength = 0;break;}}else // 消息体还没有接收完毕{// 解析了前置信息,但是没有成功解析消息体if (messageLength != -1){index -= 8; // 回退到解析 Id 的位置}// 缓存剩余的数据_cacheBytesLength -= index;Array.Copy(_cacheBytes, index, _cacheBytes, 0, _cacheBytesLength);break;}}}
}

3 测试

​ 服务端、客户端配置见 2025-03-25 Unity 网络基础4——TCP同步通信_unity tcp-CSDN博客。

3.1 服务端

​ 封装 ServerSocket 和 ClientSocket 方便通信。

  1. Program.cs
    • 创建 ServerSocket,使用本地 ip 127.0.0.1 作为服务器地址。
    • 循环监听指令。
      • “exit”:退出。
      • “B:1001”:向所有客户端发送消息。
// See https://aka.ms/new-console-template for more informationusing NetLearningTcpServerExercise2;var serverSocket = new ServerSocket();
serverSocket.Start("127.0.0.1", 8080, 1024);
Console.WriteLine("Server Start!");while (true)
{var input = Console.ReadLine();if (input == "exit"){serverSocket.Close();break;}else if (input?[..2] == "B:") // 输入命令向所有客户端广播消息{if (input[2..] == "1001"){var playerMsg = new PlayerMessage(){PlayerId = 1,Name     = "张三",Atk      = 100,Lev      = 10};serverSocket.Broadcast(playerMsg);}}
}
  1. ServerSocket.cs
// ------------------------------------------------------------
// @file       ServerSocket.cs
// @brief
// @author     zheliku
// @Modified   2025-03-24 02:03:06
// @Copyright  Copyright (c) 2025, zheliku
// ------------------------------------------------------------namespace NetLearningTcpServerExercise2;using System.Net;
using System.Net.Sockets;public class ServerSocket
{private readonly Socket _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);private Dictionary<int, ClientSocket> _clientSockets = new Dictionary<int, ClientSocket>();private bool _Running;public void Start(string ip, int port, int maxClientCount){_socket.Bind(new IPEndPoint(IPAddress.Parse(ip), port));_socket.Listen(maxClientCount);_Running = true;ThreadPool.QueueUserWorkItem(AcceptClient);ThreadPool.QueueUserWorkItem(ReceiveMessage);}public void Close(){_Running = false;foreach (var clientSocket in _clientSockets.Values){clientSocket.Close();}_clientSockets.Clear();try{_socket.Shutdown(SocketShutdown.Both);}catch { }finally{_socket.Close();}}public void Broadcast(INetMessage message){foreach (var clientSocket in _clientSockets.Values){clientSocket.SendMessage(message);}}private void AcceptClient(object? state){while (_Running){try{// 连入客户端var clientSocket = new ClientSocket(_socket.Accept());// clientSocket.SendMessage("Welcome to the server!");_clientSockets.Add(clientSocket.Id, clientSocket);}catch (Exception e){Console.WriteLine("ClientSocket Accept Wrong: " + e);}}}private void ReceiveMessage(object? state){while (_Running){if (_clientSockets.Count > 0){foreach (var clientSocket in _clientSockets.Values){clientSocket.ReceiveMessage();}}}}
}
  1. ClientSocket.cs
// ------------------------------------------------------------
// @file       ClientSocket.cs
// @brief
// @author     zheliku
// @Modified   2025-03-24 01:03:42
// @Copyright  Copyright (c) 2025, zheliku
// ------------------------------------------------------------namespace NetLearningTcpServerExercise2;using System.Net.Sockets;
using System.Text;public class ClientSocket
{private static int _ClientBeginId = 1;private readonly Socket _socket;private byte[] _cacheBytes = new byte[1024 * 1024]; // 缓冲区,大小为 1MBprivate int    _cacheBytesLength;public int Id;public bool Connected{get => _socket.Connected;}public ClientSocket(Socket socket){Id      = _ClientBeginId++;_socket = socket;}public void Close(){try{_socket.Shutdown(SocketShutdown.Both);}catch { }finally{_socket.Close();}}public void SendMessage(INetMessage message){try{_socket.Send(message.ToBytes());}catch (Exception e){Console.WriteLine("SendMessage Wrong: " + e);Close();}}public void ReceiveMessage(){try{if (_socket.Available > 0){var buffer        = new byte[1024 * 5];var receiveLength = _socket.Receive(buffer);HandleReceiveMessage(buffer, receiveLength);}}catch (Exception e){Console.WriteLine("ReceiveMessage Wrong: " + e);Close();}}private void MessageHandle(object? state){if (state == null) return;Console.WriteLine($"Receive message from client {_socket} (ID {Id}): {state}");}private void HandleReceiveMessage(byte[] receiveBytes, int receiveNum){var messageId = 0;var index     = 0;// 收到消息时看之前有没有缓存// 如果有,直接拼接到后面receiveBytes.CopyTo(_cacheBytes, _cacheBytesLength);_cacheBytesLength += receiveNum;while (true){var messageLength = -1;// 处理前置信息if (_cacheBytesLength - index >= 8){// 解析 IdmessageId =  BitConverter.ToInt32(_cacheBytes, index);index     += sizeof(int);// 解析长度messageLength =  BitConverter.ToInt32(_cacheBytes, index);index         += sizeof(int);}// 处理消息体if (messageLength != -1 && _cacheBytesLength - index >= messageLength){// 解析消息体INetMessage message = default;switch (messageId){case 1001:message = new PlayerMessage();message.FromBytes(_cacheBytes, index);break;}if (message != default){ThreadPool.QueueUserWorkItem(MessageHandle, message);}index += messageLength;// 如果消息体长度等于缓存长度,证明缓存已经处理完毕if (index == _cacheBytesLength){_cacheBytesLength = 0;break;}}else // 消息体还没有接收完毕{// 解析了前置信息,但是没有成功解析消息体if (messageLength != -1){index -= 8; // 回退到解析 Id 的位置}// 缓存剩余的数据_cacheBytesLength -= index;Array.Copy(_cacheBytes, index, _cacheBytes, 0, _cacheBytesLength);break;}}}
}

3.2 客户端

​ 在 Unity 中创建场景,4 个按钮分别发送不同消息。

image-20250404190900044

​ 编写 Lesson7.cs 测试脚本,挂载到 Lesson7 场景物体上。

​ “发送”、“黏包”、“分包”、“分包、黏包”按钮分别挂载到 Lesson7 的 BtnSendBtnSend1BtnSend2BtnSend3 成员上。

// ------------------------------------------------------------
// @file       Lesson7.cs
// @brief
// @author     zheliku
// @Modified   2025-03-24 02:03:40
// @Copyright  Copyright (c) 2025, zheliku
// ------------------------------------------------------------namespace Lesson
{using System;using System.Threading.Tasks;using UnityEngine;using UnityEngine.UI;public class Lesson7 : MonoBehaviour{public Button BtnSend;public Button BtnSend1;public Button BtnSend2;public Button BtnSend3;private void Start(){NetManager.Instance.Connect("127.0.0.1", 8080);BtnSend.onClick.AddListener(() =>{...});BtnSend1.onClick.AddListener(() =>{...});BtnSend2.onClick.AddListener(async () =>{...});BtnSend3.onClick.AddListener(async () =>{...});}}
}

3.3 直接发送

​ 启动服务端,再启动 Unity。直接发送的逻辑如下:

BtnSend.onClick.AddListener(() =>
{var playerMsg = new PlayerMessage(){PlayerId = 1001,Name     = "发送",Atk      = 100,Lev      = 1};NetManager.Instance.Send(playerMsg);
});

​ 点击 Unity 中的“发送”按钮,服务器端接收到消息。

image-20250404191338724

3.4 黏包发送

image-20250404192804164

​ 黏包发送定义了两条消息,顺序存放在一个长字节数组中一并发送。具体逻辑如下:

BtnSend1.onClick.AddListener(() =>
{var playerMsg1 = new PlayerMessage(){PlayerId = 1002,Name     = "黏包1",Atk      = 100,Lev      = 1};var playerMsg2 = new PlayerMessage(){PlayerId = 1003,Name     = "黏包2",Atk      = 63,Lev      = 5};var bytes = new byte[playerMsg1.BytesLength + playerMsg2.BytesLength];playerMsg1.ToBytes().CopyTo(bytes, 0);playerMsg2.ToBytes().CopyTo(bytes, playerMsg1.BytesLength);NetManager.Instance.SendTest(bytes);
});

​ 点击 Unity 中的“黏包”按钮,服务器端一次性接收到 2 条消息。

image-20250404191754725

3.5 分包发送

image-20250404192814792

​ 分包发送定义了 1 条消息,将前 10 个字节拷贝到一个数组中,剩余字节拷贝到另一个数组中。两个数组间隔 0.5s 发送。具体逻辑如下:

BtnSend2.onClick.AddListener(async () =>
{var playerMsg = new PlayerMessage(){PlayerId = 1004,Name     = "分包",Atk      = 100,Lev      = 1};var bytes  = playerMsg.ToBytes();var bytes1 = new byte[10];var bytes2 = new byte[bytes.Length - 10];Array.Copy(bytes, 0, bytes1, 0, 10);Array.Copy(bytes, 10, bytes2, 0, bytes.Length - 10);NetManager.Instance.SendTest(bytes1);await Task.Delay(500); // 注释改行后会自动黏包,服务端立刻接收到消息NetManager.Instance.SendTest(bytes2);
});

​ 点击 Unity 中的“分包”按钮,服务器端等待 0.5s 后,才接收到消息。

image-20250404192025667

3.6 分包、黏包发送

image-20250404192824995

​ 分包、黏包发送定义了 2 条消息,将消息 1 和消息 2 的前 10 个字节拷贝到一个数组中,消息 2 剩余字节拷贝到另一个数组中。两个数组间隔 0.5s 发送。具体逻辑如下:

BtnSend3.onClick.AddListener(async () =>
{var playerMsg1 = new PlayerMessage(){PlayerId = 1005,Name     = "分包黏包1",Atk      = 9,Lev      = 1};var playerMsg2 = new PlayerMessage(){PlayerId = 1006,Name     = "分包黏包2",Atk      = 63,Lev      = 55};var bytes1 = playerMsg1.ToBytes();var bytes2 = playerMsg2.ToBytes();var bytes2_1 = new byte[10];var bytes2_2 = new byte[bytes2.Length - 10];Array.Copy(bytes2, 0, bytes2_1, 0, 10);Array.Copy(bytes2, 10, bytes2_2, 0, bytes2.Length - 10);var firstBytes = new byte[bytes1.Length + bytes2_1.Length];Array.Copy(bytes1, 0, firstBytes, 0, bytes1.Length);Array.Copy(bytes2_1, 0, firstBytes, bytes1.Length, bytes2_1.Length);var secondBytes = bytes2_2;NetManager.Instance.SendTest(firstBytes);await Task.Delay(500); // 注释改行后会自动黏包,服务端立刻接收到消息NetManager.Instance.SendTest(secondBytes);
});

​ 点击 Unity 中的“分包、黏包”按钮,服务器端立刻接收到消息 1。

image-20250404192344679

​ 等待 0.5s 后,接收到消息 2。

image-20250404192411217

3.7 其他

​ 在服务端输入 “B:1001” 命令后,回到 Unity,发现接收到消息。

image-20250404192625942 image-20250404192650849

相关文章:

2025-04-04 Unity 网络基础5——TCP分包与黏包

文章目录 1 分包与黏包2 解决方案2.1 数据接口2.2 定义消息2.3 NetManager2.4 分包、黏包处理 3 测试3.1 服务端3.2 客户端3.3 直接发送3.4 黏包发送3.5 分包发送3.6 分包、黏包发送3.7 其他 1 分包与黏包 ​ 分包、黏包指在网络通信中由于各种因素&#xff08;网络环境、API …...

Ubuntu 安装 JMeter:为你的服务器配置做好准备

Apache JMeter 是一个开源的负载测试工具&#xff0c;可以用于测试静态和动态资源&#xff0c;确定服务器的性能和稳定性。在本文中&#xff0c;我们将讨论如何下载和安装 JMeter。 安装 Java&#xff08;已安装 Java 的此步骤可跳过&#xff09; 要下载 Java&#xff0c;请遵…...

swift-oc和swift block和代理

一、闭包或block 1.1、swift 闭包表达式作为参数的形式 一、闭包的定义 func exec(v1: Int, v2: Int, fn: (Int, Int) -> Int) { print(fn(v1, v2)) } 二、调用 exec(v1: 10, v2: 20, fn: { (v1: Int, v2: Int) -> Int in return v1 v2 }) 1.2、swift 闭包表达式作为…...

【数据结构】_队列

hello 友友们~ 今天我们要开始学习队列啦~ 话不多说&#xff0c;让我们开始吧&#xff01;GO! 1.队列的概念及结构 队列&#xff1a;只允许在一端进行插入数据操作&#xff0c;在另一端进行删除数据操作的特殊线性表。 队列具有先进先出FIFO(First In First Out) 入队列&#x…...

【软考中级软件设计师】数据表示:原码、反码、补码、移码、浮点数

数据表示 一、数据表示1、整数的表示(1) 原码(2) 反码(3) 补码&#xff1a;(4) 移码 2、浮点数的表示&#xff08;IEEE 754标准&#xff09; 一、数据表示 计算机使用的是二进制&#xff0c;也就是0和1的组合。所有的数据&#xff0c;无论是数字、文字还是图片、声音&#xff…...

Linux(十二)信号

今天我们就要来一起学习信号啦&#xff01;&#xff01;&#xff01;还记得小编在之前的文章中说过的ctrlc吗&#xff1f;之前小编没有详细介绍过&#xff0c;现在我们就要来学习啦&#xff01;&#xff01;&#xff01; 一、信号的基本介绍 首先&#xff0c;小编带领大家先一…...

迪杰斯特拉+二分+优先队列+拓扑+堆优化(奶牛航线Cowroute、架设电话线dd、路障Roadblocks、奶牛交通Traffic)

原文地址 https://fmcraft.top/index.php/Programming/2025040402.html 主要算法 迪杰斯特拉Dijkstra 题目列表 P1&#xff1a;奶牛航线Cowroute 题目描述 题目描述 Bessie已经厌倦了农场冬天的寒冷气候&#xff0c;她决定坐飞机去更温暖的地方去度假。不幸的是&#xf…...

【数据结构】树的介绍

目录 一、树1.1什么是树&#xff1f;1.2 树的概念与结构1.3树的相关术语1.4 树形结构实际运用场景 二、二叉树2.1 概念与结构2.2 特殊的二叉树2.2.1 满二叉树2.2.2 完全二叉树 个人主页&#xff0c;点击这里~ 数据结构专栏&#xff0c;点击这里~ 一、树 1.1什么是树&#xff1…...

【CF】Day24——Codeforces Round 994 (Div. 2) D

D. Shift Esc 题目&#xff1a; 思路&#xff1a; 典DP的变种 如果这一题没有这个变换操作&#xff0c;那么是一个很典型的二维dp&#xff0c;每一个格子我们都选择上面和左边中的最小值即可 而这题由于可以变换&#xff0c;那我们就要考虑变换操作&#xff0c;首先一个显然…...

Python 字典

Python 字典 字典的介绍 字典不仅可以保存值&#xff0c;还能对值进行描述使用大括号来表示一个字典&#xff0c;不仅有值 value &#xff0c;还有值的描述 key字典里的数据都是以键值对 key-value 的形式来保留的key 和 value 之间用冒号 : 来连接多个键值对之间用逗号 , 来…...

yolov12检测 聚类轨迹运动速度

目录 分割算法api版: 分割算法: yolo_kmean.py 优化版: 第1步,检测生成json 第2步骤聚类: 分割算法api版: import json import os from glob import globimport cv2 import imageio import numpy as np from scipy.ndimage import gaussian_filter1d from scipy.s…...

【Lua】pcall使用详解

目录 基本语法核心作用基础示例示例 1&#xff1a;捕获一个简单错误示例 2&#xff1a;调用不存在的函数 高级用法1. 传递多个参数和接收多个返回值2. 捕获带 error 主动抛出的错误3. 匿名函数与 pcall 使用场景注意事项总结 在 Lua 中&#xff0c;pcall&#xff08;Protected …...

Floyd 算法 Java

图论算法实践&#xff1a;使用 Floyd 求任意两点最短路&#xff08;Java 实现&#xff09; 在图论算法中&#xff0c;Floyd-Warshall 算法是一个经典的动态规划算法&#xff0c;用于在一个加权图中寻找所有点对之间的最短路径。 场景描述 假设我们有一个包含 n 个点的无向图&…...

List结构之非实时榜单实战

像京东、淘宝等电商系统一般都会有热销的商品榜单&#xff0c;比如热销手机榜单&#xff0c;热销电脑榜单&#xff0c;这些都是非实时的榜单。为什么是非实时的呢&#xff1f;因为完全实时的计算和排序对于资源消耗较大&#xff0c;尤其是当涉及大量交易数据时。 一般来说&…...

OCR的备份与恢复

1.简介 在Oracle RAC环境中&#xff0c;ASM&#xff08;Automatic Storage Management&#xff09;管理的OCR&#xff08;Oracle Cluster Registry&#xff09;是集群的关键组件&#xff0c;存储集群配置和状态信息。 OCR的备份一般指物理备份&#xff0c;系统默认每4个小时自…...

算法思想之双指针(一)

欢迎拜访&#xff1a;雾里看山-CSDN博客 本篇主题&#xff1a;算法思想之双指针(一) 发布时间&#xff1a;2025.4.4 隶属专栏&#xff1a;算法 目录 双指针算法介绍对撞指针&#xff1a;快慢指针&#xff1a; 例题移动零题目链接题目描述算法思路代码实现 复写零题目链接题目描…...

Pascal语言的设备管理

Pascal语言的设备管理 引言 在计算机科学中&#xff0c;设备管理是操作系统的重要组成部分之一。设备管理指的是操作系统对外部设备的控制和协调&#xff0c;实现对各种设备的有效利用。Pascal语言作为一种教育性编程语言&#xff0c;虽然最初并不是为了直接进行设备管理而设…...

【MySQL】DML:添加-修改-删除数据 (数据操作语言) 学习笔记

DML (数据操作语言) 学习笔记 1. 数据表结构 首先创建员工表 employee&#xff1a; CREATE TABLE employee (id int unsigned NOT NULL AUTO_INCREMENT COMMENT ID,username varchar(20) NOT NULL COMMENT 用户名,password varchar(32) DEFAULT 123456 COMMENT 密码,name va…...

React编程高级主题:背压(Backpressure)处理

文章目录 **5.1 背压&#xff08;Backpressure&#xff09;概述****5.1.1 缓冲&#xff08;Buffer&#xff09;****1. 基本概念****2. 缓冲的实现方式****3. 适用场景****4. 潜在问题** **5.1.2 丢弃&#xff08;Drop&#xff09;****1. 基本概念****2. 丢弃的实现方式****3. 适…...

Spring IoCDI

IoC容器 前⾯我们提到IoC控制反转&#xff0c; 就是将对象的控制权交给Spring的IOC容器 &#xff0c;由IOC容器创建及管理对 象。 也就是bean的存储. 在类上⾯添加 RestController 和 Controller 注解, 就是把这个对象交给Spring管理 , Spring 框架启动时就会加载该类. 把对象…...

COBOL语言的数据库交互

COBOL语言的数据库交互 引言 随着信息技术的不断发展&#xff0c;数据库管理系统&#xff08;DBMS&#xff09;已经成为现代应用程序中不可或缺的组成部分。在众多编程语言中&#xff0c;COBOL&#xff08;Common Business-Oriented Language&#xff09;以其在商业应用中的稳…...

【C++11(中)】—— 我与C++的不解之缘(三十一)

一、可变参数模版 基本语法&#xff1a; C11支持可变参数模版&#xff0c;简单来说就是支持可变数量参数的函数模版或者类模版&#xff1b; 可变数目的参数被称为参数包&#xff0c;存在两种参数包&#xff1a;模版参数包(表示0个或者多个模版参数)&#xff0c;函数参数包(表示…...

JavaScript学习19-事件类型之鼠标事件

1. 2. 3....

文件或目录损坏且无法读取:数据恢复的实战指南

在数字化时代&#xff0c;数据的重要性不言而喻。然而&#xff0c;在日常使用电脑、移动硬盘、U盘等存储设备时&#xff0c;我们难免会遇到“文件或目录损坏且无法读取”的提示。这一提示如同晴天霹雳&#xff0c;让无数用户心急如焚&#xff0c;尤其是当这些文件中存储着重要的…...

python爬虫:小程序逆向实战教程

根据我之前发表的文章&#xff0c;我们进行延伸实战https://blog.csdn.net/weixin_64809364/article/details/146981598?spm1001.2014.3001.5501 1. 想要爬取什么小程序&#xff0c;我们进行搜索 2. 找到我们vx小程序的文件地址&#xff0c;我们就可以进行破解 破解步骤强看…...

第二十节课:python实例五:身体质量指数BMI计算

python实例五&#xff1a;身体质量指数BMI计算 一、问题分析 BMI计算公式&#xff1a; BMI 体重(kg) / 身高(m)^2国际与国内标准对比 分类国际标准国内标准偏瘦<18.5<18.5正常18.5-2518.5-24偏胖25-3024-28肥胖≥30≥28 二、实现要点 输入处理 # 同时接收身高体重…...

八、重学C++—动态多态(运行期)

上一章节&#xff1a; 七、重学C—静态多态&#xff08;编译期&#xff09;-CSDN博客https://blog.csdn.net/weixin_36323170/article/details/146999362?spm1001.2014.3001.5502 本章节代码&#xff1a; cpp/dynamicPolymorphic.cpp CuiQingCheng/cppstudy - 码云 - 开源中…...

思维链 Chain-of-Thought(COT)

思维链 Chain-of-Thought&#xff08;COT&#xff09;&#xff1a;思维链的启蒙 3. 思维链 Chain-of-Thought&#xff08;COT&#xff09;存在问题&#xff1f;2. 思维链 Chain-of-Thought&#xff08;COT&#xff09;是思路是什么&#xff1f;1. 什么是 思维链 Chain-of-Thoug…...

React框架的Fiber架构

以下是关于 Fiber 架构 的系统梳理: 一、Fiber 架构的出现背景 React 15 及之前的问题 同步递归渲染:虚拟DOM的diff过程不可中断,导致主线程长时间阻塞。掉帧问题:复杂组件树渲染时,用户交互无法及时响应。无法实现增量渲染:无法拆分任务优先级,无法利用浏览器空闲时间。…...

PCI与PCIe接口的通信架构是主从模式吗?

PCI&#xff08;Peripheral Component Interconnect&#xff09;总线在通信架构上本质是主从模式&#xff0c;但其具体实现和角色分配在不同版本&#xff08;如传统PCI与PCI Express&#xff09;中存在差异。以下是详细分析&#xff1a; 传统PCI总线的主从模式 (1) 基本架构 主…...

【2011】【论文笔记】THz保护文化遗产——

前言 类型 太赫兹 + 文化保护 太赫兹 + 文化保护 太赫兹+文化保护 期刊 I E E E T R A N S A C T I O N S O N T E R A H E R...

状态机思想编程练习

状态机实现LED流水灯 本次实验&#xff0c;我们将利用状态机的思想来进行Verilog编程实现一个LED流水灯&#xff0c;并通过Modelsim来进行模拟仿真&#xff0c;再到DE2-115开发板上进行验证。 ​ 首先进行主要代码的编写。 module led (input sys_clk,input sys_…...

三部门新政力推智能家居 居然智家数智化转型迎利好东风

2025年3月&#xff0c;工业和信息化部、教育部、市场监管总局联合印发《轻工业数字化转型实施方案》&#xff0c;明确提出重点培育智能家居、智能穿戴、智能骑行、智慧养老等消费端场景&#xff0c;深化人工智能技术在家电、家具等领域的应用&#xff0c;推动产业链供应链智能化…...

CCF GESP C++编程 七级认证真题 2025年3月

C 七级 2025 年 03 月 题号 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 答案 B A B C B B B A D D A C B B D 一、单选题 第 1 题 下列哪个选项是C中的关键字&#xff1f; A. function B. class C. method D. object 第 2 题 下面代码输出的是&#xff08;&#xff09; int main()…...

【MySQL】navicat16 result字段识别不了

在mysql里面使用result字段 打印出来为空 之后换了个字段命名 使用outcome 成功能打印出来了。。 不知道是不是版本的问题...

【教学类-102-02】自制剪纸图案(留白边、沿线剪)02——Python+PS自动化添加虚线边框

背景需求: 01版本实现了对透明背景png图案边界线的扩展,黑线实线描边 【教学类-102-01】自制剪纸图案(留白边、沿线剪)01-CSDN博客文章浏览阅读974次,点赞15次,收藏7次。【教学类-102-01】自制剪纸图案(留白边、沿线剪)01https://blog.csdn.net/reasonsummer/article…...

CExercise_05_1函数_1.1素数(要对键盘录入的数据做参数校验)

题目&#xff1a; 编写函数实现以下功能&#xff1a; 键盘录入一个正整数&#xff0c;请判断它是否是一个素数&#xff0c;然后控制台输出对应的结果。要对键盘录入的数据做参数校验&#xff0c;素数是一个大于1的自然数&#xff0c;它仅能被1和自身整除。 关键点 分析&#xf…...

运算放大器(五)电压比较器

比较器在最常用的简单集成电路中排名第二&#xff0c;仅次于排名第一的运算放大器。 电压比较器是一种用来比较输入信号电压与参考电压大小&#xff0c;并将比较结果以高电平或低电平形式输出的一种信号处理电路&#xff0c;广泛应用于各种非正弦波的产生和变换电路中&#xf…...

蓝桥杯_PCF8591

目录 一 前言 二 引言 三 PCF8591介绍 &#xff08;1&#xff09;I2C通信 &#xff08;2&#xff09;原理图中的8591 四 代码层面 &#xff08;1&#xff09;根据题目所给的示范代码&#xff0c;实现ADC 1 为什么需要返回值&#xff0c;同时返回值是unsigned char&#x…...

Windows修改hosts文件让向日癸软件联网

Windows修改hosts文件让向日癸软件联网 前言一、查看向日葵软件使用的网址及IP1.清除dns记录2.打开向日葵软件并将dns记录导出txt 二、修改Windows服务器的hosts文件1.winx选择Windows PowerShell(管理员)2.在Windows PowerShell中输入如下内容&#xff1a;3.在hosts文件最后添…...

[MySQL初阶]MySQL数据类型

MySQL数据类型 1. 数据类型分类2. 数值类型2.1 tinyint类型2.2 bit类型2.3 float类型2.4 decimal类型3. 字符串类型3.1 char3.2 varchar3.3 日期和时间类型3.4 enum和set1. 数据类型分类 数据库中的类型决定了在存储位置中,占据的空间大小以及如何识别的问题。 2. 数值类型 2…...

JS用ES6和ES5分别实现:8字节长整数和字节数组的互转

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;精通Java编…...

【学Rust写CAD】29 Alpha256结构体(alpha256.rs)

源码 #[derive(Clone, Copy)] pub struct Alpha256(u32);impl Alpha256{#[inline]pub fn from(alpha:u32)->Alpha256{Alpha256(alpha1)}// Calculates 256 - (value * alpha256) / 255 in range [0,256],// for [0,255] value and [0,256] alpha256.#[inline]fn alpha_mul_…...

Titanic - Machine Learning from Disaster

数据集 通过网盘分享的文件&#xff1a; 链接: https://pan.baidu.com/s/17TLeF8PW2GSWTbAIJC69-A?pwd4dak 提取码: 4dak 准备工作 # 导入必要的库 import numpy as np # 用于数值计算&#xff08;如矩阵运算、数学函数等&#xff09; import pandas as pd # 用于数据…...

GoFrame框架中Prometheus Metric组件监控的优势与实践

文章摘要 GoFrame 是一款轻量、高效且模块化的 Go 语言全能型框架&#xff0c;在 Go 生态中以其企业级应用能力和简洁设计受到开发者青睐。随着微服务架构的普及&#xff0c;性能监控成为开发中不可或缺的一环&#xff0c;而 Prometheus 凭借其强大的时间序列数据处理能力和灵…...

SQL语言的物联网

以SQL语言的物联网 引言 物联网&#xff08;IoT&#xff0c;Internet of Things&#xff09;作为一个新兴的技术领域&#xff0c;正迅速改变着我们的生活方式和工作模式。它通过将各种物体连接到互联网&#xff0c;实现了设备之间的智能通信与数据交换。随着物联网的普及&…...

戴尔笔记本 ubuntu 22.04 开机后进入initramfs界面

问题 在 Ubuntu 22.04 启动时进入 initramfs 界面&#xff0c;并提示 Gave up waiting for root device. Common problems: - Boot args (cat /proc/cmdline)- Check rootdelay (did the system wait long enough?) - Missing modules (cat /proc/modules; ls /dev) ALERT! …...

JavaScript BOM、事件循环

目录 BOM&#xff08;浏览器对象模型&#xff09; 一、window 对象 1. 窗口控制 2. 定时器 二、location 对象 三、navigator 对象 四、history 对象 五、screen 对象 六、本地存储 1. localStorage 2. sessionStorage 七、BOM 应用场景 八、总结 JavaScript 执行…...

归并排序算啊模板and三道困难级别的归并力扣算法题--LCR170,493,315

目录 归并排序算法模板&#xff1a; 1.1题目链接&#xff1a;LCR.170.交易逆序对的总数 1.2题目描述&#xff1a; 1.3解法(利用归并排序的过程--分治)&#xff1a; 2.1题目链接&#xff1a;315.计算右侧小于当前元素的个数 2.2题目描述&#xff1a; 2.3解法&#xff1a; …...

鸿蒙 harmonyOS:项目实战 :倒计时器

代码&#xff1a; import { promptAction } from kit.ArkUIEntry Component struct Index {State count: number 10 // 初始倒计时秒数State timerId: number -1 // 定时器ID// 开始倒计时startCountdown() {if (this.timerId -1) {this.timerId setInterval(() > {if …...