【问题标题】:How to use structure with dynamically changing size of data?如何使用动态改变数据大小的结构?
【发布时间】:2019-03-01 01:29:39
【问题描述】:

仅针对 C 的问题,C++ 和向量不能解决问题。

我有这样的结构:

typedef __packed struct Packet_s
{
  U8  head;
  U16 len;
  U32 id;
  U8  data;
  U8  end;
  U16 crc;
} Packet_t, *Packet_p;

编辑:U8是uint8_t(无符号字符)等等)

例如,我收到了数据包(十六进制):

24 0B 00 07 00 00 00 AA 0D 16 1C

在哪里

头 = 0x24

len = 0x0B 0x00

id = 0x07 0x00 0x00 0x00

数据 = 0xAA

结束 = 0x0D

crc = 0x16 0x1C

我可以像这样从传入的缓冲区复制它

U8 Buffer[SIZE]; // receives all bytes here
memcpy(&Packet, &Buffer, buffer_len);

并继续努力。

如果字段“DATA”超过 1 个字节,是否可以使用我的结构?

我该如何处理这样的事情?

24 0F 00 07 00 00 00 AA BB CC DD EE 0D BD 66

数据包的长度总是已知的(2 和 3 个字节有长度信息)。

编辑:在“句柄”下我的意思是我接下来要做:

  if (isCRCmatch() )
  {
    if(Packet.id == SPECIAL_ID_1)
    {
      // do something
    }

    if(Packet.id == SPECIAL_ID_2)
    {
      // do other 
    }

    if(Packet.data[0] == 0xAA)
    {
      // do one stuff
    }

    if(Packet.data[1] == 0xBB && Packet.id == SPECIAL_ID_3 )
    {
      // do another stuff
    }

  }

而且(如果可能的话)我想使用相同的结构发送“答案”:

U8 rspData[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06};

SendResponce(Packet.id, rspData);

void SendResponce (U8 id_rsp, uint8_t* data_rsp)
{
  Packet_t ResponceData;
  uint16_t crc;
  uint8_t *data;

  ResponceData.head  = 0x24;
  ResponceData.len   = sizeof(ResponceData); // HERE IS PROBLEM ONE
  ResponceData.id   = id_rsp;
  ResponceData.data  = *data_rsp; // HERE IS PROBLEM TWO
  ResponceData.end   = 0x0D; // symbol '\r'

  data = (uint8_t*)&ResponceData;
  crc = GetCrc(data, sizeof(ResponceData)-2); // last 2 bytes with crc

  ResponceData.crc = crc;//(ack_crc >> 8 | ack_crc);

  SendData((U8*)&ResponceData, sizeof(ResponceData));  // Send rsp packet
}

第一个问题 - 我不能自动获取所有结构的大小,因为指针总是 4 字节... 第二个问题 - 我确定我会丢失 rsp 数据,因为我不知道它在哪里结束。

【问题讨论】:

  • 数据永远不会超过一个字节,因为它被声明为 U8 数据;
  • U8 类型是什么?
  • U8 是 uint8_t(无符号字符)
  • 那么我怎样才能将第二个数据示例“放置”到结构中呢?我应该用指针 U8* 数据声明新结构吗?怎么会这样包装?...
  • 从通信的角度来看,显然U8 data; 在概念上是U8 data[len - 10];。作为 C 结构,中间的可变长度数组是不允许的,但我敢打赌,这是用于序列化消息的数据包。 OP 需要发布更多内容以显示“处理”目标是什么。 “我怎么能处理这样的事情?”太模糊了。

标签: c structure packet packet-capture


【解决方案1】:

如果字段“DATA”超过 1 个字节,是否可以使用我的结构?

不,因为它只有 1 个data 字节的空间。但您可以使用稍作修改的结构版本。

typedef __packed struct Packet_s
{
  U8  head;
  U16 len;
  U32 id;
  U8  data[DATALENMAX]; // define appropriately
  U8  end;
  U16 crc;
} Packet_t, *Packet_p;

当然,您必须相应地调整复制:

memcpy(&Packet, &Buffer, buffer_len), memmove(&Packet.end, &Packet.data[buffer_len-7-3], 3);

关于添加的问题,需要将数据长度传递给SendResponce()

SendResponce(rspData, sizeof rspData);

void SendResponce(uint8_t* data_rsp, int datalen)
{
  Packet_t ResponceData;
  uint16_t crc;
  uint8_t *data;

  ResponceData.head  = 0x24;
  ResponceData.len   = 7+datalen+3; // HERE WAS PROBLEM ONE
  ResponceData.id   = SPECIAL_ID_X;
  memcpy(ResponceData.data, data_rsp, datalen); // HERE WAS PROBLEM TWO
  ResponceData.data[datalen] = 0x0D; // symbol '\r'
  data = (uint8_t*)&ResponceData;
  crc = GetCrc(data, 7+datalen+1); // last 2 bytes with crc
  ResponceData.crc = crc;//(ack_crc >> 8 | ack_crc);
  memmove(ResponceData.data+datalen+1, &ResponceData.crc, 2);
  SendData((U8*)&ResponceData, ResponceData.len);  // Send rsp packet
}

【讨论】:

    【解决方案2】:

    你不能在结构中间有动态缓冲区。

    解决问题的另一种方法是将结构分成两部分。 例如(注意data 在这里是flexible array member):

    typedef __packed struct Packet_s
    {
      U8  head;
      U16 len;
      U32 id;
      U8  data[];
    } Packet_t, *Packet_p;
    
    typedef __packed struct PacketEnd_s
    {
      U8  end;
      U16 crc;
    } PacketEnd_t, *PacketEnd_p;
    

    然后使用

    Packet_t *pPacket = (Packet_t *)&Buffer;
    PacketEnd_t *pPacketEnd = (PacketEnd_t *)( count pointer here by using pPacket->len );
    

    假设 __packed 允许对 __packed 结构的成员使用非对齐访问。

    【讨论】:

    • 如何设置正确的偏移量?我不能使用 len = (pPacket->len) + 2,因为步骤是机器字(32 位)。
    • @MichaelVLV : 也许这样: pPacketEnd = (PacketEnd_t *)(((U8 *)&Buffer) + pPacket->len - sizeof(PacketEnd_t))
    【解决方案3】:

    您应该将您的处理功能分成两个不同的功能:

    • 将丢弃所有内容,直到找到head 字节。这个字节通常是一个常量字节,标志着一个数据包的开始。这样做是为了避免在之前发送的数据包的中间开始读取(考虑即发送方和侦听设备的启动顺序)。

      一旦找到数据包的开头,它就可以读取包头lenid 并将所有数据存储到您的Buffer 变量中,直到它读取end 字节或存在缓冲区溢出,在这种情况下,它只会丢弃数据并重新开始。

      请注意,仅应将实际数据写入 Buffer 变量。所有其他字段(len、id 等)可以存储在不同的变量中,或者存储在仅包含 Packet information 但不包含数据的结构中。这样,您就可以从传输信息中吐出应用程序数据。

      另请注意,此函数不解释 idfield,也不解释 datafield。它只是将此信息发送到其他功能,如果iddata 不正确/已知,它将进行处理或丢弃。

    • 一旦找到end 字节,您就可以将信息传递给实际的processing 函数。它的标题类似于:

      void processPacket(U8 *data, U32 id, U16 len);
      

      然后调用它是:

      void receiveFrame() {
          //Receive head
          //Receive id
          //Receive len
          //Fill in Buffer with the actual data
      
          //Call the processPacket function
          processPacket(&Buffer[0], id, len);
      }
      

    一个更完整的例子可能是:

    //It is not packet, since you fill it reading from the buffer and assigning
    //to it, not casting the buffer into it.
    //It has no data field. The data is in a different variable.
    typedef struct Packet_s
    {
        U8  head;
        U16 len;
        U32 id;
        U8  end;
        U16 crc;
    } PacketInfo_t;
    
    U8 Buffer[MAX_BUFFER_SIZE];
    PacketInfo_t info;
    void receiveFrame() {
        info.head=//Receive head
        info.len=//Receive len
        info.id=//Receive id
        //Fill the buffer variable
        processPacket(&Buffer[0], &info);
    }
    void processPacket(U8 *data, PacketInfo_t *info);
    

    对于发送,只需使用相同的格式:

    void sendPacket(U8 *data, PacketInfo_t *info);
    

    此函数会从info 准备数据包头并从data 读取数据。


    最后,提醒一句:将接收到的数据包直接转换(或 memcpy)到结构中几乎不是一个好主意。您不仅要考虑零孔(使用 __packet 属性),还要考虑发送方和接收方系统的字节顺序和数据格式表示,因为如果它们不同,您最终会得到错误的值。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-09-20
      • 1970-01-01
      • 2021-02-03
      • 1970-01-01
      • 1970-01-01
      • 2012-04-17
      相关资源
      最近更新 更多