如何为大的和小的终端系统编写通用的数据报解析器?我不明白的是如何从字节缓冲器16位或32位一次传递字节.
假设您有此数据报有效载荷
uint8_t datagram[8] = {0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8};
一些协议说
g 210
因此,您需要一个通用的解析器,它可以在大的和小的终端机器上工作。
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?不如直接转换为结构化结构好吗?
发布于 2020-12-18 02:13:18
是更好的只是转换为结构化吗?
不是的。除了endianness,还有其他问题;比如结构填充(其中的数量是“编译器实现特定的”)和对齐。例如,这种结构:
struct myStructure {
uint8_t Param_a;
uint16_t Param_b;
uin8_t Param_c;
uint16_t Param_d;
}..is可能会变得更像:
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也可以变成这个(或其他任何东西):
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)。
例如:
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
几乎所有的计算机都是小终端,所以(在设计任何东西-例如网络协议、文件格式等)时,您都希望使用小终端来提高几乎所有计算机的性能。由于历史的原因,“网络秩序”是大端的,这使得ntohl、htonl、ntohs、htons当您想要确保数据是小端时就变得无用了。
发布于 2020-12-17 21:22:19
假设你指的是位而不是字节。
只有当您的数据包是大端时,ntohs和ntohl才能提供帮助。所以我们得把它拼出来。
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或其他无稽之谈。
https://stackoverflow.com/questions/65348163
复制相似问题