我正在使用boost :: asio进行串行通信,我想在某个端口上监听传入的数据。因此,我使用serialport::async_read_some()
注册了一个ReadHandler,然后创建了一个单独的线程来处理异步处理程序(调用io_service::run()
)。我的ReadHandler在结束时通过再次调用async_read_some()
来重新注册,这似乎是一种常见模式。
这一切正常,并且我的示例可以在接收到数据时将数据打印到stdout-除非我注意到在ReadHandler运行时接收的数据在ReadHandler执行完毕并接收到新数据之前不会被“读取”之后发生。也就是说,当ReadHandler运行时接收到数据时,尽管在ReadHandler结束时调用了async_read_some,但它不会立即再次为该数据调用ReadHandler。只有在初始ReadHandler完成后收到 additional 数据时,才会再次调用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(),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(),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”。 Example program output
以下是此测试和我想要的内容的更清晰说明:
测试:启动程序,等待1秒钟,键入hi,等待9秒钟,键入t
我想发生的事情(由该程序打印到stdout):
1000ms:h
6010ms:i
11020ms:t
实际发生的情况:
1000ms:h
10000ms:它
程序具有识别两次读取之间接收到的数据的方式似乎非常重要。我知道无法使用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进行构建,但是我真的希望这与这个问题无关。