gpt4 book ai didi

linux - SO_RCVTIMEO 醒得太早

转载 作者:塔克拉玛干 更新时间:2023-11-03 01:56:47 28 4
gpt4 key购买 nike

Linux manpage对于 SO_RCVTIMEO 说:

Specify the receiving or sending timeouts until reporting an error... If an input or output function blocks for this period of time... [and] no data has been transferred and the timeout has been reached, then -1 is returned with errno set to EAGAIN or EWOULDBLOCK, or EINPROGRESS (for connect(2))

这听起来像是 I/O 在将执行返回给调用者之前至少应该等待 SO_RCVTIMEO。同时,at the Open Group ,他们记录了相反的情况:

Sets the timeout value that specifies the maximum amount of time an input function waits until it completes.

那么它是最小阻塞时间还是最大阻塞时间?答案似乎是:是的。以下是我在 Linux 系统上请求 .500 秒超时时发生的情况:

time: 0.497054 result: 0
time: 0.495352 result: 0
time: 0.504948 result: 0
time: 0.495119 result: 0
time: 0.507884 result: 0
time: 0.491892 result: 0
time: 0.500764 result: 0

我们看到时间是错误的,通常是 7 毫秒左右,这是一个很长的错误时间。错误发生在两个方向。同时在 Darwin :

time: 0.500426 result: -1
time: 0.501144 result: -1
time: 0.500507 result: -1
time: 0.501119 result: -1
time: 0.501016 result: -1
time: 0.500540 result: -1
time: 0.500127 result: -1
time: 0.500815 result: -1
time: 0.500341 result: -1
time: 0.500871 result: -1
time: 0.500835 result: -1
time: 0.501138 result: -1
time: 0.501087 result: -1
time: 0.501153 result: -1
time: 0.501149 result: -1

误差要低得多(~1 毫秒),但仍然存在,并且他们清楚地将 500 毫秒解释为最短时间,而不是最长时间。

现在有一些问题:

  • SO_RCVTIMEO 应该是阻止调用者的最短或最长持续时间吗?
  • 如果是最长持续时间,那么最短时间是多少?当要求 500 毫秒超时时,实现当然不能自由选择非阻塞读取?
  • 如果是最短持续时间, Darwin 错了吗?
  • 如果我想保证我尝试阅读至少 500 毫秒,我是否应该一直循环尝试直到 500 毫秒过去?实现“至少 X 毫秒”行为的“正确方法”是什么?
  • 为什么 Linux 上的调用之间存在如此大的差异?错误的来源是什么?
  • 我应该使用更好的 API 来从套接字读取数据吗?

我用来衡量这个的代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <time.h>
#include <fcntl.h>

#ifdef __MACH__
#include <mach/clock.h>
#include <mach/mach.h>
#endif
void error(const char *msg)
{
perror(msg);
exit(1);
}

struct timespec os_time() {
struct timespec ts;
#ifdef __MACH__ // OS X does not have clock_gettime, use clock_get_time
clock_serv_t cclock;
mach_timespec_t mts;
host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock);
clock_get_time(cclock, &mts);
mach_port_deallocate(mach_task_self(), cclock);
ts.tv_sec = mts.tv_sec;
ts.tv_nsec = mts.tv_nsec;

#else
clock_gettime(CLOCK_REALTIME, &ts);
#endif
return ts;
}

int main(int argc, char *argv[])
{
int sockfd, newsockfd, portno;
socklen_t clilen;
char buffer[256];
struct sockaddr_in serv_addr, cli_addr;
int n;
if (argc < 2) {
fprintf(stderr,"ERROR, no port provided\n");
exit(1);
}
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
error("ERROR opening socket");
bzero((char *) &serv_addr, sizeof(serv_addr));
portno = atoi(argv[1]);
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = INADDR_ANY;
serv_addr.sin_port = htons(portno);
if (bind(sockfd, (struct sockaddr *) &serv_addr,
sizeof(serv_addr)) < 0)
error("ERROR on binding");
listen(sockfd,5);
clilen = sizeof(cli_addr);
newsockfd = accept(sockfd,
(struct sockaddr *) &cli_addr,
&clilen);
if (newsockfd < 0)
error("ERROR on accept");
for (int i = 0; i < 100; i++) {
struct timeval tv;

tv.tv_sec = 0;
tv.tv_usec = 500000;
char buf[1];
if (setsockopt(newsockfd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv,sizeof(struct timeval)) != 0){
error("setsockopt error");
}
struct timespec start = os_time();
int result = recv(newsockfd,buf,1,0);
struct timespec end = os_time();

double end_time = (double)end.tv_sec + ((double)end.tv_nsec)/1.0E9;
double start_time = (double)start.tv_sec + ((double)start.tv_nsec)/1.0E9;
printf("time: %f result: %d\n",end_time-start_time, result);
}
return 0;
}

复制:

clang test.c && ./a.out 5551 &
telnet localhost 5551
time: 0.497839 result: 0
time: 0.501052 result: 0
time: 0.498565 result: 0
time: 0.500741 result: 0
time: 0.500108 result: 0
time: 0.500244 result: 0
time: 0.499040 result: 0
time: 0.500212 result: 0
time: 0.500137 result: 0
time: 0.499920 result: 0
time: 0.500758 result: 0
time: 0.498068 result: 0

最佳答案

This sounds to me like the I/O should wait at least the SO_RCVTIMEO before returning execution to the caller.

没有。它应该等待至多 超时。如果数据已经存在,或在超时之前到达,则该方法会在该点返回,而无需等待超时到期。

Meanwhile, at the Open Group, they document the opposite:

Sets the timeout value that specifies the maximum amount of time an input function waits until it completes.

那么它是最小阻塞时间还是最大阻塞时间?

最大阻塞时间。

they clearly interpret 500ms as a minimum time, not maximum.

在这里,您要询问和测试两个不同的问题:计时器的分辨率和操作系统在超时后重新安排线程的速度。两者均未指定。

Is SO_RCVTIMEO supposed to be a minimum or a maximum duration for blocking the caller?

最大值,在其(即操作系统的)分辨率范围内,并受进一步调度延迟的影响。

If it's a maximum duration, what is the minimum?

零。

Surely an implementation is not free to choose a nonblocking read when asked for a 500ms timeout?

当然可以。如果套接字接收缓冲区中已存在数据,recv() 会传输该数据并立即返回。为什么要等待?

If it's a minimum duration, is Darwin wrong?

不是,它只是有不同的分辨率和重新安排延迟。

If I want to guarantee I tried to read for at least 500ms, am I supposed to keep trying in a loop until 500ms elapses? What is the "right way" to implement "at least X ms" behavior?

你必须用你自己的计时器来做,但我不明白这一点。如果数据已经存在或更早到达,您到底为什么要延迟?

Why is there so much variation from call to call on Linux? What is the source of the error?

定时器抖动;重新安排抖动。它不是实时操作系统

Is there a better API I should be using to read from sockets instead?

定义“更好”。你的期望似乎很奇怪。 30 多年来,这个 API 对其他人来说已经足够好了。

关于linux - SO_RCVTIMEO 醒得太早,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35734784/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com