1

I have a simple UDP server created using boost\asio which, for now, only prints the data received, as shown below.

using boost::asio::ip::udp;

enum { max_length = 2048 };

void server(boost::asio::io_context& io_context, unsigned short port)
{
    std::cout << "Server Created on port " + std::to_string(port) << " \n" << std::flush;
    udp::socket sock(io_context, udp::endpoint(udp::v4(), port));
    while(true)
    {
        udp::endpoint sender_endpoint;
        std::vector<char> v(max_length);
        sock.receive_from(boost::asio::buffer(v), sender_endpoint);
        for (int i = 0; i < v.size(); i++)
        {
            std::cout << v[i] <<std::flush;
        }
        std::cout << std::endl;
    }
}

I create 3 threads of the server function using boost::thread which I assign to a thread_group:

boost::asio::io_context io_context;

            boost::thread_group tg;

            boost::thread *t = new boost::thread(server, std::ref(io_context), 8889);
            boost::thread *t1 = new boost::thread(server, std::ref(io_context), 8890);
            boost::thread *t2 = new boost::thread(server, std::ref(io_context), 11111);

            tg.add_thread(t);
            tg.add_thread(t1);
            tg.add_thread(t2);

            tg.join_all();

To test the server I have used Packet Sender. The problem is that the output is... scrambled. When sending packets on the 3 ports at (more or less) the same time once per second the output is slightly misplaced, but when increasing the packet frequency to once per 0.1 second, the output becomes unreadable, as shown in these two images. I have tried giving one separate io_context object to every server, but the output remains the same at high frequency. Is there any way to avoid this?

WKstraw
  • 67
  • 1
  • 13
  • 1
    I don't know a lot about boost so I may be talking out the wazoo. Does this help? https://stackoverflow.com/questions/6374264/is-cout-synchronized-thread-safe I feel like the solution should just be to use a shared cout mutex that you pass to each thread. – JohnFilleau Feb 25 '20 at 13:30
  • 1
    `io_context` doesn't have relation to `std::cout`. You need to use some synchronization method to avoid scrambling. For example you can use `std::mutex` and lock it when you output in `std::cout` – sklott Feb 25 '20 at 13:31
  • @sklott actually you don't unless [you've turned off locking already.](https://en.cppreference.com/w/cpp/io/ios_base/sync_with_stdio) – Mgetz Feb 25 '20 at 13:45
  • @Mgetz Read carefully `(individual characters output from multiple threads may interleave, but no data races occur)` – sklott Feb 25 '20 at 13:48

2 Answers2

3

The logical (and correct) solution is to use mutual exclusion on std::cout. You know the appropriate scope of the lock (in your case, just a single UDP packet, but std::cout cannot guess that).

The fancy solution is boost.asio.strand. You don't need that for simple cases like this, but since you're trying to use boost.asio.io_context you should know that there is another class in boost.asio that could work like you intended.

MSalters
  • 173,980
  • 10
  • 155
  • 350
  • I tried using `strand`, however, as I don't explicitely call `io_context.run()` (yet), it doesn't work. I will definetely need it for when I later post-process the data, though, thank you. – WKstraw Feb 25 '20 at 14:31
1

std::cout automatically does some locking for you, a single print operation won't overlap with another print from a different thread. However as you are printing one character at a time the output from each thread is likely to overlap. Flushing after every printed character is also likely to lead to poor performance.

If you build what you want to print into a single string it should print correctly:

    std::vector<char> v(max_length);
    size_t bytes = sock.receive_from(boost::asio::buffer(v), sender_endpoint);
    std::string str(v.data(), bytes);
    str += '\n';
    std::cout << str;

Or you can skip the vector and save straight to a string:

    std:: string str(max_length);
    size_t bytes = sock.receive_from(boost::asio::buffer(str), sender_endpoint);
    str.resize(bytes)
    str += '\n';
    std::cout << str;
Alan Birtles
  • 32,622
  • 4
  • 31
  • 60