简体   繁体   中英

Async read completes, but buffer does not contain expected results

I've been following numerous tutorials online on learning Asynchronous Networking in Asio, so if I've made a really obvious mistake, there's your explanation.

Nonetheless, I've written a program that sets up both a client and server simultaneously and tries to communicate between the two. Simply connecting and making requests to send/receive data seem to be working fine, but the data itself isn't being sent.

#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;
}

As output, I've been getting the following:

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.

However, what I'm expecting is:

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.

Clearly what's happening is that the Server buffer is getting updated (when I manually increment the value), while the Client Buffer never gets updated by the async_read_some function. Additionally, because the client buffer never gets updated, the server is just reading in old values (also without getting updated) and thus technically has incorrect output as well. However, I don't know what's wrong. I'm passing in all my buffers the way I think I'm supposed to, and all the functions seem to be bound correctly, but the data isn't being passed. So what did I do wrong?

The problem is that a copy of the buffer is being bound to the completion handler, which is a different buffer than that which is provided to the asynchronous operations:

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

In the above snippet, the async_read_some() operation will operate on buffer , and the completion handler will be provided a copy of buffer before the operation has made any modifications. To resolve this, use std::ref() to pass a reference to std::bind() .

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

In this case, passing a reference will also fix a potential case where undefined behavior could have been invoked. The async_write_some() and async_read_some() operations require that ownership of the underlying buffer memory is retained by the caller, who must guarantee that it remains valid until the completion handler is called. When std::bind() was being provided a copy of the buffer, the buffer's lifetime was bound to the functor object returned from std::bind() , which may have ended before the completion handler was invoked.

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, ...));

Here is an example demonstrating the fundamental problem and behavior:

#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());
}

Your Readhandler and WriteHander :

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

don't conform to the asio Read handler and Write handler requirements. Ie just:

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

Your application needs to "own" the read and write buffers and not expect their locations to be sent back to you by the handlers. If you use clientBuffer and serverBuffer where appropriate, it should work correctly.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM