使用 boost::asio::async_wait_until 和 boost::asio::streambuf

Posted

技术标签:

【中文标题】使用 boost::asio::async_wait_until 和 boost::asio::streambuf【英文标题】:Using boost::asio::async_wait_until with boost::asio::streambuf 【发布时间】:2017-09-02 03:53:50 【问题描述】:

我目前正在开发一个应用程序,用于与使用串行通信的设备进行通信。为此,我使用了 boost 库 basic_serial_port。现在,我只是尝试从设备中读取数据,并且正在使用 async_wait_until 函数以及 deadline_timer 类中的 async_wait。设置读取和等待的代码如下所示:

async_read_until(port,readData,io_params.delim,
                  boost::bind(&SerialComm::readCompleted,
                  this,boost::asio::placeholders::error,
                  boost::asio::placeholders::bytes_transferred));

timer.expires_from_now(boost::posix_time::seconds(1));
timer.async_wait(boost::bind(&SerialComm::timeoutExpired,this,
                 boost::asio::placeholders::error));

async_read_until 上的回调看起来像

void SerialComm::readCompleted(const boost::system::error_code& error,
                               const size_t bytesTransferred)
    if (!error)
        wait_result = success;
        bytes_transferred = bytesTransferred;
    
    else 
        if (error.value() != 125) wait_result = error_out;
        else wait_result = op_canceled;

        cout << "Port handler called with error code " + to_string(error.value()) << endl;
    


成功读取后触发以下代码

string msg;
getline(istream(&readData), msg, '\r');
boost::trim_right_if(msg, boost::is_any_of("\r"));

在此设备的情况下,所有消息都以回车结束,因此在async_read_until 中指定回车应该检索单个消息。但是,我看到的是,在触发处理程序时,新数据不一定会输入缓冲区。所以,我可能看到的是,如果处理程序被触发 20x

在第一次调用中将一条线泵入缓冲区 在接下来的 6 次调用中都没有 下次调用6行 接下来的 10 天没有数据 10 行以下 ...

我显然没有正确地做某事,但它是什么?

【问题讨论】:

【参考方案1】:

所以,这里发现了问题。这个程序的工作方式是它应该

    发送数据请求 启动async_read_until 以读取端口上的数据。 启动async_wait,这样我们就不会永远等待。 使用io_service::run_one 等待超时成功读取。

第四步的代码看起来如下:

for (;;)
    // This blocks until an event on io_service_ is set.
    n_handlers = io_service_.run_one();


    // Brackets in success case limit scope of new variables
    switch(wait_result)
    case success:

        char c_[1024];
        //string msg;
        string delims = "\r";

        std::string msgbuffers_begin(readData.data()), buffers_begin(readData.data()) + bytes_transferred- delims.size();
            // Consume through the first delimiter.
            readData.consume(bytes_transferred);

        data_out = msg;
        cout << msg << endl;

        data_handler(msg);

        return data_out;

        
    case timeout_expired:

        //Set up for wait and read.
        wait_result = in_progress;
        cout << "Time is up..." << endl;
        return data_out;
        break;
    case error_out:
        cout << "Error out..." << endl;
        return data_out;
        break ;
    case op_canceled:
        return data_out;
        break;

    case in_progress:
        cout << "In progress..." << endl;
        break;
    


只有两种情况会触发退出循环 - timeout_expiredsuccess。但是,如您所见,如果操作被取消(op_canceled如果出现错误(error_out),系统将退出。

问题在于,当异步操作被取消(即deadline_timer::cancel())时,它将触发io_service::run_one 拾取的事件,这会将switch 语句评估的状态设置为op_canceled。这会使异步操作在事件循环中堆积。简单的解决方法是在 所有 情况下将 return 语句注释掉,successtimeout_expired 除外。

【讨论】:

【参考方案2】:

async_read_until 不保证它只会读到第一个分隔符。

由于底层的实现细节,它只会在大多数系统上“读取可用的内容”,如果 streambuf 包含分隔符,它将返回。 其他数据将在流缓冲区中。此外,EOF 可能会返回,即使您还没有预料到。

查看背景Read until a string delimiter in boost::asio::streambuf

【讨论】:

对不起 - 我不明白这一点。您链接到的帖子表明,如果我返回的东西看起来像 cmd1\r,我应该期望 async_read_until 将其放入缓冲区。这不是我看到的行为。如上所述,处理程序会触发(即到达分隔符)但没有数据放在缓冲区中(甚至没有流浪\r)。 我链接到的帖子表明,如果您返回的东西看起来像 cmd1\rcmd2\rcmd3\rcmd4\rcmd5\r,我可以期望 async_read_untilcmd1\rcm 放在该缓冲区中。或cmd1\rcmd2\rcmd3\rc。或者实际上,如果您在读取时已经获得完整数据。 根据您管理流缓冲区的确切方式,这可以解释您似乎阅读“没有新数据”(它已经存在)。当然,直到整个缓冲区被消耗并且您进行实际的新读取。如果您将帖子设为 SSCCE,我将向您展示固定样本。 SSCCE?也许我只是我只是误会。以下是事件的顺序:1)我发送数据请求,2)我将async_wait_until 设置为读取,3)我将计时器设置为超时,4)当处理程序触发时,我们设置一个标志来读取缓冲区,5)我们尝试从缓冲区中读取新数据。似乎正在发生的事情(但也许我只是不明白boost::asio::streambuf)是新数据被洗牌到前面。但是我会定期在缓冲区中得到垃圾,这从一开始的空字节就可以看出。我应该期望新数据加载到缓冲区的前面吗? 你有谷歌吗? SSCCE 表示Simple Selfcontained Complete Correct Example。你当然需要一个。它可能会让你自己看到问题。如果没有,我会帮忙的。

以上是关于使用 boost::asio::async_wait_until 和 boost::asio::streambuf的主要内容,如果未能解决你的问题,请参考以下文章

在使用加载数据流步骤的猪中,使用(使用 PigStorage)和不使用它有啥区别?

今目标使用教程 今目标任务使用篇

Qt静态编译时使用OpenSSL有三种方式(不使用,动态使用,静态使用,默认是动态使用)

MySQL db 在按日期排序时使用“使用位置;使用临时;使用文件排序”

使用“使用严格”作为“使用强”的备份

Kettle java脚本组件的使用说明(简单使用升级使用)