【问题标题】:Why is the timeout on a windows udp receive socket always 500ms longer than set by SO_RCVTIMEO?为什么 windows udp 接收套接字上的超时总是比 SO_RCVTIMEO 设置的长 500 毫秒?
【发布时间】:2018-01-02 13:20:00
【问题描述】:

易于复制,这是我正在做的伪代码:

  1. 设置 UDP 套接字
  2. 将超时设置为一个值 (Timeout set)
  3. 检查我设置的超时时间 (Timeout checked)
  4. 尝试在该套接字上接收(当没有流量时)。
  5. Time 超时需要多长时间。 (Time until Timeout)

当我这样做时,我得到以下输出:

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 socket timeout总是比setsockopt中设置的时间长500ms?

查看setsockopt here,在涉及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 是值得的,看看平台真正将它设置为什么。
  • 一般来说,根据经验,当某个特定任务请求超时时,可以预期操作将等待直到请求的操作发生“大约”请求的时间。永远不要期望任何形式的超时实时保证,除非使用专门设计的RTOS 明确为您提供这种性质的保证。
  • 可能是Nagle's Algorithm。更新 - 哎呀,你说 udp ......那么不是它。
  • @EJP - 添加了这段代码,是的,它是我设置的。还更改了 DWORD,但在我的情况下应该没关系,因为我的系统上是 sizeof int == sizeof DWORD
  • @SamVarshavchik 当然,但这非常一致,正好是 500 毫秒。 Windows 肯定会记录这一点。人们也可能想要 10 毫秒超时。使用该设置,您可以期望的最小值是 510 毫秒,直到超时。如果您尝试快速接收和超时,这可能会严重限制您的应用程序。

标签: c++ sockets


【解决方案1】:

据说here:

在 SO_RCVTIMEO 上有一个大约 500 毫秒的无证最小限制。

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

【讨论】:

  • 这取决于 Windows 版本(或我不知道的某些 lib 的版本)在 Win10 中,如预期的那样,在 Win7 中,如问题中所述。奇怪的是,两个操作系统上的 WSA 版本相同(2.0 - std::cout &lt;&lt; "WSA version is " &lt;&lt; wsaData.szDescription &lt;&lt; std::endl;
猜你喜欢
  • 1970-01-01
  • 2012-11-12
  • 2012-08-17
  • 1970-01-01
  • 2012-04-08
  • 1970-01-01
  • 2012-04-20
  • 1970-01-01
相关资源
最近更新 更多