C ++通过网络发送结构 [英] c++ Sending struct over network

查看:93
本文介绍了C ++通过网络发送结构的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用具有预定义结构的Intel SGX.我需要通过使用boost::asio进行操作的网络连接发送这些结构. 需要发送的结构具有以下格式:

I'm working with Intel SGX which has predefined structures. I need to send these structures over a network connection which is operated by using boost::asio. The structure that needs to be send has the following format:

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

For the sending and receiving, the methods async_write and async_async_read_some are used

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分隔.

My first approach was to transform the single structure elements into strings and store them in a vector<string> which is then further transformed into char* whereas each element is separated by \n.

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

But when assembling the received char* on the receiver side back to the original structure I run into some troubles.

这真的是应该完成的方式吗?还是有更好的方法来转移结构

Is this really the way this should be done or is there a nicer more sufficient way of transfering the structure

推荐答案

您需要它可移植吗?

如果不是:

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

如果需要携带

  1. 通过ntohlhtonl调用等简化了简化方法.
  2. 通过 EOS便携式归档文件
  3. 使用Boost序列化
  1. complicate the simplistic approach with ntohl and htonl calls etc.
  2. use Boost Serialization with EOS Portable Archives

1.简单的方法

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

1. simplistic approach

Just send the struct as POD data (assuming it is actually POD, which given the code in your question is a fair assumption as the struct is clearly not C++).

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

A simple sample that uses synchronous calls on 2 threads (listener and client) shows how the server sends a packet to the client which the client receives correctly.

注意:

  • 使用异步调用是一个微不足道的更改(将writeread更改为async_writeasync_write,除非使用协程,否则这会使控制流变得不太清晰)
  • 我展示了如何在C ++ 11中以(例外)安全的方式使用malloc/free.您可能想在代码库中创建一个简单的零规则包装器.
  • using async calls is a trivial change (change write and read into async_write and async_write, which just makes control flow a bit less legible unless using coroutines)
  • I showed how I'd use malloc/free in a (exceptio) safe manner in C++11. You may want to make a simple Rule-Of-Zero wrapper instead in your codebase.

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

1b. simplistic with wrapper

Because for Boost Serialization it would be convenient to have such a wrapper anyways, let's rewrite that using such a "Rule Of Zero" wrapper:

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

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

2. using Boost Serialization

Without much ado, here's a simplistic way to implement serialization in-class for that wrapper:

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简化测试驱动程序:

Which then simplifies the test driver to this - using 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";
    }
});

所有其他代码均未更改,请参见 在线直播Coliru .输出保持不变,只是在使用Boost 1.62的64位计算机上,数据包大小增加到683.

All other code is unchanged from the above, see it Live On Coliru. Output unchanged, except packet sizes grew to 683 on my 64-bit machine using Boost 1.62.

我不想演示这个.感觉就像是C程序员,而不是C ++程序员.当然,有一些聪明的方法可以避免编写字节序乱七八糟的东西等.有关现代方法,请参见例如

I'm not in the mood to demo this. It feels like being a C programmer instead of a C++ programmer. Of course there are clever ways to avoid writing the endian-ness twiddling etc. For a modern approach see e.g.

这是使用3的代码进行的简单插入练习.

Is a simple drop-in exercise using the code of 3.

这篇关于C ++通过网络发送结构的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆