使用 boost::asio 库进行文件传输

File transfer using the boost::asio libary

提问人:Johannes W. 提问时间:11/16/2023 最后编辑:Johannes W. 更新时间:11/18/2023 访问量:59

问:

可悲的是,当我尝试将文件从我的 nfoserver 传输到我的 PC 时,会错误地读取文件大小并且永远不会结束 do while 循环。任何帮助都会被赞赏!当发送了正确的文件大小时,它会停止,但不会达到 eof,因为它从服务器读取了错误的文件大小。我真的不知道是什么原因造成的,因为我对boost::asio库或一般网络没有太多经验。这就是为什么任何帮助将不胜感激,这样我就可以更好地理解图书馆:)

客户:

void receive_file_content_over_tcp(const std::string& filePath, boost::asio::ip::tcp::socket& socket) {
    std::ofstream file(filePath, std::ios::binary | std::ios::trunc);
    if (!file.is_open()) {
        std::cerr << "Failed to open file for receiving." << std::endl;
        return;
    }

    boost::asio::streambuf receive_buffer;
    boost::asio::streambuf fileSizeBuffer;
    boost::system::error_code error;

    std::size_t totalBytesReceived = 0;
    std::size_t fileSize = 0;

    // Receive file size
    boost::asio::read(socket, fileSizeBuffer, boost::asio::transfer_exactly(sizeof(std::size_t)), error);

    if (error) {
        std::cerr << "Failed to receive file size: " << error.message() << std::endl;
        return;
    }

    // Extract file size from the buffer
    std::istream fileSizeStream(&fileSizeBuffer);
    fileSizeStream.read(reinterpret_cast<char*>(&fileSize), sizeof(std::size_t));

    std::cout << "Receiving file. File size: " << fileSize << " bytes." << std::endl;

    // Receive file content
    while (totalBytesReceived < fileSize) {
        std::size_t bytesRead = boost::asio::read(socket, receive_buffer, boost::asio::transfer_at_least(1), error);

        if (error && error != boost::asio::error::eof) {
            std::cerr << "Error while receiving file content: " << error.message() << std::endl;
            return;
        }

        if (bytesRead > 0) {
            // Write the received data to the file
            totalBytesReceived += bytesRead;
            std::istream is(&receive_buffer);
            file << is.rdbuf();

            // Display progress
            std::cout << "Received: " << totalBytesReceived << " bytes out of " << fileSize << " bytes (" << (totalBytesReceived * 100 / fileSize) << "%)." << std::endl;
        }
    }

    std::cout << "File content received successfully. Total bytes received: " << totalBytesReceived << std::endl;
}

服务器:

void send_file_content_over_tcp(const std::string& filePath, boost::asio::ip::tcp::socket& socket) {
    std::ifstream file(filePath, std::ios::binary | std::ios::ate);
    if (!file.is_open()) {
        std::cerr << "Failed to open file for sending." << std::endl;
        return;
    }

    // Get the file size
    std::size_t fileSize = file.tellg();
    file.seekg(0, std::ios::beg);

    // Create a buffer to hold the file content
    std::vector<char> buffer(fileSize);
    if (!file.read(buffer.data(), fileSize)) {
        std::cerr << "Failed to read file for sending." << std::endl;
        return;
    }
    std::cout << "File read successfully. File size: " << fileSize << " bytes." << std::endl;

    // Send the file content
    boost::system::error_code error;
    std::size_t bytesSent = boost::asio::write(socket, boost::asio::buffer(buffer), error);

    if (error) {
        std::cerr << "Error while sending file content: " << error.message() << std::endl;
    }
    else {
        std::cout << "File content sent successfully. Bytes sent: " << bytesSent << std::endl;
    }
}

我希望它读取正确的文件大小并传输它,但输出如下:

接收文件。文件大小:12894362189字节。

接收:512 字节(12894362189 字节 (0%)。

服务器输出:

文件读取成功。文件大小:2975744字节。

文件内容已成功发送。发送的字节数:2975744 文件大小为 ~2.9mb。

在过去的几天里,我尝试了很多东西,但它从未打破 eof 标志的循环。所以,如果你能帮助我,那会让我的一天变得非常愉快!谢谢。

C++ TCP boost-ASIO

评论


答:

0赞 sehe 11/16/2023 #1

您永远不会发送大小。你为什么会期望阅读它?send_file_contents

即使你这样做了,你也需要确保你以可移植的方式发送它(例如,考虑到字节序),例如使用

 using NetworkSize = boost::endian::big_uint64_t;

我会简化发送,并包括大小:

static std::vector<char> read_file(path const& fspec) {
    std::ifstream file(fspec, std::ios::binary);
    return std::vector<char>(std::istreambuf_iterator<char>(file), {});
}

void send_file_content_over_tcp(path const& fspec, tcp::socket& socket) {
    auto const contents = read_file(fspec);
    std::cout << "File read successfully. File size: " << contents.size() << " bytes." << std::endl;

    // Send the file content
    NetworkSize fileSize = contents.size();

    std::vector<asio::const_buffer> payload{
        asio::buffer(&fileSize, sizeof(fileSize)),
        asio::buffer(contents),
    };
    error_code ec;
    auto       n = write(socket, payload, ec);

    std::cout << "Content bytes sent: " << n << " (" << ec.message() << ")\n";
}

现在您可以完全对称地接收它:

void receive_file_content_over_tcp(path fspec, tcp::socket& socket) {
    error_code  ec;
    NetworkSize expected = 0;
    read(socket, asio::buffer(&expected, sizeof(expected)), ec);
    std::cout << "Expected file size: " << expected << " bytes (" << ec.message() << ")" << std::endl;

    if (!ec.failed()) {
        std::vector<char> data(expected);
        size_t actual = read(socket, asio::buffer(data), ec);

        std::cout << "Received " << actual << "/" << expected << ": " << ec.message();

        if (!ec || (actual == expected && ec == asio::error::eof)) {
            std::cout << "File content received successfully." << std::endl;
            std::ofstream(fspec, std::ios::binary | std::ios::trunc) //
                .write(data.data(), data.size());
        }
    }
}

这可以测试是否有效: 住在科里鲁

int main() {
    asio::io_context ioc;

    std::thread server([ex = ioc.get_executor()] {
        auto s = tcp::acceptor(ex, {{}, 7878}).accept();
        send_file_content_over_tcp("main.cpp", s);
    });

    ::sleep(1); // make sure server is accepting
    {
        tcp::socket client(ioc);
        client.connect({{}, 7878});
        receive_file_content_over_tcp("main-clone.cpp", client);
    }

    server.join();
}

印刷:

g++ -std=c++20 -O2 -Wall -pedantic -pthread main.cpp && ./a.out
md5sum *.cpp
File read successfully. File size: 2265 bytes.
Content bytes sent: 2273 (Success)
Expected file size: 2265 bytes (Success)
Received 2265/2265: SuccessFile content received successfully.
d47f7c90dfe3ca271f81ca203d53798e  main-clone.cpp
d47f7c90dfe3ca271f81ca203d53798e  main.cpp

您的发送不是流媒体,所以我认为这并不重要。如果是,只需按块阅读:Live On Coliru

std::ofstream ofs(fspec, std::ios::trunc);
// ofs.exceptions(std::ios::badbit | std::ios::failbit);

for (std::array<char, 1024> buf; expected && !ec;) {
    size_t actual = read(socket, asio::buffer(buf), ec);
    assert(actual <= expected);

    std::cout << "Received " << actual << "/" << expected << ": " << ec.message() << "\n";
    ofs.write(buf.data(), actual);

    expected -= actual;
}
if (ofs.good() && expected == 0 && (!ec || ec == asio::error::eof))
    std::cout << "File content received successfully." << std::endl;

印刷

g++ -std=c++20 -O2 -Wall -pedantic -pthread main.cpp && ./a.out
md5sum *.cpp
File read successfully. File size: 2342 bytes.
Content bytes sent: 2350 (Success)
Expected file size: 2342 bytes (Success)
Received 1024/2342: Success
Received 1024/1318: Success
Received 294/294: End of file
File content received successfully.
fc9f108bcee2fd7f711ab7a63d81baae  main-clone.cpp
fc9f108bcee2fd7f711ab7a63d81baae  main.cpp

怎么样?streambuf

它不是适合这项工作的工具。您当然应该对所有读取使用单个缓冲区。如果必须的话,我认为以下几点是不错的

void receive_file_content_over_tcp(path fspec, tcp::socket& socket) {
    asio::streambuf buf;
    NetworkSize     expected = 0;

    error_code ec;
    auto       n = read(socket, buf, asio::transfer_exactly(sizeof(expected)), ec);
    std::memcpy(&expected, buf.data().data(), n);
    buf.consume(n);

    std::cout << "Expected file size: " << expected << " bytes (" << ec.message() << "), REMAINING "
              << buf.size() << std::endl;

    std::ofstream ofs(fspec, std::ios::trunc);
    // ofs.exceptions(std::ios::badbit | std::ios::failbit);

    while (expected && !ec) {
        size_t actual = read(socket, buf, asio::transfer_at_least(1024), ec);
        assert(actual <= expected);
        assert(buf.size() == actual);

        std::cout << "Received " << actual << "/" << expected << ": " << ec.message() << "\n";
        ofs << &buf;

        expected -= actual;
    }
    if (ofs.good() && expected == 0 && (!ec || ec == asio::error::eof))
        std::cout << "File content received successfully." << std::endl;
}

评论

0赞 Johannes W. 11/18/2023
使用确切代码输出: 预期文件大小:8872762386475541857 字节(操作成功完成) 按任意键继续 . . .可悲的是,这也行不通。
0赞 sehe 11/18/2023
我可以说这不是我的确切代码,因为您可以在在线演示中看到它正在实时工作。您可以编辑现场演示以重现问题吗?如果您需要更大的文件,请考虑删除优化并使用 a.out 进行测试
0赞 sehe 11/20/2023
自上次以来,你去过任何地方吗?
0赞 Johannes W. 11/23/2023
可悲的是,我现在使用不同的方法,从 https 服务器流式传输。