简体   繁体   中英

Boost Asio callback doesn't get called

I'm using Boost.Asio for network operations, they have to (and actually, can, there's no complex data structures or anything) remain pretty low level since I can't afford the luxury of serialization overhead (and the libs I found that did offer well enough performance seemed to be badly suited for my case).

The problem is with an async write I'm doing from the client (in QT, but that should probably be irrelevant here). The callback specified in the async_write doesn't get called, ever, and I'm at a complete loss as to why. The code is:

void SpikingMatrixClient::addMatrix() {
    std::cout << "entered add matrix" << std::endl;
    int action = protocol::Actions::AddMatrix;
    int matrixSize = this->ui->editNetworkSize->text().toInt();
    std::ostream out(&buf);
    out.write(reinterpret_cast<const char*>(&action), sizeof(action));
    out.write(reinterpret_cast<const char*>(&matrixSize), sizeof(matrixSize));
    boost::asio::async_write(*connection.socket(), buf.data(),
                             boost::bind(&SpikingMatrixClient::onAddMatrix, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}

which calls the first write. The callback is

void SpikingMatrixClient::onAddMatrix(const boost::system::error_code& error, size_t bytes_transferred) {
    std::cout << "entered onAddMatrix" << std::endl;
    if (!error) {
        buf.consume(bytes_transferred);
        requestMatrixList();
    } else {
        QString message = QString::fromStdString(error.message());
        this->ui->statusBar->showMessage(message, 15000);
    }
}

The callback never gets called, even though the server receives all the data. Can anyone think of any reason why it might be doing that?

PS There was a wrapper for that connection, and yes there will probably be one again. Ditched it a day or two ago because I couldn't find the problem with this callback.

As suggested, posting a solution I found to be the most suitable (at least for now).

The client application is [being] written in QT, and I need the IO to be async. For the most part, the client receives calculation data from the server application and has to render various graphical representations of them.

Now, there's some key aspects to consider:

  1. The GUI has to be responsive, it should not be blocked by the IO.
  2. The client can be connected / disconnected.
  3. The traffic is pretty intense, data gets sent / refreshed to the client every few secs and it has to remain responsive (as per item 1.).

As per the Boost.Asio documentation,

Multiple threads may call io_service::run() to set up a pool of threads from which completion handlers may be invoked. Note that all threads that have joined an io_service's pool are considered equivalent, and the io_service may distribute work across them in an arbitrary fashion.

Note that io_service.run() blocks until the io_service runs out of work.

With this in mind, the clear solution is to run io_service.run() from another thread. The relevant code snippets are

void SpikingMatrixClient::connect() {
    Ui::ConnectDialog ui;
    QDialog *dialog = new QDialog;
    ui.setupUi(dialog);
    if (dialog->exec()) {
        QString host = ui.lineEditHost->text();
        QString port = ui.lineEditPort->text();
        connection = TcpConnection::create(io);
        boost::system::error_code error = connection->connect(host, port);
        if (!error) {
            io = boost::shared_ptr<boost::asio::io_service>(new boost::asio::io_service);
            work = boost::shared_ptr<boost::asio::io_service::work>(new boost::asio::io_service::work(*io));
            io_threads.create_thread(boost::bind(&SpikingMatrixClient::runIo, this, io));
        }
        QString message = QString::fromStdString(error.message());
        this->ui->statusBar->showMessage(message, 15000);
    }
}

for connecting & starting IO, where:

  • work is a private boost::shared_ptr to the boost::asio::io_service::work object it was passed,
  • io is a private boost::shared_ptr to a boost::asio::io_service ,
  • connection is a boost::shared_ptr to my connection wrapper class, and the connect() call uses a resolver etc. to connect the socket, there's plenty examples of that around
  • and io_threads is a private boost::thread_group .

Surely it could be shortened with some typedefs if needed.

TcpConnection is my own connection wrapper implementation, which sortof lacks functionality for now, and I suppose I could move the whole thread thing into it when it gets reinstated. This snippet should be enough to get the idea anyway...

The disconnecting part goes like this:

void SpikingMatrixClient::disconnect() {
    work.reset();
    io_threads.join_all();
    boost::system::error_code error = connection->disconnect();
    if (!error) {
        connection.reset();
    }
    QString message = QString::fromStdString(error.message());
    this->ui->statusBar->showMessage(message, 15000);
}
  • the work object is destroyed, so that the io_service can run out of work eventually,
  • the threads are joined, meaning that all work gets finished before disconnecting, thus data shouldn't get corrupted,
  • the disconnect() calls shutdown() and close() on the socket behind the scenes, and if there's no error, destroys the connection pointer.

Note, that there's no error handling in case of an error while disconnecting in this snippet, but it could very well be done, either by checking the error code (which seems more C-like), or throwing from the disconnect() if the error code within it represents an error after trying to disconnect.

I encountered a similar problem (callbacks not fired) but the circumstances are different from this question ( io_service had jobs but still would not fire the handlers ). I will post this anyway and maybe it will help someone.

In my program, I set up an async_connect() then followed by io_service.run() , which blocks as expected.

async_connect() goes to on_connect_handler() as expected, which in turn fires async_write() .

on_write_complete_handler() does not fire, even though the other end of the connection has received all the data and has even sent back a response.

I discovered that it is caused by me placing program logic in on_connect_handler() . Specifically, after the connection was established and after I called async_write() , I entered an infinite loop to perform arbitrary logic, not allowing on_connect_handler() to exit. I assume this causes the io_service to not be able to execute other handlers, even if their conditions are met because it is stuck here. ( I had many misconceptions, and thought that io_service would automagically spawn threads for each async_x() call )

Hope that helps.

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