c ++通过网络发送结构

c++ Sending struct over network

我正在使用具有预定义结构的英特尔 SGX。我需要通过使用 boost::asio 操作的网络连接发送这些结构。 需要发送的结构体格式如下:

typedef struct _ra_samp_request_header_t{
    uint8_t  type;     /* set to one of ra_msg_type_t*/
    uint32_t size;     /*size of request body*/
    uint8_t  align[3];
    uint8_t body[];
} ra_samp_request_header_t;

对于发送和接收,使用方法async_writeasync_async_read_some

boost::asio::async_write(socket_, boost::asio::buffer(data_, max_length),
                                          boost::bind(&Session::handle_write, this,
                                          boost::asio::placeholders::error));

socket_.async_read_some(boost::asio::buffer(data_, max_length),
                            boost::bind(&Session::handle_read, this,
                            boost::asio::placeholders::error,
                            boost::asio::placeholders::bytes_transferred));

data_ 被定义为

enum { max_length = 1024 };
char data_[max_length];

我的第一个方法是将单个结构元素转换为字符串并将它们存储在 vector<string> 中,然后进一步转换为 char*,而每个元素由 \n 分隔。

但是在receiver端把接收到的char*组装回原来的结构我运行遇到了一些麻烦

这真的是应该这样做的方式还是有更好更充分的方式来转移结构

你需要它便携吗?

如果没有:

  1. 简单方法
  2. 使用 Boost 序列化

如果需要便携

  1. 通过 ntohlhtonl 调用等使简单方法复杂化。
  2. 将 Boost 序列化与 EOS Portable Archives
  3. 结合使用

1。简单的方法

只需将结构作为 POD 数据发送(假设它实际上是 POD,鉴于您问题中的代码是一个合理的假设,因为结构显然不是 C++)。

一个在 2 个线程(侦听器和客户端)上使用同步调用的简单示例显示了服务器如何将数据包发送到客户端并被客户端正确接收。

备注:

  • 使用异步调用是一个微不足道的更改(将 writeread 更改为 async_writeasync_write,除非使用协同程序,否则这只会使控制流变得不那么清晰)
  • 我展示了如何在 C++11 中以(异常)安全的方式使用 malloc/free。您可能想在您的代码库中创建一个简单的 Rule-Of-Zero 包装器。

Live On Coliru

#include <boost/asio.hpp>
#include <cstring>

namespace ba = boost::asio;
using ba::ip::tcp;

typedef struct _ra_samp_request_header_t{
    uint8_t  type;     /* set to one of ra_msg_type_t*/
    uint32_t size;     /*size of request body*/
    uint8_t  align[3];
    uint8_t  body[];
} ra_samp_request_header_t;

#include <iostream>
#include <thread>
#include <memory>

int main() {
    auto unique_ra_header = [](uint32_t body_size) {
        static_assert(std::is_pod<ra_samp_request_header_t>(), "not pod");

        auto* raw = static_cast<ra_samp_request_header_t*>(::malloc(sizeof(ra_samp_request_header_t)+body_size));
        new (raw) ra_samp_request_header_t { 2, body_size, {0} };
        return std::unique_ptr<ra_samp_request_header_t, decltype(&std::free)>(raw, std::free);
    };

    auto const& body = "There are many variations of passages of Lorem Ipsum available, but the majority have suffered alteration in some form, by injected humour, or randomised words which don't look even slightly believable. If you are going to use a passage of Lorem Ipsum, you need to be sure there isn't anything embarrassing hidden in the middle of text. All the Lorem Ipsum generators on the Internet tend to repeat predefined chunks as necessary, making this the first true generator on the Internet. It uses a dictionary of over 200 Latin words, combined with a handful of model sentence structures, to generate Lorem Ipsum which looks reasonable.";

    auto sample = unique_ra_header(sizeof(body));
    std::strncpy(reinterpret_cast<char*>(+sample->body), body, sizeof(body));

    ba::io_service svc;
    ra_samp_request_header_t const& packet = *sample;
    auto listener = std::thread([&] {
        try {
            tcp::acceptor a(svc, tcp::endpoint { {}, 6767 });
            tcp::socket s(svc);
            a.accept(s);

            std::cout << "listener: Accepted: " << s.remote_endpoint() << "\n";
            auto written = ba::write(s, ba::buffer(&packet, sizeof(packet) + packet.size));
            std::cout << "listener: Written: " << written << "\n";
        } catch(std::exception const& e) {
            std::cerr << "listener: " << e.what() << "\n";
        }
    });

    std::this_thread::sleep_for(std::chrono::milliseconds(10)); // make sure listener is ready

    auto client = std::thread([&] {
        try {
            tcp::socket s(svc);
            s.connect(tcp::endpoint { {}, 6767 });

            // this is to avoid the output to get intermingled, only
            std::this_thread::sleep_for(std::chrono::milliseconds(200));

            std::cout << "client: Connected: " << s.remote_endpoint() << "\n";

            enum { max_length = 1024 };
            auto packet_p = unique_ra_header(max_length); // slight over allocation for simplicity
            boost::system::error_code ec;
            auto received = ba::read(s, ba::buffer(packet_p.get(), max_length), ec); 

            // we expect only eof since the message received is likely not max_length
            if (ec != ba::error::eof) ba::detail::throw_error(ec);

            std::cout << "client: Received: " << received << "\n";
            (std::cout << "client: Payload: ").write(reinterpret_cast<char const*>(packet_p->body), packet_p->size) << "\n";
        } catch(std::exception const& e) {
            std::cerr << "client: " << e.what() << "\n";
        }
    });

    client.join();
    listener.join();
}

版画

g++ -std=gnu++11 -Os -Wall -pedantic main.cpp -pthread -lboost_system && ./a.out
listener: Accepted: 127.0.0.1:42914
listener: Written: 645
client: Connected: 127.0.0.1:6767
client: Received: 645
client: Payload: There are many variations of passages of Lorem Ipsum available, but the majority have suffered alteration in some form, by injected humour, or randomised words which don't look even slightly believable. If you are going to use a passage of Lorem Ipsum, you need to be sure there isn't anything embarrassing hidden in the middle of text. All the Lorem Ipsum generators on the Internet tend to repeat predefined chunks as necessary, making this the first true generator on the Internet. It uses a dictionary of over 200 Latin words, combined with a handful of model sentence structures, to generate Lorem Ipsum which looks reasonable.

1b。包装简单

因为对于 Boost 序列化来说,无论如何拥有这样的包装器会很方便,让我们使用这样的 "Rule Of Zero" 包装器重写它:

Live On Coliru

namespace mywrappers {
    struct ra_samp_request_header {
        enum { max_length = 1024 };

        // Rule Of Zero - https://rmf.io/cxx11/rule-of-zero
        ra_samp_request_header(uint32_t body_size = max_length) : _p(create(body_size)) {}

        ::ra_samp_request_header_t const& get() const { return *_p; };
        ::ra_samp_request_header_t&       get()       { return *_p; };

      private:
        static_assert(std::is_pod<::ra_samp_request_header_t>(), "not pod");
        using Ptr = std::unique_ptr<::ra_samp_request_header_t, decltype(&std::free)>;
        Ptr _p;

        static Ptr create(uint32_t body_size) {
            auto* raw = static_cast<::ra_samp_request_header_t*>(::malloc(sizeof(::ra_samp_request_header_t)+body_size));
            new (raw) ::ra_samp_request_header_t { 2, body_size, {0} };
            return Ptr(raw, std::free);
        };
    };
}

2。使用 Boost 序列化

事不宜迟,这里有一个在class 中为该包装器实现序列化的简单方法:

friend class boost::serialization::access;

template<typename Ar>
void save(Ar& ar, unsigned /*version*/) const {
    ar & _p->type
       & _p->size
       & boost::serialization::make_array(_p->body, _p->size);
}

template<typename Ar>
void load(Ar& ar, unsigned /*version*/) {
    uint8_t  type = 0;
    uint32_t size = 0;
    ar & type & size;

    auto tmp = create(size);
    *tmp = ::ra_samp_request_header_t { type, size, {0} };

    ar & boost::serialization::make_array(tmp->body, tmp->size);

    // if no exceptions, swap it out
    _p = std::move(tmp);
}

BOOST_SERIALIZATION_SPLIT_MEMBER()

然后将测试驱动程序简化为 - 使用 streambuf:

auto listener = std::thread([&] {
    try {
        tcp::acceptor a(svc, tcp::endpoint { {}, 6767 });
        tcp::socket s(svc);
        a.accept(s);

        std::cout << "listener: Accepted: " << s.remote_endpoint() << "\n";

        ba::streambuf sb;
        {
            std::ostream os(&sb);
            boost::archive::binary_oarchive oa(os);
            oa << sample;
        }

        auto written = ba::write(s, sb);
        std::cout << "listener: Written: " << written << "\n";
    } catch(std::exception const& e) {
        std::cerr << "listener: " << e.what() << "\n";
    }
});

std::this_thread::sleep_for(std::chrono::milliseconds(10)); // make sure listener is ready

auto client = std::thread([&] {
    try {
        tcp::socket s(svc);
        s.connect(tcp::endpoint { {}, 6767 });

        // this is to avoid the output to get intermingled, only
        std::this_thread::sleep_for(std::chrono::milliseconds(200));

        std::cout << "client: Connected: " << s.remote_endpoint() << "\n";

        mywrappers::ra_samp_request_header packet;
        boost::system::error_code ec;

        ba::streambuf sb;
        auto received = ba::read(s, sb, ec); 

        // we expect only eof since the message received is likely not max_length
        if (ec != ba::error::eof) ba::detail::throw_error(ec);

        std::cout << "client: Received: " << received << "\n";

        {
            std::istream is(&sb);
            boost::archive::binary_iarchive ia(is);
            ia >> packet;
        }

        (std::cout << "client: Payload: ").write(reinterpret_cast<char const*>(packet.get().body), packet.get().size) << "\n";
    } catch(std::exception const& e) {
        std::cerr << "client: " << e.what() << "\n";
    }
});

其他代码与上面相同,请看Live On Coliru。输出不变,除了数据包大小在我使用 Boost 1.62 的 64 位机器上增长到 683。

3。使简单的方法复杂化

我没心情演示这个。感觉就像是 C 程序员而不是 C++ 程序员。当然,有一些聪明的方法可以避免写字节序等问题。对于现代方法,请参见例如

4。使用 EAS 便携式存档

是使用代码 3 的简单插入式练习。