构建数据包的更好方法 - 逐字节?

发布于 2024-08-16 17:05:42 字数 476 浏览 4 评论 0原文

这与我今天在此处提出的问题有关所以。有没有更好的方法来构建通过串行发送的数据包,而不是这样做:

unsigned char buff[255];

buff[0] = 0x02
buff[1] = 0x01
buff[2] = 0x03

WriteFile(.., buff,3, &dwBytesWrite,..);

注意:我有大约二十个命令要发送,所以是否有更好的方法将这些字节发送到串行设备以更简洁的方式而不是必须指定每个字节,那就太好了。每个字节都是十六进制,最后一个字节是校验和。我应该澄清一下,我知道我必须指定每个字节来构建命令,但是有没有比指定每个数组位置更好的方法?

This is related to my question asked here today on SO. Is there a better way to build a packet to send over serial rather than doing this:

unsigned char buff[255];

buff[0] = 0x02
buff[1] = 0x01
buff[2] = 0x03

WriteFile(.., buff,3, &dwBytesWrite,..);

Note: I have about twenty commands to send, so if there was a better way to send these bytes to the serial device in a more concise manner rather than having to specify each byte, it would be great. Each byte is hexadecimal, with the last byte being the checksum. I should clarify that I know I will have to specify each byte to build the commands, but is there a better way than having to specify each array position?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

风吹雪碎 2024-08-23 17:05:42

您可以像这样初始化静态缓冲区:

const unsigned char command[] = {0x13, 0x37, 0xf0, 0x0d};

您甚至可以使用它们来初始化非常量缓冲区,然后按索引仅替换更改的字节。

You can initialize static buffers like so:

const unsigned char command[] = {0x13, 0x37, 0xf0, 0x0d};

You could even use these to initialize non-const buffers and then replace only changing bytes by index.

神也荒唐 2024-08-23 17:05:42

不确定你在问什么。如果您询问逐一设置字节并弄乱数据的问题,通常这是通过包含具有有意义名称的成员的打包结构来完成的。喜欢:

#pragma push(pack)
#pragma pack(1)

struct FooHeader {
  uint someField;
  byte someFlag;
  dword someStatus;
};

#pragma pack(pop)

FooHeader hdr;
hdr.someField = 2;
hdr.someFlag = 3;
hdr.someStatus = 4;

WriteFile(..., sizeof(hdr), &hdr);

Not sure what you're asking. If you ask about the problem of setting the byte one by one and messing up the data, usually this is doen with a packed struct with members having meaningful names. Like:

#pragma push(pack)
#pragma pack(1)

struct FooHeader {
  uint someField;
  byte someFlag;
  dword someStatus;
};

#pragma pack(pop)

FooHeader hdr;
hdr.someField = 2;
hdr.someFlag = 3;
hdr.someStatus = 4;

WriteFile(..., sizeof(hdr), &hdr);
洒一地阳光 2024-08-23 17:05:42

是否有比逐字节组装数据包更好的方法来构建数据包?

是的,但这需要一些思考和精心的设计。许多其他答案告诉您其他机制,您可以通过这些机制将 C++ 中的字节序列组合在一起。但我建议您设计一个代表数据包一部分的抽象:

class PacketField {
    void add_to_packet(Packet p);
};

然后您可以定义各种子类:

  • 将单个字节添加到数据包中
  • 按大端顺序添加一个 16 位整数。另一个是小端字节序。除16以外的其他宽度。
  • 在数据包中添加字符串;通过插入长度然后插入字节来对字符串进行编码。

您还可以定义一个高阶版本:

PacketField sequence(PacketField first, PacketField second);

返回一个按顺序包含两个参数的字段。如果您喜欢运算符重载,可以将其重载为 +<<

您的底层 Packet 抽象只是一个可扩展的字节序列(动态数组),具有某种 write 方法。

如果您最终编写了许多网络协议,您会发现这种设计会带来巨大的回报。

编辑PacketField 类的要点是可组合性和重用性:

  • 通过组合数据包字段,您可以创建更复杂的数据包字段。例如,您可以将“添加 TCP 标头”定义为从 PacketFieldsPacketFields 的函数。

  • 如果幸运的话,您可以构建一个特定于您的应用程序或协议系列或其他内容的 PacketFields 库。然后您可以重用库中的字段。

  • 您可以创建采用额外参数的 PacketField 子类。

很可能您可以做同样好的事情,而不必具有这种额外的间接级别;我推荐它是因为我已经看到它在其他应用程序中得到了有效的使用。您正在将如何构建数据包的知识(可以随时应用于任何数据包)与实际构建特定数据包的行为分开。像这样分离关注点可以帮助重用。

Is there a better way to build a packet than assembling it byte by byte?

Yes, but it will require some thought and some careful engineering. Many of the other answers tell you other mechanisms by which you can put together a sequence of bytes in C++. But I suggest you design an abstraction that represents a part of a packet:

class PacketField {
    void add_to_packet(Packet p);
};

Then you can define various subclasses:

  • Add a single byte to the packet
  • Add a 16-bit integer in big-endian order. Another for little-endian. Other widths besides 16.
  • Add a string to the packet; code the string by inserting the length and then the bytes.

You also can define a higher-order version:

PacketField sequence(PacketField first, PacketField second);

Returns a field that consists of the two arguments in sequence. If you like operator overloading you could overload this as + or <<.

Your underlying Packet abstraction will just be an extensible sequence of bytes (dynamic array) with some kind of write method.

If you wind up programming a lot of network protocols, you'll find this sort of design pays off big time.

Edit: The point of the PacketField class is composability and reuse:

  • By composing packet fields you can create more complex packet fields. For example, you could define "add a TCP header" as a function from PacketFields to PacketFields.

  • With luck you build up a library of PacketFields that are specific to your application or protocol family or whatever. Then you reuse the fields in the library.

  • You can create subclasses of PacketField that take extra parameters.

It's quite possibly that you can do something equally nice without having to have this extra level of indirection; I'm recommending it because I've seen it used effectively in other applications. You are decoupling the knowledge of how to build a packet (which can be applied to any packet, any time) from the act of actually building a particular packet. Separating concerns like this can help reuse.

凑诗 2024-08-23 17:05:42

是的,有更好的方法。让您的类读取和写入打包缓冲区。您甚至可以将其实现为接口。模板会有所帮助。

编写示例:

template <typename Member_Type>
void Store_Value_In_Buffer(const Member_Type&, member,
                           unsigned char *& p_buffer)
{
  *((Member_Type *)(p_buffer)) = member;
  p_buffer += sizeof(Member_Type);
  return;
}

struct My_Class
{
    unsigned int datum;

    void store_to_buffer(unsigned char *& p_buffer)
    {
      Store_Value_In_Buffer(datum, buffer);
      return;
    }
};

//...
unsigned char    buffer[256];
unsigned char *  p_buffer(buffer);
MyClass object;
object.datum = 5;
object.store_to_buffer(p_buffer);
std::cout.write(p_buffer, 256);

接口的一部分还用于查询对象在缓冲区中占用的大小,例如方法 size_in_buffer。这留给读者作为练习。 :-)

Yes, there is a better method. Have your classes read from and write to a packed buffer. You could even implement this as an interface. Templates would help to.

An example of writing:

template <typename Member_Type>
void Store_Value_In_Buffer(const Member_Type&, member,
                           unsigned char *& p_buffer)
{
  *((Member_Type *)(p_buffer)) = member;
  p_buffer += sizeof(Member_Type);
  return;
}

struct My_Class
{
    unsigned int datum;

    void store_to_buffer(unsigned char *& p_buffer)
    {
      Store_Value_In_Buffer(datum, buffer);
      return;
    }
};

//...
unsigned char    buffer[256];
unsigned char *  p_buffer(buffer);
MyClass object;
object.datum = 5;
object.store_to_buffer(p_buffer);
std::cout.write(p_buffer, 256);

Part of the interface is also to query the objects for the size that they would occupy in the buffer, say a method size_in_buffer. This is left as an exercise for the reader. :-)

2024-08-23 17:05:42

有一种更好的方法,那就是使用结构体来设置结构体。这通常是网络数据包在低级别上构建的方式。

例如,假设您有包含 id、长度、标志字节和数据的数据包,您将执行以下操作:

struct packet_header {
   int id;
   byte length;
   byte flags;
}; 

byte my_packet[] = new byte[100];
packet_header *header = &my_packet;
header->id = 20;
header->length = 10; // This can be set automatically by a function, maybe?
// etc.
header++; // Header now points to the data section.

请注意,您必须确保结构已“打包”,即当你写字节长度,它确实占用了一个字节。通常,您可以使用诸如#pragma pack 或类似的东西来实现这一点(您必须阅读编译器的编译指示设置)。

另请注意,您可能应该使用函数来执行常见操作。例如,创建一个函数,获取大小、要发送的数据和其他信息作为输入,并为您填写数据包标头和数据。这样,您可以计算要在长度字段中写入的实际大小,可以计算函数内部的 CRC,等等。

编辑:这是一种以 C 为中心的做事方式,这是很多网络代码的风格。更以 C++ 为中心(面向对象)的方法也可以工作,但我对它们不太熟悉。

There is a much better way, which is using structs to set the structures. This is usually how network packets are built on a low level.

For example, say you have packets which have an id, length, flag byte, and data, you'd do something like this:

struct packet_header {
   int id;
   byte length;
   byte flags;
}; 

byte my_packet[] = new byte[100];
packet_header *header = &my_packet;
header->id = 20;
header->length = 10; // This can be set automatically by a function, maybe?
// etc.
header++; // Header now points to the data section.

Do note that you're going to have to make sure that the structures are "packed", i.e. when you write byte length, it really takes up a byte. Usually, you'd achieve this using something like #pragma pack or similar (you'll have to read about your compiler's pragma settings).

Also, note that you should probably use functions to do common operations. For example, create a function which gets as input the size, data to send, and other information, and fills out the packet header and data for you. This way, you can perform calculations about the actual size you want to write in the length field, you can calculate the CRC inside the function, etc.

Edit: This is a C-centric way of doing things, which is the style of a lot of networking code. A more C++-centric (object oriented) approach could also work, but I'm less familiar with them.

我很坚强 2024-08-23 17:05:42

const char *c = "\x02\x02\x03";

const char *c = "\x02\x02\x03";

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文