你需要它便携吗?
如果没有:
- 简单的方法
- 使用 Boost 序列化
如果需要便携
- 使用
ntohl 和htonl 调用等使简单化的方法复杂化。
- 使用 Boost 序列化和EOS Portable Archives
1。简单的方法
只需将结构作为 POD 数据发送(假设它实际上是 POD,鉴于您问题中的代码是一个公平的假设,因为结构显然不是 C++)。
在 2 个线程(侦听器和客户端)上使用同步调用的简单示例显示了服务器如何将数据包发送到客户端正确接收的客户端。
注意事项:
- 使用异步调用是一个微不足道的更改(将
write 和 read 更改为 async_write 和 async_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 的代码的简单的插入式练习。