草庐IT

c++ - 异步读取完成,但缓冲区不包含预期结果

coder 2023-09-20 原文

我一直在关注大量关于在 Asio 中学习异步网络的在线教程,所以如果我犯了一个非常明显的错误,您会得到解释。

尽管如此,我编写了一个程序来同时设置客户端和服务器并尝试在两者之间进行通信。简单地连接并发出发送/接收数据的请求似乎工作正常,但数据本身并没有被发送。

#define ASIO_STANDALONE
#include<asio.hpp>
#include<thread>
#include<iostream>
#include<vector>
#include<array>
#include<mutex>
#include<memory>
#include<functional>

#define IPADDRESS   "127.0.0.1"
#define PORT        "6118"

enum side_type {
    t_server, t_client
};

std::mutex m_lock;
std::array<char, 32> clientBuffer;
std::array<char, 32> serverBuffer;
bool stop(false);

void read_function(const asio::error_code&, size_t, std::shared_ptr<asio::ip::tcp::socket>, std::array<char, 32> &, side_type &);
void write_function(const asio::error_code&, size_t, std::shared_ptr<asio::ip::tcp::socket>, std::array<char, 32> &, side_type &);

void read_function(const asio::error_code& ec, size_t bytes_read, std::shared_ptr<asio::ip::tcp::socket> socket, std::array<char, 32> & buffer, side_type & type) {
    if (ec) return;
    using namespace std;
    using namespace std::placeholders;
    char value = buffer[0];
    {
        lock_guard<mutex> guard(m_lock);
        string type_str = type == t_server ? "Server" : "Client";
        cout << "Value of " << int(value) << " read by " << type_str << "." << endl;
    }
    if (value >= 100) stop = true;
    else {
        if(type == t_server)
            buffer[0] = value + 1;
        socket->async_write_some(asio::buffer(&buffer[0], buffer.max_size()), bind(write_function, _1, _2, socket, buffer, type));
    }
}

void write_function(const asio::error_code& ec, size_t bytes_written, std::shared_ptr<asio::ip::tcp::socket> socket, std::array<char, 32> & buffer, side_type & type) {
    if (ec) return;
    using namespace std;
    using namespace std::placeholders;
    socket->async_read_some(asio::buffer(&buffer[0], buffer.max_size()), bind(read_function, _1, _2, socket, buffer, type));
}

void work_function(std::shared_ptr<asio::io_service> io_service) {
    using namespace std;
    asio::error_code ec;
    while (!ec) {
        try {
            io_service->run(ec);
            break;
        }
        catch (exception & e) {
            lock_guard<mutex> guard(m_lock);
            cout << "Exception thrown: \"" << e.what() << "\"." << endl;
        }
    }
}

void connect_function(const asio::error_code & ec, std::shared_ptr<asio::ip::tcp::socket> socket) {
    using namespace std;
    using namespace std::placeholders;
    lock_guard<mutex> guard(m_lock);
    if (ec) {
        cout << "Error Connecting: " << ec << endl;
    }
    else {
        cout << "Successful Connection!" << endl;
        socket->async_read_some(asio::buffer(&clientBuffer[0], clientBuffer.max_size()), bind(read_function, _1, _2, socket, clientBuffer, t_client));
    }
}

void accept_function(const asio::error_code & ec, std::shared_ptr<asio::ip::tcp::socket> socket) {
    using namespace std;
    using namespace std::placeholders;
    lock_guard<mutex> guard(m_lock);
    if (ec) {
        cout << "Error Accepting: " << ec << endl;
    }
    else {
        cout << "Successful Acception!" << endl;
        serverBuffer[0] = 0;
        socket->async_write_some(asio::buffer(&serverBuffer[0], serverBuffer.max_size()), bind(write_function, _1, _2, socket, serverBuffer, t_server));
    }
}

int main(int argc, char** argv) {
    using namespace std;
    using namespace std::placeholders;
    shared_ptr<asio::io_service> io_service(new asio::io_service());
    shared_ptr<asio::io_service::work> work(new asio::io_service::work(*io_service));

    vector<shared_ptr<thread>> threads;
    int num_of_threads = thread::hardware_concurrency();
    for (auto i = 0; i < thread::hardware_concurrency(); i++) {
        threads.push_back(shared_ptr<thread>(new thread(work_function, io_service)));
    }

    using namespace asio::ip;
    tcp::resolver resolver(*io_service);
    tcp::resolver::query query(IPADDRESS, PORT);
    tcp::resolver::iterator iterator = resolver.resolve(query);
    tcp::endpoint endpoint = *iterator;

    cout << "Connecting to " << endpoint << endl;

    shared_ptr<tcp::acceptor> acceptor(new tcp::acceptor(*io_service));
    shared_ptr<tcp::socket> acc_socket(new tcp::socket(*io_service));
    shared_ptr<tcp::socket> socket(new tcp::socket(*io_service));

    acceptor->open(endpoint.protocol());
    acceptor->set_option(tcp::acceptor::reuse_address(false));
    acceptor->bind(endpoint);
    acceptor->listen(asio::socket_base::max_connections);
    acceptor->async_accept(*acc_socket, bind(accept_function, _1, acc_socket));

    asio::error_code ec;
    socket->async_connect(endpoint, bind(connect_function, _1, socket));

    //while (!stop);

    cout << "Press Any Key to Continue..." << endl;
    cin.get();

    socket->shutdown(tcp::socket::shutdown_both, ec);
    socket->close(ec);

    work.reset();

    while (!io_service->stopped());

    for (shared_ptr<thread> & t : threads) {
        t->join();
    }

    return 0;
}

作为输出,我得到了以下内容:

Connecting to 127.0.0.1:6118
Press Any Key to Continue...
Successful Connection!
Successful Acception!
Value of 0 read by Client.
Value of 0 read by Server.
Value of 0 read by Client.
Value of 1 read by Server.
Value of 0 read by Client.
Value of 2 read by Server.
Value of 0 read by Client.
Value of 3 read by Server.
......
Value of 0 read by Client.
Value of 98 read by Server.
Value of 0 read by Client.
Value of 99 read by Server.
Value of 0 read by Client.
Value of 100 read by Server.

然而,我期待的是:

Connecting to 127.0.0.1:6118
Press Any Key to Continue...
Successful Connection!
Successful Acception!
Value of 0 read by Client.
Value of 0 read by Server.
Value of 1 read by Client.
Value of 1 read by Server.
Value of 2 read by Client.
Value of 2 read by Server.
Value of 3 read by Client.
Value of 3 read by Server.
......
Value of 98 read by Client.
Value of 98 read by Server.
Value of 99 read by Client.
Value of 99 read by Server.
Value of 100 read by Client.
Value of 100 read by Server.

很明显,服务器缓冲区正在更新(当我手动增加值时),而客户端缓冲区永远不会被 async_read_some 函数更新。此外,由于客户端缓冲区永远不会更新,服务器只是读取旧值(也没有更新),因此技术上也有不正确的输出。但是,我不知道出了什么问题。我以我认为应该的方式传递所有缓冲区,并且所有函数似乎都正确绑定(bind),但数据未传递。那我做错了什么?

最佳答案

问题是缓冲区的拷贝被绑定(bind)到完成处理程序,它与提供给异步操作的缓冲区不同:

socket->async_read_some(asio::buffer(buffer), std::bind(..., buffer, ...));
                                  // ^~~~~~ = reference      ^~~~~~ = copy

在上面的代码片段中,async_read_some() 操作将对 buffer 进行操作,完成处理程序将提供一份 buffer在操作进行任何修改之前。要解决此问题,请使用 std::ref() 传递对 std::bind() 的引用。

socket->async_read_some(asio::buffer(buffer), std::bind(..., std::ref(buffer), ...));
                                  // ^~~~~~ = reference               ^~~~~~ = reference

在这种情况下,传递引用也将解决可能已调用未定义行为的潜在情况。 async_write_some()async_read_some()操作要求调用者保留底层缓冲区内存的所有权,调用者必须保证它在调用完成处理程序之前保持有效。当 std::bind() 被提供缓冲区的拷贝时,缓冲区的生命周期被绑定(bind)到从 std::bind() 返回的仿函数对象,这可能在调用完成处理程序之前已经结束。

void read_function(
  ...,
  std::shared_ptr<asio::ip::tcp::socket> socket,
  std::array<char, 32>& buffer,
  ...)
{
  ...
  socket->async_write_some(asio::buffer(buffer), handler);
} // buffer's lifetime ends shortly after returning from this function

socket->async_read_some(
  asio::buffer(buffer),
  std::bind(&read_function, ..., socket, buffer, ...));

这是一个例子 demonstrating基本问题和行为:

#include <array>
#include <cassert>
#include <functional>

int get_data(std::array<char, 32>& data)
{
  return data[0];
}

int main()
{
  std::array<char, 32> data;
  data[0] = 0;
  auto fn_copy = std::bind(&get_data, data);
  auto fn_ref = std::bind(&get_data, std::ref(data));
  data[0] = 1;
  assert(0 == fn_copy());
  assert(1 == fn_ref());
}

关于c++ - 异步读取完成,但缓冲区不包含预期结果,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32686696/

有关c++ - 异步读取完成,但缓冲区不包含预期结果的更多相关文章

  1. ruby - 如何将脚本文件的末尾读取为数据文件(Perl 或任何其他语言) - 2

    我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚

  2. ruby - 检查 "command"的输出应该包含 NilClass 的意外崩溃 - 2

    为了将Cucumber用于命令行脚本,我按照提供的说明安装了arubagem。它在我的Gemfile中,我可以验证是否安装了正确的版本并且我已经包含了require'aruba/cucumber'在'features/env.rb'中为了确保它能正常工作,我写了以下场景:@announceScenario:Testingcucumber/arubaGivenablankslateThentheoutputfrom"ls-la"shouldcontain"drw"假设事情应该失败。它确实失败了,但失败的原因是错误的:@announceScenario:Testingcucumber/ar

  3. ruby-on-rails - 如何在 ruby​​ 中使用两个参数异步运行 exe? - 2

    exe应该在我打开页面时运行。异步进程需要运行。有什么方法可以在ruby​​中使用两个参数异步运行exe吗?我已经尝试过ruby​​命令-system()、exec()但它正在等待过程完成。我需要用参数启动exe,无需等待进程完成是否有任何ruby​​gems会支持我的问题? 最佳答案 您可以使用Process.spawn和Process.wait2:pid=Process.spawn'your.exe','--option'#Later...pid,status=Process.wait2pid您的程序将作为解释器的子进程执行。除

  4. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

    我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

  5. Ruby 写入和读取对象到文件 - 2

    好的,所以我的目标是轻松地将一些数据保存到磁盘以备后用。您如何简单地写入然后读取一个对象?所以如果我有一个简单的类classCattr_accessor:a,:bdefinitialize(a,b)@a,@b=a,bendend所以如果我从中非常快地制作一个objobj=C.new("foo","bar")#justgaveitsomerandomvalues然后我可以把它变成一个kindaidstring=obj.to_s#whichreturns""我终于可以将此字符串打印到文件或其他内容中。我的问题是,我该如何再次将这个id变回一个对象?我知道我可以自己挑选信息并制作一个接受该信

  6. ruby - 检查字符串是否包含散列中的任何键并返回它包含的键的值 - 2

    我有一个包含多个键的散列和一个字符串,该字符串不包含散列中的任何键或包含一个键。h={"k1"=>"v1","k2"=>"v2","k3"=>"v3"}s="thisisanexamplestringthatmightoccurwithakeysomewhereinthestringk1(withspecialcharacterslike(^&*$#@!^&&*))"检查s是否包含h中的任何键的最佳方法是什么,如果包含,则返回它包含的键的值?例如,对于上面的h和s的例子,输出应该是v1。编辑:只有字符串是用户定义的。哈希将始终相同。 最佳答案

  7. 世界前沿3D开发引擎HOOPS全面讲解——集3D数据读取、3D图形渲染、3D数据发布于一体的全新3D应用开发工具 - 2

    无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD

  8. ruby - 使用 `+=` 和 `send` 方法 - 2

    如何将send与+=一起使用?a=20;a.send"+=",10undefinedmethod`+='for20:Fixnuma=20;a+=10=>30 最佳答案 恐怕你不能。+=不是方法,而是语法糖。参见http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_expressions.html它说Incommonwithmanyotherlanguages,Rubyhasasyntacticshortcut:a=a+2maybewrittenasa+=2.你能做的最好的事情是:

  9. python - 如何读取 MIDI 文件、更改其乐器并将其写回? - 2

    我想解析一个已经存在的.mid文件,改变它的乐器,例如从“acousticgrandpiano”到“violin”,然后将它保存回去或作为另一个.mid文件。根据我在文档中看到的内容,该乐器通过program_change或patch_change指令进行了更改,但我找不到任何在已经存在的MIDI文件中执行此操作的库.他们似乎都只支持从头开始创建的MIDI文件。 最佳答案 MIDIpackage会为您完成此操作,但具体方法取决于midi文件的原始内容。一个MIDI文件由一个或多个音轨组成,每个音轨是十六个channel中任何一个上的

  10. STM32读取串口传感器数据(颗粒物传感器,主动上传) - 2

    文章目录1.开发板选择*用到的资源2.串口通信(个人理解)3.代码分析(注释比较详细)1.主函数2.串口1配置3.串口2配置以及中断函数4.注意问题5.源码链接1.开发板选择我用的是STM32F103RCT6的板子,不过代码大概在F103系列的板子上都可以运行,我试过在野火103的霸道板上也可以,主要看一下串口对应的引脚一不一样就行了,不一样的就更改一下。*用到的资源keil5软件这里用到了两个串口资源,采集数据一个,串口通信一个,板子对应引脚如下:串口1,TX:PA9,RX:PA10串口2,TX:PA2,RX:PA32.串口通信(个人理解)我就从串口采集传感器数据这个过程说一下我自己的理解,

随机推荐