【问题标题】:c++ Sending struct over networkc ++通过网络发送结构
【发布时间】:2016-12-26 12:44:48
【问题描述】:

我正在使用具有预定义结构的英特尔 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 分隔。

但是当在接收器端将收到的char* 组装回原始结构时,我遇到了一些麻烦。

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

【问题讨论】:

标签: c++ structure boost-asio


【解决方案1】:

你需要它便携吗?

如果没有:

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

如果需要便携

  1. 使用ntohlhtonl 调用等使简单化的方法复杂化。
  2. 使用 Boost 序列化和EOS Portable Archives

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 序列化,无论如何都有这样的包装器会很方便,让我们使用这样的“零规则”包装器重写它:

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 序列化

事不宜迟,下面是一种为该包装器实现类内序列化的简单方法:

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 的代码的简单的插入式练习。

【讨论】:

  • 为其他方法添加了示例实现。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-06-10
  • 1970-01-01
  • 2013-08-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多