首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >使用boost-asio时,将缓冲区实时写入磁盘。

使用boost-asio时,将缓冲区实时写入磁盘。
EN

Stack Overflow用户
提问于 2021-01-14 14:23:35
回答 1查看 337关注 0票数 3

我有一个由boost.asio编写的服务器。此服务器从客户端获取文件并将其写入磁盘。我只是有点问题。当服务器获得该文件时,当它完全接收到该文件时,它会将其写入磁盘。我想让服务器将缓冲区实时写入磁盘。例如,服务器将从客户端获得的文件大小的100 to写入磁盘。我编写了以下代码,但我不知道如何编辑才能达到这个目标。

代码语言:javascript
复制
void Session::DoReadFileContent(size_t arg_bytes_transferred)
{
    if (arg_bytes_transferred > 0)
    {
        m_outputFile.write(m_buffer.data(), static_cast<std::streamsize>(arg_bytes_transferred));

        if (m_outputFile.tellp() >= static_cast<std::streamsize>(m_fileSize))
        {
            std::cout << "Received file: " << m_fileName << std::endl;
            return;
        }
    }

    auto self = shared_from_this();

    m_socket.async_read_some(boost::asio::buffer(m_buffer.data(), m_buffer.size()),
        [this, self](boost::system::error_code arg_error_code, size_t arg_bytes)
        {
            DoReadFileContent(arg_bytes);
        });
}
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2021-01-14 16:58:31

首先,在这种情况下,最好是读取显式大小的数据,而不是读取任何可用的read_some

在这种模式中,跟踪“剩余的可接收字节”比跟踪m_fileSize更容易。

下面是一些小的重新洗牌,使您的代码成为一个独立的例子。它期望服务器发送一行文本,给出有效负载大小和输出文件名,然后是该文件的内容。示例服务器可以使用netcat运行,例如:

代码语言:javascript
复制
(stat -c '%soutput.dat' main.cpp; cat main.cpp) | netcat -l -p 6969

住在Coliru

代码语言:javascript
复制
#include <boost/asio.hpp>
#include <fstream>
#include <iostream>

using boost::system::error_code;
using boost::asio::ip::tcp;

struct Session : std::enable_shared_from_this<Session> {

    Session(boost::asio::io_context& io, uint16_t port)
     : m_socket(io) 
    {
        m_socket.connect({{}, port});
    }

    void Start();
    void DoReadFileContent(size_t transferred = 0);

  private:
    std::array<char, 1024> m_buffer;
    std::streamsize m_remainingSize = 0;
    std::string     m_fileName      = "noname.dat";
    std::ofstream   m_outputFile;

    tcp::socket m_socket;
};

void Session::Start() {
    // Reading a size (in text for simplicity) and subsequently receive as many bytes
    //
    // I'm keeping this sync for simplicity, because you probably already have
    // this coded somehwere
    boost::asio::streambuf buf;
    error_code ec;
    auto n = read_until(m_socket, buf, "\n", ec);

    std::istream is(&buf);
    if (is >> m_remainingSize && getline(is, m_fileName)) {
        std::cerr << "Protocol trace: n:" << n << ", fileName:" << m_fileName << " payload_size:" << m_remainingSize << "\n";

        m_outputFile.exceptions(std::ios::failbit | std::ios::badbit);
        m_outputFile.open(m_fileName, std::ios::binary);

        // write excess buffer contents as part of payload
        if (buf.size()) {
            std::cerr << "Writing " << buf.size() << " bytes\n";
            m_remainingSize -= buf.size();
            m_outputFile << &buf;
        }

        DoReadFileContent();
    } else {
        std::cerr << "Protocol error, payload_size expected\n";
    }
}
void Session::DoReadFileContent(size_t transferred) {
    if (transferred > 0) {
        std::cerr << "Writing " << transferred << " bytes\n";
        m_remainingSize -= transferred;
        m_outputFile.write(m_buffer.data(), transferred);
    }
    if (m_remainingSize <= 0) {
        std::cout << "Completed file: " << m_fileName << std::endl;
        return;
    }

    auto self = shared_from_this();
    auto expect = std::min(size_t(m_remainingSize), m_buffer.size());
    std::cout << "Trying to receive next " << expect << " bytes" << std::endl;
    async_read(m_socket,
        boost::asio::buffer(m_buffer.data(), expect),
        [this, self](error_code ec, size_t arg_bytes) {
            std::cerr << "async_read: " << ec.message() << " - " << arg_bytes << " bytes\n";
            if (!ec) {
                DoReadFileContent(arg_bytes);
            }
        });
}

int main() {
    boost::asio::io_context io;

    std::make_shared<Session>(io, 6868) // download from port 6868
        ->Start();

    io.run(); // complete
}

测试用

代码语言:javascript
复制
(stat -c '%soutput.dat' main.cpp; cat main.cpp) | netcat -l -p 6868&
./a.out
md5sum main.cpp output.dat

印刷品,例如:

代码语言:javascript
复制
Protocol trace: n:15, fileName:output.dat payload_size:2654
Trying to receive next 1024 bytes
async_read: Success - 1024 bytes
Writing 1024 bytes
Trying to receive next 1024 bytes
async_read: Success - 1024 bytes
Writing 1024 bytes
Trying to receive next 606 bytes
async_read: Success - 606 bytes
Writing 606 bytes
Completed file: output.dat

最后两行

代码语言:javascript
复制
b4eec7203f6a1dcbfbf3d298c7ec0832  main.cpp
b4eec7203f6a1dcbfbf3d298c7ec0832  output.dat

指示接收到的文件与原始文件相同。

备注:

  • 在我的系统上,数据包以未指定的大小传送,例如,接收相同的文件如下: 协议跟踪: n:15,fileName:output.dat payload_size:2654写497字节尝试接收下一个1024字节async_read:成功1024字节写1024字节尝试接收下一个1024字节async_read:成功1024字节写1024字节尝试接收下109个字节async_read:成功109个字节写109个字节完成文件: output.dat b4eec7203f6a1dcbfbf3d298c7ec0832 main.cpp b4eec7203f6a1dcbfbf3d298c7ec0832 output.dat 请注意,它从从输入缓冲区中的497字节开始。
  • 协议不安全:
    • 应该验证文件名。想象一下,如果文件是‘/home/sehe/my事_file.txt’或者更糟,例如/dev/sde1 1,并且我们有权限进行原始块设备访问,那么会发生什么情况呢?
    • 您可能需要为streambuf指定一个最大值大小,这样如果您得到一个不发送'\n'的模糊程序,就不会占用所有内存。

  • 文件IO上的错误处理非常粗糙。我使用了io异常,但是您可能希望在不同的地方检查m_outputFile.good()
票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/65720813

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档