简体   繁体   中英

Simple Boost::Asio asynchronous UDP echo server

I'm currently making my way through a book on C++ called "C++ Crash Course". The chapter on networking shows how to use Boost::Asio to write a simple uppercasing TCP server (synchronously or asynchronously). One of the excersises is to recreate it with UDP, which is what I'm having trouble with. Here's my implementation:

#include <iostream>
#include <boost/asio.hpp>
#include <boost/algorithm/string/case_conv.hpp>

using namespace boost::asio;
struct UdpServer {
    explicit UdpServer(ip::udp::socket socket)
    : socket_(std::move(socket)) {
        read();
    }
private:
    void read() {
        socket_.async_receive_from(dynamic_buffer(message_),
            remote_endpoint_,
            [this](boost::system::error_code ec, std::size_t length) {
                if (ec || this->message_ == "\n") return;
                boost::algorithm::to_upper(message_);
                this->write();
            }
        );
    }
    void write() {
        socket_.async_send_to(buffer(message_),
            remote_endpoint_,
            [this](boost::system::error_code ec, std::size_t length) {
                if (ec) return;
                this->message_.clear();
                this->read();
            }
        );
    }
    ip::udp::socket socket_;
    ip::udp::endpoint remote_endpoint_;
    std::string message_;
};

int main() {
    try {
        io_context io_context;
        ip::udp::socket socket(io_context, ip::udp::v4(), 1895);
        UdpServer server(std::move(socket));
        io_context.run();
    } catch (std::exception & e) {
        std::cerr << e.what() << std::endl;
    }
}

(Note: The original example uses enable_shared_from_this to capture this by shared_ptr into the lambdas, but I deliberately omitted it to see what would happen without it.)

My code does not compile, and I feel it will take me a thousand years to fully parse the error message (posted on pastebin.com since it's enormous).

It seems the issue is that the buffers are being used/constructed the wrong way, but I have no idea what exactly is wrong with this code. The few answers here on SO concerning Asio either use TCP or tackle an entirely different problem, so the mistake I made has to be really basic. I didn't find anything relevant in the Asio docs.

To be fair, Asio seems way too complicated to my newbie self. Maybe I just don't have the qualifications to use it right now. Nonetheless, I would still like to get the exercise done and move on. Any help would be appreciated.

Templates have the ugliest of compiler error messages. You often just have to go through the compiler error output and look for the first reference in your own source file. Ala:

/home/atmaks/Code/CCC_chapter20/main.cpp:53:9: required from here

In any case, on Visual Studio, the error was a bit more clear. (Not really, it just identified the offending line better).

Stare at it and contemplate all your life's decisions that led you to want to be developing in C++ in the first place. :)

I can't for the life of me figure out how to get dynamic_buffer to work. It may simply be the case that async_read doesn't like this type. And I think that actually makes sense for UDP. The receive buffer has to be sized before the recvfrom call in a synchronous mode. And I suspect async UDP, especially for Windows, the buffer has to be passed down to the kernel to be filled up. By then it's too late to be sized.

Asio lacks proper documentation and leaves us with cryptic template types to figure out. And the only Asio documentation that is worthwhile are the decent examples - none of which reference dynamic_buffer .

So let's change to a fixed sized buffer for receiving.

While we're at it, it didn't like your socket constructor and threw an exception. So I fixed it up such that it will work.

#include <iostream>
#include <boost/asio.hpp>
#include <boost/algorithm/string/case_conv.hpp>

using namespace boost::asio;
struct UdpServer {
    explicit UdpServer(ip::udp::socket socket)
        : socket_(std::move(socket)) {
        read();
    }
private:
    void read() {
        socket_.async_receive_from(buffer(data_, 1500),
            remote_endpoint_,
            [this](boost::system::error_code ec, std::size_t length) {

                if (ec)
                {
                    return;
                }

                data_[length] = '\0';

                if (strcmp(data_, "\n") == 0)
                {
                    return;
                }

                boost::algorithm::to_upper(data_);
                this->write();
            }
        );
    }
    void write() {
        socket_.async_send_to(buffer(data_, strlen(data_)),
            remote_endpoint_,
            [this](boost::system::error_code ec, std::size_t length) {
                if (ec) return;
                data_[0] = '\0';
                this->read();
            }
        );
    }
    ip::udp::socket socket_;
    ip::udp::endpoint remote_endpoint_;
    char data_[1500 + 1]; // +1 for we can always null terminate safely
};


int main() {

    try {
        io_context io_context;

        ip::udp::endpoint ep(ip::udp::v6(), 1895); // also listens on ipv4
        ip::udp::socket sock(io_context, ep);
        UdpServer server(std::move(sock));
        io_context.run();
    }
    catch (std::exception& e) {
        std::cerr << e.what() << std::endl;
    }
}

Update I did get dynamic_buffer to work, but it still requires a pre-allocation to be made.

Update the the start of the read() function as follows:

void read() {

    auto db = dynamic_buffer(message_);
    auto b = db.prepare(1500);

    socket_.async_receive_from(b,
    ...

That at least lets you stick with std::string instead of using a flat C array.

And now for evidence that it's working:

在此处输入图像描述

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM