为啥 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 无限循环,接收数据为零字节