Simple Boost::Asio 异步 UDP 回显服务器

Posted

技术标签:

【中文标题】Simple Boost::Asio 异步 UDP 回显服务器【英文标题】:Simple Boost::Asio asynchronous UDP echo server 【发布时间】:2020-05-23 04:29:54 【问题描述】:

我目前正在阅读一本名为“C++ Crash Course”的 C++ 书籍。关于网络的章节展示了如何使用 Boost::Asio 编写一个简单的大写 TCP 服务器(同步或异步)。其中一项练习是使用 UDP 重新创建它,这就是我遇到的麻烦。这是我的实现:

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

(注意:原始示例使用enable_shared_from_this 通过shared_ptrthis 捕获到lambdas 中,但我故意省略了它以查看没有它会发生什么。)

我的代码无法编译,我觉得完全解析 error message 需要一千年的时间(因为它很大,所以发布在 pastebin.com 上)。

问题似乎是缓冲区的使用/构造方式错误,但我不知道这段代码到底有什么问题。这里关于 Asio 的几个答案要么使用 TCP,要么解决一个完全不同的问题,所以我犯的错误必须是非常基本的。我在 Asio 文档中没有找到任何相关内容。

公平地说,Asio 对我的新手来说似乎太复杂了。可能我现在还没有资格使用它。尽管如此,我仍然希望完成练习并继续前进。任何帮助将不胜感激。

【问题讨论】:

【参考方案1】:

模板具有最难看的编译器错误消息。您通常只需要检查编译器错误输出并在您自己的源文件中查找第一个引用。阿拉:

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

无论如何,在 Visual Studio 上,错误更加明显。 (不是真的,它只是更好地识别了违规行)。

凝视它并思考你一生中所有最初导致你想要使用 C++ 进行开发的决定。 :)

我一辈子都想不出如何让dynamic_buffer 工作。可能只是async_read 不喜欢这种类型。我认为这对 UDP 实际上是有意义的。接收缓冲区必须在同步模式下recvfrom 调用之前调整大小。而且我怀疑异步 UDP,尤其是对于 Windows,缓冲区必须向下传递给内核才能被填满。到那时,调整大小为时已晚。

Asio 缺乏适当的文档,给我们留下了难以理解的模板类型。唯一有价值的 Asio 文档是体面的示例 - 没有一个引用 dynamic_buffer

所以让我们改成一个固定大小的缓冲区来接收。

虽然我们在处理它,但它不喜欢您的套接字构造函数并引发了异常。所以我把它修好了,这样它就可以工作了。

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

更新 我确实让 dynamic_buffer 工作了,但它仍然需要预先分配。

更新 read() 函数的开头如下:

void read() 

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

    socket_.async_receive_from(b,
    ...

这至少可以让您坚持使用 std::string 而不是使用平面 C 数组。

现在证明它有效:

【讨论】:

谢谢!至少现在它正在编译。但是,在我的系统上,它会转储整个缓冲区而不是空终止符。如果长度从第一个回调传递到 write(size_t len),然后用于构造写缓冲区,一切正常。 将这个:socket_.async_send_to(buffer(data_), 改为 socket_.async_send_to(buffer(data_, strlen(data_)), 呃,我相信你的 write() 方法现在是错误的,socket 调用看起来是从 read() 复制过来的。只是为了后代,请您将其编辑回来吗? 很抱歉。内联快速编辑太多。再次修复。 还更新了答案以解释(松散地)为什么 dynamic_buffer 不适用于 UDP。接收缓冲区必须在recvfrom 调用之前调整大小。这可能是问题的根源。

以上是关于Simple Boost::Asio 异步 UDP 回显服务器的主要内容,如果未能解决你的问题,请参考以下文章

创建使用 Boost ASIO 且不公开它的静态库

boost::asio::ip::tcp实现网络通信的小例子

使用 boost::asio 通过 UDP 发送结构

Boost::Asio 点对点 udp 聊天

使用 boost::asio 获取 UDP 套接字远程地址

boost :: asio取消或关闭对async_handle不起作用