boost::asio::read() blocks forever

Viewed 773

I'm trying to learn how Boost.asio works. I've written a basic server and client example as such:

server.cpp

#include <iostream>
#include <string>
#include <boost/asio.hpp>

#define PORT 27015

using namespace boost::asio;
using ip::tcp;

std::string read(tcp::socket& socket) {
    boost::system::error_code error;
    boost::asio::streambuf buffer;
    boost::asio::read(socket, buffer, boost::asio::transfer_all(), error);
    if (error) {
        std::cerr << "read error: " << error.message() << "\n";
        return "ERROR";
    }
    else {
        std::string data = boost::asio::buffer_cast<const char*>(buffer.data());
        return data;
    }
}

void send(tcp::socket& socket, const std::string& message) {
    boost::system::error_code error;
    boost::asio::write(socket, boost::asio::buffer(message), error);
    if (error)
        std::cerr << "send error: " << error.message() << "\n";
    else
        std::cout << "sent \"" << message << "\" to the client" << "\n";
}

int main() {
    boost::asio::io_service io_service;

    tcp::acceptor acceptor(io_service, tcp::endpoint(tcp::v4(), PORT)); // create listener for new connection(s)
    tcp::socket socket(io_service); // socket creation

    std::cout << "awaiting connection..." << "\n";
    acceptor.accept(socket); // direct connection(s) to the socket we created
    std::cout << "accepted connection!" << "\n";

    std::string received = read(socket); // receive data
    std::cout << "received message: " << received << "\n";

    send(socket, "hello from server!"); // send data
}

client.cpp

#include <iostream>
#include <string>
#include <boost/asio.hpp>

#define PORT 27015

using namespace boost::asio;
using ip::tcp;

int main(int argc, char *argv[])
{
    boost::asio::io_service io_service;
    tcp::socket socket(io_service); // socket creation

    std::string server_ipv4_address = "192.168.1.2";
    std::cout << "connecting to server at " << server_ipv4_address << "\n";

    try {
        socket.connect(tcp::endpoint(boost::asio::ip::address::from_string(server_ipv4_address), PORT)); // connection
        std::cout << "connected!" << "\n";
    }
    catch (const boost::system::system_error& e) {
        std::cerr << "error while connecting: " << e.what() << "\n";
        return -1;
    }

    boost::system::error_code error; // error holder

    std::string message = "hello from client!!\n";
    boost::asio::write(socket, boost::asio::buffer(message), error); // send message to server
    if (error)
        std::cerr << "send failed: " << error.message() << "\n";
    else
        std::cout << "sent \"" << message << "\" to the server" << "\n";

    boost::asio::streambuf receive_buffer;
    boost::asio::read(socket, receive_buffer, boost::asio::transfer_all(), error); // receive from server
    if (error && error != boost::asio::error::eof)
        std::cerr << "receive failed: " << error.message() << "\n";
    else {
        std::string data = boost::asio::buffer_cast<const char*>(receive_buffer.data());
        std::cout << "received data: " << data << "\n";
    }
}

The connection gets established properly, but the read() function from the server blocks the program as either it does not receive data from the client, or there is a problem with the way I'm calling it. What seems to be the issue with the boost::asio::read() here? Everything works properly if I swap boost::asio::read with boost::asio::read_until as shown below. Why does the function work properly in the client but not in the server?

std::string read(tcp::socket& socket) {
    boost::system::error_code error;
    boost::asio::streambuf buffer;
    boost::asio::read_until(socket, buffer, "\n");
    std::string data = boost::asio::buffer_cast<const char*>(buffer.data());
    return data;
}
1 Answers

Read with the completion condition transfer_all means it will just keep reading until the buffer is full or the connection becomes invalid.

The buffer will "never" be full (since it's a DynamicBuffer).

So that leaves the cause that the client never hangs up.

Everything works properly if I swap boost::asio::read with boost::asio::read_until as shown below.

Exactly. Because then you have another reason to stop reading. Mind you, it could still block forever (when a '\n' never arrives).

Why does the function work properly in the client but not in the server?

It doesn't. It appears to because the server, apparently, does shutdown the connection (signalling EOF). [You would notice this because a subsequent read would return error_code boost::asio::error::eof.]

Related