3

I am trying to write a simple server using Boost.Asio library. I want my server to receive a message from the client and print that message on the console. Here is the code of my server program:

#include <iostream>
#include <string>
#include <memory>

#include <boost/asio.hpp>

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

class Session : public std::enable_shared_from_this<Session> {
public:
    Session(tcp::socket socket);

    void start();
private:
    tcp::socket socket_;
    std::string data_;
};

Session::Session(tcp::socket socket) : socket_(std::move(socket))
{}

void Session::start()
{
    socket_.async_read_some(buffer(data_), [this](error_code errorCode, size_t length) {
        if (!errorCode) {
            std::cout << "received: " << data_ << std::endl;
        }
        start();
    });
}

class Server {
public:
    Server(io_context& context);
private:
    tcp::acceptor acceptor_;

    void accept();
};

Server::Server(io_context& context) : acceptor_(context, tcp::endpoint(tcp::v4(), 8888))
{
    accept();
}

void Server::accept()
{
    acceptor_.async_accept([this](error_code errorCode, tcp::socket socket) {
        if (!errorCode) {
            std::make_unique<Session>(std::move(socket))->start();
        }
        accept();
    });
}

int main()
{
    boost::asio::io_context context;
    Server server(context);
    context.run();
    return 0;
}

And here is the code of my client program:

#include <iostream>
#include <string>

#include <boost/asio.hpp>

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

int main()
{
    io_context context;
    tcp::socket socket(context);
    tcp::resolver resolver(context);
    connect(socket, resolver.resolve("127.0.0.1", "8888"));
    while (true) {
        try {
            std::string data;
            std::cin >> data;
            write(socket, buffer(data));
        } catch (const std::exception& exception) {
            std::cerr << exception.what() << std::endl;
        }
    }
    return 0;
}

But when I start the client, the server throws exception "read access violation". What do I do wrong?

Count Zero
  • 495
  • 6
  • 15
  • Part of that message in your debugger will include the stack trace. Look at that and it'll tell you exactly what's going on. (If I were a betting man, it would revolve around the boost::buffer(string) call - and it trying to write to invalid memory. https://stackoverflow.com/questions/4068249/how-to-use-stdstring-with-asiobuffer – UKMonkey Jun 25 '18 at 17:17
  • The exception is thrown from `buffer()` function that is called from my `start()`. – Count Zero Jun 25 '18 at 17:20
  • Re-read the link .... you'll find it's a duplicate (You'll find that creating a mutablebuffer with a std::string doesn't really work - use a std::vector instead) – UKMonkey Jun 25 '18 at 17:21
  • Thank you for your comment! I've changed `std::string data_;` to `std::vector data_ = std::vector(1024);` and `buffer(data_)` to `buffer(data_, 1024)`, but now it fails on debug assertion showing message "vector iterator not dereferencable". – Count Zero Jun 25 '18 at 17:37
  • "Part of that message in your debugger will include the stack trace. Look at that and it'll tell you exactly what's going on" Different error - different stack trace ... give full details first time and you'll get answers in better time ... but really, you should get practice looking at it and understanding what's going wrong. I'd guess that it's now probably failing at a different place; probably because data has size 1024, but only length bytes were written. – UKMonkey Jun 25 '18 at 17:42
  • It is now from `io_context::run()`. – Count Zero Jun 25 '18 at 17:49
  • @UKMonkey I think you're quite a way off the mark here. Sure the buffer has zero capacity (that's not useful, but also no concern). The problem is that Session doesn't exist after starting. – sehe Jun 25 '18 at 18:42
  • @sehe good catch - the joys of trying to do a couple of questions before heading home for the night ;) I only checked if the buffer was still in scope! – UKMonkey Jun 25 '18 at 19:22

1 Answers1

2

You're using enable_shared_from_this and but nothing keeps your Session alive because you only use unique_ptr<Session>.

This means your Session goes away during running operations.

Fix it:

std::make_shared<Session>(std::move(socket))->start();

Next, hold a shared pointer in your completion handler:

void Session::start()
{
    auto self = shared_from_this();
    socket_.async_read_some(buffer(data_), [this, self](error_code errorCode, size_t /*length*/) {
        if (!errorCode) {
            std::cout << "received: " << data_ << std::endl;
        }
        start();
    });
}

Next, CUT the async loop if there is an error (or your session will loop infinitely):

socket_.async_read_some(buffer(data_), [this, self](error_code errorCode, size_t length) {
    if (!errorCode && length) {
        std::cout << "received: " << data_ << std::endl;
        start();
    }
});

Finally, resize the buffer so you can actually receive data (!):

data_.resize(32);
socket_.async_read_some(buffer(data_), [this, self](error_code errorCode, size_t length) {
    if (!errorCode) {
        data_.resize(length);
        std::cout << "received: '" << data_ << "'" << std::endl;
        start();
    }
});

There are still some issues left, but hey, the program won't crash immediately and you have some results.

Update

Added a live demo showing some more suggestions Live On Coliru

#include <iostream>
#include <string>
#include <memory>

#include <boost/asio.hpp>

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

class Session : public std::enable_shared_from_this<Session> {
public:
    Session(tcp::socket socket);

    void start();
private:
    tcp::socket socket_;
    boost::asio::streambuf _sb;
};

Session::Session(tcp::socket socket) : socket_(std::move(socket))
{}

void Session::start()
{
    auto self = shared_from_this();
    async_read_until(socket_, _sb, '\n', [this, self](error_code errorCode, size_t /*length*/) {
        std::cout << "completion " << errorCode.message() << "\n";
        if (!errorCode) {
            std::string line;
            {
                std::istream is(&_sb);
                if (getline(is, line)) {
                    std::cout << "received: '" << line << "'" << std::endl;
                }
                start();
            }
        }
    });
}

class Server {
public:
    Server(io_context& context);
private:
    tcp::acceptor acceptor_;

    void accept();
};

Server::Server(io_context& context) : acceptor_(context, tcp::endpoint(tcp::v4(), 8888))
{
    accept();
}

void Server::accept()
{
    acceptor_.async_accept([this](error_code errorCode, tcp::socket socket) {
        if (!errorCode) {
            std::make_shared<Session>(std::move(socket))->start();
        }
        accept();
    });
}

int main(int argc, char**) {
    if (argc>1) {
        io_context context;
        tcp::socket socket(context);
        tcp::resolver resolver(context);
        connect(socket, resolver.resolve("127.0.0.1", "8888"));
        std::string data;
        while (getline(std::cin, data)) {
            try {
                data += '\n';
                write(socket, buffer(data));
            } catch (const std::exception& exception) {
                std::cerr << exception.what() << std::endl;
            }
        }
    } else {
        boost::asio::io_context context;
        Server server(context);
        context.run();
    }
}
sehe
  • 374,641
  • 47
  • 450
  • 633
  • Thank you very much! But I still have some questions. First, why do we need that `self` pointer in `start()` function? As I can see, we don't use it in the closure. – Count Zero Jun 25 '18 at 18:46
  • This requires you to understand both shared pointers and asynchronous operations. – sehe Jun 25 '18 at 19:15
  • 1
    The `async_*` always returns immediately (so before the operation completes, or even might have started). This means that after exiting `start()` you still need to make sure the `Session` instance "stays" around. Otherwise, using `this` in the completion handler (which you do) is illegal. – sehe Jun 25 '18 at 19:16
  • 1
    The way `shared_ptr` works is that it keeps the pointee alive until the last `shared_ptr` pointing to it goes away. By capturing that `shared_from_this()` pointer (which is such a `shared_ptr`) you make sure that as long as the completion handler is queued somewhere, the corresponding `Session` instance cannot be destroyed, so that you can legally use `this` within the lambda ("closure"). – sehe Jun 25 '18 at 19:18
  • Ok, I see now! Could you please just point out other issues that you told about? – Count Zero Jun 25 '18 at 20:21
  • I don't think pointing those out here is very fruitful. I invite you to read some other answers though like https://stackoverflow.com/questions/40561097/read-until-a-string-delimiter-in-boostasiostreambuf/40613876#40613876 or https://stackoverflow.com/a/8377771/85371 - If you have something that doesn't do what you want, you should see that during testing. If you can't solve it you may always search/post another question about that _specific_ task – sehe Jun 25 '18 at 21:18
  • Added a live demo showing some more suggestions [Live On Coliru](http://coliru.stacked-crooked.com/a/51224f2b15e4b352) – sehe Jun 25 '18 at 21:39
  • Thank you for your suggestions! – Count Zero Jun 27 '18 at 17:40