C:在超时的阻塞套接字上等待 n 个字符
C: Wait for n characters on a blocking socket with timeout
我需要在 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;
}
在串行端口上,轮询 returns 每个字节并导致多次迭代。
有没有更优雅的方法来解决这个问题?
我知道根据波特率,该代码部分的超时可能不会减少。计算纳秒可能是更好的方法。
一个简单的解决方案可能是将 alarm
(if your timeout is in seconds) or setitimer
与 ITIMER_REAL
计时器一起使用。然后在信号发生时 read
调用 return 并出错(使用 errno == EINTR
)
感谢大家的宝贵提示!
经过一些测试,我最终决定不使用信号,因为一旦我将我的函数移植到库中或将它们发布为源代码,它们可能会干扰应用程序。
我最终找到了一个使用 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 选项 "VMIN" 很方便,因为我可以指定要接收的字符数。通常读取会阻塞,直到 n 个字符到达。
如果没有答案,命令将永远阻塞。
termios 选项 "VTIME" 与 VMIN > 0 一起指定以分秒为单位的字符间超时(1 = 100 毫秒)。这很方便,但只有在接收到第一个字符后才会开始超时。否则字符间超时将毫无意义。
因此,如果我只使用 termios 选项,读取将从属串行设备的块已死。
为了避免这个问题,我在读取之前使用轮询。
一旦第一个字符到达(poll returns with rc=1),我就开始阅读。 "VTIME" 也处于活动状态,并将强制执行 100 毫秒的字符间时间(可能的最低设置)。
作为奖励,超时处理得到了优化:
假设超时为 400 毫秒
- 如果从设备挂掉,poll会在400ms后return
- 如果从机工作并在 50ms(第一个字符)内回复,轮询 returns 并开始读取。如果从设备发送的数据太少,VTIME 将在 50ms + 100ms 后启动并停止接收。我们不必为最后一个(丢失的)字节的到达等待整个 400 毫秒。
我需要在 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;
}
在串行端口上,轮询 returns 每个字节并导致多次迭代。
有没有更优雅的方法来解决这个问题?
我知道根据波特率,该代码部分的超时可能不会减少。计算纳秒可能是更好的方法。
一个简单的解决方案可能是将 alarm
(if your timeout is in seconds) or setitimer
与 ITIMER_REAL
计时器一起使用。然后在信号发生时 read
调用 return 并出错(使用 errno == EINTR
)
感谢大家的宝贵提示!
经过一些测试,我最终决定不使用信号,因为一旦我将我的函数移植到库中或将它们发布为源代码,它们可能会干扰应用程序。
我最终找到了一个使用 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 选项 "VMIN" 很方便,因为我可以指定要接收的字符数。通常读取会阻塞,直到 n 个字符到达。
如果没有答案,命令将永远阻塞。
termios 选项 "VTIME" 与 VMIN > 0 一起指定以分秒为单位的字符间超时(1 = 100 毫秒)。这很方便,但只有在接收到第一个字符后才会开始超时。否则字符间超时将毫无意义。
因此,如果我只使用 termios 选项,读取将从属串行设备的块已死。
为了避免这个问题,我在读取之前使用轮询。 一旦第一个字符到达(poll returns with rc=1),我就开始阅读。 "VTIME" 也处于活动状态,并将强制执行 100 毫秒的字符间时间(可能的最低设置)。
作为奖励,超时处理得到了优化:
假设超时为 400 毫秒
- 如果从设备挂掉,poll会在400ms后return
- 如果从机工作并在 50ms(第一个字符)内回复,轮询 returns 并开始读取。如果从设备发送的数据太少,VTIME 将在 50ms + 100ms 后启动并停止接收。我们不必为最后一个(丢失的)字节的到达等待整个 400 毫秒。