假設我有一個應用程式不斷從套接字接收位元組流。我有描述資料包外觀的檔案。例如,總標頭大小和總有效負載大小,資料型別對應不同的位元組偏移量。我想將其決議為struct. 我能想到的方法是我將宣告 astruct并使用一些編譯器宏禁用填充,可能類似于:
struct Payload
{
char field1;
uint32 field2;
uint32 field3;
char field5;
} __attribute__((packed));
然后我可以宣告一個緩沖區和緩沖區memcpy的位元組以及reinterpret_cast我的結構。我能想到的另一種方法是逐個處理位元組并將資料填充到struct. 我認為任何一個都應該作業,但它有點老派,可能不安全。
提到的 reinterpret_cast 方法應該類似于:
void receive(const char*data, std::size_t data_size)
{
if(data_size == sizeof(payload)
{
const Payload* payload = reinterpret_cast<const Payload*>(data);
// ... further processing ...
}
}
我想知道這種用例是否有更好的方法(更現代的 C 風格?更優雅?)?我覺得使用元編程應該會有所幫助,但我不知道如何使用它。
任何人都可以分享一些想法嗎?或者指點我一些相關的參考資料或資源,甚至是相關的開源代碼,以便我可以看看并了解更多關于如何以更優雅的方式解決此類問題的資訊。
uj5u.com熱心網友回復:
有許多不同的方法可以解決這個問題。這是一個:
請記住,從網路流中讀取結構在語意上與讀取單個值相同,因此在任何一種情況下操作看起來都應該相同。
請注意,根據您發布的內容,我推斷您不會使用非平凡的默認建構式處理型別。如果是這樣的話,我會以不同的方式處理事情。
在這種方法中,我們:
- 定義一個
read_into(src&, dst&)接收原始位元組源的函式,以及一個要填充的物件。 - 提供所有算術型別的通用實作,在適當時從網路位元組順序切換。
- 為我們的結構多載函式,按照線路上預期的順序呼叫
read_into()每個欄位。
#include <cstdint>
#include <bit>
#include <concepts>
#include <array>
#include <algorithm>
// Use std::byteswap when available. In the meantime, just lift the implementation from
// https://en.cppreference.com/w/cpp/numeric/byteswap
template<std::integral T>
constexpr T byteswap(T value) noexcept
{
static_assert(std::has_unique_object_representations_v<T>, "T may not have padding bits");
auto value_representation = std::bit_cast<std::array<std::byte, sizeof(T)>>(value);
std::ranges::reverse(value_representation);
return std::bit_cast<T>(value_representation);
}
template<typename T>
concept DataSource = requires(T& x, char* dst, std::size_t size ) {
{x.read(dst, size)};
};
// General read implementation for all arithmetic types
template<std::endian network_order = std::endian::big>
void read_into(DataSource auto& src, std::integral auto& dst) {
src.read(reinterpret_cast<char*>(&dst), sizeof(dst));
if constexpr (sizeof(dst) > 1 && std::endian::native != network_order) {
dst = byteswap(dst);
}
}
struct Payload
{
char field1;
std::uint32_t field2;
std::uint32_t field3;
char field5;
};
// Read implementation specific to Payload
void read_into(DataSource auto& src, Payload& dst) {
read_into(src, dst.field1);
read_into<std::endian::little>(src, dst.field2);
read_into(src, dst.field3);
read_into(src, dst.field5);
}
// mind you, nothing stops you from just reading directly into the struct, but beware of endianness issues:
// struct Payload
// {
// char field1;
// std::uint32_t field2;
// std::uint32_t field3;
// char field5;
// } __attribute__((packed));
// void read_into(DataSource auto& src, Payload& dst) {
// src.read(reinterpret_cast<char*>(&dst), sizeof(Payload));
// }
// Example
struct some_data_source {
std::size_t read(char*, std::size_t size);
};
void foo() {
some_data_source data;
Payload p;
read_into(data, p);
}
一個替代 API 可能是dst.field2 = read<std::uint32_t>(src),它的缺點是需要明確型別,但如果您必須處理非平凡的建構式,則更合適。
在 Godbolt 上查看它:https ://gcc.godbolt.org/z/77rvYE1qn
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/484056.html
上一篇:如何將numba用于以下代碼?
