1

I am reading data from a asio socket in c++.

I need to parse the incoming data as json. To do this, i need to get a single json string entry. I am adding a character ';' at the end of the json string, now i need to split at that character on read. i am trying this:

int main()
{
    asio::io_service service;
    asio::ip::tcp::endpoint endpoint(asio::ip::address::from_string("127.0.0.1"), 4444);
    asio::ip::tcp::socket socket(service);
    std::cout << "[Client] Connecting to server..." << std::endl;
    socket.connect(endpoint);
    std::cout << "[Client] Connection successful" << std::endl;


    while (true)
    {

        std::string str;
        str.resize(2048);
        asio::read(socket, asio::buffer(str));

        std::string parsed;
        std::stringstream input_stringstream(str);


        if (std::getline(input_stringstream, parsed, ';'))
        {
                            
            std::cout << parsed << std::endl;
            std::cout<<std::endl;
        }

    

    }
}

But it gives me random sections of the string.

The full message is: (for testing, not json formatted)

this is the message in full, no more no less ;

and I get:

 full, no more no less

this is the message in full, no more no less

ull, no more no less

is is the message in full, no more no less

l, no more no less

 is the message in full, no more no less

 no more no less

Where am i going wrong here?

Thanks!

Mgetz
  • 5,108
  • 2
  • 33
  • 51
anti
  • 3,011
  • 7
  • 36
  • 86
  • Does this answer your question? [Read until a string delimiter in boost::asio::streambuf](https://stackoverflow.com/questions/40561097/read-until-a-string-delimiter-in-boostasiostreambuf) – Mgetz Feb 24 '22 at 15:09
  • Hi, that seems to suggest using getline, as in my code above. – anti Feb 24 '22 at 15:21
  • See the answers. They use `async_read_until`. The question is not the answer. – Mgetz Feb 24 '22 at 15:22
  • Apologies. i am trying to understand how to use this function, but the simplest example I can find is : ` std::vector buffer; asio::async_read_until(socket, asio::dynamic_buffer(buffer, 16), ';', [&](asio::error_code error, std::size_t bytes_transferred); { std::cout << error.message() << ", bytes transferred: " << bytes_transferred << "\n"; });` Which reads a set number of chars, rather than splitting. – anti Feb 24 '22 at 15:41
  • [The first answer](https://stackoverflow.com/a/40613876/332733) shows how to use a delimiter. – Mgetz Feb 24 '22 at 15:42
  • @anti the 16 is not a fixed number of characters, it's a limit: https://www.boost.org/doc/libs/1_78_0/doc/html/boost_asio/reference/dynamic_buffer/overload2.html#:~:text=std%3A%3Asize_t%20max_size – sehe Feb 24 '22 at 15:55

1 Answers1

2

I'd use read_until:

#include <boost/asio.hpp>
#include <iostream>
#include <iomanip>
namespace asio = boost::asio;
using asio::ip::tcp;

int main()
{
    asio::io_service service;
    tcp::socket      socket(service);
    socket.connect({{}, 4444});

    std::string str;
    while (auto n = asio::read_until(socket, asio::dynamic_buffer(str), ';')) {
        std::cout << std::quoted(std::string_view(str).substr(0, n - 1)) << std::endl;
        str.erase(0, n);
    }
}

For example with a sample server:

paste -sd\; /etc/dictionaries-common/words  | netcat -l -p 4444

Output is:

"A"
"A's"
"AMD"
"AMD's"
"AOL"
"AOL's"
"Aachen"
"Aachen's"
"Aaliyah"
"Aaliyah's"
"Aaron"
"Aaron's"
"Abbas"
"Abbas's"
"Aberdeen's"
...
"zucchinis"
"zwieback"
"zwieback's"
"zygote"
"zygote's"
"zygotes"
"Ångström"
"Ångström's"
"éclair"
"éclair's"
"éclairs"
"éclat"
"éclat's"
"élan"
"élan's"
"émigré"
"émigré's"
"émigrés"
"épée"
"épée's"
"épées"
"étude"
"étude's"

Additional hints

You can use any dynamic buffer. Here's streambuf:

for (asio::streambuf buf; auto n = asio::read_until(socket, buf, ';');) {
    std::cout << std::string_view(
                     asio::buffer_cast<char const*>(buf.data()), n)
              << std::endl;
    buf.consume(n);
}

Or, hybrid, showing that dynamic_string_buffer models the same concept as streambuf:

std::string str;
for (auto buf = asio::dynamic_buffer(str);
     auto n   = asio::read_until(socket, buf, ';');) {
    std::cout << std::string_view(
                     asio::buffer_cast<char const*>(buf.data()), n)
              << std::endl;
    buf.consume(n);
}

Or also:

std::vector<unsigned char> vec;
for (auto buf = asio::dynamic_buffer(vec);
     auto n   = asio::read_until(socket, buf, ';');) {
    std::cout << std::string_view(
                     asio::buffer_cast<char const*>(buf.data()), n)
              << std::endl;
    buf.consume(n);
}
sehe
  • 374,641
  • 47
  • 450
  • 633
  • Hi , and thank you. i am running this code on my string, and it does not print anything.. My connection is valid, and the string has the `;` char. What might be wrong? – anti Feb 24 '22 at 15:53
  • Ah! Removing the spaces has this working. is there a way to use a string containing spaces with this approach? Thanks again! – anti Feb 24 '22 at 15:55
  • 1
    It already works with them: https://i.imgur.com/aA80PPw.gif I'm pretty sure you're still using `istream` instead (https://en.cppreference.com/w/cpp/string/basic_string/operator_ltltgtgt#:~:text=std%3A%3Aisspace(c,the%20input%20stream).) – sehe Feb 24 '22 at 16:01