[英]How to create a C struct with specific size to send over socket to DalmatinerDB?
我正在尝试为dalmatinerdb创建一个C客户端,但是很难理解如何组合变量,将其写入缓冲区并将其发送到数据库。 dalmatinerdb用Erlang编写的事实使其变得更加困难。 但是,通过查看dalmatinerdb的python客户端,我(大概)发现了必要的变量大小和顺序。
erlang客户端具有称为“编码”的功能,请参见下文:
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>>;
根据官方的dalmatinerdb协议,我们可以看到以下内容:
-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界面)仅在将值发送到存储桶时才做出反应和更新。 换句话说,仅发送第一个struct不会给我任何线索,无论它是对还是错。 因此,我将尝试使用实际值创建辅助结构。
编码值的erland代码段如下所示:
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;
};
对这个结构有何评论?
由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>>
现在我们可以更容易地看到此二进制文件包含:
Delay
的值,以字节为单位 Bucket
二进制文件的大小(以字节为单位) Bucket
二进制值 由于末尾有Bucket
二进制文件,因此整个二进制文件的大小是可变的。
可以如下定义一个类似于该值的C99结构:
struct EncodedStream {
unsigned char mode;
unsigned char delay;
unsigned char bucket_size;
unsigned char bucket[];
};
此方法将bucket
字段使用C99灵活数组成员 ,因为它的实际大小取决于bucket_size
字段中设置的值,并且您大概通过分配足够大的内存以将固定大小的字段与变量一起存储而使用此结构大小的bucket
字段,其中bucket
本身被分配为容纳bucket_size
字节。 如果您#include <stdint.h>
也可以用uint8_t
替换所有对unsigned char
使用。 在传统的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编译器将time
字段对齐在8字节边界上,这意味着在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二进制文件中的字段为big-endian。 如果您恰巧在大型字节序的计算机上运行C代码,那么事情就可以了,但是如果不行-可能您就不行了-您的C代码读取和写入的数据值将与Erlang不匹配。
幸运的是,字节序很容易处理:您可以使用字节交换来编写C代码,该代码可移植地读取和写入大字节序数据,而与主机的字节序无关。
要同时使用这两个结构,您首先必须分配足够的内存以容纳这两个结构以及metric
和points
可变长度字段。 将指针转换为分配的内存(我们称其为p
)到Metric*
,然后使用Metric
指针将适当的值存储在struct字段中。 只需确保在存储它们time
将time
和size
值转换为big-endian。 然后,可以假设p
是char
或unsigned char
的指针,计算出指向Points
结构在分配的内存中的位置的指针:
Points* points = (Points*)(p + sizeof(Metric) + <length of Metric.metric>);
请注意,您不能仅在此处将Metric
实例的size
字段用作最终加数,因为您将其值存储为big-endian。 然后,一旦您填写了Points
结构的字段,并再次确保将size
值存储为big-endian,就可以将p
发送到Erlang,在此处应该与Erlang系统期望的相匹配。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.