如何使用 boost::asio 的 async_read_some() 读取所有可用数据,而无需等待新数据到达?

2024-01-12

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

这一切都有效,我的示例可以在接收到数据时将其打印到标准输出 - 但我注意到在 ReadHandler 运行时接收到的数据将不会被“读取”,直到 ReadHandler 执行完毕并且之后会收到新数据。也就是说,当ReadHandler运行时接收到数据时,虽然在ReadHandler结束时调用了async_read_some,但它不会立即再次调用该数据的ReadHandler。 ReadHandler 仅在以下情况下才会被再次调用:额外的初始 ReadHandler 完成后接收数据。此时,ReadHandler 运行时接收到的数据将与“新”数据一起正确地存储在缓冲区中。

这是我的最小可行示例 - 我最初将其放入 Wandbox 中,但意识到它无助于在线编译它,因为无论如何它都需要串行端口才能运行。

// 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”。但是 5 秒后并没有打印“i”——下一个 async_read_some 会忽略它,直到我输入“t”,此时它突然打印“i”和“t”。示例程序输出 https://i.stack.imgur.com/RC8NN.png

这是此测试以及我想要的内容的更清晰描述:

测试:启动程序,等待 1 秒,输入 hi,等待 9 秒,输入 t
我想要发生什么(由该程序打印到标准输出):
1000毫秒:小时
6010ms:我
11020ms:t

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

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

我已经在 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叫做。

无论我使 ReadHandler 有多快,仍然有可能“错过”数据(因为直到稍后收到一些新数据后才会看到它)

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

如果您只想在读取完成后开始处理,则需要以下之一async_read https://www.boost.org/doc/libs/1_65_1/doc/html/boost_asio/reference/async_read.html相反,超载。这实际上会在流上执行多个 read_somes 直到满足某些条件。这可能只是意味着一切在端口/套接字上,或者您可以提供一些自定义CompletionCondition。每个 read_some 都会调用此方法,直到返回 0,此时读取被视为完成,并且ReadHandler然后被调用。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何使用 boost::asio 的 async_read_some() 读取所有可用数据,而无需等待新数据到达? 的相关文章

  • 使用 std::packaged_task/std::exception_ptr 时,线程清理程序报告数据争用

    我遇到了线程清理程序 TSan 的一些问题 抱怨某些生产代码中的数据争用 其中 std packaged task 通过将它们包装在 std function 中而移交给调度程序线程 对于这个问题 我简化了它在生产中的作用 同时触发 TSa
  • 计算 Richtextbox 中所有单词的最有效方法是什么?

    我正在编写一个文本编辑器 需要提供实时字数统计 现在我正在使用这个扩展方法 public static int WordCount this string s s s TrimEnd if String IsNullOrEmpty s re
  • 提交后禁用按钮

    当用户提交付款表单并且发布表单的代码导致 Firefox 中出现重复发布时 我试图禁用按钮 去掉代码就不会出现这个问题 在firefox以外的任何浏览器中也不会出现这个问题 知道如何防止双重帖子吗 System Text StringBui
  • 在 DataView 的 RowFilter 中选择 DISTINCT

    我试图根据与另一个表的关系缩小 DataView 中的行范围 我使用的 RowFilter 如下 dv new DataView myDS myTable id IN SELECT DISTINCT parentID FROM myOthe
  • 错误:表达式不产生值

    我尝试将以下 C 代码转换为 VB NET 但在编译代码时出现 表达式不产生值 错误 C Code return Fluently Configure Mappings m gt m FluentMappings AddFromAssemb
  • 在 C 中匹配二进制模式

    我目前正在开发一个 C 程序 需要解析一些定制的数据结构 幸运的是我知道它们是如何构造的 但是我不确定如何在 C 中实现我的解析器 每个结构的长度都是 32 位 并且每个结构都可以通过其二进制签名来识别 举个例子 有两个我感兴趣的特定结构
  • 使用 Newtonsoft 和 C# 反序列化嵌套 JSON

    我正在尝试解析来自 Rest API 的 Json 响应 我可以获得很好的响应并创建了一些类模型 我正在使用 Newtonsoft 的 Json Net 我的响应中不断收到空值 并且不确定我的模型设置是否正确或缺少某些内容 例如 我想要获取
  • 如何创建包含 IPv4 地址的文本框? [复制]

    这个问题在这里已经有答案了 如何制作一个这样的文本框 我想所有的用户都见过这个并且知道它的功能 您可以使用带有 Mask 的 MaskedTestBox000 000 000 000 欲了解更多信息 请参阅文档 http msdn micr
  • 回发后刷新时提示确认表单重新提交。我做错了什么?

    我有一个以空白 默认状态启动的仪表板 我让用户能够将保存的状态加载到仪表板中 当他们单击 应用 按钮时 我运行以下代码 function CloseAndSave var radUpload find radUpload1ID var in
  • qdbusxml2cpp 未知类型

    在使用 qdbusxml2cpp 程序将以下 xml 转换为 Qt 类时 我收到此错误 qdbusxml2cpp c ObjectManager a ObjectManager ObjectManager cpp xml object ma
  • 是否有实用的理由使用“if (0 == p)”而不是“if (!p)”?

    我倾向于使用逻辑非运算符来编写 if 语句 if p some code 我周围的一些人倾向于使用显式比较 因此代码如下所示 if FOO p some code 其中 FOO 是其中之一false FALSE 0 0 0 NULL etc
  • 具有交替类型的可变参数模板参数包

    我想知道是否可以使用参数包捕获交替参数模式 例如 template
  • Qt - ubuntu中的串口名称

    我在 Ubuntu 上查找串行端口名称时遇到问题 如您所知 为了在 Windows 上读取串口 我们可以使用以下代码 serial gt setPortName com3 但是当我在 Ubuntu 上编译这段代码时 我无法使用这段代码 se
  • C# HashSet 只读解决方法

    这是示例代码 static class Store private static List
  • 等待进程释放文件

    我如何等待文件空闲以便ss Save 可以用新的覆盖它吗 如果我紧密地运行两次 左右 我会得到一个generic GDI error
  • AES 128 CBC 蒙特卡罗测试

    我正在 AES 128 CBC 上执行 MCT 如中所述http csrc nist gov groups STM cavp documents aes AESAVS pdf http csrc nist gov groups STM ca
  • 使用 %d 打印 unsigned long long

    为什么我打印以下内容时得到 1 unsigned long long int largestIntegerInC 18446744073709551615LL printf largestIntegerInC d n largestInte
  • 调用堆栈中的“外部代码”是什么意思?

    我在 Visual Studio 中调用一个方法 并尝试通过检查调用堆栈来调试它 其中一些行标记为 外部代码 这到底是什么意思 方法来自 dll已被处决 外部代码 意味着该dll没有可用的调试信息 你能做的就是在Call Stack窗口中单
  • 如何从 ODBC 连接获取可用表的列表?

    在 Excel 中 我可以转到 数据 gt 导入外部数据 gt 导入数据 然后选择要使用的数据源 然后在提供登录信息后 它会给我一个表格列表 我想知道如何使用 C 以编程方式获取该列表 您正在查询什么类型的数据源 SQL 服务器 使用权 看
  • 从列表中选择项目以求和

    我有一个包含数值的项目列表 我需要使用这些项目求和 我需要你的帮助来构建这样的算法 下面是一个用 C 编写的示例 描述了我的问题 int sum 21 List

随机推荐