c++ - 如何在不等待新数据到达的情况下使用 boost::asio 的 async_read_some() 读取所有可用数据?

标签 c++ boost serial-port boost-asio

我正在使用 boost::asio 进行串行通信,我想监听某个端口上的传入数据。因此,我使用 serialport::async_read_some() 注册了一个 ReadHandler然后创建一个单独的线程来处理异步处理程序(调用 io_service::run() )。我的 ReadHandler 通过再次调用 async_read_some() 来重新注册自己。 ,这似乎是一种常见的模式。

这一切都有效,我的示例可以在收到数据时将数据打印到标准输出 - 除了我注意到在 ReadHandler 运行时接收到的数据将不会被“读取”,直到 ReadHandler 完成执行并且在此之后接收到新数据发生.也就是说,当ReadHandler运行时收到数据,虽然在ReadHandler结束时调用了async_read_some,但不会立即为该数据再次调用ReadHandler。只有在初始 ReadHandler 完成后收到附加数据时,才会再次调用 ReadHandler。此时,ReadHandler 运行时接收到的数据将与"new"数据一起正确地在缓冲区中。

这是我的最小可行示例 - 我最初将它放在 Wandbox 中,但意识到它不会帮助在线编译它,因为它需要一个串行端 Eloquent 能运行。

// Include standard libraries
#include <iostream>
#include <string>
#include <memory>
#include <thread>

// Include ASIO networking library
#include <boost/asio.hpp>

class SerialPort
{
public:
    explicit SerialPort(const std::string& portName) :
        m_startTime(std::chrono::system_clock::now()),
        m_readBuf(new char[bufSize]),
        m_ios(),
        m_ser(m_ios)
    {
        m_ser.open(portName);
        m_ser.set_option(boost::asio::serial_port_base::baud_rate(115200));

        auto readHandler = [&](const boost::system::error_code& ec, std::size_t bytesRead)->void
        {
            // Need to pass lambda as an input argument rather than capturing because we're using auto storage class
            // so use trick mentioned here: http://pedromelendez.com/blog/2015/07/16/recursive-lambdas-in-c14/
            // and here: https://stackoverflow.com/a/40873505
            auto readHandlerImpl = [&](const boost::system::error_code& ec, std::size_t bytesRead, auto& lambda)->void
            {
                if (!ec)
                {
                    const auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - m_startTime);

                    std::cout << elapsed.count() << "ms: " << std::string(m_readBuf.get(), m_readBuf.get() + bytesRead) << std::endl;

                    // Simulate some kind of intensive processing before re-registering our read handler
                    std::this_thread::sleep_for(std::chrono::seconds(5));

                    //m_ser.async_read_some(boost::asio::buffer(m_readBuf.get(), bufSize), lambda);
                    m_ser.async_read_some(boost::asio::buffer(m_readBuf.get(), bufSize), std::bind(lambda, std::placeholders::_1, std::placeholders::_2, lambda));
                }
            };

            readHandlerImpl(ec, bytesRead, readHandlerImpl);
        };

        m_ser.async_read_some(boost::asio::buffer(m_readBuf.get(), bufSize), readHandler);

        m_asioThread = std::make_unique<std::thread>([this]()
            {
                this->m_ios.run();
            });
    }

    ~SerialPort()
    {
        m_ser.cancel();
        m_asioThread->join();
    }

private:
    const std::chrono::system_clock::time_point m_startTime;
    static const std::size_t bufSize = 512u;
    std::unique_ptr<char[]> m_readBuf;
    boost::asio::io_service m_ios;
    boost::asio::serial_port m_ser;
    std::unique_ptr<std::thread> m_asioThread;
};


int main()
{
    std::cout << "Type q and press enter to quit" << std::endl;
    SerialPort port("COM1");

    while (std::cin.get() != 'q')
    {
        std::this_thread::sleep_for(std::chrono::milliseconds(200));
    }

    return 0;
}

(不要介意奇怪的 lambda 事情发生)

该程序只是在收到数据时将数据打印到标准输出,以及时间戳(自程序启动以来的毫秒数)。通过将虚拟串行设备连接到虚拟串行端口对,我可以向程序发送数据(实际上只需输入 RealTerm)。当我输入一个短字符串时,我可以看到问题。

在这种情况下,我输入了“hi”,然后立即打印了“h”。不久之后我输入了“i”,但在计算机速度下,它是相当长的一段时间,所以它不是读入缓冲区的初始数据的一部分。此时,ReadHandler 执行,耗时 5 秒。在此期间,操作系统收到了“i”。但是 'i' 在 5 秒后没有被打印出来——下一个 async_read_some 会忽略它,直到我输入一个 't',此时它突然打印出 'i' 和 't'。
Example program output

这是对这个测试和我想要的更清晰的描述:

测试:启动程序,等待1秒,输入hi,等待9秒,输入t
我想要发生的事情(由这个程序打印到标准输出):
1000 毫秒:小时
6010 毫秒:我
11020毫秒:吨

实际发生的情况:
1000 毫秒:小时
10000 毫秒:它

程序有一种方法来识别在读取之间接收到的数据似乎非常重要。我知道没有办法使用 ASIO 串行端口(无论如何不使用 native_handle)检查数据是否可用(在 OS 缓冲区中)。但我真的不需要,只要读取调用返回。这个问题的一个解决方案可能是确保 ReadHandler 尽快完成运行——显然这个例子中的 5 秒延迟是人为的。但这并不是一个好的解决方案。无论我制作 ReadHandler 的速度有多快,仍然有可能“错过”数据(因为在稍后收到一些新数据之前不会看到它)。有没有办法确保 我的处理程序将在收到数据后的短时间内读取所有数据,而不依赖于收到更多数据?

我已经在 SO 和其他地方进行了大量搜索,但到目前为止我发现的所有内容都只是讨论导致系统根本无法工作的其他陷阱。

作为极端措施,我的工作线程似乎可以调用 io_service::run_for()有超时,而不是 run() ,然后每隔一段时间让该线程以某种方式触发手动读取。我还不确定它会采用什么形式——它可以调用 serial_port::cancel()我想,然后再调用async_read_some .但这对我来说听起来很老套,即使它可能会起作用——而且它需要更新版本的 boost 才能启动。

我正在使用 VS2019 在 Windows 10 上使用 boost 1.65.1 构建,但我真的希望这与这个问题无关。

最佳答案

回答标题中的问题:你不能。根据 async_read_some 的性质您要求进行部分读取并在读取任何内容后立即调用您的处理程序。然后你在另一个 async_read_some 之前睡了很长时间叫做。

no matter how fast I make ReadHandler, it will still be possible to 'miss' data (in that it will not be seen until some new data is received later)



如果我正确理解您的担忧,那没关系 - 您不会错过任何事情。数据仍然在套接字/端口缓冲区上等待,直到您下次读取它。

如果您只想在读取完成后开始处理,则需要 async_read 之一而是重载。这实际上将在流上执行多个 read_somes,直到满足某些条件。这可能意味着端口/套接字上的所有内容,或者您​​可以提供一些自定义 CompletionCondition .这在每个 read_some 上调用,直到它返回 0,此时读取被认为是完整的,ReadHandler然后被调用。

关于c++ - 如何在不等待新数据到达的情况下使用 boost::asio 的 async_read_some() 读取所有可用数据?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58721410/

相关文章:

c++ - Visual Studio 在作者的 Boost Asio 示例中提示 co_await

c++ - 获取 boost spirit 语法中的当前行

serial-port - 在 W10 (WSL) 的 Ubuntu 中使用串口

C++ pugiXML,在节点中的第一个子节点之前附加一个子节点

c++ - unordered_multimap.empty() 返回 true,即使我认为它应该返回 false?

c++ - boost :scoped_ptr<T> pimpl 的最佳选择

serial-port - 通过 "Null Modem"将 2 个 USB 端口连接在一起,就像过去使用 COM 端口一样?

C++ : How to reduce conditional statements

c++ - 未定义对 `boost::program_options::options_description::m_default_line_length' 的引用

serial-port - MODBUS自动地址分配