嵌入式开发中自定义协议的解析与组包

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: 嵌入式开发中自定义协议的解析与组包

在嵌入式产品开发中,经常会遇到两个设备之间的通信、设备与服务器的通信、设备和上位机的通信等,很多时候通信协议都是自定义的,所以就涉及到自定义协议的解析和组包问题。

比如针对下面的这样一个协议:

帧头1 帧头2 字段1 字段2 校验
固定值:0x55 固定值:0xAA 设备ID 电压值 前面所有数据异或值
char char short float char
1字节 1字节 2字节 4字节 1字节

数据在发送时涉及到一个大小端的概念,大小端是针对多字节数据的传输,比如上述协议中字段1,假设两字节内容为0x0001,先发送0x01后发送0x00,称为小端模式;先发送0x00后发送0x01,称为大端模式。

假设字段1内容为0x001,字段2内容为0x40533333 (对应为3.3)

假设按照小端方式发送,下面是帧数据:

55 AA 01 00 33 33 53 40 ED

下面来看看如何解析,

若干年前,在第一次面对这种问题时,用的如下傻瓜式的代码方式实现:

#include <stdio.h>
int main()
{
    unsigned char Rxbuf[9] = {0x55,0xAA,0x01,0x00,0x33,0x33,0x53,0x40,0xED};
    short DeviceId;
    float Voltage;
    unsigned char check = 0;
    int i;
    for(i=0;i<8;i++)
    {
        check ^= Rxbuf[i];
    }
    if(Rxbuf[0]==0x55 && Rxbuf[1]==0xAA && Rxbuf[8]==check )
    {
        DeviceId=(Rxbuf[3]<<8)|Rxbuf[2];
        Voltage= *((float *)&Rxbuf[4]);
        printf("DeviceId:%d\n",DeviceId);
        printf("Voltage:%f\n",Voltage);
    }
    return 0;
}

简单来说就是硬来,按照数组的先后顺序逐个重组解析,如果协议比较长,代码里会充斥着很多的数组下标,一不小心就数错了。而且如果更改协议的话,代码要改动很多地方。

后来有人告诉我可以定义个结构体,然后使用memcpy函数直接复制过去就完事了,

#include <stdio.h>
#include <string.h>
#pragma pack(1)
struct RxFrame
{
    unsigned char header1;   
    unsigned char header2;   
    short deviceId;  
    float voltage;    
    unsigned char check;
};
int main()
{
    unsigned char Rxbuf[9] = {0x55,0xAA,0x01,0x00,0x33,0x33,0x53,0x40,0xED};
    struct RxFrame RxData;
    unsigned char check = 0;
    int i;
    for(i=0;i<8;i++)
    {
        check ^= Rxbuf[i];
    }
    memcpy(&RxData,Rxbuf,sizeof(Rxbuf));
    if(Rxbuf[0]==0x55 && Rxbuf[1]==0xAA && RxData.check==check )
    {
        printf("DeviceId:%d\n",RxData.deviceId);
        printf("Voltage:%f\n",RxData.voltage);
    }
    return 0;
}

嗯,的确是方便了很多。不过该方式仅适合小端传输方式。

再后来,又见到有人用如下代码实现,

#include <stdio.h>
#include "convert.h"
int main()
{
    unsigned char Rxbuf[9] = {0x55,0xAA,0x01,0x00,0x33,0x33,0x53,0x40,0xED};
    short DeviceId;
    float Voltage;
    unsigned char check = 0;
    int i;
    int index = 0;
    for(i=0;i<8;i++)
    {
        check ^= Rxbuf[i];
    }
    if(Rxbuf[0]==0x55 && Rxbuf[1]==0xAA && Rxbuf[8]==check )
    {
        index += 2;
        ByteToShort(Rxbuf, &index, &DeviceId);
        ByteToFloat(Rxbuf, &index, &Voltage);
        printf("DeviceId:%d\n",DeviceId);
        printf("Voltage:%f\n",Voltage);
    }
    return 0;
}

其中convert.h如下:

#ifndef CONVERT_H
#define CONVERT_H
void  ShortToByte(unsigned char* dest, int* index, short value);
void  FloatToByte(char* dest, int* index, float value);
#endif // CONVERT_H

convert.c如下:

#include "convert.h"
#include <string.h>
#include <stdbool.h>
static bool Endianflag = 0;
void ByteToShort(const unsigned char* source, int* index, short* result)
{
    int i, len = sizeof(short);
    char p[len];
    memset(p, 0, len);
    if(Endianflag == 1 )
    {
        for( i = 0; i < len; i++ )
            *(p+i) = *(source + *index + len - i - 1);
    }
    else
    {
        for( i = 0; i < len; i++ )
            *(p+i) = *(source + *index + i);
    }
    *result = *((short*)p);
    *index += len;
}
void ByteToFloat(unsigned char* source, int* index, float* result)
{
    int i, len = sizeof(float);
    char p[len];
    memset(p, 0, len);
    if(Endianflag == 1 )
    {
        for( i = 0; i < len; i++ )
            *(p+i) = *(source + *index + len - i - 1);
    }
    else
    {
        for( i = 0; i < len; i++ )
            *(p+i) = *(source + *index + i);
    }
    *result = *((float*)p);
    *index += len;
}

该方法既可以支持小端模式,也可以支持大端模式,使用起来也是比较方便。

除了上述2个函数,完整的转换包含以下函数,就是将Bytes转换为不同的数据类型,以及将不同的数据类型转换为Bytes。

#ifndef CONVERT_H
#define CONVERT_H
void  ByteToShort(const unsigned char* source, int* index, short* result);
void  ByteToInt(unsigned char* source, int* index, int* result);
void  ByteToLong(char* source, int* index, long long* result);
void  ByteToFloat(unsigned char* source, int* index, float* result);
void  ByteToDouble(unsigned char* source, int* index, double* result);
void  ByteToString(unsigned char* source, int* index, char* result, int length);
void  ShortToByte(unsigned char* dest, int* index, short value);
void  IntToByte(char* dest, int* index, int value);
void  LongToByte(char* dest, int* index, long long value);
void  FloatToByte(char* dest, int* index, float value);
void  DoubleToByte(unsigned char* dest, int* index, double value);
void  StringToByte(char* dest, int* index, int length, char* value);
#endif // CONVERT_H

组包的过程和解析的过程正好相反,这里不再赘述。你在开发中遇到这种问题,是如何处理的呢?欢迎留言讨论

相关文章
|
19天前
|
并行计算 Java 数据处理
SpringBoot高级并发实践:自定义线程池与@Async异步调用深度解析
SpringBoot高级并发实践:自定义线程池与@Async异步调用深度解析
99 0
|
14天前
|
开发框架 供应链 监控
并行开发模型详解:类型、步骤及其应用解析
在现代研发环境中,企业需要在有限时间内推出高质量的产品,以满足客户不断变化的需求。传统的线性开发模式往往拖慢进度,导致资源浪费和延迟交付。并行开发模型通过允许多个开发阶段同时进行,极大提高了产品开发的效率和响应能力。本文将深入解析并行开发模型,涵盖其类型、步骤及如何通过辅助工具优化团队协作和管理工作流。
47 3
|
17天前
|
XML JSON API
ServiceStack:不仅仅是一个高性能Web API和微服务框架,更是一站式解决方案——深入解析其多协议支持及简便开发流程,带您体验前所未有的.NET开发效率革命
【10月更文挑战第9天】ServiceStack 是一个高性能的 Web API 和微服务框架,支持 JSON、XML、CSV 等多种数据格式。它简化了 .NET 应用的开发流程,提供了直观的 RESTful 服务构建方式。ServiceStack 支持高并发请求和复杂业务逻辑,安装简单,通过 NuGet 包管理器即可快速集成。示例代码展示了如何创建一个返回当前日期的简单服务,包括定义请求和响应 DTO、实现服务逻辑、配置路由和宿主。ServiceStack 还支持 WebSocket、SignalR 等实时通信协议,具备自动验证、自动过滤器等丰富功能,适合快速搭建高性能、可扩展的服务端应用。
75 3
|
19天前
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
99 2
|
3天前
|
监控 安全 Serverless
"揭秘D2终端大会热点技术:Serverless架构最佳实践全解析,让你的开发效率翻倍,迈向技术新高峰!"
【10月更文挑战第23天】D2终端大会汇聚了众多前沿技术,其中Serverless架构备受瞩目。它让开发者无需关注服务器管理,专注于业务逻辑,提高开发效率。本文介绍了选择合适平台、设计合理函数架构、优化性能及安全监控的最佳实践,助力开发者充分挖掘Serverless潜力,推动技术发展。
9 1
|
11天前
|
机器学习/深度学习 安全 搜索推荐
中国CRM市场深度解析:本土化定制开发的领军厂商与未来趋势
国内CRM软件企业正面临日益增长的本土定制需求,这不仅考验服务商的综合水平,也推动了市场的快速发展。本文将深入解析中国CRM市场的现状,探讨领军厂商的优势,并预测未来趋势,包括智能化、集成化、本土化与国际化并行及云服务模式的普及。
|
19天前
|
XML Java 数据格式
手动开发-简单的Spring基于注解配置的程序--源码解析
手动开发-简单的Spring基于注解配置的程序--源码解析
34 0
|
19天前
|
XML Java 数据格式
手动开发-简单的Spring基于XML配置的程序--源码解析
手动开发-简单的Spring基于XML配置的程序--源码解析
61 0
|
21天前
|
程序员 开发者 Python
深度解析Python中的元编程:从装饰器到自定义类创建工具
【10月更文挑战第5天】在现代软件开发中,元编程是一种高级技术,它允许程序员编写能够生成或修改其他程序的代码。这使得开发者可以更灵活地控制和扩展他们的应用逻辑。Python作为一种动态类型语言,提供了丰富的元编程特性,如装饰器、元类以及动态函数和类的创建等。本文将深入探讨这些特性,并通过具体的代码示例来展示如何有效地利用它们。
23 0
|
24天前
|
前端开发 JavaScript Java
【SpringBoot系列】视图解析器的搭建与开发
【SpringBoot系列】视图解析器的搭建与开发
20 0

推荐镜像

更多