首页 > 其他分享 >在物联网应用中需要经常处理数据帧,请你写一段处理数据帧的代码将收到的数据进行解析输出

在物联网应用中需要经常处理数据帧,请你写一段处理数据帧的代码将收到的数据进行解析输出

时间:2023-07-17 16:12:47浏览次数:37  
标签:读取 处理 D2 receivedData 解析 数据 服务端 D1

提示: 1、数据帧的长度不定,但是帧头帧尾是固定的 2、数据帧的参数数量不定,请注意 3、每次收到的数据可能不是完整的一帧,但是不能把不完整的数据帧丢弃,应该等待到下一完整帧接收到后才丢弃 4、一次可能接受到不止一个数据帧,可能是多个,需要针对不同数据帧进行分割

搜说关键词:数据帧粘包 数据帧拆包 帧处理

以下内容文献摘抄CSDN博客的bandaoyu博主,加上自己的一些总结:

什么是分包与拆包呢?
       假设客户端分别发送了两个数据包D1和D2给服务端,由于服务端一次读取到的字节数是不确定的,故可能存在以下4种情况:
       (1)服务端分两次读取到了两个独立的数据包,分别是D1和D2,没有粘包和拆包;
       (2)服务端一次接收到了两个数据包,D1和D2粘合在一起,被称为TCP粘包;
       (3)服务端分两次读取到了两个数据包,第一次读取到了完整的D1包和D2包的部分内容,第二次读取到了D2包的剩余内容,这被称为TCP拆包;
       (4)服务端分两次读取到了两个数据包,第一次读取到了D1包的部分内容D1_1,第二次读取到了D1包的剩余内容D1_2和D2包的整包。

粘包和拆包原因
(1)要发送的数据小于TCP发送缓冲区的大小,TCP将多次写入缓冲区的数据一次发送出去,将会发生粘包;
(2)接收数据端的应用层没有及时读取接收缓冲区中的数据,将发生粘包;
(3)要发送的数据大于TCP发送缓冲区剩余空间大小,将会发生拆包;
(4)待发送数据大于MSS(最大报文长度),TCP在传输前将进行拆包。即TCP报文长度-TCP头部长度>MSS。

如何识别帧头帧尾

在单片机上有2种方式。

方式1:利用2帧数据报文间隔时间来确定一帧数据包。如果在其后连续3.5字节时间内都没有收到一个字节,则认为之前收到的
报文,就是一条完整的报文。假定收到该报文长度为10,则接收缓冲区数组[0]=帧头,接收缓冲区数组[9]=帧尾。
方式2:利用特殊字符串1作为帧头,特殊字符串2作为帧尾。而且特殊字符串1不允许出现在报文中,只能作为帧头;特殊字符串2不允许出现在报文中,只能作为帧尾。回车+换行符不可能出现在正常报文中,只能用于帧尾。
方式3:帧头+帧长度

缓存解包的方式识别帧

1、缓存解包

单片机上普通采用环形缓冲区来解决缓存解包问题。
     所谓缓存解包就是建立一个大的环形缓冲区,环形缓冲区的长度至少是【最长报文】的n倍。环形缓冲区有独立的写指针和读指针,因此就算出现粘包,也不会影响解析报文工作。

2、拆包
   假设客户端分别发送了两个数据包D1和D2给服务端,由于服务端一次读取到的字节数是不确定的,故可能存在以下4种情况。
       (1)服务端分两次读取到了两个独立的数据包,分别是D1和D2,【报文是完整的】,没有粘包和拆包;
       (2)服务端一次接收到了两个数据包,D1和D2粘合在一起,被称为TCP粘包;
       (3)服务端分两次读取到了两个数据包,第一次读取到了完整的D1包和D2包的部分内容,第二次读取到了D2包的剩余内容,这被称为TCP拆包;
       (4)服务端分两次读取到了两个数据包,第一次读取到了D1包的部分内容D1_1,第二次读取到了D1包的剩余内容D1_2和D2包的整包
        上述(2)、(3)、(4)种情况,就是拆包。上述(1)情况不需要拆包,因为D1和D2分别都是独自完整的包。
下一是法一,但是有错误

#include<iostream>
#include<vector>
using namespace std;

//定义数据帧结构体
struct DataFrame
{
	//假设数据帧由4个字节的数据和1个字节的校验码组成
	vector<unsigned char>data;
	unsigned char checksum;
};

//数据帧解析函数
void pareaDataFrame(const vector<unsigned char >& receiveData)
{
	if (receiveData.size() != sizeof(DataFrame))
	{
		std::cout << "接收的数据不是有效的数据框" << endl;
		return;
	}

}

//将接受到的数据进行类型转换,以便访问各个字段
const DataFrame* frame = reinterpret_cast<const DataFrame*>(receivedData.data());

std::cout << "接收的数据框" << endl;
std::cout << "Data:"; // 这里缺少打印data字段的代码
std::cout << "checksum:" << static_cast<int>(frame->checksum) << endl;


int main()
{
	//假设受到的数据帧保存在receiveData变量中
	vector<unsigned char>receiveData = { 0x01,0x02,0x03,0x04,0xAB };
	pareaDataFrame(receiveData);

	return 0;
}

一下是法二:
上述代码是一个用于处理数据帧的函数processDataFrame和主函数main。其中,processDataFrame函数根据传入的接收到的数据receivedData,查找并处理数据帧。

在主函数main中,定义了一个接收到的数据receivedData,其中包含了多个数据帧以及部分不完整的数据帧。

processDataFrame函数首先声明一个静态变量isIncompleteFrame用于标记是否存在不完整的数据帧。然后通过查找字符串"<frame>"获取第一个数据帧的起始位置frameStart,如果未找到帧头并且上一帧数据还未完整接收到,则直接返回。

之后使用while循环处理多个数据帧的情况,首先查找帧尾的位置frameEnd,如果未找到帧尾则表示接收到的是不完整的数据帧,将isIncompleteFrame设为true,并退出循环。否则,取出完整的数据帧frame,并解析输出数据帧的内容。

继续查找下一个帧头的位置frameStart,直到所有数据帧都被处理完。

最后,如果存在不完整的数据帧且已经处理完所有数据帧,则将剩余数据保存到receivedData以便下次拼接。

主函数main中调用processDataFrame函数,并传入接收到的数据receivedData。

整体来说,该代码实现了处理数据帧的功能,可以识别并处理完整的数据帧,以及保存并等待下次拼接的不完整的数据帧。
```cpp
#include <iostream>
#include <string>
using namespace std;

void processDataFrames(const std::string& receivedData)
{
    static bool isIncompleteFrame = false; // 标记是否存在不完整的数据帧

    // 查找帧头的位置
    size_t frameStart = receivedData.find("<frame>");

    // 如果未找到帧头,表示收到的数据不完整,与上次接收到的数据拼接起来
    if (frameStart == std::string::npos && !isIncompleteFrame) 
    {
        // 上一帧数据还未完整接收到,丢弃这部分数据
        return;
    }

    // 处理多个数据帧的情况
    while (frameStart != std::string::npos) 
    {
        // 查找帧尾的位置
        size_t frameEnd = receivedData.find("</frame>", frameStart);

        // 如果未找到帧尾,表示接收到的是不完整的数据帧
        if (frameEnd == std::string::npos)
        {
            isIncompleteFrame = true;
            break;
        }

        // 取出完整的数据帧
        std::string frame = receivedData.substr(frameStart, frameEnd - frameStart + 8);

        // 解析输出数据帧的内容
        std::cout << "Received data frame: " << frame << std::endl;

        // 继续查找下一个帧头的位置
        frameStart = receivedData.find("<frame>", frameEnd);
    }

    // 如果存在不完整的数据帧,将其保存以便下次拼接
    if (isIncompleteFrame && frameStart == std::string::npos)
    {
        receivedData == receivedData.substr(frameStart);
    }
}

int main() 
{
    std::string receivedData = "<frame>data1</frame><frame>data2</frame><partial_frame/>";

    processDataFrames(receivedData);

    return 0;
}

标签:读取,处理,D2,receivedData,解析,数据,服务端,D1
From: https://www.cnblogs.com/zongmeijiezuishuai/p/17557173.html

相关文章

  • 文件内指针的移动 、内数据的修改 、函数(次函数非数学中的函数)(非常重要)
    文件的操作模式"""1.如果是t模式,read里面写的数字代表的是读取的字符个数2.如果是b模式,read里面写的数字代表的是读取的字节个数3.一个字节代表一个英文字符4.一个中文字符使用三个字节保存"""#withopen('a.txt','r',encoding='utf8')asf:#......
  • 在 3ds Max 中对链模型进行摆放姿势处理
    推荐:NSDT场景编辑器助你快速搭建可二次开发的3D应用场景建模和“摆姿势”3D链可能看起来是一项繁琐的工作,但实际上可以通过使用阵列工具并将链中的链接视为骨骼来轻松完成。在本教程中,我将向您展示如何对链条进行建模,并通过几个简单的步骤对其进行装配。这使您可以以有效的方式......
  • sun.misc.BASE64Decoder包异常问题处理
    之前有一个用的是jdk1.7的,最近导入发现各种报错。sun.misc.BASE64Decoder包找不到了,解决:展开后,找到Accessrules:,增加**,然后保存就可以生效。 ......
  • 7.17 数据结构
    线段树小白逛公园动态维护最大子段和,没啥好说的文文的摄影布置考虑清楚标记分讨合并算术天才⑨与等差数列维护区间最大最小,如果是等差数列,有了端点就可以知道整个序列了,再维护哈希值对比就可以了,突然发现我之前这个解法是乱搞,只有充分性没有必要性,只是题目没有卡正解:维护......
  • MySQL中对JSON数据操作(较全)
    MySQL对JSON数据操作链接:(160条消息)【MySQL】对JSON数据操作(全网最全)_mysqljson_0世界和平0的博客-CSDN博客创建json格式字段CREATETABLE`dept`(`id`int(11)NOTNULL,`dept`varchar(255)DEFAULTNULL,`json_value`jsonDEFAULTNULL,PRIMARYKEY(`id......
  • Elasticsearch date数据类型
    时间和日期类型是我们作为开发每天都会遇到的一种常见数据类型。和Java中有所不同,Elasticsearch 在索引创建之前并不是必须要创建索引的mapping。关系型数据库的思维就是在于写入数据之前,并不强制创建表结构。我们不用事先声明字段名称,字段类型以及长度等属性就可以直接向一个不......
  • 六步带你体验EDS交换数据全流程
    本期我们将走进XX医疗集团向某慢病院共享数据的场景,如何通过EDS完成数据交换,进而实现医疗数据的安全可控共享。本文分享自华为云社区《【EDS从小白到专家】第1期—六步带你体验EDS交换数据全流程》,作者:开天aPaaS小助手。本期我们将走进XX医疗集团向某慢病院共享数据的场景,如......
  • 同比环比数据可视化
    引言数据分析和可视化在现代商业环境中变得越来越重要。随着数据的迅速增长,我们需要有效的工具来解释和理解这些数据。数据可视化提供了一种直观的方式,帮助我们从海量数据中提取有意义的见解,以支持业务决策。 同比环比图作为一种常见的数据可视化工具,提供了一种简单而有效的......
  • 案例:基于RESTful页面数据交互
        ......
  • JAVA实现多数据源分页
    简述下思路:举例:有A,B两个数据源的数据需要查询展示,优先展示A数据源数据,我使用的数据源是clickhouse+mysql1,因为需要优先展示A数据源嘛,那就直接通过条件优先查询A数据源数据2,然后根据分页情况,判断A数据源数据量是否满足当前分页所需数据量。   有以下三种情况:先获取当前......