为什么Windows UDP接收套接字的超时时间总是比SO_RCVTIMEO设置的时间长500毫秒?

3
易于重现,以下是我正在进行的伪代码:
  1. 设置一个UDP套接字
  2. 将超时设置为一个值(已设置超时
  3. 检查我设置的超时时间(已检查超时
  4. 尝试在该套接字上接收数据(当没有流量时)。
  5. 计算超时所需的时间(直到超时的时间
当我这样做时,我会得到以下输出:
Timeout set: 0.1s | Timeout checked: 0.1s | Time until timeout: 0.6s | difference: 0.5s
Timeout set: 0.2s | Timeout checked: 0.2s | Time until timeout: 0.7s | difference: 0.5s
Timeout set: 0.4s | Timeout checked: 0.4s | Time until timeout: 0.9s | difference: 0.5s
Timeout set: 0.8s | Timeout checked: 0.8s | Time until timeout: 1.3s | difference: 0.5s
Timeout set: 1.6s | Timeout checked: 1.6s | Time until timeout: 2.1s | difference: 0.5s
Timeout set: 3.2s | Timeout checked: 3.2s | Time until timeout: 3.7s | difference: 0.5s

为什么Windows UDP套接字超时总是比setsockopt中设置的时间长500毫秒?

这里的setsockopt来看,我没有找到有关涉及SO_RCVTIMEO部分的原因说明。


复现代码:

#include "stdafx.h"
#include "winsock2.h"
#include <chrono>
#include <iostream>

int main() {
    WORD wVersionRequested;
    WSADATA wsaData;

    wVersionRequested = MAKEWORD(2, 2);
    int err = WSAStartup(wVersionRequested, &wsaData);
    if (err != 0) {
        printf("WSAStartup failed with error: %d\n", err);
        while (true);
    }

    sockaddr_in socketAddress = { 0 };
    socketAddress.sin_family = PF_INET;
    socketAddress.sin_port = htons(1010);
    socketAddress.sin_addr.s_addr = INADDR_ANY;

    // Create the socket
    SOCKET mSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (!mSocket) { 
        printf("Socket failed with error code : %d", WSAGetLastError());
        while (true);
    }

    //Bind
    if (bind(mSocket, (struct sockaddr *)&socketAddress, sizeof(socketAddress)) == SOCKET_ERROR) {
        printf("Bind failed with error code : %d", WSAGetLastError());
        while (true);
    }

    // Receive nothing over several different set timeouts
    for (double timeout = 0.1; timeout < 4.0; timeout *= 2) {

        // Set timeout
        DWORD lBuffer[2] = { 0, 0 };
        int lSize;
        lBuffer[0] = static_cast<DWORD>(1000.0 * timeout);
        lSize = sizeof(DWORD);
        if (setsockopt(mSocket, SOL_SOCKET, SO_RCVTIMEO, (char*)lBuffer, lSize) != 0) {
            printf("Set socket option failed with error code : %d", WSAGetLastError());
            while (true);
        }

        // Check that we get what we set.
        DWORD lBufferout[2] = { 0, 0 };
        if (getsockopt(mSocket, SOL_SOCKET, SO_RCVTIMEO, (char*)lBufferout, &lSize) != 0) {
            printf("Set socket option failed with error code : %d", WSAGetLastError());
            while (true);
        }

        // Receive and time
        char buffer[50];
        sockaddr_in senderAddr;
        int senderAddrSize = sizeof(senderAddr);

        auto s = std::chrono::steady_clock::now();

        int transferred = recvfrom(mSocket, (char*)buffer, 50, 0,
            (sockaddr*)&senderAddr, &senderAddrSize);

        auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - s).count() / 1000.0;

        std::cout << "Timeout set: " << timeout << "s | Timeout checked: " << lBufferout[0] / 1000.0 << "s | Time until timeout: " << duration << "s | difference: " << duration - timeout << "s\n";
    }

    while (true);

    return 0;
}

注意:此代码需要在1010端口无流量的情况下运行。如果不是这种情况,请更改该端口号。


"int lBuffer[2]" 应该改成 "DWORD lBuffer[2]"。在设置之后,获取 SO_RCVTIMEO 值可以看看平台实际设置的值是多少,这个操作值得考虑。 - user207421
一般来说,当为某个特定任务请求超时时,可以预期操作将等待请求的动作发生“大约”请求的时间。除非使用专门设计的实时操作系统(RTOS)明确提供此类保证,否则不应期望任何实时保证。 - Sam Varshavchik
可能是Nagle算法。更新-哎呀,你说的是UDP...那就不是这个了。 - Eljay
@SamVarshavchik 当然可以,但这个问题非常稳定,恰好相差500毫秒。Windows肯定会有相关文档的。而且有些人可能需要10毫秒的超时时间。如果设置了这个值,最少也要等待510毫秒才能超时。如果你想快速接收并超时,这可能会严重限制你的应用程序。 - Fantastic Mr Fox
可能与套接字完全无关。可能是Windows操作系统总是在唤醒并发出准备运行信号后的500毫秒内调度正在等待I/O事件的任务。至于短时间超时,如果需要实时功能,应该使用RTOS而不是Windows。 - Sam Varshavchik
显示剩余3条评论
1个回答

0

在这里声明

SO_RCVTIMEO存在一个未记录的最小限制,约为500毫秒。

很可能是通过始终将500毫秒添加到设置为SO_RCVTIMEO的任何值来实现的。


这取决于Windows版本(或某些我不知道的库的版本)。在Win10中,它如预期般工作,在Win7中,它与问题描述相同。奇怪的是,两个操作系统上的WSA版本相同(2.0-std :: cout <<“WSA版本为”<< wsaData.szDescription << std :: endl;)。 - kuga

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接