【发布时间】:2021-08-08 08:25:22
【问题描述】:
我有一个智能电表,它每秒发送一次能耗数据。我编写的用于读取数据的守护程序(C++/C Arch Linux)在 USB 电缆断开时不会退出,并且在阻塞的 read() 调用中无限期停止。
如何中断阻塞的 read() 调用(即以 EINTR 失败而不是等待下一个字符)?
我在 Google 上进行了广泛的搜索,并在 SO 中查看了此处,但找不到此问题的答案。
详情:
- Smartmeter Github 上的项目源码
- 带有 FT232RL USB 转 UART 桥接器的红外加密狗
- 数据报的固定长度为每秒发送 328 个字节
- Read 方法检测到开头 \ 和结尾!数据报的标记
- sigaction 捕获 CTRL+C SIGINT 和 SIGTERM 信号
- termios 设置为使用 VMIN = 1 和 VTIME = 0 执行阻塞 read()。
试过了:
- 使用 VMIN 和 VTIME
- 已删除 SA_RESTART
可能的解决方案:
- 使用非阻塞读取方法,可能使用 select() 和 poll()
- 或 VMIN > 0(数据报超过 255 个字符,我需要以更小的块读取数据报)
- 不确定如何处理数据报开始/结束检测以及非阻塞读取方法的数据报之间的一秒间隔
编辑:下面的代码现在将 read() 调用缓冲到一个 255 字节的中间缓冲区(VMIN = 255 和 VTIME = 5),改编自 here。这避免了为每个字符调用 read() 的小开销。实际上,与一次读取一个字符相比,这并没有什么不同。 Read() 仍然没有在电缆断开时正常退出。守护进程需要用kill -s SIGQUIT $PID 杀死。 SIGKILL 无效。
main.cpp:
volatile sig_atomic_t shutdown = false;
void sig_handler(int)
{
shutdown = true;
}
int main(int argc, char* argv[])
{
struct sigaction action;
action.sa_handler = sig_handler;
sigemptyset(&action.sa_mask);
action.sa_flags = SA_RESTART;
sigaction(SIGINT, &action, NULL);
sigaction(SIGTERM, &action, NULL);
while (shutdown == false)
{
if (!meter->Receive())
{
std::cout << meter->GetErrorMessage() << std::endl;
return EXIT_FAILURE;
}
}
Smartmeter.cpp:
bool Smartmeter::Receive(void)
{
memset(ReceiveBuffer, '\0', Smartmeter::ReceiveBufferSize);
if (!Serial->ReadBytes(ReceiveBuffer, Smartmeter::ReceiveBufferSize))
{
ErrorMessage = Serial->GetErrorMessage();
return false;
}
}
SmartMeterSerial.cpp:
#include <cstring>
#include <iostream>
#include <thread>
#include <unistd.h>
#include <termios.h>
#include <sys/file.h>
#include <sys/ioctl.h>
#include "SmartmeterSerial.h"
const unsigned char SmartmeterSerial::BufferSize = 255;
SmartmeterSerial::~SmartmeterSerial(void)
{
if (SerialPort > 0) {
close(SerialPort);
}
}
bool SmartmeterSerial::Begin(const std::string &device)
{
if (device.empty()) {
ErrorMessage = "Serial device argument empty";
return false;
}
if ((SerialPort = open(device.c_str(), (O_RDONLY | O_NOCTTY))) < 0)
{
ErrorMessage = std::string("Error opening serial device: ")
+ strerror(errno) + " (" + std::to_string(errno) + ")";
return false;
}
if(!isatty(SerialPort))
{
ErrorMessage = std::string("Error: Device ") + device + " is not a tty.";
return false;
}
if (flock(SerialPort, LOCK_EX | LOCK_NB) < 0)
{
ErrorMessage = std::string("Error locking serial device: ")
+ strerror(errno) + " (" + std::to_string(errno) + ")";
return false;
}
if (ioctl(SerialPort, TIOCEXCL) < 0)
{
ErrorMessage = std::string("Error setting exclusive access: ")
+ strerror(errno) + " (" + std::to_string(errno) + ")";
return false;
}
struct termios serial_port_settings;
memset(&serial_port_settings, 0, sizeof(serial_port_settings));
if (tcgetattr(SerialPort, &serial_port_settings))
{
ErrorMessage = std::string("Error getting serial port attributes: ")
+ strerror(errno) + " (" + std::to_string(errno) + ")";
return false;
}
cfmakeraw(&serial_port_settings);
// configure serial port
// speed: 9600 baud, data bits: 7, stop bits: 1, parity: even
cfsetispeed(&serial_port_settings, B9600);
cfsetospeed(&serial_port_settings, B9600);
serial_port_settings.c_cflag |= (CLOCAL | CREAD);
serial_port_settings.c_cflag &= ~CSIZE;
serial_port_settings.c_cflag |= (CS7 | PARENB);
// vmin: read() returns when x byte(s) are available
// vtime: wait for up to x * 0.1 second between characters
serial_port_settings.c_cc[VMIN] = SmartmeterSerial::BufferSize;
serial_port_settings.c_cc[VTIME] = 5;
if (tcsetattr(SerialPort, TCSANOW, &serial_port_settings))
{
ErrorMessage = std::string("Error setting serial port attributes: ")
+ strerror(errno) + " (" + std::to_string(errno) + ")";
return false;
}
tcflush(SerialPort, TCIOFLUSH);
return true;
}
char SmartmeterSerial::GetByte(void)
{
static char buffer[SmartmeterSerial::BufferSize] = {0};
static char *p = buffer;
static int count = 0;
if ((p - buffer) >= count)
{
if ((count = read(SerialPort, buffer, SmartmeterSerial::BufferSize)) < 0)
{
// read() never fails with EINTR signal on cable disconnect
ErrorMessage = std::string("Read on serial device failed: ")
+ strerror(errno) + " (" + std::to_string(errno) + ")";
return false;
}
p = buffer;
}
return *p++;
}
bool SmartmeterSerial::ReadBytes(char *buffer, const int &length)
{
int bytes_received = 0;
char *p = buffer;
bool message_begin = false;
tcflush(SerialPort, TCIOFLUSH);
while (bytes_received < length)
{
if ((*p = GetByte()) == '/')
{
message_begin = true;
}
if (message_begin)
{
++p;
++bytes_received;
}
}
if (*(p-3) != '!')
{
ErrorMessage = "Serial datagram stream not in sync.";
return false;
}
return true;
}
非常感谢您的帮助。
【问题讨论】:
-
本文修改后的代码真的有效吗? VMIN 和 VTIME 的设置是什么,即阻塞或非阻塞读取?我看过这个,但对我来说不是结论。
-
这似乎也与您的问题不太直接相关,但还有另一篇文章。 Linux read() call not returning error when i Unplug serial cable [duplicate]
-
我也看过这个。但是这个 SO 中的解决方案在哪里?如果出现错误,使用 termios 并执行阻塞 read() 的程序如何返回?我的程序仅在 read() 收到另一个字节后终止,而不是在阻塞读取调用中。如果电缆断开连接,它将无法接收另一个字节,因此永远停止。有什么解决办法吗?
-
您可能会问一个 XY 问题。为什么“USB 电缆已断开” 对您的守护程序来说是个问题?与主机的稳定 USB 连接如何,但仪表失败并停止发送数据;那是怎么处理的?这真的是错误情况吗?
标签: serial-port timeout usb blocking termios