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

时间:2015-09-21 03:00:49

标签: c++ asynchronous tcp network-programming boost-asio

我一直在网上学习很多关于在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函数更新。此外,由于客户端缓冲区永远不会更新,服务器只是读取旧值(也没有更新),因此技术上也有不正确的输出。但是,我不知道出了什么问题。我按照我认为的方式传递所有缓冲区,并且所有函数似乎都被正确绑定,但数据未被传递。那我做错了什么?

2 个答案:

答案 0 :(得分:1)

问题是缓冲区的副本被绑定到完成处理程序,该处理程序与提供给异步操作的缓冲区不同:

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()提供缓冲区的副本时,缓冲区的生命周期绑定到从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());
}

答案 1 :(得分:-1)

您的ReadhandlerWriteHander

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 &);

不符合asio Read handlerWrite handler要求。即只是:

void read_function(const asio::error_code&, size_t);
void write_function(const asio::error_code&, size_t);

您的应用程序需要“拥有”读取和写入缓冲区,并且不希望处理程序将其位置发送给您。如果您在适当的地方使用clientBufferserverBuffer,它应该可以正常工作。