[英]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.