标头,使用字节限制用于UDP套接字

时间:2018-09-27 08:11:18

标签: c++ sockets header udp

我正在为UDP套接字设置标头,该协议使用字节有限制。

  

|数据包ID(1字节)|数据包大小(2个字节)|子数据包ID(1字节)|等等

我做了一个存储这种属性的结构,如:

typedef struct WHEATHER_STRUCT
{
   unsigned char packetID[1];
   unsigned char packetSize[2];
   unsigned char subPacketID[1];
   unsigned char subPacketOffset[2];
   ...
} wheather_struct;

我使用new初始化了此结构,并更新了值。问题是关于我是否只想在Packet Size属性中使用2个字节。我在下面写的这两种形式中正确的是哪种?

*weather_struct->packetSize = '50';

*weather_struct->packetSize = 50;

3 个答案:

答案 0 :(得分:2)

如果您可以使用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 charunsigned short

如果您使用的是Visual C,则可以执行以下操作:

#pragma pack (push, 1)
typedef struct ...
#pragma (pop)

还要注意字节顺序问题,具体取决于您需要支持的体系结构。您可以使用htons()ntohs()来解决此问题。

https://codepen.io/imsontosh/pen/yRBXKg?editors=0010上的实时演示

答案 1 :(得分:1)

从IP数据包打包和解包数据是一个与Internet本身一样古老的问题(实际上是更老的)。

不同的机器体系结构具有不同的布局来表示整数,这会在机器之间进行通信时引起问题。

由于这个原因,IP堆栈对“网络字节顺序”(基本上意味着最高有效字节在前)的整数编码进行了标准化。

存在标准功能,可以将网络字节顺序的值转换为本地类型,反之亦然。我敦促您考虑使用这些代码,因为这样您的代码将更易于移植。

此外,从程序的角度抽象数据表示形式是有意义的。 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版本的链接:

https://godbolt.org/z/oRASjI

关于转换成本的进一步说明:

数据进入或离开程序是一个事件,每个有效负载发生一次。在此遭受转换费用的损失微不足道。

一旦数据到达您的程序,或者在数据离开之前,您的代码可能会对其进行多次操作。

如果数据未在自然字边界上对齐,则某些处理器体系结构将在数据访问期间遭受巨大的性能损失。这就是存在诸如packed之类的属性的原因-编译器将尽一切可能避免未对齐的数据。使用压缩属性等于故意告诉编译器生成次优代码。

由于这个原因,我建议不要对程序逻辑所引用的数据使用打包结构(例如__attribute__((packed))等)。

与RAM相比,网络要慢许多个数量级。与实际传输数据包的成本相比,在对网络数据包进行编码或解码时,微不足道的性能损失(毫微秒)是无关紧要的。

打包结构可能导致程序代码中可怕的性能问题,并经常导致可移植性的困扰。

答案 2 :(得分:0)

都不正确,您需要将两个字节视为一个16位数字。您可能还需要考虑网络流与处理器体系结构的不同字节序(取决于协议,大多数是大字节序)。

因此,正确的代码应为:

*((uint16_t*)weather_struct->packetSize) = htons(50);

如果packetSizeuint16_t开头,则会更简单:

weather_struct->packetSize = htons(50);