AlexINF
AlexINF

Reputation: 250

Socket from async server writes -- but doesn't read

Okay, I'm pretty new with sockets and I'm trying to do an asynchronous server with multiple synchronous clients. The problem is, my server can't read any data from the client, and also, no errors are given!

Here's my server class:

#define READ_BUF_SIZE 512

struct Connection {
    boost::asio::ip::tcp::socket socket;
    boost::asio::streambuf read_buffer;
    Connection(boost::asio::io_service & io_service) : socket(io_service), read_buffer() { }
    Connection(boost::asio::io_service & io_service, size_t max_buffer_size) : socket(io_service), read_buffer(max_buffer_size) { }
};

class CServer {
    boost::asio::io_service m_ioservice;
    boost::asio::ip::tcp::acceptor m_acceptor;
    std::list<Connection> m_connections;
    using con_handle_t = std::list<Connection>::iterator;

public:

    CServer() : m_ioservice(), m_acceptor(m_ioservice), m_connections() { }

    void handle_read(con_handle_t con_handle, boost::system::error_code const & err, size_t bytes_transfered) {
        if (bytes_transfered > 0) {
            std::istream is(&con_handle->read_buffer);
            std::string line;
            std::getline(is, line);
            std::cout << "Message Received: " << line << std::endl;
        }

        if (!err) {
            do_async_read(con_handle);
        }
        else {
            std::cerr << "Error on read: " << err.message() << std::endl;
            m_connections.erase(con_handle);
        }
    }

    void do_async_read(con_handle_t con_handle) {
        auto handler = boost::bind(&CServer::handle_read, this, con_handle, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred);
        boost::asio::async_read(con_handle->socket, con_handle->read_buffer, boost::asio::transfer_exactly(READ_BUF_SIZE), handler);
    }

    void handle_write(con_handle_t con_handle, std::shared_ptr<std::string> msg_buffer, boost::system::error_code const & err) {
        if (!err) {
            std::cout << "Finished sending message\n";
            if (con_handle->socket.is_open()) {
                // Write completed successfully and connection is open
            }
        }
        else {
            std::cerr << "Error on write: " << err.message() << std::endl;
            m_connections.erase(con_handle);
        }
    }

    void handle_accept(con_handle_t con_handle, boost::system::error_code const & err) {
        if (!err) {
            std::cout << "Connection from: " << con_handle->socket.remote_endpoint().address().to_string() << "\n";
            std::cout << "Sending message\n";
            auto buff = std::make_shared<std::string>("Hello World!\r\n\r\n");
            auto handler = boost::bind(&CServer::handle_write, this, con_handle, buff, boost::asio::placeholders::error);
            boost::asio::async_write(con_handle->socket, boost::asio::buffer(*buff), handler);
            do_async_read(con_handle);
        }
        else {
            std::cerr << "We had an error: " << err.message() << std::endl;
            m_connections.erase(con_handle);
        }
        start_accept();
    }

    void start_accept() {
        auto con_handle = m_connections.emplace(m_connections.begin(), m_ioservice);
        auto handler = boost::bind(&CServer::handle_accept, this, con_handle, boost::asio::placeholders::error);
        m_acceptor.async_accept(con_handle->socket, handler);
    }

    void listen(uint16_t port) {
        auto endpoint = boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port);
        m_acceptor.open(endpoint.protocol());
        m_acceptor.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
        m_acceptor.bind(endpoint);
        m_acceptor.listen();
        start_accept();
    }

    void run() {
        m_ioservice.run();
    }
};

This class is first created via the constructor and then called by first using the listen function on it, and then by using its run instruction.

While the "Hello World!" test message is sent, the server does not receive any information back from the client (handle_read() does not get called)

Client::Client() : io_context(), resolver(io_context), endpoints(resolver.resolve("localhost", "daytime")), socket(io_context)
{
    try
    {
        boost::asio::connect(socket, endpoints);

        boost::array<unsigned char, PACKET_LENGTH> buf;
        boost::system::error_code error;

        socket.read_some(boost::asio::buffer(buf), error);
        std::cout << "Got message." << std::endl;

        boost::asio::write(socket, boost::asio::buffer("test message"), error);
    }
    catch (std::exception& e)
    {
        std::cerr << e.what() << std::endl;
    }
}

Finally, this is my client class. It contains an io_context object, a socket, a resolver, and a tcp::resolver::results_type object type called endpoints.

The debug "Got message" entry is actually outputted to the console, so the server can write to the client, while the "test message" entry is never actually seen in the server, presumably because it cannot read it.

Where is the problem here? Thanks in advance.

Upvotes: 0

Views: 169

Answers (1)

rafix07
rafix07

Reputation: 20918

Problem is here

boost::asio::async_read(con_handle->socket, con_handle->read_buffer,
      boost::asio::transfer_exactly(READ_BUF_SIZE), handler);
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ where READ_BUF_SIZE is 512

your handler is invoked when exactly 512 bytes were read or socket at the client side was destroyed or its sends operations was disabled by socket.shutdown().

So the point is the lifetime of Client object in the client code. If your client codes looks like:

int main() {
  Client c; // send data, delete object, socket is destroyed
  return 0;
}

in the server handle_read is called with error_code == boost::asio::error::eof (end of file or stream) what means that the socket in client was destroyed. But you can check content of streambuf, it contains "test message".

But if your client's code looks like this:

int main() {
  Client c;
  // wait here for X seconds
  return 0;
}

at the server side you will not see any output, until X seconds elapsed. You can use

boost::asio::write(socket, boost::asio::buffer("test message"), error);
socket.shutdown(boost::asio::ip::tcp::socket::shutdown_send);  // <---

shutdown makes end_of_file is sent to the server, read operation is aborted, handler for handle_read is called, you can check if error_code is error::eof, if so, you can print content of strambuf.

If you don't know how many bytes are sent, you can use async_read_until() fuction with the delimiter to indicate the end of a message (in this example it is newline character):

// server side
boost::asio::async_read_until(con_handle->socket, con_handle->read_buffer, "\n", handler);
                                                                           ^^^^ delimiter
// client side
boost::asio::write(socket, boost::asio::buffer("test message\n"), error);
                                                         ^^^^ added delimiter

Upvotes: 1

Related Questions