2017-03-21 23 views
2

我需要在Linux上的串行端口或套接字上等待n个字节的数据(计数已知)。 目前我使用一个循环与调查,测量时间和减少超时:C:在超时的阻塞套接字上等待n个字符

static int int_read_poll(int fd, uint8_t *buffer, size_t count, int timeout) 
{ 
    struct pollfd pfd; 
    int rc; 

    pfd.fd = fd; 
    pfd.events = POLLIN; 

    rc = poll(&pfd, 1, timeout); 
    if (rc < 0) { 
     perror("poll"); 
     return 0; 
    } 

    if (rc > 0) { 
     if (pfd.revents & POLLIN) { 
      rc = read(fd, buffer, count); 
      return rc; 
     } 
    } 
    return 0; 
} 

static int int_read_waitfor(int fd, uint8_t *buffer, size_t count, int timeout) 
{ 
    int rc; 
    struct timespec start, end; 
    int delta_ms; 
    int recv = 0; 

    do { 
     clock_gettime(CLOCK_MONOTONIC_RAW, &start); 
      rc = int_read_poll(fd, buffer + recv, count - recv, timeout); 
     clock_gettime(CLOCK_MONOTONIC_RAW, &end); 
     delta_ms = (end.tv_nsec - start.tv_nsec)/1000000; 

     if (!rc || (rc < 0)) return 0; 
     recv += rc; 
     timeout -= delta_ms; 

     if (timeout <= 0) 
      return 0; 

    } while (recv != count); 
    return recv; 
} 

上的串行端口,每个单字节poll返回,并导致多次迭代。

有没有更好的方法来解决这个问题?

我知道,根据波特率,超时可能不会在该代码部分递减。计数纳秒可能是更好的方法。

回答

0

感谢所有您宝贵的提示!

经过一些测试后,我终于决定不使用信号,因为它们可能会干扰应用程序,只要我将函数移植到库中或将它们作为源发布即可。

我最终发现其使用轮询和termios的(只有四个系统调用)纯溶液:

static int int_read_waitfor(int fd, uint8_t *buffer, size_t count, int timeout) 
{ 
    struct termios tm; 
    struct pollfd pfd; 
    int rc; 

    tcgetattr(fd, &tm); 
    tm.c_cc[VTIME] = 1; // inter-character timeout 100ms, valid after first char recvd 
    tm.c_cc[VMIN] = count; // block until n characters are read 
    tcsetattr(fd, TCSANOW, &tm); 

    pfd.fd = fd; 
    pfd.events = POLLIN; 

    rc = poll(&pfd, 1, timeout); 
    if (rc > 0) { 
     rc = read(fd, buffer, count); 
     if (rc == -1) { 
      perror("read"); 
      return 0; 
     } 
     return rc; 
    } 

    return 0; 
} 

不同于其通常基于分组的网络套接字,串行端口(NB:在非经典模式)是字符根据。预计每个到达的字符都会迭代轮询循环,特别是在低波特率下。

我的应用程序我发送一个命令通过串口线到设备并等待答案。 如果未收到答案,则会发生超时,可能我们会重试。

termios选项“VMIN”非常方便,因为我可以指定多少个我喜欢的角色。通常读取会阻塞,直到n个字符到达。

如果没有答案,命令将永远阻止。

与VMIN> 0结合使用的termios选项“VTIME”指定字符间隔(十进制)(1 = 100ms)。这很方便,但超时只会在接收到第一个字符后才会启动。否则,字符间超时将没有意义。

因此,如果我只使用termios选项,读取会阻塞从站串行设备已死亡。

为了规避这个问题,我在阅读前使用了poll。 第一个字符到达后(轮询返回rc = 1),我开始阅读。 “VTIME”也处于活动状态,并会强制执行100ms的字符间时间(可能的最低设置)。

作为奖励,超时处理进行了优化:

让我们假设的400ms的

  • 超时如果从设备是死的,民意调查后400毫秒
  • 如果从工作和回复返回在50ms内(第一个字符),轮询返回并开始读取。如果从机发送的数据太少,VTIME会踢和后50个毫秒+ 100毫秒停止接收。我们不必等待最后(缺失)字节的整个400ms到达。
+0

在一些串行命令我的示例应用程序,以前的代码共有272个系统调用,新的版本仅使用142 –

0

一个简单的解决方案可能是使用alarm(如果您的超时时间为秒)或setitimerITIMER_REAL定时器。然后,只要read呼叫在信号发生时返回错误(使用errno == EINTR

+0

我修改的插座水平'int_read_waitfor'与setitimer和使用VMIN =计数。它仍然使用六个系统调用,但工作正常。无论如何:有没有一种方式来发信号只有插座,而不是整个过程? –

+0

@肉汤-ITK不,这不可能只发出信号插座。信号是一个过程基元,而不是套接字基元。 –

+1

@肉汤-ITK:这是很可能然而,只有信号的特殊*线程*是在插座读阻塞。您可以通过使用'timer_create为此在Linux操作系统()'用'SIGEV_THREAD_ID'型'结构sigevent'并以某种方式映射'pthread_t'对'tid's。一个更好的POSIX可移植选项是使用POSIX实时信号(例如'SIGRTMIN + 0)和一个空处理程序体(因此它的传递将中断一个阻塞的系统调用)和一个使用'pthread_kill()'的专用线程'在超时到期时中断其他线程。 –