为啥 boost asio async_read_some 在特定情况下不调用回调?

Posted

技术标签:

【中文标题】为啥 boost asio async_read_some 在特定情况下不调用回调?【英文标题】:Why boost asio async_read_some does not invoke the callback in a certain situation?为什么 boost asio async_read_some 在特定情况下不调用回调? 【发布时间】:2015-05-21 07:52:55 【问题描述】:

我正在实现一个串行通信协议,以通过 UART 与外部设备进行通信。我为此使用了boost asio。到目前为止,除了极少数情况外,一切正常。我发现在某些情况下,从套接字读取可能会 "miss" 1 个字节。

我使用 UART 嗅探器检查了串行通信,以查看通过套接字传入和传出的原始数据。这很可悲,我确实看到了通过套接字传输的完整字节帧。但在我的应用程序中,最后一个字节没有收到。

我发现他的字节不是“丢失”,它只是在内核缓冲区或其他东西中“卡住”。因为在接收下一帧时,我确实在所有新字节之前得到了这个 "missing" 字节。如果帧连续进入,这不会是一个大问题。但只有在前一帧被程序确认后,设备才会发送下一帧。

这是我的 write 和 handle 接收方法的代码:

void serial::write(std::vector<uint8_t> message) 
    static boost::mutex mu;
    mu.lock();
    uint8_t cmd = message.at(3);
    //if its an ACK frame - just send it
    if ((message[3]>>4)==0xE) 
        // --- Write message to serial port --- //
        boost::asio::write(serial_,boost::asio::buffer(message));
        usleep(20000);
        mu.unlock();
        return;
    
    //if its not an ACK frame write the frame and wait for an ACK!
    //create new promise for the request
    promise = new boost::promise<deque<uint8_t>>;
    boost::unique_future<deque<uint8_t>> future = promise->get_future();
    // --- Write message to serial port --- //
    boost::asio::write(serial_,boost::asio::buffer(message));
    usleep(20000);
    mu.unlock();
    //wait for ACK or timeout
    if     (future.wait_for(boost::chrono::milliseconds(100))==boost::future_status::timeout) 
        spdlog::get("logger")->trace("ACK timeout!");
        //delete pointer and set it to 0
        delete promise;
        promise=nullptr;
        //need to resend this frame
    
    //delete pointer and set it to 0 after getting a message
    delete promise;
    promise=nullptr;

处理接收:

void serial::handle_receive(const boost::system::error_code& error, size_t bytes_transferred) 
     static deque<uint8_t> read_buffer;
     static boost::posix_time::ptime start =    boost::posix_time::microsec_clock::local_time( );
    if (boost::posix_time::microsec_clock::local_time( ) - start > boost::posix_time::milliseconds(99)  && !read_buffer.empty()) 
        spdlog::get("logger")->trace("time condition, clearing buffer!");
        read_buffer.clear();
    
    start = boost::posix_time::microsec_clock::local_time( );
    if (!error) 
        //push all the recieved data into the deque
        for (unsigned int i = 0; i < bytes_transferred; i++) 
            read_buffer.push_back((int)data_[i]);
        
    
    else if (error)
    spdlog::get("logger")->error("\n\n\n\nERROR: 0\n\n\n\n", error);
    
    while ( !read_buffer.empty()) 
        /*do here some checks if this could be a correct frame
       ...
       ...
       ...
       */
        //received frame is ready
        //check if it is an ACK frame
        //check if its a data frame and ACK it!
        //send an ACK if its a dataframe
        if (ACK)) 
            write(frame);
        
        //give the data to the upper layer                  
        m_Receive_data_handler(receivedFrame,receivedFrame.size());
        
    serial_.async_read_some(boost::asio::buffer(data_,max_length),
                        boost::bind(&serial::handle_receive, this,
                                    boost::asio::placeholders::error,
                                    boost::asio::placeholders::bytes_transferred));


也许有人知道为什么它在大多数情况下都有效,但仅在帧的最后一个字节就失败了?

谢谢!

【问题讨论】:

使用 BOOST_ASIO_ENABLE_HANDLER_TRACKING 定义的构建可能有助于提供对处理程序的一些了解。另外,在serial::handle_receive() 中,如果没有读取完整的帧,while (!read_buffer.empty()) 循环中会发生什么? 哦,对不起!在此期间,它执行验证检查并将帧提供给上层,直到它为空。缓冲区中可能存在不止一个有效帧。 但是如果一个完整的帧没有被读取会发生什么?它会无限循环吗? 不,在循环的更下方有破坏条件。抱歉,我删除了它以使其更短。 使用 Boost.Asio 使用的默认串行选项,如果有任何数据可用,则未完成的 async_read_some() 操作应该完成(请参阅 here 以查询多少字节可用)。显示处理程序跟踪、串行端口初始化/配置和代码流的输出可能会说明问题。不需要显示较低的细节(例如,如何验证缓冲区是否包含帧),但显示流程是有帮助的(例如,当没有帧时,跳出循环)。 【参考方案1】:

您正在使用异步调用来读取数据,并且无法保证您会读取所有内容。

如果您查看文档: http://www.boost.org/doc/libs/1_58_0/doc/html/boost_asio/reference/basic_stream_socket/async_read_some.html

备注

读取操作可能不会读取所有请求的字节数。 如果需要确保 在异步操作之前读取请求的数据量 完成。

可能发生的情况是,在您的情况下,您的内核缓冲区很少,它会在应用程序有一些数据时向应用程序发出信号,但您正试图发送比这更多的数据。一旦它被困在缓冲区中只剩下很少的数据,它就会等待更多数据的到来并保存调用。不过,这些只是假设,我对 UART 没有第一手经验。

是否可以切换到同步读取调用 (read_some)?

【讨论】:

我知道不能保证一次接收到所有需要的数据,为了解决这个问题,我实现了一种机制来构建帧,每当新调用句柄接收完成时。所以这不是问题。问题实际上只是没有使用缓冲区中的最后一个字节调用句柄接收。但有时在缓冲区中只有 1 个字节时调用它。所以我不认为缓冲区中的字节数是原因。我经常看到使用 1 字节数据处理接收呼叫。我认为同步读取调用不是选项。 那么这听起来像是一个经典的索引问题,也许你从0开始计数,传输的字节数不是零索引的?我正在考虑对 handle_receive 的调用,在您为处理帧而添加的额外代码中,您可能没有以正确的读取字节数回调它。 如果传输的字节数有误,我认为无论如何它都不会工作? 如果您告诉您的应用程序您收到了少一个字节,那么下一次您从那里提取并且您有剩余的一个。如果我是你,我会将日志记录添加到你通过不同函数传递的所有不同大小。 在我的句柄接收结束时,我调用了一个新的 read_some,只是带有一个占位符(实际上我不太明白,这是什么意思?)所以 bytes_received 不是由我计算的,我思考。您建议在哪里添加日志记录?【参考方案2】:

难道最后一个字节没有卡在某个地方的内核缓冲区中,而是卡在了 UART 的接收 FIFO 中?也许您需要为驱动程序配置字符间超时?

【讨论】:

嗯,我该怎么做? 这取决于底层串行驱动程序的实现、操作系统、平台等。如果您使用的是 boost::asio 串行端口类,那么原则上它会为您设置这些东西,但你永远不知道...

以上是关于为啥 boost asio async_read_some 在特定情况下不调用回调?的主要内容,如果未能解决你的问题,请参考以下文章

优雅地取消 boost::asio::async_read

boost::asio::async_read 不回调我的处理函数

boost::asio::async_read 无限循环,接收数据为零字节

如何在到达终止字符时返回 boost::asio::async_read

boost asio async_read中的随机EOF

boost::asio::async_read 在接收到完整的 Content-Length 之前接收 EOF