Search code examples
c++multithreadingboostcoutasio

Strange behaviour of either io_context or std::cout in multi-threaded UDP server


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?


Solution

  • 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;