前言
做应用的话就难免和协议打交道,最基础简单的协议可能像单片机一样通篇的ifelse,虽然逻辑严谨,但是不论编写还是理解难度都非常头疼。且一旦更改协议,直呼骂人。介绍一种协议定长时的处理方法,解析包头内容和校验,这样搭配着我们之前提到的配置文件,理论上甚至变协议可以做到代码和变文档一样简单!
原理
既然是定长的协议,那就可以使用一个固定容量的队列用来缓存接收到的数据,队列容量等于一帧数据的大小,每来一个数据就将数据往队列里面加,当完整接收到一帧数据时此时队列中的全部数据也就是一帧完整的数据,因此只需要判断队列是否是数据校验头,队列尾是否是数据校验尾就可以得知当前是否已经接收到了一帧完整的数据,然后在将数据从队列中取出即可。原理图如下:
每来一个数据就往队列里面加:
当接收到一帧完整数据时队列头和数据校验头重合:
此时只需要从队列中取出有效数据即可。
如果有数据尾校验,仅仅只需要添加一个校验尾即可,如下图所示:
代码
首先需要一个队列,为了保证通用性,队列底层使用类似于双向链表的实现(当然也可以使用数组实现),需要封装的结构有队列容量、队列大小、队头节点和队尾节点,需要实现的操作有队列初始化、数据入队、数据出队、清空队列和释放队列,
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; }
效果
可以看到 ,如果没按照协议发送的,只会打印原始数据,按照协议发送的,就会按照我们预设将包拆开,方便提取功能码和数据内容
补充
结合之前的配置文件,协议规定内容均由配置文件给出的话,一定程度上可以做到功能更改而不需要重新编译应用只需要更改配置文件