boost::asio how to send string data from client and store it at server side for processing

I am trying to use boost::asio library to send raw strings from multiple clients. I need to sort the strings at server side and send it back to clients.

void start()
  {
    sock.async_read_some(
        boost::asio::buffer(data),
        boost::bind(&con_handler::handle_read,
                    shared_from_this(),
                    boost::asio::placeholders::error,
                    boost::asio::placeholders::bytes_transferred));

    
    sock.async_write_some(
    boost::asio::buffer(data),
        boost::bind(&con_handler::handle_write, 
            shared_from_this(),
                boost::asio::placeholders::error,
                boost::asio::placeholders::bytes_transferred));
  }

  void handle_read(const boost::system::error_code& err,
                   size_t bytes_transferred)
  {
      std::sort(data.begin(), data.end());
  }

  void handle_write(const boost::system::error_code& err,
               size_t bytes_transferred)
  {
    if (!err) {
    cout<<"server side "<<data<<endl;
    
    } else {
      std::cerr << "err (recv): " << err.message() << std::endl;
      sock.close();
    }
  }

However, this does not work. I get empty string on printing and on client side.

Can you please how to fix this?

Answer

There’s a lot of relevant code missing, but

std::sort(data.begin(), data.end());

Tells me that data is probably array<char, N> or std::vector<char>.

It cannot be std::string because you’d have needed asio::dynamic_buffer(data) instead of asio::buffer(data).

Now

  • if array<char, N> is used, then end() iterator will always be the end of the buffer, regardless of how many bytes were received. That’s not what you want because the buffer will contain trailing garbage – and/or likely NUL characters which will mess up your output.

  • if vector<char> is used, then the classical mistake is that its size is zero (it’s empty()) so buffer(data) actually is a zero-byte buffer and nothing ever gets received, the sort does nothing and nothing gets sent.

Regardless, the sort call doesn’t do what you describe:

I am trying to use boost::asio library to send raw strings from multiple clients. I need to sort the strings at server side and send it back to clients.

This suggests that you sort separate “strings” (maybe separate lines of text?). You code sorts characters in a string. Not the same thing.

Here’s a rough sketch of a solution where the server accumulates _strings (line-wise) from several clients, and writes the sorted collection back to each when their connection ends:

Live On Coliru

#include <boost/asio.hpp>
#include <iostream>
#include <iomanip>
#include <deque>

namespace net = boost::asio;
using namespace std::chrono_literals;
using net::ip::tcp;
using boost::system::error_code;

using Executor = net::io_context::executor_type;
using Acceptor = net::basic_socket_acceptor<tcp, Executor>;
using Socket   = net::basic_stream_socket<tcp, Executor>;

struct Server {
    Acceptor _acc;
    Server(Executor ex) : _acc(ex, {{}, 7878}) {
        _acc.listen();
        do_accept();
    }

    void addString(std::string line) {
        std::cerr << "addString: " << std::quoted(line) << std::endl;
        line += 'n';
        _strings.push_back(std::move(line));
    }

    auto getStrings() const { return _strings; }

  private:
    void do_accept()
    {
        _acc.async_accept([this](error_code ec, Socket&& s) {
            std::cerr << "do_accept: " << ec.message() << std::endl;
            if (!ec.failed()) {
                std::cerr << "Accepted " << s.remote_endpoint() << std::endl;
                std::make_shared<Session>(std::move(s), *this)->start();

                do_accept();
            }
        });
    }

    struct Session : std::enable_shared_from_this<Session> {
        Session(Socket&& s, Server& server)
            : _socket(std::move(s))
            , _server(server)
        {
        }

        void start() {
            read_loop();
        }

      private:
        void read_loop()
        {
            async_read_until(_socket, _buf, "n",
                 [self = this->shared_from_this(),
                  this](error_code ec, size_t bytes) {
                     std::cerr << "read_loop: " << ec.message() << std::endl;
                     if (!ec || ec == net::error::eof) {
                         std::istream is(&_buf);
                         std::string  line;
                         if (getline(is, line)) {
                             _server.addString(std::move(line));
                         }
                     }

                     if (ec == net::error ::eof) {
                         _reply = _server.getStrings();
                         std::sort(_reply.begin(), _reply.end());
                         write_loop();
                     }

                     if (!ec) {
                         read_loop();
                     }
                 });
        }

        void write_loop() {
            if (_reply.empty())
                return;

            async_write(
                _socket, net::buffer(_reply.front()),
                [this, self = shared_from_this()](error_code ec, size_t) {
                    std::cerr << "write_loop: " << ec.message() << std::endl;
                    if (!ec) {
                        _reply.pop_front();
                        write_loop();
                    }
                });
        }

        Socket                  _socket;
        net::streambuf          _buf;
        Server&                 _server;
        std::deque<std::string> _reply;
    };

    std::deque<std::string> _strings;
};

int main() {
    net::io_context io;

    Server s(io.get_executor());

    io.run_for(10s);
}

With the sample clients:

for a in foo 'barnqux' xyz abc
do printf "=== %sn" "$a"
    echo -e "$a" | netcat -N 127.0.0.1 7878
done

Prints

do_accept: Success
Accepted 127.0.0.1:34582
read_loop: End of file
addString: "foo"
write_loop: Success
do_accept: Success
Accepted 127.0.0.1:34584
read_loop: Success
addString: "bar"
read_loop: End of file
addString: "qux"
write_loop: Success
write_loop: Success
write_loop: Success
do_accept: Success
Accepted 127.0.0.1:34586
read_loop: End of file
addString: "xyz"
write_loop: Success
write_loop: Success
write_loop: Success
write_loop: Success
do_accept: Success
Accepted 127.0.0.1:34588
read_loop: End of file
addString: "abc"
write_loop: Success
write_loop: Success
write_loop: Success
write_loop: Success
write_loop: Success

And the clients output:

=== foo
foo
=== barnqux
bar
foo
qux
=== xyz
bar
foo
qux
xyz
=== abc
abc
bar
foo
qux
xyz