如何在阻止 read() 调用中检测 USB 电缆断开连接?

如何解决如何在阻止 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 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-