海思3559万能平台搭建:协议的采集和解析

简介: 海思3559万能平台搭建:协议的采集和解析

前言

 做应用的话就难免和协议打交道,最基础简单的协议可能像单片机一样通篇的ifelse,虽然逻辑严谨,但是不论编写还是理解难度都非常头疼。且一旦更改协议,直呼骂人。介绍一种协议定长时的处理方法,解析包头内容和校验,这样搭配着我们之前提到的配置文件,理论上甚至变协议可以做到代码和变文档一样简单!

原理

 既然是定长的协议,那就可以使用一个固定容量的队列用来缓存接收到的数据,队列容量等于一帧数据的大小,每来一个数据就将数据往队列里面加,当完整接收到一帧数据时此时队列中的全部数据也就是一帧完整的数据,因此只需要判断队列是否是数据校验头,队列尾是否是数据校验尾就可以得知当前是否已经接收到了一帧完整的数据,然后在将数据从队列中取出即可。原理图如下:

 每来一个数据就往队列里面加:

9831d28417ac4f6ea1f69533b04bf120.png

 当接收到一帧完整数据时队列头和数据校验头重合:

5a118482441c45e8a46c803bae6d13bc.png

 此时只需要从队列中取出有效数据即可。

 如果有数据尾校验,仅仅只需要添加一个校验尾即可,如下图所示:

67f0cb33eede42b3b42b7ba7774cf919.png

代码

 首先需要一个队列,为了保证通用性,队列底层使用类似于双向链表的实现(当然也可以使用数组实现),需要封装的结构有队列容量、队列大小、队头节点和队尾节点,需要实现的操作有队列初始化、数据入队、数据出队、清空队列和释放队列,

queue.h

#ifndef _QUEUE_H_
#define _QUEUE_H_
#ifndef NULL
#define NULL  ((void *)0)
#endif
typedef unsigned char uint8;
/* 队列节点 */
typedef struct Node
{
  uint8 data;
  struct Node *pre_node;
  struct Node *next_node;
} Node;
/* 队列结构 */
typedef struct Queue
{
  uint8 capacity;     // 队列总容量
  uint8 size;         // 当前队列大小
  Node *front;        // 队列头节点
  Node *back;         // 队列尾节点
} Queue;
/* 初始化一个队列 */
Queue *init_pqueue(uint8 _capacity);
/* 数据入队 */
uint8 en_queue(Queue *_queue, uint8 _data);
/* 数据出队 */
uint8 de_queue(Queue *_queue);
/* 清空队列 */
void clear_queue(Queue *_queue);
/* 释放队列 */
void release_queue(Queue *_queue);
#endif

queue.c

#include <stdlib.h>
#include "parser.h"
/**
 * 初始化一个队列
 *
 * @_capacity: 队列总容量
 */
Queue *init_pqueue(uint8 _capacity)
{
  Queue *queue = (Queue *)malloc(sizeof(Queue));
  queue->capacity = _capacity;
  queue->size = 0;
  return queue;
}
/**
 * 数据入队
 *
 * @_queue: 队列
 * @_data: 数据
 **/
uint8 en_queue(Queue *_queue, uint8 _data)
{
  if(_queue->size < _queue->capacity)
  {
  Node *node = (Node *)malloc(sizeof(Node));
  node->data = _data;
  node->next_node = NULL;
        if(_queue->size == 0)
        {
            node->pre_node = NULL;
            _queue->back = node;
            _queue->front = _queue->back;
        }
        else
        {
            node->pre_node = _queue->back;
            _queue->back->next_node = node;
            _queue->back = _queue->back->next_node;
        }
  _queue->size++;
  }
  else
  {
  Node *temp_node = _queue->front->next_node;
  _queue->front->pre_node = _queue->back;
  _queue->back->next_node = _queue->front;
  _queue->back = _queue->back->next_node;
  _queue->back->data = _data;
  _queue->back->next_node = NULL;
  _queue->front = temp_node;
  }
  return _queue->size-1;
}
/**
 * 数据出队
 *
 * @_queue: 队列
 *
 * @return: 出队的数据
 */
uint8 de_queue(Queue *_queue)
{
    uint8 old_data = 0;
    if(_queue->size > 0)
    {
        old_data = _queue->front->data;
        if(_queue->size == 1)
        {
            free(_queue->front);
            _queue->front = NULL;
            _queue->back = NULL;
        }
        else
        {
            _queue->front = _queue->front->next_node;
            free(_queue->front->pre_node);
            _queue->front->pre_node = NULL;
        }
        _queue->size--;
    }
    return old_data;
}
/**
 * 清空队列
 *
 * @_queue: 队列
 */
void clear_queue(Queue *_queue)
{
    while(_queue->size > 0)
    {
        de_queue(_queue);
    }
}
/**
 * 释放队列
 *
 * @_queue: 队列
 */
void release_queue(Queue *_queue)
{
    clear_queue(_queue);
    free(_queue);
    _queue = NULL;
}

 其次是解析器,需要封装的结构有解析数据队列、数据校验头、数据校验尾、解析结果以及指向解析结果的指针,需要实现的操作有解析器初始化、添加数据解析、获取解析结果、重置解析器和释放解析器,具体代码如下:

parser.h

#ifndef _PARSER_H_
#define _PARSER_H_
#include "queue.h"
typedef enum
{
    RESULT_FALSE,
    RESULT_TRUE
} ParserResult;
/* 解析器结构 */
typedef struct DataParser
{
    Queue *parser_queue; // 数据解析队列
    Node *resule_pointer;// 解析结果数据指针
    uint8 *data_header;// 数据校验头指针
    uint8 header_size;// 数据校验头大小
    uint8 *data_footer;// 数据校验尾指针
    uint8 footer_size;// 数据校验尾大小
    uint8 result_size;// 解析数据大小
    ParserResult parserResult;// 解析结果
} DataParser;
/* 初始化一个解析器 */
DataParser *parser_init(uint8 *_data_header, uint8 _header_size, uint8 *_data_footer, uint8 _foot_size, uint8 _data_frame_size);
/* 将数据添加到解析器中进行解析 */
ParserResult parser_put_data(DataParser *_parser, uint8 _data);
/* 解析成功后从解析器中取出解析结果 */
int parser_get_data(DataParser *_parser, uint8 _index);
/* 重置解析器 */
void parser_reset(DataParser *_parser);
/* 释放解析器 */
void parser_release(DataParser *_parser);
#endif

parser.c

#include <stdlib.h>
#include "parser.h"
/**
 * 初始化一个解析器
 *
 * @_data_header: 数据头指针
 * @_header_size: 数据头大小
 * @_data_footer: 数据尾指针
 * @_foot_size: 数据尾大小
 * @_data_frame_size: 一帧完整数据的大小
 *
 * @return: 解析器
 */
DataParser *parser_init(uint8 *_data_header, uint8 _header_size, uint8 *_data_footer, uint8 _foot_size, uint8 _data_frame_size)
{
    if((_header_size+_foot_size) > _data_frame_size || (_header_size+_foot_size) == 0)
        return NULL;
    DataParser *parser = (DataParser *)malloc(sizeof(DataParser));
    parser->parser_queue = init_pqueue(_data_frame_size);
    parser->resule_pointer = NULL;
    parser->data_header = _data_header;
    parser->header_size = _header_size;
  parser->data_footer = _data_footer;
  parser->footer_size = _foot_size;
    parser->result_size = _data_frame_size - parser->header_size - parser->footer_size;
    parser->parserResult = RESULT_FALSE;
    while(_data_frame_size-- > 0)
    {
        en_queue(parser->parser_queue, 0);
    }
    return parser;
}
/**
 * 将数据添加到解析器中进行解析
 *
 * @_parser: 解析器
 * @_data: 要解析的数据
 *
 * @return: 当前解析结果,返回 RESULT_TRUE 代表成功解析出一帧数据
 */
ParserResult parser_put_data(DataParser *_parser, uint8 _data)
{
    uint8 i;
    Node *node;
  if(_parser == NULL)
  return RESULT_FALSE;
    en_queue(_parser->parser_queue, _data);
  /* 校验数据尾 */
  node = _parser->parser_queue->back;
  for(i = _parser->footer_size; i > 0; i--)
  {
  if(node->data != _parser->data_footer[i-1])
            goto DATA_FRAME_FALSE;
        node = node->pre_node;
  }
  /* 校验数据头 */
    node = _parser->parser_queue->front;
    for(i = 0; i < _parser->header_size; i++)
    {
        if(node->data != _parser->data_header[i])
            goto DATA_FRAME_FALSE;
        node = node->next_node;
    }
    if(_parser->resule_pointer == NULL && _parser->result_size > 0)
        _parser->resule_pointer = node;
    if(_parser->parserResult != RESULT_TRUE)
      _parser->parserResult = RESULT_TRUE;
    return _parser->parserResult;
DATA_FRAME_FALSE:
    if(_parser->resule_pointer != NULL)
        _parser->resule_pointer = NULL;
    if(_parser->parserResult != RESULT_FALSE)
        _parser->parserResult = RESULT_FALSE;
    return _parser->parserResult;
}
/**
 * 解析成功后从解析器中取出解析结果
 *
 * @_parser: 解析器
 * @_index: 解析结果集合中的第 _index 个数据
 *
 * @return: 获取解析成功的数据,返回 -1 代表数据获取失败
 */
int parser_get_data(DataParser *_parser, uint8 _index)
{
    Node *node;
    if(_parser == NULL
  || _parser->parserResult != RESULT_TRUE
    || _index >= _parser->result_size
    || _parser->resule_pointer == NULL)
        return -1;
    node = _parser->resule_pointer;
    while(_index > 0)
    {
        node = node->next_node;
        _index--;
    }
    return node->data;
}
/**
 * 重置解析器
 *
 * @_parser: 解析器
 */
void parser_reset(DataParser *_parser)
{
  uint8 _data_frame_size;
  if(_parser == NULL)
  return;
  _data_frame_size = _parser->parser_queue->size;
  while(_data_frame_size-- > 0)
    {
        en_queue(_parser->parser_queue, 0);
    }
    _parser->resule_pointer = NULL;
    _parser->parserResult = RESULT_FALSE;
}
/**
 * 释放解析器
 *
 * @_parser: 解析器
 */
void parser_release(DataParser *_parser)
{
  if(_parser == NULL)
  return;
    release_queue(_parser->parser_queue);
    free(_parser);
    _parser = NULL;
}

校验尾

 校验尾大多数不会是固定的,所以我们在添加时校验尾是空着的,当数据处理,收到后在单独计算,比如最简单的累加校验。累加除去头和尾的所有数据,取低8位,就可以申明一个u8的变量,然后收到后去头去尾累加,和尾比较,一样的话进行处理,不一样则这一包清空不作数


HI_VOID * eth_client_recv_task(HI_VOID *arg)
{
  cpu_set_t mask;//cpu核的集合
    cpu_set_t get;//获取在集合中的cpu
    int num = sysconf(_SC_NPROCESSORS_CONF);
    printf("frame_check_task:system has %d processor(s)\n", num);
    CPU_ZERO(&mask);//置空
    CPU_SET(0, &mask);//设置亲和力值
    if (pthread_setaffinity_np(pthread_self(), sizeof(mask), &mask) < 0)//设置线程CPU亲和力
    {
        fprintf(stderr, "set thread affinity failed\n");
    }
    if (pthread_getaffinity_np(pthread_self(), sizeof(get), &get) < 0)//获取线程CPU亲和力
    {
        fprintf(stderr, "get thread affinity failed\n");
    }
  int connect_fd = -1;
    int recv_len,i;
  struct sockaddr_in server;
  socklen_t saddrlen = sizeof(server);
  uint8 data_header[] = {0x55, 0xAA};
  unsigned char addchk=0;
  // uint8 data_header[] = {0x55, 0xAA};
  // printf("\nbeforen DataParser\n ");
  DataParser *data_parser = parser_init(data_header, sizeof(data_header), NULL, 0, CMD_LENGTH);
  // printf("\nDataParser INIT SUCCESS\n ");
  char* tmp_cmd=(char*)&g_trk_cmd;
  memset(&server, 0, sizeof(server));
  connect_fd = socket(AF_INET, SOCK_STREAM, 0);
  if (connect_fd < 0)
  {
  printf("socket error!\n");
  // return NULL;
  }
    printf("\nETH CLIENT RECV TEST\n");
  server.sin_family = AF_INET;
  server.sin_port = htons(SERVER_PORT);
  server.sin_addr.s_addr = inet_addr(SERVER_IP);
  if (connect(connect_fd, (struct sockaddr *)&server, saddrlen) < 0)
  {
  printf("connect failed!\n");
  // return -1;
  }
  char recvbuf[1024]={0};
  while (1)
  {
  recv_len = read(connect_fd, recvbuf, sizeof(recvbuf));
        if(recv_len)
        {
            // printf("\nEth:recv origin data: ");
            // recvbuf[len] = '\0';  
            for (i = 0;i < recv_len ;i++)
            {
                // printf("%02x  ",recvbuf[i]);  
    if(parser_put_data(data_parser, recvbuf[i]) == RESULT_TRUE)
    {
      // printf("成功解析出一帧数据...\n");
      // /* 一位一位取出解析后的数据 */
      // g_trk_cmd.length  = parser_get_data(data_parser, 0);
      // g_trk_cmd.CmdType = parser_get_data(data_parser, 1);
      // for (int ii = 0; ii < DATA_LENGTH ;ii++)
      // {
      //  g_trk_cmd.Para[ii] = parser_get_data(data_parser, 2+ii);
      // }
      tmp_cmd=(char*)&g_trk_cmd;
      // memset_s(tmp_cmd, sizeof(g_trk_cmd)-sizeof(data_header), 0, sizeof(g_trk_cmd)-sizeof(data_header));
      addchk = 0;
      for(int jj=0;jj<sizeof(g_trk_cmd)-sizeof(data_header);jj++)
      {
      tmp_cmd[jj+2]=parser_get_data(data_parser, jj);
      printf("%x ",tmp_cmd[jj+2]);
      }
      printf("\n");
      for(int kk = 2;kk < sizeof(g_trk_cmd)-1;kk++)
      {
      // addchk += parser_get_data(data_parser, kk);
       addchk+=tmp_cmd[kk];
       printf("%x ",tmp_cmd[kk]);
      }
      printf("\n");
      printf("addchk is %x\n",addchk);
      g_trk_cmd.addchk = parser_get_data(data_parser,(CMD_LENGTH-1-2));//第19个数,从0开始计18,故-1,又因为去头在-2
      printf("g_trk_cmd.addchk is %x\n",g_trk_cmd.addchk);
      if(addchk != g_trk_cmd.addchk )
      {
      memset_s(((uint8_t*)&g_trk_cmd)+2,
                        sizeof(g_trk_cmd)-sizeof(data_header),
                        0,
                        sizeof(g_trk_cmd)-sizeof(data_header));
                  continue;
      }
      g_trk_cmd.length  = tmp_cmd[2];
      printf(" tmp_cmd[2]is %x\n",tmp_cmd[2]);
      g_trk_cmd.CmdType = tmp_cmd[3];
      printf(" tmp_cmd[3]is %x\n",tmp_cmd[3]);
      for (int ii = 0; ii < DATA_LENGTH ;ii++)
      {
      g_trk_cmd.Para[ii] = tmp_cmd[ii+4];;
      }
      // printf("数据长度是0x%x\n", parser_get_data(data_parser, 0));
      // printf("功能码是0x%x\n", parser_get_data(data_parser, 1));
      // printf("具体功能是0x%x\n\n\n", parser_get_data(data_parser, 2));
    }
            }
            // printf("\n"); 
        }
  protocol_parser();
  }
  close(connect_fd);
  return 0;
}

效果

 可以看到 ,如果没按照协议发送的,只会打印原始数据,按照协议发送的,就会按照我们预设将包拆开,方便提取功能码和数据内容

8e183e272a344363a562df71da633848.png

补充

 结合之前的配置文件,协议规定内容均由配置文件给出的话,一定程度上可以做到功能更改而不需要重新编译应用只需要更改配置文件


相关文章
|
7月前
|
存储 算法 安全
.NET 平台 SM2 国密算法 License 证书生成深度解析
授权证书文件的后缀通常取决于其编码格式和具体用途。本文档通过一个示例程序展示了如何在 .NET 平台上使用国密 SM2 算法生成和验证许可证(License)文件。该示例不仅详细演示了 SM2 国密算法的实际应用场景,还提供了关于如何高效处理大规模许可证文件生成任务的技术参考。通过对不同并发策略的性能测试,开发者可以更好地理解如何优化许可证生成流程,以满足高并发和大数据量的需求。 希望这段描述更清晰地传达了程序的功能和技术亮点。
721 14
.NET 平台 SM2 国密算法 License 证书生成深度解析
|
7月前
|
Web App开发 数据采集 开发者
某查”平台请求头反爬技术解析与应对
某查”平台请求头反爬技术解析与应对
|
6月前
|
弹性计算 运维 安全
优化管理与服务:操作系统控制平台的订阅功能解析
本文介绍了如何通过操作系统控制平台提升系统效率,优化资源利用。首先,通过阿里云官方平台开通服务并安装SysOM组件,体验操作系统控制平台的功能。接着,详细讲解了订阅管理功能,包括创建订阅、查看和管理ECS实例的私有YUM仓库权限。订阅私有YUM仓库能够集中管理软件包版本、提升安全性,并提供灵活的配置选项。最后总结指出,使用阿里云的订阅和私有YUM仓库功能,可以提高系统可靠性和运维效率,确保业务顺畅运行。
|
5月前
|
网络协议
为何UDP协议不可靠?DNS为何选择UDP?
总的来说,UDP和TCP各有优势,选择哪种协议取决于应用的具体需求。UDP可能不如TCP可靠,但其简单、快速的特性使其在某些场景下成为更好的选择。而DNS就是这样的一个例子,它利用了UDP的优势,以实现快速、高效的名字解析服务。
265 14
|
6月前
|
数据采集 安全 数据挖掘
淘宝天猫宝贝详情页面商品评论采集接口全解析
淘宝天猫商品评论采集接口为电商数据挖掘提供了重要工具。通过分析海量评论,消费者可获取购买决策参考,商家能优化产品与服务,市场研究者则能洞察行业趋势与竞品表现。该接口支持Python请求,助力开发者构建智能分析应用,推动电商生态中各方价值提升。使用时需遵守平台规则,确保数据安全与合法利用。
203 15
|
7月前
|
存储 缓存 网络协议
DNS协议详解
通过本文,您可以全面了解DNS协议的各个方面,从而更好地理解和应用这一重要的互联网基础服务。
1475 44
|
6月前
|
编解码 监控 网络协议
RTSP协议规范与SmartMediaKit播放器技术解析
RTSP协议是实时流媒体传输的重要规范,大牛直播SDK的rtsp播放器基于此构建,具备跨平台支持、超低延迟(100-300ms)、多实例播放、高效资源利用、音视频同步等优势。它广泛应用于安防监控、远程教学等领域,提供实时录像、快照等功能,优化网络传输与解码效率,并通过事件回调机制保障稳定性。作为高性能解决方案,它推动了实时流媒体技术的发展。
316 5
|
6月前
|
监控 Shell Linux
Android调试终极指南:ADB安装+多设备连接+ANR日志抓取全流程解析,覆盖环境变量配置/多设备调试/ANR日志分析全流程,附Win/Mac/Linux三平台解决方案
ADB(Android Debug Bridge)是安卓开发中的重要工具,用于连接电脑与安卓设备,实现文件传输、应用管理、日志抓取等功能。本文介绍了 ADB 的基本概念、安装配置及常用命令。包括:1) 基本命令如 `adb version` 和 `adb devices`;2) 权限操作如 `adb root` 和 `adb shell`;3) APK 操作如安装、卸载应用;4) 文件传输如 `adb push` 和 `adb pull`;5) 日志记录如 `adb logcat`;6) 系统信息获取如屏幕截图和录屏。通过这些功能,用户可高效调试和管理安卓设备。
|
9月前
|
人工智能 搜索推荐 API
Cobalt:开源的流媒体下载工具,支持解析和下载全平台的视频、音频和图片,支持多种视频质量和格式,自动提取视频字幕
cobalt 是一款开源的流媒体下载工具,支持全平台视频、音频和图片下载,提供纯净、简洁无广告的体验
1297 9
Cobalt:开源的流媒体下载工具,支持解析和下载全平台的视频、音频和图片,支持多种视频质量和格式,自动提取视频字幕
|
9月前
|
JSON JavaScript 前端开发
一次采集JSON解析错误的修复
两段采集来的JSON格式数据存在格式问题,直接使用PHP的`json_decode`会报错。解决思路包括:1) 手动格式化并逐行排查错误;2) 使用PHP-V8JS扩展在JavaScript环境中解析。具体方案一是通过正则表达式和字符串替换修复格式,方案二是利用V8Js引擎执行JS代码并返回JSON字符串,最终实现正确解析。 简介: 两段采集的JSON数据因掺杂JavaScript代码导致PHP解析失败。解决方案包括手动格式化修复和使用PHP-V8JS扩展在JavaScript环境中解析,确保JSON数据能被正确处理。

推荐镜像

更多
  • DNS