簡體   English   中英

Boost asio UDP 客戶端收不到數據

[英]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 編解碼器並使用它實現了一個簡單的同步文件傳輸。

  • 一個限制是不能傳輸零長度文件( 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM