【问题标题】:How can I implement timeout for read() when reading from a serial port (C/C++)从串行端口(C/C++)读取时,如何实现 read() 超时
【发布时间】:2012-05-18 08:00:21
【问题描述】:

我正在使用文件描述符和 posix/unix read() 函数从 C++ 中的串行端口读取字节。在本例中,我从串口读取 1 个字节(为清楚起见,省略了波特率设置和类似设置):

#include <termios.h>
#include <fcntl.h>
#include <unistd.h>

int main(void)
{
   int fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY);
   char buf[1];
   int bytesRead = read(fd, buf, 1);
   close(fd);
   return 0;
}

如果连接到 /dev/ttyS0 的设备没有发送任何信息,程序将挂起。如何设置超时?

我尝试过这样设置超时:

struct termios options;
tcgetattr(fd, &options);
options.c_cc[VMIN] = 0;
options.c_cc[VTIME] = 10;
tcsetattr(fd, TCSANOW, &options);

我认为它应该给 1 秒超时,但它没有区别。我想我误解了 VMIN 和 VTIME。 VMIN 和 VTIME 是干什么用的?

然后我在网上搜索,发现有人在谈论 select() 函数。这是解决方案吗?如果是这样,如何将其应用于上面的程序以使 1 秒超时?

感谢任何帮助。在此先感谢:-)

【问题讨论】:

  • 使用tcsetattr()VTIME 并不简单;它需要一些串行驱动程序不支持的其他模式设置。请参阅我的答案以获得一般解决方案。
  • 这是我在网上看到的关于 VMIN 和 VTIME http://unixwiz.net/techtips/termios-vmin-vtime.html 的最佳解释。根据文章,当 ICANON 位关闭时,它会启用“原始模式”来改变 VMIN 和 VTIME 的解释。设置 ICANON 位将使代码按预期工作。
  • 一般在设置options.c_cc[VMIN] = 0;options.c_cc[VTIME] = 10;时。 read() 应该在一个或多个字节可用时返回,否则会超时。在后一种情况下,read 应该表明已经读取了 0 个字节。

标签: c unix timeout tty


【解决方案1】:

是的,使用select(2)。传入一个文件描述符集,其中仅包含读取集中的 fd 和空的写入/异常集,并传入适当的超时。例如:

int fd = open(...);

// Initialize file descriptor sets
fd_set read_fds, write_fds, except_fds;
FD_ZERO(&read_fds);
FD_ZERO(&write_fds);
FD_ZERO(&except_fds);
FD_SET(fd, &read_fds);

// Set timeout to 1.0 seconds
struct timeval timeout;
timeout.tv_sec = 1;
timeout.tv_usec = 0;

// Wait for input to become ready or until the time out; the first parameter is
// 1 more than the largest file descriptor in any of the sets
if (select(fd + 1, &read_fds, &write_fds, &except_fds, &timeout) == 1)
{
    // fd is ready for reading
}
else
{
    // timeout or error
}

【讨论】:

  • 非常感谢。这似乎就像我想要的那样工作。我还有三个问题:我可以在打开端口时也使用 select 来超时吗?我可以通过设置 FD_SET(fd, &write_fds) 对写操作使用相同的代码来超时吗?最后:except_fds 是做什么用的?
  • poll() 通常比 select() 好。
  • 效果很好!如果我在循环中运行它,我是否需要在每次迭代中使用 FD_ZERO 和 FD_SET,还是在循环之前一次可以?
【解决方案2】:

VMIN 和 VTIME 是干什么用的?

如果 MIN > 0 且 TIME = 0,则 MIN 设置要接收的字符数 在阅读满意之前。由于 TIME 为零,因此不使用计时器。

如果 MIN = 0 且 TIME > 0,则 TIME 用作超时值。阅读将 如果读取单个字符或超过 TIME(t = 时间 *0.1 秒)。如果超过 TIME,则不会返回任何字符。

如果 MIN > 0 且 TIME > 0,则 TIME 用作字符间计时器。这 如果收到 MIN 个字符或时间 两个字符之间超过 TIME。每次都会重新启动计时器 接收到一个字符,并且仅在第一个字符后才变为活动状态 已收到字符。

如果 MIN = 0 和 TIME = 0,读取将立即满足。这 当前可用的字符数,或字符数 请求将被退回。根据安东尼诺(见贡献), 你可以发出 fcntl(fd, F_SETFL, FNDELAY);在阅读之前得到 同样的结果。

来源:http://tldp.org/HOWTO/Serial-Programming-HOWTO/x115.html

【讨论】:

    【解决方案3】:

    您可以尝试捕获信号来停止读取操作。在读取之前使用alarm(1),如果读取函数没有返回,alarm会发送SIGALRM信号,那么你可以创建信号处理函数来捕获这个信号,如下所示:

    #include <stdio.h>
    #include <stdlib.h>
    #include <signal.h>
    #include <setjmp.h>
    
    static jmp_buf env_alarm;
    
    static void sig_alarm(int signo)
    {
        longjmp(env_alarm, 1);
    }
    
    int main(void)
    {
       int fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY);
       char buf[1];
    
       if (signal(SIGALRM, sig_alarm) == SIG_ERR)
       {
           exit(0);
       }
    
       if (setjmp(env_alarm) != 0)
       {
          close(fd);
          printf("Timeout Or Error\n");
          exit(0);
       }
    
       alarm(1);
       int bytesRead = read(fd, buf, 1);
       alarm(0);
    
       close(fd);
       return 0;
    }
    

    但是如果你的程序很大,使用 select 或 poll 或 epoll 会更好。

    【讨论】:

    • 有一个关于select的有趣案例,它检查fd是否可以写入,但是写入时会阻塞,所以我认为select方法在我的情况下不能正常工作,但是你的方法可以正常工作。这是一个很好的方法。
    【解决方案4】:

    select() 是我解决这个问题的方法。

    互联网上有几个页面会提供有关如何使用 select() 的信息,例如 http://www.unixguide.net/unix/programming/2.1.1.shtml

    【讨论】:

      【解决方案5】:

      有几种可能的方法。如果程序最终将计时多个 i/o 操作,select() 是明确的选择。

      但是,如果唯一的输入来自这个 i/o,那么选择非阻塞 i/o 和时序是一种简单的方法。我已将它从单字符 i/o 扩展为多字符,以使其成为更全面的示例:

      #include <fcntl.h>
      #include <termios.h>
      #include <unistd.h>
      #include <sys/time.h>
      
      int main(void)
      {
         int   fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY | O_NDELAY);  // sometimes "O_NONBLOCK"
         char  buf[10];
         int   done = 0, inbuf = 0;
         struct timeval start, now;
      
         gettimeofday (&start, NULL);
         while (!done)
         {
             int bytesRead = read(fd, &buf[inbuf], sizeof buf - inbuf);
             if (bytesRead < 0)
             {
                  error_processing_here();
                  continue;
             }
             if (bytesRead == 0)  // no data read to read
             {
                  gettimeofday (&now, NULL);
                  if ((now.tv.sec  - start.tv_sec) * 1000000 + 
                       now.tv.usec - start.tv_usec > timeout_value_in_microsecs)
                  {
                      done = 2;    // timeout
                      continue;
                  }
                  sleep(1);  // not timed out yet, sleep a second
                  continue;
             }
             inbuf += bytesRead;
             if (we have read all we want)
                 done = 1;
         }
         if (done == 2)
              timeout_condition_handling();
         close(fd);
         return 0;
      }
      

      【讨论】:

      • 这比使用 select() 的代码要多得多,并且在数据可用后最多可以等待一秒钟才能读取数据。
      • @MartinC.Martin:嗯,看起来可能是这样,因为我的代码是一个完整的工作示例,包括读取和错误检查,而 select 示例仅显示了所需的 sn-p。
      • 在 1 秒睡眠期间可能会出现大量数据(尤其是在高波特率下),因此使用睡眠不是一个好主意。较短的睡眠时间也会浪费处理器时间,这在低功率设备中更为重要。可以通过使用select 采用非轮询方法来改进此代码。
      猜你喜欢
      • 2020-05-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-02-12
      • 1970-01-01
      • 1970-01-01
      • 2019-12-05
      相关资源
      最近更新 更多