如何解决如何在阻止 read() 调用中检测 USB 电缆断开连接?
我有一个智能电表,每秒发送一次能耗数据。我编写的用于读取数据的守护程序 (C++/C Arch Linux) 在 USB 电缆断开连接时不会退出,并在阻塞 read() 调用中无限期停止。
如何中断阻塞的 read() 调用(即使用 EINTR 失败而不是等待下一个字符)?
我在 Google 上广泛搜索并在 SO 中查看了此处,但找不到此问题的答案。
详情:
- Smartmeter Github 上的项目源
- 带有 FT232RL USB 转 UART 桥接器的红外加密狗
- 数据报的固定长度为每秒发送 328 个字节
- Read 方法检测开始\和结束!数据报的标记
- 用于捕获 CTRL+C SIGINT 和 SIGTERM 信号的信号
- termios 设置为在 VMIN = 1 和 VTIME = 0 的情况下执行阻塞 read()。
尝试过:
- 使用 VMIN 和 VTIME
- 删除了 SA_RESTART
可能的解决方案:
- 使用非阻塞读取方法,可能使用 select() 和 poll()
- 或 VMIN > 0(数据报长于 255 个字符,我需要以较小的块读取数据报)
- 不确定如何处理数据报开始/结束检测以及非阻塞读取方法的数据报之间的一秒间隔
EDIT:下面的代码现在将 read() 调用缓冲到从 here 改编的 255 字节(VMIN = 255 和 VTIME = 5)的中间缓冲区中。这避免了为每个字符调用 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,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,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;
}
非常感谢您的帮助。
解决方法
虽然下面的代码不是关于如何中断阻塞 read() 调用的原始问题的解决方案,但至少它对我来说是一个可行的解决方法。当 VMIN = 0 和 VTIME = 0 时,这是一个非阻塞 read():
bool SmartmeterSerial::ReadBytes(char *buffer,const int &length)
{
int bytes_received = 0;
char *p = buffer;
tcflush(SerialPort,TCIOFLUSH);
bool message_begin = false;
const int timeout = 10000;
int count = 0;
char byte;
while (bytes_received < length)
{
if ((byte = read(SerialPort,p,1)) < 0)
{
ErrorMessage = std::string("Read on serial device failed: ")
+ strerror(errno) + " (" + std::to_string(errno) + ")";
return false;
}
if (*p == '/')
{
message_begin = true;
}
if (message_begin && byte)
{
++p;
bytes_received += byte;
}
if (count > timeout)
{
ErrorMessage = "Read on serial device failed: Timeout";
return false;
}
++count;
std::this_thread::sleep_for(std::chrono::microseconds(100));
}
if (*(p-3) != '!')
{
ErrorMessage = "Serial datagram stream not in sync.";
return false;
}
return true;
}
但是,我仍然很想知道是否真的可以中断阻塞的“read()”,因为这种解决方法会不断轮询串行端口。
我相信一次读取一个字符不是问题,因为从 UART 接收到的字节由操作系统缓冲 - 但不断用 read()
轮询缓冲区是!也许我会在 ioctl(SerialPort,FIONREAD,&bytes_available)
之前尝试 read()
,虽然我不知道这是否真的会有所作为。
有什么建议吗?
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。