C:在超时的阻塞套接字上等待n个字符

时间:2017-03-21 09:55:50

标签: c linux sockets select uart

我需要在Linux上的串口或套接字上等待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会在每个字节上返回并导致多次迭代。

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

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

2 个答案:

答案 0 :(得分:0)

一个简单的解决方案可能是使用alarm(如果您的超时为秒)或setitimer使用ITIMER_REAL计时器。然后只有read调用返回信号发生时出错(使用errno == EINTR

答案 1 :(得分:0)

感谢所有人提供的宝贵提示!

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

我最终找到了一个使用poll和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;
}

与通常基于数据包的网络套接字不同,串行端口(n.b:非规范模式)是基于字符的。期望对每个到达的字符迭代具有轮询的循环,特别是在低波特率下。

我的应用程序我通过串行线路将命令发送到设备并等待答案。 如果没有收到回答,将发生超时,我们可能会重试。

termios选项&#34; VMIN&#34;很方便,因为我可以指定我喜欢的人物数量。通常读取将阻塞,直到n个字符到达。

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

termios选项&#34; VTIME&#34;与VMIN结合&gt; 0表示以十分之一为单位的字符间超时(1 = 100ms)。这很方便,但只有在收到第一个字符后才会开始超时。否则,字符间超时就没有意义了。

因此,如果我只使用termios选项,则读取将阻止从属串行设备死机。

为了避免这个问题,我在阅读前使用民意调查。 一旦第一个字符到达(轮询以rc = 1返回),我开始阅读。 &#34; VTIME&#34;也是有效的,并将强制执行100ms的间距时间(可能的最低设置)。

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

假设超时为400毫秒

  • 如果从属设备已死,则轮询将在400毫秒后返回
  • 如果奴隶工作并在50ms(第一个字符)内回复,则轮询返回并开始读取。如果从站发送的数据太少,VTIME将在50ms + 100ms后启动并停止接收。我们不必等待整个400毫秒的最后一个(丢失)字节到达。