14

我正在使用文件描述符和 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 秒超时?

任何帮助表示赞赏。提前致谢 :-)

4

5 回答 5

23

是的,使用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
}
于 2012-05-09T19:52:35.157 回答
3

VMIN 和 VTIME 是做什么用的?

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

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

如果 MIN > 0 且 TIME > 0,则 TIME 用作字符间计时器。如果接收到 MIN 个字符,或者两个字符之间的时间超过 TIME,则读取将被满足。每次接收到一个字符时都会重新启动计时器,并且只有在接收到第一个字符后才会激活。

如果 MIN = 0 和 TIME = 0,读取将立即满足。当前可用的字符数,或请求的字符数将被返回。根据 Antonino(参见供稿),您可以发出 fcntl(fd, F_SETFL, FNDELAY); 在阅读之前得到相同的结果。

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

于 2015-08-21T11:43:53.117 回答
1

您可以尝试捕获信号以停止读取操作。在读取之前使用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 会更好。

于 2012-05-13T14:41:31.707 回答
0

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

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

于 2012-05-09T19:05:38.380 回答
0

有几种可能的方法。如果程序最终会为多个 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;
}
于 2012-05-09T19:48:13.123 回答