如何创建具有特定大小的 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_t
和 pointSize
应该是 uint32_t
。 metric
和 point
都很棘手,因为在 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 <stdint.h>
,您也可以将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 代码,无论主机的字节序如何,都可以便携地读写大端数据。
要同时使用这两个结构,您首先必须分配足够的内存来保存这两个结构以及metric
和points
可变长度字段。将指向已分配内存的指针(我们称之为p
)转换为Metric*
,然后使用Metric
指针在结构字段中存储适当的值。只需确保在存储 time
和 size
值时将它们转换为大端。然后,您可以计算指向Points
结构在分配内存中的位置的指针,如下所示,假设p
是指向char
或unsigned char
的指针:
Points* points = (Points*)(p + sizeof(Metric) + <length of Metric.metric>);
请注意,您不能只使用 Metric
实例的 size
字段作为此处的最终加数,因为您将其值存储为大端。然后,一旦你填写了Points
结构的字段,再次确保将size
值存储为大端,你可以将p
发送到Erlang,它应该与Erlang 系统期望的匹配。
【讨论】:
谢谢,我会尝试并回复您! 我已经按照你的解释实现了结构,但我现在按照 Luis Colorado 的建议使用固定大小的存储桶数组。但是,我需要以相同的方式创建另一个结构,但大小不同。我将在我的第一篇文章中将其添加为更新,如果您有时间,请查看并发表评论。 我已更新答案以解决问题中的更新问题。以上是关于如何创建具有特定大小的 C 结构以通过套接字发送到 DalmatinerDB?的主要内容,如果未能解决你的问题,请参考以下文章
蓝牙:如何使用 Bluez 创建一个套接字以连接到具有特定 UUID 的服务?