[英]Boost asio UDP client not receiving data
我正在嘗試使用 UDP 從服務器向特定客戶端發送數據,而不是讓客戶端先向服務器發送數據,根本不發送服務器數據。 問題是客戶端使用receive_from()
function 等待數據,但什么也得不到。 由於某種原因,服務器確實設法發送了數據,並且在發送了整個有效載荷之后,它自行關閉了,但我不知道它將數據發送到哪里。 如果我在沒有客戶端的情況下運行服務器,數據也會成功發送,我不知道為什么,在數據發送之前,服務器不應該阻止send_to()
function 嗎?
這是客戶端停止的地方(我的代碼的一部分):
void UDPclient::run()
{
std::string relative_path = "assets/";
std::thread thread_context = std::thread([&] {_io_context.run(); }); //start the context.
file_info fileInfo;
boost::asio::io_context::work idleWork(_io_context);
boost::system::error_code error;
udp::socket socket(_io_context); //the file descriptor
WirehairCodec decoder;
udp::endpoint sender;
memset(&fileInfo, sizeof(fileInfo), 0); //reset the struct to 0 (good practice)
std::size_t bytes_transferred = socket.receive_from(
boost::asio::buffer(&fileInfo, sizeof(fileInfo)),
sender);
...
socket.close();
}
服務器(我的代碼的一部分):
int main()
{
std::uint16_t port = 2000;
file_info fileInfo;
std::string filePath;
boost::asio::io_context io_context;
udp::socket socket(io_context, udp::endpoint(udp::v4(), port));
udp::endpoint destination(boost::asio::ip::address::from_string("127.0.0.1"), port);
boost::system::error_code ec;
const WirehairResult initResult = wirehair_init(); //initialize wirehair
if(initResult != Wirehair_Success)
{
std::cout << "failed to initialize wirehair: " << initResult << std::endl;
return -1;
}
if (ec)
std::cerr << ec.message() << std::endl;
else
{
std::cout << "Enter the specified file (Full path) to send: ";
std::cin >> filePath;
while (!boost::filesystem::exists(filePath))
{
std::cerr << "file doesn't exist." << std::endl;
std::cout << "Enter the specified file (Full path) to send: ";
std::cin >> filePath;
}
read_fileToVector(filePath, vBuffer);
file_info fileInfo;
memset(&fileInfo, 0, sizeof(fileInfo)); // Always set the struct values to 0, good practice.
//send file size, name
fileInfo.size = vBuffer.size();
strncpy(fileInfo.fileName, filePath.substr(filePath.find_last_of("/\\") + 1).c_str(),
sizeof(fileInfo.fileName) - 1);
std::cout << "name: " << fileInfo.fileName << std::endl;
std::cout << "size: " << fileInfo.size << std::endl;
socket.send_to(boost::asio::buffer(&fileInfo, sizeof(fileInfo)), destination);
socket.wait(socket.wait_write);
...
socket.close();
}
沒有足夠的代碼可以說明,但我可以指出一些味道:
memset 調用錯誤。 由於顯然file_info
必須是微不足道的標准布局(對於 memset 是合法的),為什么不使用空初始化程序對其進行聚合初始化,效果相同(默認初始化每個成員)?
您的上下文顯然是 class 成員,但您正在為每個接收啟動一個線程? 這似乎很奇怪。 我希望在 io_context/work 的生命周期內有一個線程。
如果您真的想重用/重新啟動相同的 io_context,請記住需要在其間調用.reset()
。
您也不需要為每個接收打開/關閉套接字
您可能希望將 UDP 套接字綁定到特定端口以接收
我希望看到的是類似於:
struct UDPclient {
UDPclient(asio::any_io_executor ex, uint16_t port = DEFAULT_PORT)
: socket_(ex, udp::v4())
, port_(port)
{
socket_.bind({{}, port_});
}
bool run()
{
file_info fi{}; // value-initializes all members
udp::endpoint sender;
if (std::size_t n =
socket_.receive_from(asio::buffer(&fi, sizeof(fi)), sender);
n > file_info::HEADERLEN && fi.magic == file_info::MAGIC) //
{
// don't assume name will be zero terminated
std::string_view name(fi.name.data(),
strnlen(fi.name.data(), fi.name.max_size()));
COUT << "Receiving " << fi.xfer_id << " length " << fi.file_length
<< " name " << std::quoted(name) << " from " << sender
<< std::endl;
decoder_.reset(
wirehair_decoder_create(nullptr, fi.file_length, PACKET_SIZE));
if (!decoder_) {
throw std::runtime_error("wirehair_decoder_create");
}
packet_info packet{};
for (bool data_complete = false; !data_complete;) {
if (auto len = socket_.receive_from(
asio::buffer(&packet, sizeof(packet)), sender);
n >= packet_info::HEADERLEN &&
packet.magic == packet_info::MAGIC &&
len == packet_info::HEADERLEN + packet.block_length) //
{
if (fi.xfer_id != packet.xfer_id)
continue; // TODO concurrent receives
COUT << "(Incoming " << packet.block_length << " for "
<< fi.xfer_id << " from " << sender << ")"
<< std::endl;
// Attempt decode
switch (wirehair_decode(decoder_.get(), packet.id,
packet.block.data(),
packet.block_length)) //
{
case Wirehair_NeedMore: continue; break;
case Wirehair_Success: data_complete = true; break;
default: throw std::runtime_error("wirehair_decode");
}
COUT << "(data complete? " << std::boolalpha
<< data_complete << ")" << std::endl;
}
}
COUT << "Receive completed for " << fi.xfer_id << std::endl;
// try to be safe about interpreting the output name
auto spec = fs::relative(
weakly_canonical(
relative_path /
fs::path(name).lexically_normal().relative_path()),
relative_path);
if (spec.empty())
throw std::runtime_error("invalid file specification " + spec.native());
auto target = relative_path / spec;
fs::create_directories(target.parent_path());
COUT << "Decoding to " << target << " for " << fi.xfer_id
<< std::endl;
std::vector<uint8_t> decoded(fi.file_length);
// Recover original data on decoder side
auto r = wirehair_recover(decoder_.get(), decoded.data(),
decoded.size());
if (r != Wirehair_Success)
throw std::runtime_error("wirehair_recover");
std::ofstream(target, std::ios::binary)
.write(reinterpret_cast<char const*>(decoded.data()),
decoded.size());
}
return true;
}
private:
udp::socket socket_;
uint16_t port_;
CodecPtr decoder_{};
fs::path relative_path = "assets/";
};
為此,我制定了一個協議,該協議由兩種類型的消息組成,魔術頭定義為:
namespace /*protocol*/ {
#pragma pack(push, 1)
struct file_info {
boost::endian::big_uint32_t magic, file_length;
boost::uuids::uuid xfer_id;
std::array<char, PATH_MAX + 1> name;
enum : unsigned {
MAGIC = 0xDEFACED,
HEADERLEN = sizeof(magic) + sizeof(file_length) + sizeof(xfer_id),
};
};
static_assert(std::is_trivial_v<file_info>);
static_assert(sizeof(file_info) + 8 <= 0xFFFF); // must fit udp
struct packet_info {
boost::endian::big_uint32_t magic, block_length, id;
boost::uuids::uuid xfer_id;
std::array<uint8_t, PACKET_SIZE> block;
enum : unsigned {
MAGIC = static_cast<unsigned>(~0xDEFACED),
HEADERLEN = sizeof(magic) + sizeof(block_length) + sizeof(id) + sizeof(xfer_id)
};
};
static_assert(std::is_trivial_v<packet_info>);
static_assert(sizeof(packet_info) + 8 <= 0xFFFF); // must fit udp
#pragma pack(pop)
// Rule Of Zero, please:
struct WHFree {
void operator()(WirehairCodec c) const { wirehair_free(c); }
};
using CodecPtr = std::unique_ptr<WirehairCodec_t, WHFree>;
} // namespace
還要注意異常安全句柄類型CodecPtr
,它避免了泄漏這些資源的任何風險。
出於興趣,我查看了 Wirehair 編解碼器並使用它實現了一個簡單的同步文件傳輸。
wirehair_encoder_create
在這種情況下失敗)。 您必須創建一個例外來涵蓋此類情況。xfer_id
字段作為相關 ID 放入協議消息中。#include <cassert>
#include <chrono>
#include <filesystem>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <thread>
using namespace std::chrono_literals;
using std::this_thread::sleep_for;
#include <boost/asio.hpp>
#include <boost/endian/arithmetic.hpp>
#include <boost/uuid/uuid_generators.hpp>
#include <boost/uuid/uuid_io.hpp>
#include <wirehair/wirehair.h>
namespace asio = boost::asio;
namespace fs = std::filesystem;
using asio::ip::udp;
static constexpr int PACKET_SIZE = 1400;
static constexpr uint16_t DEFAULT_PORT = 9797;
namespace /*protocol*/ {
#pragma pack(push, 1)
struct file_info {
boost::endian::big_uint32_t magic, file_length;
boost::uuids::uuid xfer_id;
std::array<char, PATH_MAX + 1> name;
enum : unsigned {
MAGIC = 0xDEFACED,
HEADERLEN = sizeof(magic) + sizeof(file_length) + sizeof(xfer_id),
};
};
static_assert(std::is_trivial_v<file_info>);
static_assert(sizeof(file_info) + 8 <= 0xFFFF); // must fit udp
struct packet_info {
boost::endian::big_uint32_t magic, block_length, id;
boost::uuids::uuid xfer_id;
std::array<uint8_t, PACKET_SIZE> block;
enum : unsigned {
MAGIC = static_cast<unsigned>(~0xDEFACED),
HEADERLEN = sizeof(magic) + sizeof(block_length) + sizeof(id) + sizeof(xfer_id)
};
};
static_assert(std::is_trivial_v<packet_info>);
static_assert(sizeof(packet_info) + 8 <= 0xFFFF); // must fit udp
#pragma pack(pop)
// Rule Of Zero, please:
struct WHFree {
void operator()(WirehairCodec c) const { wirehair_free(c); }
};
using CodecPtr = std::unique_ptr<WirehairCodec_t, WHFree>;
} // namespace
struct UDPclient {
UDPclient(asio::any_io_executor ex, uint16_t port = DEFAULT_PORT)
: socket_(ex, udp::v4())
, port_(port)
{
socket_.bind({{}, port_});
}
bool run()
{
file_info fi{}; // value-initializes all members
udp::endpoint sender;
if (std::size_t n =
socket_.receive_from(asio::buffer(&fi, sizeof(fi)), sender);
n > file_info::HEADERLEN && fi.magic == file_info::MAGIC) //
{
// don't assume name will be zero terminated
std::string_view name(fi.name.data(),
strnlen(fi.name.data(), fi.name.max_size()));
std::cout << "Receiving " << fi.xfer_id << " length "
<< fi.file_length << " name " << std::quoted(name)
<< " from " << sender << std::endl;
decoder_.reset(
wirehair_decoder_create(nullptr, fi.file_length, PACKET_SIZE));
if (!decoder_) {
throw std::runtime_error("wirehair_decoder_create");
}
packet_info packet{};
for (bool data_complete = false; !data_complete;) {
if (auto len = socket_.receive_from(
asio::buffer(&packet, sizeof(packet)), sender);
n >= packet_info::HEADERLEN &&
packet.magic == packet_info::MAGIC &&
len == packet_info::HEADERLEN + packet.block_length) //
{
if (fi.xfer_id != packet.xfer_id)
continue; // TODO concurrent receives
std::cout << "(Incoming " << packet.block_length << " for "
<< fi.xfer_id << " from " << sender << ")"
<< std::endl;
// Attempt decode
switch (wirehair_decode(decoder_.get(), packet.id,
packet.block.data(),
packet.block_length)) //
{
case Wirehair_NeedMore: continue; break;
case Wirehair_Success: data_complete = true; break;
default: throw std::runtime_error("wirehair_decode");
}
std::cout << "(data complete? " << std::boolalpha
<< data_complete << ")" << std::endl;
}
}
std::cout << "Receive completed for " << fi.xfer_id << std::endl;
// try to be safe about interpreting the output name
auto spec = fs::relative(
weakly_canonical(
relative_path /
fs::path(name).lexically_normal().relative_path()),
relative_path);
if (spec.empty())
throw std::runtime_error("invalid file specification " + spec.native());
auto target = relative_path / spec;
fs::create_directories(target.parent_path());
std::cout << "Decoding to " << target << " for " << fi.xfer_id
<< std::endl;
std::vector<uint8_t> decoded(fi.file_length);
// Recover original data on decoder side
auto r = wirehair_recover(decoder_.get(), decoded.data(),
decoded.size());
if (r != Wirehair_Success)
throw std::runtime_error("wirehair_recover");
std::ofstream(target, std::ios::binary)
.write(reinterpret_cast<char const*>(decoded.data()),
decoded.size());
}
return true;
}
private:
udp::socket socket_;
uint16_t port_;
CodecPtr decoder_{};
fs::path relative_path = "assets/";
};
struct Sender {
Sender(asio::any_io_executor ex, uint16_t port = DEFAULT_PORT)
: socket_(ex, udp::v4())
, port_(port)
{
}
bool send(fs::path filespec)
{
std::ifstream ifs(filespec, std::ios::binary);
std::vector<uint8_t> const contents(std::istreambuf_iterator<char>(ifs),
{});
ifs.close();
assert(contents.size() == fs::file_size(filespec));
file_info fi{}; // value-initializes all members
fi.magic = file_info::MAGIC;
fi.xfer_id = boost::uuids::random_generator{}();
fi.file_length = contents.size();
strncpy(fi.name.data(), filespec.c_str(), fi.name.size() - 1);
socket_.send_to(asio::buffer(&fi, sizeof(fi)), {{}, port_});
// Create encoder
encoder_.reset(wirehair_encoder_create(nullptr, contents.data(),
contents.size(), PACKET_SIZE));
if (!encoder_) {
throw std::runtime_error("wirehair_encoder_create");
}
auto N = contents.size() / PACKET_SIZE + 1;
N = (N * 10) / 9; // ~10% redundancy
std::cout << "Sending " << filespec << " of " << contents.size()
<< " bytes in " << N << " packets of " << PACKET_SIZE
<< std::endl;
for (unsigned block_id = 1; block_id <= N; ++block_id) {
sleep_for(500ms);
packet_info packet{};
packet.magic = packet_info::MAGIC;
packet.xfer_id = fi.xfer_id;
// Encode a packet
uint32_t writeLen = 0;
if (auto r = wirehair_encode(encoder_.get(), block_id,
packet.block.data(),
packet.block.size(), &writeLen);
r == Wirehair_Success) //
{
packet.id = block_id;
packet.block_length = writeLen;
socket_.send_to(
asio::buffer(&packet, packet_info::HEADERLEN + writeLen),
{{}, port_});
std::cout << "(Packet " << packet.block_length << " bytes)"
<< std::endl;
} else {
throw std::runtime_error("wirehair_encode");
}
}
std::cout << "Send " << filespec << " complete (xfer_id:" << fi.xfer_id
<< ")" << std::endl;
return true;
}
private:
udp::socket socket_;
uint16_t port_;
CodecPtr encoder_{};
fs::path relative_path = "assets/";
};
int main(int argc, char** argv) {
if (auto r = wirehair_init(); r != Wirehair_Success) {
std::cout << "Wirehair initialization failed: " << r << std::endl;
return 1;
}
asio::thread_pool io(1);
auto ex = io.get_executor();
post(io, [ex] {
UDPclient client{ex};
while (true)
try { client.run(); }
catch (std::exception const& e) { std::cout << "Exception: " << e.what() << "\n"; }
});
Sender sender{ex};
for (auto spec : std::vector(argv + 1, argv + argc)) {
try {
sender.send(spec);
} catch (std::exception const& e) {
std::cout << "Exception: " << e.what() << "\n";
}
}
io.join();
}
我無法在線演示,但您可以使用https://github.com/sehe/wirehair-demo的存儲庫自行構建它:
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.