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

C++ asio网络编程(8)处理粘包问题

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 前言 粘包问题
  • 一、粘包原因
    • 总结:
  • 二、如何处理粘包
    • 处理方法
  • 三、完善消息节点MsgNode
    • 代码部分
    • 细节详解
      • memcpy(_data, &max_len, HEAD_LENGTH);
      • _data[_total_len] = '\0'
  • 四、完善CSession
  • 五、完善接收逻辑
    • 代码部分
    • 流程图
  • 六、客户端修改
  • 七、测试粘包
  • 总结


前言 粘包问题

提示:这里可以添加本文要记录的大概内容:

今天介绍一下如何处理粘包,粘包问题是服务器收发数据常遇到的一个现象,下面我们介绍一下粘包问题是什么,当客户端发送多个数据包给服务器时,服务器底层的tcp接收缓冲区收到的数据为粘连在一起的,如下图所示

在这里插入图片描述

当客户端发送两个Hello World!给服务器,服务器TCP接收缓冲区接收了两次,一次是Hello World!Hello, 第二次是World!


提示:以下是本篇文章正文内容,下面案例可供参考

一、粘包原因

因为TCP底层通信是面向字节流的,TCP只保证发送数据的准确性和顺序性,字节流以字节为单位,客户端每次发送N个字节给服务端,N取决于当前客户端的发送缓冲区是否有数据,比如发送缓冲区总大小为10个字节,当前有5个字节数据(上次要发送的数据比如’loveu’)未发送完,那么此时只有5个字节空闲空间,我们调用发送接口发送hello world!其实就是只能发送Hello给服务器,那么服务器一次性读取到的数据就很可能是loveuhello。而剩余的world!只能留给下一次发送,下一次服务器接收到的就是world!
如下图
在这里插入图片描述
这是最好理解的粘包问题的产生原因。还有一些其他的原因比如
1   客户端的发送频率远高于服务器的接收频率,就会导致数据在服务器的tcp接收缓冲区滞留形成粘连,比如客户端1s内连续发送了两个hello world!,服务器过了2s才接收数据,那一次性读出两个hello world!。
2   tcp底层的安全和效率机制不允许字节数特别少的小包发送频率过高,tcp会在底层累计数据长度到一定大小才一起发送,比如连续发送1字节的数据要累计到多个字节才发送,可以了解下tcp底层的Nagle算法。
3   再就是我们提到的最简单的情况,发送端缓冲区有上次未发送完的数据或者接收端的缓冲区里有未取出的数据导致数据粘连。

总结:

💡 为什么会出现 TCP 粘包?

  1. TCP 是面向字节流的协议,没有“包”的概念。
  2. 客户端每次 send() 实际上是把数据写入 操作系统内核的发送缓冲区。
  3. 如果缓冲区有部分上次数据还没发完(如 loveu),新的数据(如 hello world)马上又加入。
  4. 接收端就可能收到合并后的数据(如 loveuhello),这就是“粘包”。

💥 导致粘包的具体原因
● 应用层发送太快,TCP 来不及分开发
● 接收方 read 时读取了多个数据一起到缓存
● TCP 对小数据包做了优化(Nagle 算法) tcp底层的安全和效率机制不允许字节数特别少的小包发送频率过高,tcp会在底层累计数据长度到一定大小才一起发送,比如连续发送1字节的数据要累计到多个字节才发送,可以了解下tcp底层的Nagle算法

二、如何处理粘包

处理方法

处理粘包的方式主要采用应用层定义收发包格式的方式,这个过程俗称切包处理,常用的协议被称为tlv协议
(消息id+消息长度+消息内容),如下图
在这里插入图片描述
为保证大家容易理解,我们先简化发送的格式,格式变为消息长度+消息内容的方式,之后再完善为tlv格式
简化后的结构如下图
在这里插入图片描述

三、完善消息节点MsgNode

之前我们设计过消息节点的数据结构MsgNode,这里需要完善一下

代码部分

class MsgNode
{friend class CSession;
public:MsgNode(char* msg,short max_len):_cur_len(0),_total_len(HEAD_LENGTH+max_len){_data = new char[_total_len+1]();memcpy(_data, &max_len, HEAD_LENGTH);memcpy(_data+ HEAD_LENGTH, msg, max_len);_data[_total_len] = '\0';}MsgNode(short max_len) :_total_len(max_len), _cur_len(0){_data = new char[_total_len + 1]();}~MsgNode(){delete[]_data;}void Clear(){::memset(_data, 0, _total_len);_cur_len = 0;}
private:short _cur_len;short _total_len;char* _data;};

1.两个参数的构造函数做了完善,之前的构造函数通过消息首地址和长度构造节点数据,现在需要在构造节点的同时把长度信息也写入节点,该构造函数主要用来发送数据时构造发送信息的节点。
2. 一个参数的构造函数为较上次新增的,主要根据消息的长度构造消息节点,该构造函数主要是接收对端数据时构造接收节点调用的。
3.新增一个Clear函数清除消息节点的数据,主要是避免多次构造节点造成开销

细节详解

memcpy(_data, &max_len, HEAD_LENGTH);

这里为什么要用&
什么意思呢,现在解释一下
首先我们要做的格式是消息长度+消息内容
在tlv协议中,这个消息长度需要用二进制来存储

#define HEAD_LENGTH  2  //用2个字节的数据存储消息长度
memcpy(_data, &max_len, HEAD_LENGTH);

✅ 解释与流程:

  1. memcpy(_data, &max_len, HEAD_LENGTH);
    ○ 目的:将消息的长度写入消息包的前面作为 “消息头”
    ○ &max_len:取 max_len 的地址,即把这个整数以原始内存二进制形式写入 _data 中。
    ○ HEAD_LENGTH:通常为 2 或 4,代表写入几个字节(例如使用 short 就是 2 字节)。
    ○ ✅ 效果:在 _data 中的前 2 字节保存了消息的长度,二进制形式。
    在这里插入图片描述
    相当于我们要先把这个5写进去,告诉后面的数据长度为5,记得用二进制!!!所以加&

_data[_total_len] = ‘\0’

为什么最后要加一个结束符号呢??
如果没有 ‘\0’,字符串函数会一直读取内存,直到碰到恰好为 0 的字节,容易造成越界访问、程序崩溃或打印乱码
如果你的有效数据长度刚好覆盖了整个数组(不留多余的 ‘\0’),字符串函数找不到结束符,就会继续往后读,导致越界访问

四、完善CSession

为能够对收到的数据切包处理,需要定义一个消息接收节点,一个bool类型的变量表示头部是否解析完成,以及将处理好的头部先缓存起来的结构

在原先代码加入就行

//收到的消息结构std::shared_ptr<MsgNode> _recv_msg_node;bool _b_head_parse;//头部是否解析完成//收到的头部结构std::shared_ptr<MsgNode> _recv_head_node;

_recv_msg_node 用来存储接受的消息体信息
_recv_head_node 用来存储接收的头部信息
_b_head_parse 表示是否处理完头部信息

五、完善接收逻辑

我们需要修改HandleRead函数

代码部分

//读的回调函数
void CSession::HandleRead(const boost::system::error_code& ec,size_t bytes_transferred, shared_ptr<CSession>_self_shared)
{if (!ec){int copy_len = 0;//本次回调读取了多少数据  后续需要偏移while (bytes_transferred > 0)//接收到了数据{if (!_b_head_parse)//先解析头部{//当前传入的数据+头部中的数据  依然没有达到2字节 那就全加入进去if (bytes_transferred + _recv_head_node->_cur_len < HEAD_LENGTH){memcpy(_recv_head_node->_data + _recv_head_node->_cur_len, _data+copy_len, bytes_transferred);_recv_head_node->_cur_len += bytes_transferred;::memset(_data, 0, max_length);//继续回调 等待数据传入_socket.async_read_some(boost::asio::buffer(_data, max_length),std::bind(&CSession::HandleRead, this, std::placeholders::_1,std::placeholders::_2, _self_shared));return;}//收到的数据比头部多 可能刚好等于或者大于//头部剩余未复制的长度int head_remain = HEAD_LENGTH - _recv_head_node->_cur_len;memcpy(_recv_head_node->_data + _recv_head_node->_cur_len, _data + copy_len, head_remain);//更新已处理的data长度和剩余未处理的长度copy_len += head_remain;_recv_head_node->_cur_len += head_remain;bytes_transferred -= head_remain;//获取头部数据进行判断short data_len = 0;memcpy(&data_len, _recv_head_node->_data, HEAD_LENGTH);cout << "data_len is" << data_len << endl;if (data_len > max_length){std::cout << "invalid data length is " << data_len << endl;_server->ClearSession(_uuid);return;}_recv_msg_node = make_shared<MsgNode>(data_len);//数据小 可以全存入 但后续还需要存入 因为没有达到头部所显示的长度if (bytes_transferred < data_len){memcpy(_recv_msg_node->_data + _recv_msg_node->_cur_len,_data + copy_len, bytes_transferred);_recv_msg_node->_cur_len += bytes_transferred;::memset(_data, 0, max_length);_socket.async_read_some(boost::asio::buffer(_data,max_length),std::bind(&CSession::HandleRead, this, std::placeholders::_1, std::placeholders::_2, _self_shared));//头部处理完成_b_head_parse = true;return;}//数据大 就只存data_len长度  和头部显示的长度一样memcpy(_recv_msg_node->_data + _recv_msg_node->_cur_len,_data + copy_len, data_len);_recv_msg_node->_cur_len += data_len;copy_len += data_len;bytes_transferred -= data_len;_recv_msg_node->_data[_recv_msg_node->_total_len] = '\0';cout << "receive data is " << _recv_msg_node->_data << endl;//此处可以调用Send发送测试Send(_recv_msg_node->_data, _recv_msg_node->_total_len);//继续轮询剩余未处理数据_b_head_parse = false;_recv_head_node->Clear();if (bytes_transferred == 0) //说明后面没有数据了 刚好是一个包{::memset(_data, 0, max_length);_socket.async_read_some(boost::asio::buffer(_data, max_length),std::bind(&CSession::HandleRead, this, std::placeholders::_1, std::placeholders::_2, _self_shared));return;}continue;//说明后面还有数据  继续轮询处理 }//头部已经解析完int data_len = _recv_msg_node->_total_len - _recv_msg_node->_cur_len;//数据小 可以全存入 但后续还需要存入 因为没有达到头部所显示的长度if (bytes_transferred < data_len) {memcpy(_recv_msg_node->_data + _recv_msg_node->_cur_len,_data + copy_len, bytes_transferred);_recv_msg_node->_cur_len += bytes_transferred;::memset(_data, 0, max_length);_socket.async_read_some(boost::asio::buffer(_data, max_length),std::bind(&CSession::HandleRead, this, std::placeholders::_1, std::placeholders::_2, _self_shared));return;}//数据大 就只存data_len长度  和头部显示的长度一样memcpy(_recv_msg_node->_data + _recv_msg_node->_cur_len,_data + copy_len, data_len);_recv_msg_node->_cur_len += data_len;bytes_transferred -= data_len;copy_len += data_len;_recv_msg_node->_data[_recv_msg_node->_total_len] = '\0';cout << "receive data is " << _recv_msg_node->_data << endl;// 此处可以调用Send发送测试Send(_recv_msg_node->_data, _recv_msg_node->_total_len);//继续轮询剩余未处理数据_b_head_parse = false;_recv_head_node->Clear();if (bytes_transferred == 0) {::memset(_data, 0,max_length);_socket.async_read_some(boost::asio::buffer(_data, max_length),std::bind(&CSession::HandleRead, this,std::placeholders::_1, std::placeholders::_2, _self_shared));return;}continue;}}else{std::cout << "handle read failed, error is " << ec.what() << endl;_server->ClearSession(_uuid);}
}

流程图

1   copy_len记录的是已经处理过数据的长度,因为存在一次接收多个包的情况,所以copy_len用来做已经处理的数据长度的。
2   首先判断_b_head_parse是否为false,如果为false则说明头部未处理,先判断接收的数据是否小于头部, 如果小于头部大小则将接收到的数据放入_recv_head_node节点保存,然后继续调用读取函数监听对端发送数据。否则进入步骤3.
3   如果收到的数据比头部多,可能是多个逻辑包,所以要做切包处理。根据之前保留在_recv_head_node的长度,计算出剩余未取出的头部长度,然后取出剩余的头部长度保存在_recv_head_node节点,然后通过memcpy方式从节点拷贝出数据写入short类型的data_len里,进而获取消息的长度。接下来继续处理包体,也就是消息体,判断接收到的数据未处理部分的长度和总共要接收的数据长度大小,如果小于总共要接受的长度,说明消息体没接收完,则将未处理部分先写入_recv_msg_node里,并且继续监听读事件。否则说明消息体接收完全,进入步骤4
4   将消息体数据接收到_recv_msg_node中,接受完全后返回给对端。当然存在多个逻辑包粘连,此时要判断bytes_transferred是否小于等于0,如果是说明只有一个逻辑包,我们处理完了,继续监听读事件,就直接返回即可。否则说明有多个数据包粘连,就继续执行上述操作。

5   因为存在_b_head_parse为true,也就是包头接收并处理完的情况,但是包体未接受完,再次触发HandleRead,此时要继续处理上次未接受完的消息体,大体逻辑和3,4一样。
以上就是处理粘包的过程,我们绘制流程图更明了一些
在这里插入图片描述

六、客户端修改

客户端的发送也要遵循先发送数据2个字节的数据长度,再发送数据消息的结构
接收时也是先接收两个字节数据获取数据长度,再根据长度接收消息

#include<boost/asio.hpp>
#include <iostream>
const int MAX_LENGTH = 1024;//表示发送和接收最大长度为1024字节
const int HEAD_LENGTH = 2;
int main()
{while (1){try{//创建上下文服务boost::asio::io_context ios;//创建endpointboost::asio::ip::tcp::endpointremote_ep(boost::asio::ip::make_address("127.0.0.1"), 10086);//127.0.0.1是本机的回路地址 因为客户端和服务端在同一地址//服务器就是本机boost::asio::ip::tcp::socket sock(ios, remote_ep.protocol());boost::system::error_code error = boost::asio::error::host_not_found;sock.connect(remote_ep, error);if (error){std::cout << "connect failed,code is:" << error.value() <<"error message is" << error.message() << std::endl;return 0;}std::cout << "请输入需要发送的信息:";char request[MAX_LENGTH];std::cin.getline(request, MAX_LENGTH);size_t request_length = strlen(request);//获取实际要发送的数据长度//发送数据char send_data[MAX_LENGTH] = { 0 };memcpy(send_data, &request_length, 2);memcpy(send_data + 2, request, request_length);boost::asio::write(sock, boost::asio::buffer(send_data, request_length+2));/*char reply[MAX_LENGTH];//存储接收数据//用read读size_t reply_length = 0;boost::system::error_code ec;reply_length = sock.read_some(boost::asio::buffer(reply, MAX_LENGTH), ec);if (ec){std::cerr << "Read failed: " << ec.message() << std::endl;return 0;}std::cout<<"长度" <<reply_length<<std::endl;std::cout << "回复:";std::cout.write(reply, reply_length);std::cout << "\n";*/char reply_head[HEAD_LENGTH];size_t reply_length = boost::asio::read(sock, boost::asio::buffer(reply_head, HEAD_LENGTH));short msglen = 0;memcpy(&msglen, reply_head, HEAD_LENGTH);char msg[MAX_LENGTH] = { 0 };size_t  msg_length = boost::asio::read(sock, boost::asio::buffer(msg, msglen));std::cout << "Reply is: ";std::cout.write(msg, msglen) ;std::cout << "Reply len is " << msglen;std::cout << "\n";}catch (std::exception& e){std::cerr << "Exception is: " << e.what() << std::endl;}}return 0;
}

服务器启动后,启动客户端,然后客户端发送Hello World,服务器收到后打印如下
在这里插入图片描述

七、测试粘包

为了测试粘包,需要制造粘包产生的现象,可以让客户端发送的频率高一些,服务器接收的频率低一些,这样造成前后端收发数据不一致导致多个数据包在服务器tcp缓冲区滞留产生粘包现象
测试粘包之前,在服务器的CSession类里添加打印二进制数据的函数,便于查看缓冲区的数据

void CSession::PrintRecvData(char* data, int length) {stringstream ss;string result = "0x";for (int i = 0; i < length; i++) {string hexstr;ss << hex << std::setw(2) << std::setfill('0') << int(data[i]) << endl;ss >> hexstr;result += hexstr;}std::cout << "receive raw data is : " << result << endl;;
}

然后将这个函数放到HandleRead里,每次收到数据就调用这个函数打印接收到的最原始的数据,然后睡眠2秒再进行收发操作,用来延迟接收对端数据制造粘包,之后的逻辑不变

void CSession::HandleRead(const boost::system::error_code& error, size_t  bytes_transferred, std::shared_ptr<CSession> shared_self){if (!error) {PrintRecvData(_data, bytes_transferred);std::chrono::milliseconds dura(2000);std::this_thread::sleep_for(dura);}
}

修改客户端逻辑,实现收发分离

int main()
{try {//创建上下文服务boost::asio::io_context   ioc;//构造endpointtcp::endpoint  remote_ep(address::from_string("127.0.0.1"), 10086);tcp::socket  sock(ioc);boost::system::error_code   error = boost::asio::error::host_not_found; ;sock.connect(remote_ep, error);if (error) {cout << "connect failed, code is " << error.value() << " error msg is " << error.message();return 0;}thread send_thread([&sock] {for (;;) {this_thread::sleep_for(std::chrono::milliseconds(2));const char* request = "hello world!";size_t request_length = strlen(request);char send_data[MAX_LENGTH] = { 0 };memcpy(send_data, &request_length, 2);memcpy(send_data + 2, request, request_length);boost::asio::write(sock, boost::asio::buffer(send_data, request_length + 2));}});thread recv_thread([&sock] {for (;;) {this_thread::sleep_for(std::chrono::milliseconds(2));cout << "begin to receive..." << endl;char reply_head[HEAD_LENGTH];size_t reply_length = boost::asio::read(sock, boost::asio::buffer(reply_head, HEAD_LENGTH));short msglen = 0;memcpy(&msglen, reply_head, HEAD_LENGTH);char msg[MAX_LENGTH] = { 0 };size_t  msg_length = boost::asio::read(sock, boost::asio::buffer(msg, msglen));std::cout << "Reply is: ";std::cout.write(msg, msglen) << endl;std::cout << "Reply len is " << msglen;std::cout << "\n";}});send_thread.join();recv_thread.join();}catch (std::exception& e) {std::cerr << "Exception: " << e.what() << endl;}return 0;
}

再次启动服务器和客户端,看到粘包现象了,我们的服务器也能稳定切割数据包并返回正确的消息给客户端。
可以看到服务器收到了大量数据,然后准确切割返回给了客户端。如下图

在这里插入图片描述

总结

该服务虽然实现了粘包处理,但是服务器仍存在不足,比如当客户端和服务器处于不同平台时收发数据会出现异常,根本原因是未处理大小端模式的问题

相关文章:

C++ asio网络编程(8)处理粘包问题

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言 粘包问题一、粘包原因总结&#xff1a; 二、如何处理粘包处理方法 三、完善消息节点MsgNode代码部分细节详解memcpy(_data, &max_len, HEAD_LENGTH);_data…...

【架构美学】Java 访问者模式:解构数据与操作的双重分发哲学

一、模式定义与核心思想 访问者模式&#xff08;Visitor Pattern&#xff09;是一种行为型设计模式&#xff0c;其核心目标是将数据操作与数据结构分离。通过定义一个独立的访问者类&#xff0c;使得新增对数据结构中元素的操作时&#xff0c;无需修改元素本身的类结构&#x…...

UE5无法编译问题解决

1. vs编译 2. 删除三个文件夹 参考...

Java可变参数与Collections工具类详解

Java可变参数与Collections工具类详解 一、可变参数&#xff08;Variable Arguments&#xff09; 1.1 基本概念 可变参数是Java 5引入的特性&#xff0c;允许在方法中定义数量可变的形参。其核心特点是&#xff1a;形参个数可以动态变化&#xff08;0个、1个、多个&#xff…...

Git版本管理命令reset

目录 命令 git reset 场景一只回退 工作区代码 场景二回退暂存库与工作区 场景三回退暂存库&#xff0c;工作区&#xff0c;版本库内容 命令 git reset git reset --[soft/mixed(默认)/hard] [文件] soft&#xff1a;只回退版本库中内容 mixed&#xff1a;回退暂存区&…...

改进模糊C均值时序聚类+编码器状态识别!IPOA-FCM-Transformer组合模型

改进模糊C均值时序聚类编码器状态识别&#xff01;IPOA-FCM-Transformer组合模型 目录 改进模糊C均值时序聚类编码器状态识别&#xff01;IPOA-FCM-Transformer组合模型效果分析基本描述程序设计参考资料 效果分析 基本描述 1.创新未发表&#xff01;研究亮点&#xff01;时序…...

Zookeeper入门(三)

Zookeeper Java 客户端 项目构建 ookeeper 官方的客户端没有和服务端代码分离&#xff0c;他们为同一个jar 文件&#xff0c;所以我们直接引入 zookeeper的maven即可&#xff0c; 这里版本请保持与服务端版本一致&#xff0c;不然会有很多兼容性的问题 1 <dependency>…...

使用Redission来实现布隆过滤器

简述布隆过滤器 布隆过滤器是一种概率型数据结构&#xff0c;它可以用来判断一个元素是否在一个集合中。我们当时使用的是Redisson实现的布隆过滤器。它的底层原理是&#xff0c;先初始化一个比较大的数组&#xff0c;里面存放的是二进制0或1。一开始都是0&#xff0c;当一个k…...

Seata源码—6.Seata AT模式的数据源代理一

大纲 1.Seata的Resource资源接口源码 2.Seata数据源连接池代理的实现源码 3.Client向Server发起注册RM的源码 4.Client向Server注册RM时的交互源码 5.数据源连接代理与SQL句柄代理的初始化源码 6.Seata基于SQL句柄代理执行SQL的源码 7.执行SQL语句前取消自动提交事务的源…...

Spring-Beans的生命周期的介绍

目录 1、Spring核心组件 2、Bean组件 2.1、Bean的定义 2.2、Bean的生命周期 1、实例化 2、属性填充 3、初始化 4、销毁 2.3、Bean的执行时间 2.4、Bean的作用域 3、常见问题解决方案 4、与Java对象区别 前言 关于bean的生命周期&#xff0c;如下所示&#xff1a; …...

目标检测新突破:用MSBlock打造更强YOLOv8

文章目录 YOLOv8的现状与挑战YOLO-MS的MSBlock简介MSBlock的工作原理MSBlock的优势 利用MSBlock改进YOLOv8替换YOLOv8主干网络中的部分模块代码实现&#xff1a;替换CSP模块为MSBlock 在YOLOv8的颈部&#xff08;Neck&#xff09;中插入MSBlock代码实现&#xff1a;在颈部区域插…...

[SpringBoot]Spring MVC(4.0)

获取Header 传统获取 header 从 HttpServletRequest 中获取 RequestMapping("/r8")public String r8(HttpServletRequest request) {String userAgent request.getHeader("User-Agent");return "userAgent: "userAgent;}使用浏览器访问后&…...

Linux概述:从内核到开源生态

Linux概述&#xff1a;从内核到开源生态 Linux 是当今计算机领域最核心的开源操作系统内核&#xff0c;其影响力已渗透到服务器、嵌入式设备、云计算甚至超级计算机等各个领域。本章将深入解析Linux的本质、核心架构及其背后的开源哲学。 1. Linux的本质&#xff1a;不只是“操…...

【ubuntu24.04】pycharm 死机结束进程

windows 远程pycharm到ubuntu执行程序 pycharm 在调试过程中&#xff0c;内存耗尽&#xff0c;然后死机了 pycharm 进程 (base) rootk8s-master-pfsrv:/home/zhangbin/下载# ps -ef | grep pycharm root 121245 3230568 0 5月14 pts/8 00:00:00 /bin/bash --rcfile …...

【PRB】深度解析GaN中最浅的受主缺陷

2025 年 1 月 16 日,Virginia Commonwealth University 的 M. A. Reshchikov 和 SUNY–Albany 的 B. McEwen 等人在《Physical Review B》期刊发表了题为《Identity of the shallowest acceptor in GaN》的文章,基于对 50 多个 Be 掺杂 GaN 样品的光致发光实验以及 Heyd-Scus…...

Flink CEP是什么?

Apache Flink 的 CEP&#xff08;Complex Event Processing&#xff0c;复杂事件处理&#xff09; 是 Flink 提供的一个库&#xff0c;用于在无界数据流中检测符合特定模式的事件组合。 &#x1f3af; 一、什么是 CEP&#xff1f; ✅ 定义&#xff1a; CEP 是一种从连续的数据…...

基于STM32的多传感器融合的设施农业小型搬运机器人避障控制系统设计

一、系统总体设计目标 针对设施农业场景中狭窄通道、障碍物多样(如农机具、作物植株、水管)的特点,设计一款基于 STM32 的小型搬运机器人避障控制系统。系统通过多传感器融合实现 360 环境感知,采用模糊 PID 控制算法实现平滑避障,满足温室、大棚等场景的搬运需求。 二、…...

从零开始实现大语言模型(十六):加载开源大语言模型参数

1. 前言 预训练大语言模型的难点不在于算法&#xff0c;而在于数据和算力&#xff0c;绝大多数企业和机构都没有预训练大语言模型的算力资源。在工业界的大语言模型应用实践中&#xff0c;通常会使用领域数据微调开源大语言模型参数&#xff0c;以构建领域大语言模型。 本文介…...

Spark,数据提取和保存

以下是使用 Spark 进行数据提取&#xff08;读取&#xff09;和保存&#xff08;写入&#xff09;的常见场景及代码示例&#xff08;基于 Scala/Java/Python&#xff0c;不含图片操作&#xff09;&#xff1a; 一、数据提取&#xff08;读取&#xff09; 1. 读取文件数据&a…...

java19

1.集合体系结构 注意&#xff1a; 2.collection遍历之迭代器遍历 一次循环只能一次next方法的原因&#xff1a; 原因&#xff1a;集合长度是单数就报错 3.collection遍历之增强for遍历 如何代码简写呢&#xff1a;集合名.for回车 4.collection遍历之Lambda表达式遍历 5.使用多态…...

从0到1吃透卷积神经网络(CNN):原理与实战全解析

一、开篇&#xff1a;CNN 在 AI 领域的地位 在当今人工智能&#xff08;AI&#xff09;飞速发展的时代&#xff0c;卷积神经网络&#xff08;Convolutional Neural Network&#xff0c;简称 CNN&#xff09;无疑是深度学习领域中最为耀眼的明星之一 。它就像是 AI 世界里的超级…...

建一个结合双向长短期记忆网络(BiLSTM)和条件随机场(CRF)的模型

构建一个结合双向长短期记忆网络&#xff08;BiLSTM&#xff09;和条件随机场&#xff08;CRF&#xff09;的模型&#xff0c;通常用于序列标注任务&#xff0c;如命名实体识别&#xff08;NER&#xff09;、词性标注&#xff08;POS Tagging&#xff09;等。下面我将通过口述的…...

mvc-ioc实现

IOC 1&#xff09;耦合/依赖 依赖&#xff0c;是谁离不开谁 就比如上诉的Controller层必须依赖于Service层&#xff0c;Service层依赖于Dao 在软件系统中&#xff0c;层与层之间存在依赖。我们称之为耦合 我们系统架构或者设计的一个原则是&#xff…...

符合Python风格的对象(再谈向量类)

再谈向量类 为了说明用于生成对象表示形式的众多方法&#xff0c;我们将使用一个 Vector2d 类&#xff0c;它与第 1 章中的类似。这一节和接下来的几节会不断实 现这个类。我们期望 Vector2d 实例具有的基本行为如示例 9-1 所示。 示例 9-1 Vector2d 实例有多种表示形式 &g…...

4.1.8文件共享

知识总览 基于索引节点的共享方式(硬链接)&#xff1a; 让不同用户的文件目录项指向同一个文件的索引节点 用户1创建文件1&#xff0c;并让文件目录项aaa指向了文件1&#xff0c;这个文件对应了一个索引节点&#xff0c;这个索引节点 包含了文件的物理地址和文件的其他属性信…...

[LevelDB]LevelDB版本管理的黑魔法-为什么能在不锁表的情况下管理数据?

文章摘要 LevelDB的日志管理系统是怎么通过双链表来进行数据管理为什么LevelDB能够在不锁表的情况下进行日志新增 适用人群: 对版本管理机制有开发诉求&#xff0c;并且希望参考LevelDB的版本开发机制。数据库相关从业者的专业人士。计算机狂热爱好者&#xff0c;对计算机的…...

普通用户的服务器连接与模型部署相关记录

普通用户的服务器连接与模型部署相关记录 一、从登录到使用自己的conda 1.账号登陆&#xff1a; ssh xxx172.31.226.236 2.下载与安装conda&#xff1a; 下载conda&#xff1a; wget -c https://repo.anaconda.com/archive/Anaconda3-2023.03-1-Linux-x86_64.sh 安装con…...

WebSocket解决方案的一些细节阐述

今天我们来看看WebSocket解决方案的一些细节问题&#xff1a; 实际上&#xff0c;集成WebSocket的方法都有相关的工程挑战&#xff0c;这可能会影响项目成本和交付期限。在最简单的层面上&#xff0c;构建 WebSocket 解决方案似乎是添加接收实时更新功能的前进方向。但是&…...

架构思维:构建高并发扣减服务_分布式无主架构

文章目录 Pre无主架构的任务简单实现分布式无主架构 设计和实现扣减中的返还什么是扣减的返还返还实现原则原则一&#xff1a;扣减完成才能返还原则二&#xff1a;一次扣减可以多次返还原则三&#xff1a;返还的总数量要小于等于原始扣减的数量原则四&#xff1a;返还要保证幂等…...

C++函数基础:定义与调用函数,参数传递(值传递、引用传递)详解

1. 引言 函数是C编程中的核心概念之一&#xff0c;它允许我们将代码模块化&#xff0c;提高代码的可读性、复用性和可维护性。本文将深入探讨&#xff1a; 函数的定义与调用参数传递方式&#xff08;值传递 vs 引用传递&#xff09;应用场景与最佳实践 2. 函数的定义与调用 …...

深入解析Python中的Vector2d类:从基础实现到特殊方法的应用

引言 在Python面向对象编程中&#xff0c;特殊方法&#xff08;或称魔术方法&#xff09;是实现对象丰富行为的关键。本文将以Vector2d类为例&#xff0c;详细讲解如何通过特殊方法为自定义类添加多种表示形式和操作能力。 Vector2d类的基本行为 Vector2d类是一个二维向量类…...

【25软考网工】第六章(7)网络安全防护系统

博客主页&#xff1a;christine-rr-CSDN博客 ​​专栏主页&#xff1a;软考中级网络工程师笔记 ​​​ 大家好&#xff0c;我是christine-rr !目前《软考中级网络工程师》专栏已经更新三十多篇文章了&#xff0c;每篇笔记都包含详细的知识点&#xff0c;希望能帮助到你&#x…...

Mac下载bilibili视频

安装 安装 yt-dlp brew install yt-dlp安装FFmpeg 用于合并音视频流、转码等操作 brew install ffmpeg使用 下载单个视频 查看可用格式 yt-dlp -F --cookies-from-browser chrome "https://www.bilibili.com/video/BV15B4y1G7F3?spm_id_from333.788.recommend_more_vid…...

6个月Python学习计划:从入门到AI实战(前端开发者进阶指南)

作者&#xff1a;一名前端开发者的进阶日志 计划时长&#xff1a;6个月 每日学习时间&#xff1a;2小时 覆盖方向&#xff1a;Python基础、爬虫开发、数据分析、后端开发、人工智能、深度学习 &#x1f4cc; 目录 学习目标总览每日时间分配建议第1月&#xff1a;Python基础与编…...

批量处理 Office 文档 高画质提取图片、视频、音频素材助手

各位办公小能手们&#xff01;你们有没有遇到过想从 Office 文档里提取图片、音频和视频&#xff0c;却又搞得焦头烂额的情况&#xff1f;今天就给大家介绍一款超厉害的工具——OfficeImagesExtractor&#xff01; 这货的核心功能那可真是杠杠的&#xff01;首先是高画质提取&a…...

【甲方安全建设】Python 项目静态扫描工具 Bandit 安装使用详细教程

文章目录 一、工具简介二、工具特点1.聚焦安全漏洞检测2.灵活的扫描配置3.多场景适配4.轻量且社区活跃三、安装步骤四、使用方法场景1:扫描单个Python文件场景2:递归扫描整个项目目录五、结果解读六、总结一、工具简介 Bandit 是由Python官方推荐的静态代码分析工具(SAST)…...

【推荐】新准则下对照会计报表172个会计科目解释

序号 科目名称 对应的会计报表项目 序号 科目名称 对应的会计报表项目   一、资产类     二、负债类   1 1001 库存现金 货币资金 103 2001 短期借款 短期借款 2 1002 银行存款 货币资金 104 2101 交易性金融负债 易性金融负债 3 1012 其他货币资…...

IntelliJ IDEA设置编码集

在IntelliJ IDEA中设置Properties文件的编码格式&#xff0c;主要涉及以下步骤和注意事项&#xff1a; 1. ‌全局和项目编码设置‌ 打开设置界面&#xff1a;File -> Settings -> Editor -> File Encodings。在Global Encoding和Project Encoding下拉菜单中均选择UT…...

类魔方 :多变组合,灵活复用

文章目录 一、类的基础1. 类的基本结构与语法1. 类的定义与实例化2. 成员变量&#xff08;属性&#xff09;3. 构造函数&#xff08;Constructor&#xff09;4. 成员方法 2. 访问修饰符1. 基本访问规则2. 子类对父类方法的重写3. 构造函数的访问修饰符4. 参数属性与继承总结 3.…...

支持多方式拼接图片的软件

软件介绍 本文介绍一款名为 PicMerger 的图片拼接软件。 拼接亮点 PicMerger 这款软件最大的亮点在于&#xff0c;它能够将不同分辨率的图片完美地拼接在一起。拼接时会自动以分辨率最小的图片为标准&#xff0c;操作十分方便。 拼接方式与设置 该软件支持横向和纵向的拼接…...

Qt音视频开发过程中一个疑难杂症的解决方法/ffmpeg中采集本地音频设备无法触发超时回调

一、前言 最近在做实时音视频通话的项目中&#xff0c;遇到一个神奇的问题&#xff0c;那就是用ffmpeg采集本地音频设备&#xff0c;当音频设备拔掉后&#xff0c;采集过程会卡死在av_read_frame函数中&#xff0c;尽管设置了超时时间&#xff0c;也设置了超时回调interrupt_c…...

Android studio Could not move temporary workspace

Android studio Could not move temporary workspace 在Window上运行AS出现Could not move temporary workspace报错方法一&#xff08;有效&#xff09;方法二方法三方法四总结 在Window上运行AS出现Could not move temporary workspace报错 Could not move temporary workspa…...

深度估计中为什么需要已知相机基线(known camera baseline)?

在计算机视觉和立体视觉的上下文中&#xff0c;“已知相机基线”&#xff08;known camera baseline&#xff09;的解释 1. 相机基线的定义 相机基线是指两个相机中心之间的距离。在立体视觉系统中&#xff0c;通常有两个相机&#xff08;或一个相机在不同位置拍摄两张图像&a…...

Spring Cloud 技术实战

Spring Cloud 简介 Spring Cloud 是基于 Spring Boot 构建的微服务框架&#xff0c;提供了一套完整的微服务解决方案。它利用 Spring Boot 的开发便利性&#xff0c;并通过各种组件简化分布式系统的开发。 核心组件 Spring Cloud Netflix Eureka: 服务注册与发现Spring Clou…...

《云端共生体:Flutter与AR Cloud如何改写社交交互规则》

当Flutter遇上AR Cloud&#xff0c;一场关于社交应用跨设备增强现实内容共享与协作的变革正在悄然发生。 Flutter是谷歌推出的一款开源UI软件开发工具包&#xff0c;其最大的优势在于能够实现一套代码&#xff0c;多平台部署&#xff0c;涵盖iOS、Android、Web、Windows、macO…...

【数据结构】1-3 算法的时间复杂度

数据结构知识点合集&#xff1a;数据结构与算法 • 知识点 • 时间复杂度的定义 1、算法时间复杂度 事前预估算法时间开销T(n)与问题规模 n 的关系&#xff08;T 表示 “time”&#xff09; 2、语句频度 算法中语句的执行次数 对于以上算法&#xff0c;语句频度&#xff1a;…...

Science Robotics 封面论文:基于形态学开放式参数化的仿人灵巧手设计用于具身操作

人形机械手具有无与伦比的多功能性和精细运动技能&#xff0c;使其能够精确、有力和稳健地执行各种任务。在古生物学记录和动物王国中&#xff0c;我们看到了各种各样的替代手和驱动设计。了解形态学设计空间和由此产生的涌现行为不仅可以帮助我们理解灵巧的作用及其演变&#…...

Vue百日学习计划Day24-28天详细计划-Gemini版

总目标: 在 Day 24-27 熟练掌握 Vue.js 的各种模板语法&#xff0c;包括文本插值、属性绑定、条件渲染、列表渲染、事件处理和表单绑定&#xff0c;并能结合使用修饰符。 所需资源: Vue 3 官方文档 (模板语法): https://cn.vuejs.org/guide/essentials/template-syntax.htmlVu…...

C++_数据结构_哈希表(hash)实现

✨✨ 欢迎大家来到小伞的大讲堂✨✨ &#x1f388;&#x1f388;养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 所属专栏&#xff1a;C学习 小伞的主页&#xff1a;xiaosan_blog 制作不易&#xff01;点个赞吧&#xff01;&#xff01;谢谢喵&#xff01;&…...

elasticsearch kibana ik 各版本下载

https://release.infinilabs.com/analysis-ik/stable/或者 https://github.com/infinilabs/analysis-ik/releases...