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

问题描述 投票:0回答:2

我需要在 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;
}

在串行端口上,轮询返回每个字节并导致多次迭代。

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

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

c linux sockets uart posix-select
2个回答
1
投票

感谢大家的宝贵意见!

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

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

与通常基于数据包的网络套接字不同,串行端口(注意:在非规范模式下)是基于字符的。预计每个到达的字符都会迭代轮询循环,特别是在低波特率下。

在我的应用程序中,我通过串行线路向设备发送命令并等待答案。 如果没有收到答复,则会发生超时,也许我们会重试。

termios 选项“VMIN”很方便,因为我可以指定我想要接收的字符数。通常读取会阻塞,直到 n 个字符到达。

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

termios 选项“VTIME”与 VMIN > 0 一起指定字符间超时,以十分秒为单位 (1 = 100ms)。这很方便,但只有在接收到第一个字符后才会开始超时。否则字符间超时就没有意义。

因此,如果我仅使用 termios 选项,则从串行设备的读取会阻塞。

为了避免这个问题,我在 read 前面使用 poll。 一旦第一个字符到达(轮询返回 rc=1),我就开始阅读。 “VTIME”也处于活动状态,并将强制字符间时间为 100 毫秒(最低可能设置)。

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

假设超时为 400 毫秒

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

0
投票

一个简单的解决方案可能是使用

alarm
(如果超时以秒为单位)或
setitimer
ITIMER_REAL
计时器。然后,当信号发生时,让
read
调用返回并返回错误(使用
errno == EINTR

© www.soinside.com 2019 - 2024. All rights reserved.