如何创建具有特定大小的 C 结构以通过套接字发送到 DalmatinerDB?

Posted

技术标签:

【中文标题】如何创建具有特定大小的 C 结构以通过套接字发送到 DalmatinerDB?【英文标题】:How to create a C struct with specific size to send over socket to DalmatinerDB? 【发布时间】:2018-06-23 10:43:58 【问题描述】:

我正在尝试为dalmatinerdb 创建一个 C 客户端,但无法理解如何组合变量、将其写入缓冲区并将其发送到数据库。 dalmatinerdb 是用 Erlang 编写的这一事实使它变得更加困难。但是,通过查看python client for dalmatinerdb,我(可能)找到了必要的变量大小和顺序。

erlang客户端有一个叫做“encode”的函数,见下图:

encode(stream, Bucket, Delay) when
      is_binary(Bucket), byte_size(Bucket) > 0,
      is_integer(Delay), Delay > 0, Delay < 256->
    <<?STREAM,
  Delay:?DELAY_SIZE/?SIZE_TYPE,
  (byte_size(Bucket)):?BUCKET_SS/?SIZE_TYPE, Bucket/binary>>;

根据official dalmatinerdb protocol我们可以看到如下:

-define(STREAM, 4).
-define(DELAY_SIZE, 8). /bits
-define(BUCKET_SS, 8). /bits

假设我想在 C 中创建这种结构, 会不会像下面这样:

struct package 
    unsigned char[1] mode; // = "4"
    unsigned char[1] delay; // = for example "5"
    unsigned char[1] bucketNameSize; // = "5"
    unsigned char[1] bucketName; // for example "Test1"
;

更新:

我意识到 dalmatinerdb 前端(Web 界面)仅在将值发送到存储桶时才会做出反应和更新。换句话说,仅发送第一个结构不会给我任何线索,它是对还是错。因此,我将尝试使用实际值创建一个辅助结构。

编码值的erland代码sn-p如下所示:

encode(stream, Metric, Time, Points) when
      is_binary(Metric), byte_size(Metric) > 0,
      is_binary(Points), byte_size(Points) rem ?DATA_SIZE == 0,
      is_integer(Time), Time >= 0->
    <<?SENTRY,
      Time:?TIME_SIZE/?SIZE_TYPE,
      (byte_size(Metric)):?METRIC_SS/?SIZE_TYPE, Metric/binary,
      (byte_size(Points)):?DATA_SS/?SIZE_TYPE, Points/binary>>;

不同的尺寸:

-define(SENTRY, 5)
-define(TIME_SIZE, 64)
-define(METRIC_SS, 16)
-define(DATA_SS, 32)

这给了我这给了我:

<<?5,
      Time:?64/?SIZE_TYPE,
      (byte_size(Metric)):?16/?SIZE_TYPE, Metric/binary,
      (byte_size(Points)):?32/?SIZE_TYPE, Points/binary>>;

我的猜测是我的结构包含一个值应该是这样的:

struct Package 
    unsigned char sentry;
    uint64_t time;
    unsigned char metricSize;
    uint16_t metric;
    unsigned char pointSize;
    uint32_t point;
;

在这个结构上有什么 cmets 吗?

【问题讨论】:

Python 和这个问题有什么关系?什么都没有。 自从我链接到 dalmatinerdb 的 python 客户端后,我觉得这很有趣,但显然不是。 unsigned char[1] 不适合 "Test1" 你至少需要 unsigned char[6] 来适应...。注意数组大小,因为它们是在编译时定义的,不能更改在运行时。 在更新中,metricSize 应该是 uint16_tpointSize 应该是 uint32_tmetricpoint 都很棘手,因为在 Erlang 二进制文件中,Metric 是一个可变大小的二进制字段,位于其他两个字段之前,因此使用灵活数组成员的方法不适用于 Metric。此外,Erlang 二进制文件没有对齐填充,因此根据Metric 的大小,pointSize 可能最终完全未对齐,因此需要特殊处理。您应该使用两个结构,因此每个可变大小的字段都在末尾,但是这里的示例太长了,无法评论。 【参考方案1】:

encode 函数创建的二进制文件格式如下:

<<?STREAM, Delay:?DELAY_SIZE/?SIZE_TYPE,
  (byte_size(Bucket)):?BUCKET_SS/?SIZE_TYPE, Bucket/binary>>

首先让我们用它们的实际值替换所有预处理器宏:

<<4, Delay:8/unsigned-integer,
  (byte_size(Bucket):8/unsigned-integer, Bucket/binary>>

现在我们可以更容易地看到这个二进制文件包含:

值 4 的字节 Delay 的值作为一个字节 Bucket 二进制文件的大小(以字节为单位) Bucket 二进制文件的值

由于末尾有Bucket 二进制文件,所以整个二进制文件是可变大小的。

类似于此值的 C99 结构可以定义如下:

struct EncodedStream 
    unsigned char mode;
    unsigned char delay;
    unsigned char bucket_size;
    unsigned char bucket[];
;

这种方法对bucket 字段使用C99 flexible array member,因为它的实际大小取决于bucket_size 字段中设置的值,并且您可能通过分配足够大的内存来使用这种结构以容纳固定的- size 字段与可变大小的bucket 字段一起,其中bucket 本身被分配以保存bucket_size 字节。如果您使用#include &lt;stdint.h&gt;,您也可以将unsigned char 的所有用法替换为uint8_t。在传统 C 中,bucket 将被定义为 0 或 1 大小的数组。

更新: OP 用另一个结构扩展了这个问题,所以我在下面扩展了我的答案以涵盖它。

编写对应于 metric/time/points 二进制文件的struct 的明显但错误的方法是:

struct Wrong 
    unsigned char sentry;
    uint64_t time;
    uint16_t metric_size;
    unsigned char metric[];
    uint32_t points_size;
    unsigned char points[];
;

Wrong 结构存在两个问题:

填充和对齐: 通常,字段在与其大小相对应的自然边界上对齐。在这里,C 编译器将在 8 字节边界上对齐 time 字段,这意味着在 sentry 字段之后将有 7 个字节的填充。但是 Erlang 二进制文件不包含这样的填充。

中间有非法的灵活数组字段:metric 字段的大小可以变化,但我们不能像前面示例中那样使用灵活数组方法,因为这样的数组只能用于结构的最后一个字段。 metric 的大小可以变化的事实意味着不可能编写与 Erlang 二进制文件匹配的单个 C 结构。

解决填充和对齐问题需要使用打包结构,您可以通过编译器支持来实现,例如 gcc 和 clang __packed__ 属性(其他编译器可能有其他实现方式)。结构体中间的可变大小metric字段可以通过使用两个结构体来解决:

typedef struct __attribute((__packed__)) 
    unsigned char sentry;
    uint64_t time;
    uint16_t size;
    unsigned char metric[];
 Metric;

typedef struct __attribute((__packed__)) 
    uint32_t size;
    unsigned char points[];
 Points;

打包这两个结构意味着它们的布局将匹配 Erlang 二进制文件中相应数据的布局。

不过,还有一个问题:字节序。默认情况下,Erlang 二进制文件中的字段是大端的。如果你碰巧在一台大端机器上运行你的 C 代码,那么一切都会正常工作,但如果不是——而且很可能你不是——你的 C 代码读取和写入的数据值将与 Erlang 不匹配。

幸运的是,字节序很容易处理:您可以使用 byte swapping 编写 C 代码,无论主机的字节序如何,都可以便携地读写大端数据。

要同时使用这两个结构,您首先必须分配足够的内存来保存这两个结构以及metricpoints 可变长度字段。将指向已分配内存的指针(我们称之为p)转换为Metric*,然后使用Metric 指针在结构字段中存储适当的值。只需确保在存储 timesize 值时将它们转换为大端。然后,您可以计算指向Points 结构在分配内存中的位置的指针,如下所示,假设p 是指向charunsigned char 的指针:

Points* points = (Points*)(p + sizeof(Metric) + <length of Metric.metric>);

请注意,您不能只使用 Metric 实例的 size 字段作为此处的最终加数,因为您将其值存储为大端。然后,一旦你填写了Points 结构的字段,再次确保将size 值存储为大端,你可以将p 发送到Erlang,它应该与Erlang 系统期望的匹配。

【讨论】:

谢谢,我会尝试并回复您! 我已经按照你的解释实现了结构,但我现在按照 Luis Colorado 的建议使用固定大小的存储桶数组。但是,我需要以相同的方式创建另一个结构,但大小不同。我将在我的第一篇文章中将其添加为更新,如果您有时间,请查看并发表评论。 我已更新答案以解决问题中的更新问题。

以上是关于如何创建具有特定大小的 C 结构以通过套接字发送到 DalmatinerDB?的主要内容,如果未能解决你的问题,请参考以下文章

通过c中的套接字发送结构

如何将数据发送到特定的套接字

蓝牙:如何使用 Bluez 创建一个套接字以连接到具有特定 UUID 的服务?

通过 C 中的套接字传递结构

C ++ MPI创建并发送具有字段char [16]和整数的结构数组

如何在linux中指定用于套接字的接口