【问题标题】:Creating and sending data packets in C/C++在 C/C++ 中创建和发送数据包
【发布时间】:2011-08-13 07:25:51
【问题描述】:

假设我想使用 C 或 C++ 将以下数据发送到套接字,全部在一个数据包中:

Headers
-------
Field 1: 2 byte hex
Field 2: 2 byte hex
Field 3: 4 byte hex

Data
----
Field1 : 2 byte hex
Field1 : 8 byte hex

创建和发送包含所有这些数据的数据包的代码通常是什么样的?

【问题讨论】:

  • 相关外部链接:tangentsoft.net/wskfaq
  • 如果是 C++,我建议查看 Boost.Asio 以获得出色的跨平台网络库。
  • 您能否详细说明“一体式数据包”的含义?您是指链路层数据包(以太网、PPP 等)吗?还是你的意思是别的?
  • @Rob,好吧,我不想多次调用 send() 来发送标头和数据。我想通过一次调用 send() 将标头和数据一起发送。除非这不是通常的做法?
  • 这是一个很好的要求,@Chad。只是“数据包”在不同的上下文中意味着不同的东西,我想确保你不是在谈论其他东西。我见过程序在一个send、2 个sends 和超过2 个sends 中发送标头和数据。那里没有“正常”,只是代码中对组织方便的东西。

标签: c++ c sockets


【解决方案1】:

嗯,通常它看起来像是将数据包结构准备到内存缓冲区中(明智地调用 htonl 系列函数)。

如果会使用 sendsendtosendmsgwrite 函数,希望对缓冲区的长度和良好的错误处理/报告非常小心。

(或用于发送的 Win32 api 之一,如果那是目标平台。)

您可以在Beej's Guide to Network Programming 找到有关这一切的精彩演示。

特别是字节打包部分(考虑字节序),请查看serialization 主题。 (该部分的详细信息比您对普通固定大小整数数据类型所需的详细信息要多得多。

【讨论】:

  • 我假设任何套接字库的 send() 方法都会将缓冲区变量作为参数,对吗?你能提供一个将数据包结构准备到内存缓冲区的例子吗?
  • 两个链接都坏了
  • @Mat 是的,我很容易找到它,但如果我发现损坏的链接我倾向于发表评论:)
【解决方案2】:

假设您的程序已经组织好,将标题放在一个struct 中,将数据放在另一个struct 中。例如,您可能有以下数据结构:

#include <stdint.h>
struct header {
    uint16_t f1;
    uint16_t f2;
    uint32_t f3;
};
struct data {
    uint16_t pf1;
    uint64_t pf2;
};

我们称这个组织为“主机格式”。对我来说,主机格式是什么并不重要,只要它对您的程序的其余部分有用。我们将您将传递给send() 的格式称为“网络格式”。 (我选择这些名称以匹配 htons(主机到网络短)和 htonl(主机到网络长)名称。)

这里有一些我们可能会觉得很方便的转换函数。其中每一个都将您的主机格式结构转换为网络格式缓冲区。

#include <arpa/inet.h>
#include <string.h>
void htonHeader(struct header h, char buffer[8]) {
    uint16_t u16;
    uint32_t u32;
    u16 = htons(h.f1);
    memcpy(buffer+0, &u16, 2);
    u16 = htons(h.f2);
    memcpy(buffer+2, &u16, 2);
    u32 = htonl(h.f3);
    memcpy(buffer+4, &u32, 4);
}
void htonData(struct data d, char buffer[10]) {
    uint16_t u16;
    uint32_t u32;
    u16 = htons(d.pf1);
    memcpy(buffer+0, &u16, 2);
    u32 = htonl(d.pf2>>32);
    memcpy(buffer+2, &u32, 4);
    u32 = htonl(d.pf2);
    memcpy(buffer+6, u32, 4);
}
void htonHeaderData(struct header h, struct data d, char buffer[18]) {
    htonHeader(h, buffer+0);
    htonData(d, buffer+8);
}

要发送您的数据,请执行以下操作:

...
char buffer[18];
htonHeaderData(myPacketHeader, myPacketData, buffer);
send(sockfd, buffer, 18, 0);
...

同样,您不必使用我定义的 headerdata 结构。只需使用您的程序需要的任何东西。关键是您有一个转换函数,可以将所有数据以明确定义的偏移量、以明确定义的字节顺序写入缓冲区,然后将该缓冲区传递给 send() 函数。

在网络连接的另一端,您需要一个程序来解释它接收到的数据。在那方面,您需要编写相应的函数(ntohHeader 等)。这些函数会将memcpy 的位从缓冲区中取出并放入局部变量,它可以将其传递给ntohsntohl。我会把这些函数留给你写。

【讨论】:

  • 正是我想要的。非常感谢!
  • 问题1:为什么是“d.pf2>>32”?问题2:什么是“d.pfd2”?
  • Answer2: d.pfd2 是一个错字。感谢您的关注,我已经解决了。答1:因为没有标准函数可以转换uint64_t的字节顺序,所以代码先转换高32位,后转换低32位。
【解决方案3】:

根据操作系统的网络库,代码看起来会有所不同(*nix 使用Berkeley sockets,Windows 使用Winsock,等等)。但是,您可以创建一个结构,其中包含您要在数据包中发送的所有数据,例如,

typedef struct
{
    short field1;
    short field2;
    int field3;
} HeaderStruct;

typedef struct
{
    short field1;
    long long field2;
} PacketDataStruct;

假设 32 位 int 大小。

编辑:

正如 cmets 中有人提醒我的那样,不要忘记与Network Order 相互转换。网络库将具有辅助功能,例如 ntohsnothlhtonshtonl

【讨论】:

  • 这与我的想法一致。发送包装会是什么样子?你能用那个更新答案吗?
  • 确保正确打包这些结构,并处理任何字节序问题!
【解决方案4】:

一个简单的答案是它将以接收者期望的格式发送。不过,这引出了一个问题。假设数据是如图所示的固定大小并且接收端期望,那么您可以使用打包(1 字节对齐)结构并将数据存储在每个字段中。使用 1 字节对齐的原因是通常更容易确保两端期待相同的数据。如果没有 1 字节对齐,那么根据编译器选项、32 位与 64 位体系结构等,结构可能看起来不同。)并且,通常,如果十六进制值,则预计您将以网络字节顺序发送值是整数。您可以使用 htonshtonl(如果可用,还可以使用 htobe64)等函数来转换它们。

假设数据在具有所需字节顺序的结构中,那么发送调用可能是这样的:

ret = send( socket, &mystruct, sizeof( mystruct ), 0 );

假设mystruct 被声明为结构的实例,而不是指向结构的指针。

【讨论】:

  • 现在假设我想像 slavik262 向我们展示的那样发送多个数据包。那么我是否只有一个包含子结构的结构,并将其传递给 send()?
  • @Chad:您可以多次调用 send() (请注意,您应该检查返回码)。如果您正在使用 TCP 并且正在使用 Nagle 的算法,那么即使 send 被多次调用,数据也很可能作为单个数据包在网络上传输。但是,如果您使用的是 UDP,则每个发送调用都会产生单独的数据包。如果是这种情况,那么您可能希望将其全部复制到一个缓冲区中,如果您希望它在一个数据包中发送(这不是必需的)。
  • 字段对齐(和排序??)不会因编译器和机器而异吗?您还必须处理字节顺序。这使得结构成为一个糟糕的选择,除非与同一程序的同一构建的另一个实例进行通信。
  • @ikegami:我在答案中讨论过(htonl等)
猜你喜欢
  • 1970-01-01
  • 2011-02-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-04
  • 2011-01-15
  • 2018-04-12
相关资源
最近更新 更多