Search code examples
c++boostboost-asioblockingboost-beast

Boost asio io_content run non-blocking


Currently i am writing a C++ Websocket Client Library which get wrapped in a C# Library.
I am using Boost Beast for the websocket connection.
Now i am at the point that i start a async_read when the handshake is completed so the websocket dont disconnect.
The Problem is the io_content blocks the thread so the C# program stops excecuting until the async_read gets a timeout and the io_content returns. But i want that the C# program keep executing.
I tried to execute the connect function in a thread, but there's the problem, that the next function that the C# program calls is a write operation and it crashes, because the connect function is still connecting...

library.cpp:

void OpenConnection(char* id)
{
    std::cout << "Opening new connection..." << std::endl;
    std::shared_ptr<Session> session = std::make_shared<Session>();
    session->connect();
    sessions.emplace(std::string(id), std::move(session));
}

session.cpp:

void Session::connect()
{
    resolver.async_resolve(
        "websocket_uri",
        "443",
        beast::bind_front_handler(
            &Session::on_resolve,
            shared_from_this()));
    ioc.run();
}

The on_resolve,on_connect,on_handshake... are the same as here: https://www.boost.org/doc/libs/1_70_0/libs/beast/example/websocket/client/async-ssl/websocket_client_async_ssl.cpp

Unless the on_handshake function:

void Session::on_handshake(beast::error_code ec)
{
    if (ec)
        return fail(ec, "handshake");

    ws.async_read(buffer, beast::bind_front_handler(&Session::on_read, shared_from_this()));
}

And the on_read function:

void Session::on_read(beast::error_code ec, std::size_t bytes_transfered)
{
    boost::ignore_unused(bytes_transfered);

    if (ec)
        return fail(ec, "read");

    std::cout << "Got message" << std::endl;
    onMessage(Message::parseMessage(beast::buffers_to_string(buffer.data())));
    ws.async_read(buffer, beast::bind_front_handler(&Session::on_read, shared_from_this()));
}

And the on_write function:

void Session::on_write(beast::error_code ec, std::size_t bytes_transfered)
{
    boost::ignore_unused(bytes_transfered);

    if (ec)
        return fail(ec, "write");

    queue.erase(queue.begin());

    if (!queue.empty())
    {
        ws.async_write(net::buffer(queue.front()->toString()), beast::bind_front_handler(&Session::on_write, shared_from_this()));
    }
}

C# Program(For testing):

[DllImport(@"/data/cpp_projects/whatsapp_web_library/build/Debug/libWhatsApp_Web_Library.so")]
public static extern void OpenConnection(string id);
[DllImport(@"/data/cpp_projects/whatsapp_web_library/build/Debug/libWhatsApp_Web_Library.so")]
public static extern void CloseConnection(string id);
[DllImport(@"/data/cpp_projects/whatsapp_web_library/build/Debug/libWhatsApp_Web_Library.so")]
public static extern void GenerateQRCode(string id);

static void Main(string[] args)
{
   string id = "test";
   OpenConnection(id);
   GenerateQRCode(id);
}

Now is my question, how can i implement this?
I have been stuck on this problem for 3 days now and am slowly despairing.

Thanks already :)


Solution

  • You need to use async_read_some instead of async_read

    From boost

    async_read_some function is used to asynchronously read data from the stream socket. The function call always returns immediately.

    The read operation may not read all of the requested number of bytes. Consider using the async_read function if you need to ensure that the requested amount of data is read before the asynchronous operation completes.

    Basically a successful call to async_read_some may read just one byte, or it may fill the whole buffer, or anywhere in between. The asio::async_read function, on the other hand, can be used to ensure that the entire buffer is filled before the operation completes. The async_write_some and asio::async_write functions have the same relationship.

    More about async_read_some

    Here is a good example of how to use async_read_some