Header 对 UDP 套接字使用字节有限制
Header with restrictions using bytes for an UDP Socket
我正在为一个使用字节有限制的 UDP 套接字做一个 Header。
| Packet ID (1 byte) | Packet Size (2 bytes) | Subpacket ID (1 Byte) | etc
我做了一个结构来存储这种属性,例如:
typedef struct WHEATHER_STRUCT
{
unsigned char packetID[1];
unsigned char packetSize[2];
unsigned char subPacketID[1];
unsigned char subPacketOffset[2];
...
} wheather_struct;
我使用 new 初始化了这个结构并更新了值。问题是我是否只想在数据包大小属性中使用 2 个字节。下面我写的这两种形式哪个是正确的?
*weather_struct->packetSize = '50';
或
*weather_struct->packetSize = 50;
两者都不正确,您需要将这两个字节视为一个 16 位数字。您可能还需要考虑网络流与您的处理器架构的不同字节序(取决于协议,大多数是大字节序)。
因此正确的代码是:
*((uint16_t*)weather_struct->packetSize) = htons(50);
如果 packetSize
是 uint16_t
开始的话会更简单:
weather_struct->packetSize = htons(50);
如果您可以使用 C++11 和 gcc(或 clang),那么我会这样做:
typedef struct WHEATHER_STRUCT
{
uint8_t packetID;
uint16_t packetSize;
uint8_t subPacketID;
uint16_t subPacketOffset;
// ...
} __attribute__((packed)) wheather_struct;
如果您不能使用 C++11,那么您可以使用 unsigned char
和 unsigned short
。
如果您使用的是 Visual C,那么您可以:
#pragma pack (push, 1)
typedef struct ...
#pragma (pop)
还要注意字节顺序问题,具体取决于您需要支持的架构。您可以使用 htons()
和 ntohs()
来克服这个问题。
现场演示
从 IP 数据包中打包和解包数据是一个与互联网本身一样古老的问题(事实上,更古老)。
不同的机器架构有不同的表示整数的布局,这可能会导致机器之间通信时出现问题。
出于这个原因,IP 堆栈对 'network byte order' 中的整数编码进行了标准化(这基本上意味着最高有效字节在前)。
存在将网络字节顺序的值转换为本机类型的标准函数,反之亦然。我敦促您考虑使用这些,因为这样您的代码将更加便携。
此外,从程序的角度抽象数据表示是有意义的。 c++ 编译器可以非常有效地执行转换。
示例:
#include <arpa/inet.h>
#include <cstring>
#include <cstdint>
typedef struct WEATHER_STRUCT
{
std::int8_t packetID;
std::uint16_t packetSize;
std::uint8_t subPacketID;
std::uint16_t subPacketOffset;
} weather_struct;
const std::int8_t* populate(weather_struct& target, const std::int8_t* source)
{
auto get16 = [&source]
{
std::uint16_t buf16;
std::memcpy(&buf16, source, 2);
source += 2;
return ntohs(buf16);
};
target.packetID = *source++;
target.packetSize = get16();
target.subPacketID = *source++;
target.subPacketOffset = get16();
return source;
}
uint8_t* serialise(uint8_t* target, weather_struct const& source)
{
auto write16 = [&target](std::uint16_t val)
{
val = ntohs(val);
std::memcpy(target, &val, 2);
target += 2;
};
*target++ = source.packetID;
write16(source.packetSize);
*target++ = source.subPacketID;
write16(source.subPacketOffset);
return target;
}
https://linux.die.net/man/3/htons
这是上面的 c++17 版本的 link:
关于转换成本的进一步说明:
数据进入或离开您的程序是每个负载发生一次的事件。在这里承受转换成本会导致微不足道的惩罚。
一旦数据到达您的程序,或者在它离开之前,您的代码可能会对其进行多次操作。
如果数据未按自然字边界对齐,则某些处理器架构在数据访问期间会遭受巨大的性能损失。这就是 packed
等属性存在的原因——编译器正在尽其所能避免数据未对齐。使用打包属性无异于故意告诉编译器生成非常次优的代码。
出于这个原因,我建议不要对程序逻辑将引用的数据使用打包结构(例如 __attribute__((packed))
等)。
与 RAM 相比,网络速度要慢很多个数量级。与实际传输它的成本相比,在编码或解码网络数据包时的微小性能损失(字面意思是纳秒)是微不足道的。
打包结构会导致程序代码出现可怕的性能问题,并且经常会导致令人头疼的可移植性问题。
我正在为一个使用字节有限制的 UDP 套接字做一个 Header。
| Packet ID (1 byte) | Packet Size (2 bytes) | Subpacket ID (1 Byte) | etc
我做了一个结构来存储这种属性,例如:
typedef struct WHEATHER_STRUCT
{
unsigned char packetID[1];
unsigned char packetSize[2];
unsigned char subPacketID[1];
unsigned char subPacketOffset[2];
...
} wheather_struct;
我使用 new 初始化了这个结构并更新了值。问题是我是否只想在数据包大小属性中使用 2 个字节。下面我写的这两种形式哪个是正确的?
*weather_struct->packetSize = '50';
或
*weather_struct->packetSize = 50;
两者都不正确,您需要将这两个字节视为一个 16 位数字。您可能还需要考虑网络流与您的处理器架构的不同字节序(取决于协议,大多数是大字节序)。
因此正确的代码是:
*((uint16_t*)weather_struct->packetSize) = htons(50);
如果 packetSize
是 uint16_t
开始的话会更简单:
weather_struct->packetSize = htons(50);
如果您可以使用 C++11 和 gcc(或 clang),那么我会这样做:
typedef struct WHEATHER_STRUCT
{
uint8_t packetID;
uint16_t packetSize;
uint8_t subPacketID;
uint16_t subPacketOffset;
// ...
} __attribute__((packed)) wheather_struct;
如果您不能使用 C++11,那么您可以使用 unsigned char
和 unsigned short
。
如果您使用的是 Visual C,那么您可以:
#pragma pack (push, 1)
typedef struct ...
#pragma (pop)
还要注意字节顺序问题,具体取决于您需要支持的架构。您可以使用 htons()
和 ntohs()
来克服这个问题。
从 IP 数据包中打包和解包数据是一个与互联网本身一样古老的问题(事实上,更古老)。
不同的机器架构有不同的表示整数的布局,这可能会导致机器之间通信时出现问题。
出于这个原因,IP 堆栈对 'network byte order' 中的整数编码进行了标准化(这基本上意味着最高有效字节在前)。
存在将网络字节顺序的值转换为本机类型的标准函数,反之亦然。我敦促您考虑使用这些,因为这样您的代码将更加便携。
此外,从程序的角度抽象数据表示是有意义的。 c++ 编译器可以非常有效地执行转换。
示例:
#include <arpa/inet.h>
#include <cstring>
#include <cstdint>
typedef struct WEATHER_STRUCT
{
std::int8_t packetID;
std::uint16_t packetSize;
std::uint8_t subPacketID;
std::uint16_t subPacketOffset;
} weather_struct;
const std::int8_t* populate(weather_struct& target, const std::int8_t* source)
{
auto get16 = [&source]
{
std::uint16_t buf16;
std::memcpy(&buf16, source, 2);
source += 2;
return ntohs(buf16);
};
target.packetID = *source++;
target.packetSize = get16();
target.subPacketID = *source++;
target.subPacketOffset = get16();
return source;
}
uint8_t* serialise(uint8_t* target, weather_struct const& source)
{
auto write16 = [&target](std::uint16_t val)
{
val = ntohs(val);
std::memcpy(target, &val, 2);
target += 2;
};
*target++ = source.packetID;
write16(source.packetSize);
*target++ = source.subPacketID;
write16(source.subPacketOffset);
return target;
}
https://linux.die.net/man/3/htons
这是上面的 c++17 版本的 link:
关于转换成本的进一步说明:
数据进入或离开您的程序是每个负载发生一次的事件。在这里承受转换成本会导致微不足道的惩罚。
一旦数据到达您的程序,或者在它离开之前,您的代码可能会对其进行多次操作。
如果数据未按自然字边界对齐,则某些处理器架构在数据访问期间会遭受巨大的性能损失。这就是 packed
等属性存在的原因——编译器正在尽其所能避免数据未对齐。使用打包属性无异于故意告诉编译器生成非常次优的代码。
出于这个原因,我建议不要对程序逻辑将引用的数据使用打包结构(例如 __attribute__((packed))
等)。
与 RAM 相比,网络速度要慢很多个数量级。与实际传输它的成本相比,在编码或解码网络数据包时的微小性能损失(字面意思是纳秒)是微不足道的。
打包结构会导致程序代码出现可怕的性能问题,并且经常会导致令人头疼的可移植性问题。