首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >解析小端和大端的网络数据

解析小端和大端的网络数据
EN

Stack Overflow用户
提问于 2020-12-17 20:51:02
回答 2查看 253关注 0票数 1

如何为大的和小的终端系统编写通用的数据报解析器?我不明白的是如何从字节缓冲器16位或32位一次传递字节.

假设您有此数据报有效载荷

uint8_t datagram[8] = {0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8};

一些协议说

  1. Param a-8位
  2. Param b- 16位(小endian)
  3. Param c-8位
  4. Param d- 32位(小endian)

g 210

因此,您需要一个通用的解析器,它可以在大的和小的终端机器上工作。

代码语言:javascript
复制
uint8_t param_a = datagram[0];
uint16_t param_b = datagram[1]; // how to use ntohs here?
uint8_t param_c = datagram[3]; 
uint32_t param_d = datagram[4]; // how to use ntohl here?

不如直接转换为结构化结构好吗?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2020-12-18 02:13:18

是更好的只是转换为结构化吗?

不是的。除了endianness,还有其他问题;比如结构填充(其中的数量是“编译器实现特定的”)和对齐。例如,这种结构:

代码语言:javascript
复制
struct myStructure {
    uint8_t Param_a;
    uint16_t Param_b;
    uin8_t Param_c;
    uint16_t Param_d;
}

..is可能会变得更像:

代码语言:javascript
复制
struct myStructure {
    uint8_t Param_a;
    uint8_t padding1;    // Inserted by compiler
    uint16_t Param_b;
    uin8_t Param_c;
    uint8_t padding2;    // Inserted by compiler
    uint16_t Param_d;
}

..but也可以变成这个(或其他任何东西):

代码语言:javascript
复制
struct myStructure {
    uint8_t Param_a;
    uint8_t padding1[3];  // Inserted by compiler
    uint16_t Param_b;
    uint8_t padding2[2];  // Inserted by compiler
    uin8_t Param_c;
    uint8_t padding3[3];  // Inserted by compiler
    uint16_t Param_d;
}

对于网络协议(数据布局必须完全匹配);这将破坏一切,即使网络上的所有计算机都是小终端。为了防止出现问题,编译器提供了强制结构“打包”的方法--比如GCC中的struct __attribute__((__packed__)) myStructure {。但是,有些CPU无法处理对齐错误的读取,因此这可以以不同的方式中断操作(例如,导致性能问题和原子操作失败),因此您不希望在以后处理数据时使用“打包”结构。

还值得一提的是,(通常)代码外部的任何内容(例如用户输入、文件数据、网络数据)都不应该被“假定为有效”。它可能是恶意构造的,目的是利用代码中的“意外情况”;它可能是其他代码中的错误的结果;也可能是硬件故障的结果。在任何情况下,您都需要在使用之前检查数据(并希望报告数据中发现的任何问题;以便更容易地完成良好的用户界面,或者更快地在其他人的代码中查找/修复bug,并避免代码中的“无法解释的症状”)。为了确保这种情况的正确发生,最好使用语言的类型系统--特别是;有一种类型用于“原始和未检查”数据(例如,一个uint8_t数组),另一种类型用于“正常检查数据”(例如,一个struct myStructure),这样任何意外/错误(例如假设数据未被检查)在编译时都会导致“类型不匹配”错误。当然,这意味着您将编写代码从一种类型转换到另一种类型(同时进行正常检查),这也解决了涉及数据布局的问题(例如编译器特定的填充、endianness)。

例如:

代码语言:javascript
复制
struct myStructure {
    uint8_t Param_a;           // Must be a value from 0 to 100
    uint16_t Param_b;          // Must be a value >= "year 2000"
    uin8_t Param_c;            // Flags. Must be 1, 2, 4 or 6.
    uint16_t Param_d;          // Sender's "Request ID" (can be anything - always returned as is in reply packet so sender can figure out which reply is for which request)
}

int parseRawData(struct myStructure *outData, uint8_t **inputBuffer, size_t *inputBufferSize) {
    uint8_t a;
    uint16_t b;
    uin8_t c;
    uint16_t d;

    // Check size of data received

    if(*inputBufferSize == 0) {
        return 1;   // No data
    }
    if(*inputBufferSize <= 6) {
        return 2;   // Not enough data (yet) - can happen for "split packets" in TCP streams
    }

    // Parse raw data and do sanity checks

    a = (*inputBuffer)[0];
    if(a > 100) {
        return 10;    // Value out of range for param_a
    }

    b = (*inputBuffer)[1] | (*inputBuffer)[2];
    if(b < 2000) {
        return 20;    // Value out of range param_b
    }

    c = (*inputBuffer)[3];
    switch(c) {
    case 1:
    case 2:
    case 4:
    case 6:
        break;
    default:
        return 30;    // Bad value or unsupported value for param_c
    }

    d = (*inputBuffer)[4] | (*inputBuffer)[5];

    // Data was valid, so store it and update the buffer tracking

    outData->Param_a = a;
    outData->Param_b = b;
    outData->Param_c = c;
    outData->Param_d = d;
    *inputBuffer += 20;
    *inputBufferSize -= 20;
    return 0;                     // No problem!
}

当然,您也可能希望使用enum作为错误代码,并且可能需要某种“将缓冲区中的2个字节转换为uint16_t”宏。

关于ntohl,htonl,ntohs,hton

几乎所有的计算机都是小终端,所以(在设计任何东西-例如网络协议、文件格式等)时,您都希望使用小终端来提高几乎所有计算机的性能。由于历史的原因,“网络秩序”是大端的,这使得ntohlhtonlntohshtons当您想要确保数据是小端时就变得无用了。

票数 1
EN

Stack Overflow用户

发布于 2020-12-17 21:22:19

假设你指的是位而不是字节。

只有当您的数据包是大端时,ntohsntohl才能提供帮助。所以我们得把它拼出来。

代码语言:javascript
复制
uint8_t param_a = datagram[0];
uint16_t param_b = (uint16_t)datagram[1] | ((uint16_t)datagram[2] << 8);
uint8_t param_c = datagram[3]; 
uint32_t param_d = (uint32_t)datagram[4] | ((uint32_t)datagram[5] << 8) | ((uint32_t)datagram[6] << 16) | ((uint32_t)datagram[7] << 24);

额外好处:我们也不需要谈论对齐、volatile或其他无稽之谈。

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/65348163

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档